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