]> Creatis software - gdcm.git/blob - src/gdcmDataEntry.cxx
Doxygenation
[gdcm.git] / src / gdcmDataEntry.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmDataEntry.cxx,v $
5   Language:  C++
6   Date:      $Date: 2006/06/29 13:26:08 $
7   Version:   $Revision: 1.39 $
8                                                                                 
9   Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de
10   l'Image). All rights reserved. See Doc/License.txt or
11   http://www.creatis.insa-lyon.fr/Public/Gdcm/License.html for details.
12                                                                                 
13      This software is distributed WITHOUT ANY WARRANTY; without even
14      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15      PURPOSE.  See the above copyright notices for more information.
16                                                                                 
17 =========================================================================*/
18
19 #include "gdcmDataEntry.h"
20 #include "gdcmVR.h"
21 #include "gdcmTS.h"
22 #include "gdcmGlobal.h"
23 #include "gdcmUtil.h"
24 #include "gdcmDebug.h"
25
26 #include <fstream>
27
28 #if defined(__BORLANDC__)
29  #include <mem.h> // for memcpy
30  #include <stdlib.h> // for atof
31  #include <ctype.h> // for isdigit
32 #endif
33
34 namespace gdcm 
35 {
36 //-----------------------------------------------------------------------------
37 #define MAX_SIZE_PRINT_ELEMENT_VALUE 0x7fffffff
38 uint32_t DataEntry::MaxSizePrintEntry = MAX_SIZE_PRINT_ELEMENT_VALUE;
39
40 //-----------------------------------------------------------------------------
41 // Constructor / Destructor
42 /**
43  * \brief   Constructor for a given DataEntry
44  * @param   group group number of the Data Entry to be created
45  * @param   elem element number of the Data Entry to be created
46  * @param   vr Value Representation of the Data Entry to be created 
47  */
48 DataEntry::DataEntry(uint16_t group,uint16_t elem,
49                                      VRKey const &vr) 
50             : DocEntry(group,elem,vr)
51 {
52    State = STATE_LOADED;
53    Flag = FLAG_NONE;
54
55    BinArea = 0;
56    SelfArea = true;
57 }
58
59 /**
60  * \brief   Constructor for a given DocEntry
61  * @param   e Pointer to existing Doc entry
62  */
63 DataEntry::DataEntry(DocEntry *e)
64             //: DocEntry(e->GetDictEntry())
65             : DocEntry(e->GetGroup(),e->GetElement(), e->GetVR()  )
66 {
67    Flag = FLAG_NONE;
68    BinArea = 0;
69    SelfArea = true;
70
71    Copy(e);
72 }
73
74 /**
75  * \brief   Canonical destructor.
76  */
77 DataEntry::~DataEntry ()
78 {
79    DeleteBinArea();
80 }
81
82 //-----------------------------------------------------------------------------
83 // Print
84
85 //-----------------------------------------------------------------------------
86 // Public
87 /**
88  * \brief Sets the value (non string) of the current DataEntry
89  * @param area area
90  * @param self self
91  */
92 void DataEntry::SetBinArea( uint8_t *area, bool self )  
93
94    DeleteBinArea();
95
96    BinArea = area;
97    SelfArea = self;
98
99    State = STATE_LOADED;
100 }
101 /**
102  * \brief Inserts the value (non string) into the current DataEntry
103  * @param area area
104  * @param length length 
105  */
106 void DataEntry::CopyBinArea( uint8_t *area, uint32_t length )
107 {
108    DeleteBinArea();
109
110    uint32_t lgh = length + length%2;
111    SetLength(lgh);
112
113    if( area && length > 0 )
114    {
115       NewBinArea();
116       memcpy(BinArea,area,length);
117       if( length!=lgh )
118          BinArea[length]=0;
119
120       State = STATE_LOADED;
121    }
122 }
123
124 /**
125  * \brief Inserts the elementary (non string) value into the current DataEntry
126  * @param id index of the elementary value to be set
127  * @param val value, passed as a double 
128  */
129 void DataEntry::SetValue(const uint32_t &id, const double &val)
130 {
131    if( !BinArea )
132       NewBinArea();
133    State = STATE_LOADED;
134
135    if( id > GetValueCount() )
136    {
137       gdcmErrorMacro("Index (" << id << ")is greater than the data size");
138       return;
139    }
140
141    const VRKey &vr = GetVR();
142    if( vr == "US" || vr == "SS" )
143    {
144       uint16_t *data = (uint16_t *)BinArea;
145       data[id] = (uint16_t)val;
146    }
147    else if( vr == "UL" || vr == "SL" )
148    {
149       uint32_t *data = (uint32_t *)BinArea;
150       data[id] = (uint32_t)val;
151    }
152    else if( vr == "FL" )
153    {
154       float *data = (float *)BinArea;
155       data[id] = (float)val;
156    }
157    else if( vr == "FD" )
158    {
159       double *data = (double *)BinArea;
160       data[id] = (double)val;
161    }
162    else if( Global::GetVR()->IsVROfStringRepresentable(vr) )
163    {
164       gdcmErrorMacro("SetValue on String representable not implemented yet");
165    }
166    else
167    {
168       BinArea[id] = (uint8_t)val;
169    }
170 }
171 /**
172  * \brief returns, as a double (?!?) one of the values 
173  *      (when entry is multivaluated), identified by its index.
174  *      Returns 0.0 if index is wrong
175  * @param id id
176  */
177 double DataEntry::GetValue(const uint32_t &id) const
178 {
179    if( !BinArea )
180    {
181       if (GetLength() != 0) // avoid stupid messages
182    /// \todo warn the user there was a problem !
183          gdcmErrorMacro("BinArea not set " << std::hex 
184                      << GetGroup() << " " << GetElement() 
185                      << " Can't get the value");
186       return 0.0;
187    }
188
189    uint32_t count = GetValueCount();
190    if( id > count )
191    {
192       gdcmErrorMacro("Index (" << id << ") is greater than the data size");
193       return 0.0;
194    }
195
196    /// \todo FIX the API : user *knows* that entry contains a US
197    ///               and he receives a double ?!?
198    
199    const VRKey &vr = GetVR();
200    /// \todo FIX the API : user *knows* that entry contains a US,
201    ///       the method is supposed to return a double
202    ///       but sends a US ?!? 
203    if( vr == "US" || vr == "SS" )
204       return ((uint16_t *)BinArea)[id];
205    else if( vr == "UL" || vr == "SL" )
206       return ((uint32_t *)BinArea)[id];
207    else if( vr == "FL" )
208       return ((float *)BinArea)[id];
209    else if( vr == "FD" )
210       return ((double *)BinArea)[id];
211    else if( Global::GetVR()->IsVROfStringRepresentable(vr) )
212    {
213       // this is for VR = "DS", ...
214       if( GetLength() )
215       {
216          // Don't use std::string to accelerate processing
217          double val;
218          char *tmp = new char[GetLength()+1];
219          memcpy(tmp,BinArea,GetLength());
220          tmp[GetLength()]=0;
221
222          if( count == 0 )
223          {
224             val = atof(tmp);
225          }
226          else
227          {
228             count = id;
229             char *beg = tmp;
230             for(uint32_t i=0;i<GetLength();i++)
231             {
232                if( tmp[i] == '\\' )
233                {
234                   if( count == 0 )
235                   {
236                      tmp[i] = 0;
237                      break;
238                   }
239                   else
240                   {
241                      count--;
242                      beg = &(tmp[i+1]);
243                   }
244                }
245             }
246             val = atof(beg);
247          }
248
249          delete[] tmp;
250          return val;
251       }
252       else 
253          return 0.0;
254    }
255    else
256       return BinArea[id];
257 }
258
259 /**
260  * \brief Checks if the multiplicity of the value follows Dictionary VM
261  */
262 bool DataEntry::IsValueCountValid() /*const*/
263 {
264   uint32_t vm;
265   const std::string &strVM = GetVM();
266   uint32_t vc = GetValueCount();
267   bool valid = vc == 0;
268   if( valid )
269     return true;
270   
271   // FIXME : what shall we do with VM = "2-n", "3-n", etc
272   
273   if( strVM == "1-n" )
274   {
275     // make sure there is at least one ??? FIXME
276     valid = vc >= 1;
277   }
278   else
279   {
280     std::istringstream os;
281     os.str( strVM );
282     os >> vm;
283     // Two cases:
284     // vm respects the one from the dict
285     // vm is 0 (we need to check if this element is allowed to be empty) FIXME
286
287     // note  (JPR)
288     // ----    
289     // Entries whose type is 1 are mandatory, with a mandatory value.
290     // Entries whose type is 1c are mandatory-inside-a-Sequence,
291     //                          with a mandatory value.
292     // Entries whose type is 2 are mandatory, with an optional value.
293     // Entries whose type is 2c are mandatory-inside-a-Sequence,
294     //                          with an optional value.
295     // Entries whose type is 3 are optional.
296
297     // case vc == 0 is only applicable for 'type 2' entries.
298     // Problem : entry type may depend on the modality and/or the Sequence
299     //           it's embedded in !
300     //          (Get the information in the 'Conformance Statements' ...)  
301     valid = vc == vm;
302   }
303   return valid;
304 }
305
306 /**
307  * \brief returns the number of elementary values
308  */ 
309 uint32_t DataEntry::GetValueCount( ) const
310 {
311    const VRKey &vr = GetVR();
312    if( vr == "US" || vr == "SS" )
313       return GetLength()/sizeof(uint16_t);
314    else if( vr == "UL" || vr == "SL" )
315       return GetLength()/sizeof(uint32_t);
316    else if( vr == "FL" || vr == "OF" )
317       return GetLength()/4 ; // FL has a *4* length! sizeof(float);
318    else if( vr == "FD" )
319       return GetLength()/8;  // FD has a *8* length! sizeof(double);
320    else if( Global::GetVR()->IsVROfStringRepresentable(vr) )
321    {
322       // Some element in DICOM are allowed to be empty
323       if( !GetLength() ) 
324          return 0;
325       // Don't use std::string to accelerate processing
326       uint32_t count = 1;
327       for(uint32_t i=0;i<GetLength();i++)
328       {
329          if( BinArea[i] == '\\')
330             count++;
331       }
332       return count;
333    }
334    return GetLength();
335 }
336
337 /**
338  * \brief Gets a std::vector <double> holding the value(s) of a DS DataEntry
339  * @param valueVector std::vector double of value(s)
340  * \return false if VR not "DS" or DataEntry empty
341  */
342  bool DataEntry::GetDSValue(std::vector <double> &valueVector)
343  {
344     /// \todo rewrite the whole method, in order *not to use* std::string !
345     std::vector<std::string> tokens;
346     
347     if (GetVR() != "DS") // never trust a user !
348        return false;    
349        
350     Util::Tokenize ( GetString().c_str(), tokens, "\\" );
351         
352     int nbValues= tokens.size();
353     if (nbValues == 0)
354        return false;
355                
356     for (int loop=0; loop<nbValues; loop++) 
357        valueVector.push_back(atof(tokens[loop].c_str()));
358     
359     return true;  
360  }
361  
362 /**
363  * \brief Sets the 'value' of a DataEntry, passed as a std::string
364  * @param value string representation of the value to be set
365  */ 
366 void DataEntry::SetString(std::string const &value)
367 {
368    DeleteBinArea();
369
370    const VRKey &vr = GetVR();
371    if ( vr == "US" || vr == "SS" )
372    {
373       std::vector<std::string> tokens;
374       Util::Tokenize (value, tokens, "\\");
375       SetLength(tokens.size()*sizeof(uint16_t));
376       NewBinArea();
377
378       uint16_t *data = (uint16_t *)BinArea;
379       for (unsigned int i=0; i<tokens.size();i++)
380          data[i] = atoi(tokens[i].c_str());
381       tokens.clear();
382    }
383    else if ( vr == "UL" || vr == "SL" )
384    {
385       std::vector<std::string> tokens;
386       Util::Tokenize (value, tokens, "\\");
387       SetLength(tokens.size()*sizeof(uint32_t));
388       NewBinArea();
389
390       uint32_t *data = (uint32_t *)BinArea;
391       for (unsigned int i=0; i<tokens.size();i++)
392          data[i] = atoi(tokens[i].c_str());
393       tokens.clear();
394    }
395    else if ( vr == "FL" )
396    {
397       std::vector<std::string> tokens;
398       Util::Tokenize (value, tokens, "\\");
399       SetLength(tokens.size()*sizeof(float));
400       NewBinArea();
401
402       float *data = (float *)BinArea;
403       for (unsigned int i=0; i<tokens.size();i++)
404          data[i] = (float)atof(tokens[i].c_str());
405       tokens.clear();
406    }
407    else if ( vr == "FD" )
408    {
409       std::vector<std::string> tokens;
410       Util::Tokenize (value, tokens, "\\");
411       SetLength(tokens.size()*sizeof(double));
412       NewBinArea();
413
414       double *data = (double *)BinArea;
415       for (unsigned int i=0; i<tokens.size();i++)
416          data[i] = atof(tokens[i].c_str());
417       tokens.clear();
418    }
419    else
420    {
421       //if( value.size() > 0 )  // when user sets a string to 0, *do* the job.
422       {
423          size_t l =  value.size();    
424          SetLength(l + l%2);
425          NewBinArea();
426          memcpy(BinArea, value.c_str(), l);
427          if (l%2)
428             BinArea[l] = '\0';
429       }      
430    }
431    State = STATE_LOADED;
432 }
433 /**
434  * \brief   returns as a string (when possible) the value of the DataEntry
435  */
436 std::string const &DataEntry::GetString() const
437 {
438   static std::ostringstream s;
439   const VRKey &vr = GetVR();
440   s.str("");
441   StrArea="";
442
443   if( !BinArea )
444      return StrArea;
445       // When short integer(s) are stored, convert the following (n * 2) characters
446   // as a displayable string, the values being separated by a back-slash
447   if( vr == "US" )
448   {
449      uint16_t *data=(uint16_t *)BinArea;
450      for (unsigned int i=0; i < GetValueCount(); i++)
451      {
452         if( i!=0 )
453            s << '\\';
454         s << data[i];
455      }
456      StrArea=s.str();
457   }
458   else if (vr == "SS" )
459   {
460      int16_t *data=(int16_t *)BinArea;
461      for (unsigned int i=0; i < GetValueCount(); i++)
462      {
463         if( i!=0 )
464            s << '\\';
465         s << data[i];
466      }
467      StrArea=s.str();
468   }      // See above comment on multiple short integers (mutatis mutandis).
469   else if( vr == "UL" )
470   {
471      uint32_t *data=(uint32_t *)BinArea;
472      for (unsigned int i=0; i < GetValueCount(); i++)
473      {
474         if( i!=0 )
475            s << '\\';
476         s << data[i];
477      }
478      StrArea=s.str();
479   }
480   else if( vr == "SL" )
481   {
482      int32_t *data=(int32_t *)BinArea;
483      for (unsigned int i=0; i < GetValueCount(); i++)
484      {
485         if( i!=0 )
486            s << '\\';
487         s << data[i];
488      }
489      StrArea=s.str();
490   }    else if( vr == "FL" )
491   {
492      float *data=(float *)BinArea;
493      for (unsigned int i=0; i < GetValueCount(); i++)
494      {
495         if( i!=0 )
496            s << '\\';
497         s << data[i];
498      }
499      StrArea=s.str();
500   }
501   else if( vr == "FD" )
502   {
503      double *data=(double *)BinArea;
504      for (unsigned int i=0; i < GetValueCount(); i++)
505      {
506         if( i!=0 )
507            s << '\\';
508         s << data[i];
509      }
510      StrArea=s.str();
511   }
512   else
513   {
514      StrArea.append((const char *)BinArea,GetLength());
515      // to avoid gdcm to propagate oddities in lengthes
516      if ( GetLength()%2)
517         StrArea.append(" ",1);   }
518   return StrArea;
519 }
520
521
522 /**
523  * \brief Copies all the attributes from an other DocEntry 
524  * @param doc entry to copy from
525  * @remarks The content BinArea is copied too
526  */
527 void DataEntry::Copy(DocEntry *doc)
528 {
529    DocEntry::Copy(doc);
530
531    DataEntry *entry = dynamic_cast<DataEntry *>(doc);
532    if ( entry )
533    {
534       State = entry->State;
535       Flag = entry->Flag;
536       CopyBinArea(entry->BinArea,entry->GetLength());
537    }
538 }
539
540 /**
541  * \brief   Writes the 'value' area of a DataEntry
542  * @param fp already open ofstream pointer
543  * @param filetype type of the file (ACR, ImplicitVR, ExplicitVR, ...)
544  */
545 void DataEntry::WriteContent(std::ofstream *fp, FileType filetype)
546
547    DocEntry::WriteContent(fp, filetype);
548
549    if ( GetGroup() == 0xfffe )
550    {
551       return; //delimitors have NO value
552    }
553    
554    // --> We only deal with Little Endian writting.
555    // --> forget Big Endian Transfer Syntax writting!
556    //     Next DICOM version will give it up ...
557  
558    // WARNING - For Implicit VR private element,
559    //           we have *no choice* but considering them as
560    //           something like 'OB' values.
561    //           we rewrite them as we found them on disc.
562    //           Some trouble will occur if element was 
563    //           *actually* OW, if image was produced 
564    //           on Big endian based processor, read and writen 
565    //           on Little endian based processor
566    //           and, later on, somebody needs
567    //           this 'OW' Implicit VR private element (?!?)
568    //           (Same stuff, mutatis mutandis, for Little/Big)
569  
570    // 8/16 bits Pixels problem should be solved automatiquely,
571    // since we ensure the VR (OB vs OW) is conform to Pixel size.
572         
573    uint8_t *data = BinArea; //safe notation
574    size_t l = GetLength(); 
575 //   gdcmDebugMacro("in DataEntry::WriteContent " << GetKey() << " AtomicLength: "
576 //              << Global::GetVR()->GetAtomicElementLength(this->GetVR() ) // << " BinArea in :" << &BinArea
577 //             );
578    if (BinArea) // the binArea was *actually* loaded
579    {
580 #if defined(GDCM_WORDS_BIGENDIAN) || defined(GDCM_FORCE_BIGENDIAN_EMULATION)
581       unsigned short vrLgth = 
582                         Global::GetVR()->GetAtomicElementLength(this->GetVR());
583       unsigned int i;
584       switch(vrLgth)
585       {
586          case 1:
587          {
588             binary_write (*fp, data, l );           
589             break;
590          }     
591          case 2:
592          {
593             uint16_t *data16 = (uint16_t *)data;
594             for(i=0;i<l/vrLgth;i++)
595                binary_write( *fp, data16[i]);
596             break;
597          }
598          case 4:
599          {
600             uint32_t *data32 = (uint32_t *)data;
601             for(i=0;i<l/vrLgth;i++)
602                binary_write( *fp, data32[i]);
603             break;
604          }
605          case 8:
606          {
607             double *data64 = (double *)data;
608             for(i=0;i<l/vrLgth;i++)
609                binary_write( *fp, data64[i]);
610             break;
611          }
612       }
613 #else
614    binary_write (*fp, data, l );
615 #endif //GDCM_WORDS_BIGENDIAN
616
617    }
618    else
619    {
620       // nothing was loaded, but we need to skip space on disc     
621       if (l != 0)
622       {
623       //  --> WARNING : nothing is written; 
624       //  --> the initial data (on the the source image) is lost
625       //  --> user is *not* informed !      
626          gdcmDebugMacro ("Nothing was loaded, but we need to skip space on disc. "
627                       << "Length =" << l << " for " << GetKey() );   
628          fp->seekp(l, std::ios::cur); // At Write time, for unloaded elems
629       }
630    }
631    // to avoid gdcm to propagate oddities
632    // (length was already modified)  
633    if (l%2)
634       fp->seekp(1, std::ios::cur);  // At Write time, for non even length elems
635 }
636
637 /**
638  * \brief   Compute the full length of the elementary DataEntry (not only value
639  *          length) depending on the VR.
640  */
641 uint32_t DataEntry::ComputeFullLength()
642 {
643    return GetFullLength();
644 }
645
646 //-----------------------------------------------------------------------------
647 // Protected
648 /// \brief Creates a DataEntry owned BinArea (remove previous one if any)
649 void DataEntry::NewBinArea( )
650 {
651    DeleteBinArea();
652    if( GetLength() > 0 )
653       BinArea = new uint8_t[GetLength()];
654    SelfArea = true;
655 }
656 /// \brief Removes the BinArea, if owned by the DataEntry
657 void DataEntry::DeleteBinArea(void)
658 {
659    if (BinArea && SelfArea)
660    {
661       delete[] BinArea;
662       BinArea = NULL;
663    }
664 }
665
666 //-----------------------------------------------------------------------------
667 // Private
668
669 //-----------------------------------------------------------------------------
670 // Print
671 /**
672  * \brief   Prints a DataEntry (Dicom entry)
673  * @param   os ostream we want to print in
674  * @param indent Indentation string to be prepended during printing
675  */
676 void DataEntry::Print(std::ostream &os, std::string const & )
677 {
678    os << "D ";
679    DocEntry::Print(os);
680
681    uint16_t g = GetGroup();
682    if (g == 0xfffe) // delimiters have NO value
683    {          
684       return; // just to avoid identing all the remaining code 
685    }
686
687    std::ostringstream s;
688    TSAtr v;
689
690    if( BinArea )
691    {
692       v = GetString();
693       const VRKey &vr = GetVR();
694
695       if( vr == "US" || vr == "SS" || vr == "UL" || vr == "SL" 
696        || vr == "FL" || vr == "FD")
697          s << " [" << GetString() << "]";
698       else
699       { 
700          if(Global::GetVR()->IsVROfStringRepresentable(vr))
701          {
702             // replace non printable characters by '.'
703             std::string cleanString = Util::CreateCleanString(v);
704             if ( cleanString.length() <= GetMaxSizePrintEntry()
705               || PrintLevel >= 3
706               || IsNotLoaded() )
707            // FIXME : when IsNotLoaded(), you create a Clean String ?!?
708            // FIXME : PrintLevel<2 *does* print the values 
709            //        (3 is only for extra offsets printing)
710            // What do you wanted to do ? JPR
711             {
712                s << " [" << cleanString << "]";
713             }
714             else
715             {
716                s << " [gdcm::too long for print (" << cleanString.length() << ") ]";
717             }
718          }
719          else
720          {
721             // A lot of Private elements (with no VR) contain actually 
722             // only printable characters;
723             // Let's deal with them as is they were VR std::string representable
724     
725             if ( Util::IsCleanArea( GetBinArea(), GetLength()  ) )
726             {
727                // FIXME : since the 'Area' *is* clean, just use
728                //         a 'CreateString' method, to save CPU time.
729                std::string cleanString = 
730                      Util::CreateCleanString( BinArea,GetLength()  );
731                s << " [" << cleanString << "]";
732             }
733             else
734             {
735                s << " [" << GDCM_BINLOADED << ";"
736                << "length = " << GetLength() << "]";
737             }
738          }
739       }
740    }
741    else
742    {
743       if( IsNotLoaded() )
744          s << " [" << GDCM_NOTLOADED << "]";
745       else if( IsUnfound() )
746          s << " [" << GDCM_UNFOUND << "]";
747       else if( IsUnread() )
748          s << " [" << GDCM_UNREAD << "]";
749       else if ( GetLength() == 0 )
750          s << " []";
751    }
752
753    if( IsPixelData() )
754       s << " (" << GDCM_PIXELDATA << ")";
755
756    // Display the UID value (instead of displaying only the rough code)
757    // First 'clean' trailing character (space or zero) 
758    if(BinArea)
759    {
760       const uint16_t &gr = GetGroup();
761       const uint16_t &elt = GetElement();
762       TS *ts = Global::GetTS();
763
764       if (gr == 0x0002)
765       {
766          // Any more to be displayed ?
767          if ( elt == 0x0010 || elt == 0x0002 )
768          {
769             if ( v.length() != 0 )  // for brain damaged headers
770             {
771                if ( ! isdigit((unsigned char)v[v.length()-1]) )
772                {
773                   v.erase(v.length()-1, 1);
774                }
775             }
776             s << "  ==>\t[" << ts->GetValue(v) << "]";
777          }
778       }
779       else if (gr == 0x0008)
780       {
781          if ( elt == 0x0016 || elt == 0x1150 )
782          {
783             if ( v.length() != 0 )  // for brain damaged headers
784             {
785                if ( ! isdigit((unsigned char)v[v.length()-1]) )
786                {
787                   v.erase(v.length()-1, 1);
788                }
789             }
790             s << "  ==>\t[" << ts->GetValue(v) << "]";
791          }
792       }
793       else if (gr == 0x0004)
794       {
795          if ( elt == 0x1510 || elt == 0x1512  )
796          {
797             if ( v.length() != 0 )  // for brain damaged headers  
798             {
799                if ( ! isdigit((unsigned char)v[v.length()-1]) )
800                {
801                   v.erase(v.length()-1, 1);  
802                }
803             }
804             s << "  ==>\t[" << ts->GetValue(v) << "]";
805          }
806       }
807    }
808
809    os << s.str();
810 }
811
812 //-----------------------------------------------------------------------------
813 } // end namespace gdcm
814