/*========================================================================= Program: GDCM (Grassroots DICOM). A DICOM library Copyright (c) 2006-2011 Mathieu Malaterre All rights reserved. See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "gdcmXMLPrinter.h" #include "gdcmSequenceOfItems.h" #include "gdcmSequenceOfFragments.h" #include "gdcmDict.h" #include "gdcmDicts.h" #include "gdcmGroupDict.h" #include "gdcmVR.h" #include "gdcmVM.h" #include "gdcmElement.h" #include "gdcmGlobal.h" #include "gdcmAttribute.h" #include "gdcmDataSetHelper.h" #include "gdcmUUIDGenerator.h" #include "gdcmDataSet.h" #include namespace gdcm { //----------------------------------------------------------------------------- XMLPrinter::XMLPrinter():PrintStyle(XMLPrinter::OnlyUUID),F(nullptr) { } //----------------------------------------------------------------------------- XMLPrinter::~XMLPrinter() = default; // Carried forward from Printer Class // SIEMENS_GBS_III-16-ACR_NEMA_1.acr is a tough kid: 0009,1131 is supposed to be VR::UL, but // there are only two bytes... VR XMLPrinter::PrintDataElement(std::ostream &os, const Dicts &dicts, const DataSet & ds, const DataElement &de, const TransferSyntax & ts) { const ByteValue *bv = de.GetByteValue(); const SequenceOfItems *sqi = nullptr; const SequenceOfFragments *sqf = de.GetSequenceOfFragments(); std::string strowner; const char *owner = nullptr; const Tag& t = de.GetTag(); UUIDGenerator UIDgen; if( t.IsPrivate() && !t.IsPrivateCreator() ) { strowner = ds.GetPrivateCreator(t); owner = strowner.c_str(); } const DictEntry &entry = dicts.GetDictEntry(t,owner); const VR &vr = entry.GetVR(); const VM &vm = entry.GetVM(); (void)vm; const char *name = entry.GetKeyword(); bool retired = entry.GetRetired(); const VR &vr_read = de.GetVR(); const VL &vl_read = de.GetVL(); //Printing Tag os << " tag = \"" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << t.GetGroup() << std::setw(4) << t.GetElement() << std::nouppercase <<"\"" << std::dec; //Printing Private Creator if( owner && *owner ) { os << " privateCreator = \"" << owner << "\" "; } VR refvr; // Prefer the vr from the file: if( vr_read == VR::INVALID ) { refvr = vr; } else if ( vr_read == VR::UN && vr != VR::INVALID ) // File is explicit, but still prefer vr from dict when UN { refvr = vr; } else // The file is Explicit ! { refvr = vr_read; } if( refvr.IsDual() ) // This means vr was read from a dict entry: { refvr = DataSetHelper::ComputeVR(*F,ds, t); } //as DataSetHelper would have been called assert( refvr != VR::US_SS ); assert( refvr != VR::OB_OW ); if( !de.IsEmpty() ) { const Value &value = de.GetValue(); if( dynamic_cast( &value ) ) { sqi = de.GetValueAsSQ(); refvr = VR::SQ; assert( refvr == VR::SQ ); } #if 0 else if( vr == VR::SQ && vr_read != VR::SQ ) { sqi = de.GetValueAsSQ(); refvr = VR::SQ; assert( refvr == VR::SQ ); } #endif } if( (vr_read == VR::INVALID || vr_read == VR::UN ) && vl_read.IsUndefined() ) { assert( refvr == VR::SQ ); } // if( vr_read == VR::SQ || vr_read == VR::UN ) // { // sqi = de.GetValueAsSQ(); // } if( vr != VR::INVALID && (!vr.Compatible( vr_read ) || vr_read == VR::INVALID || vr_read == VR::UN ) ) { assert( vr != VR::INVALID ); /* No need as we will save only the VR to which it is stored by GDCM in the XML file if( vr == VR::US_SS || vr == VR::OB_OW ) { os << "(" << vr << " => " << refvr << ") "; } else { os << "(" << vr << ") "; } */ } else if( sqi /*de.GetSequenceOfItems()*/ && refvr == VR::INVALID ) { // when vr == VR::INVALID and vr_read is also VR::INVALID, we have a seldom case where we can guess // the vr // eg. CD1/647662/647663/6471066 has a SQ at (2001,9000) assert( refvr == VR::INVALID ); refvr = VR::SQ; } if(refvr == VR::INVALID) refvr = VR::UN; // Printing the VR -- Value Representation os << " vr = \"" << refvr << "\" "; // Add the keyword attribute : if( t.IsPublic()) { if( name && *name ) { os <<"keyword = \""; /* No owner */ if( t.IsPrivate() && (owner == nullptr || *owner == 0 ) && !t.IsPrivateCreator() ) { //os << name; //os = PrintXML_char(os,name); } /* retired element */ else if( retired ) { assert( t.IsPublic() || t.GetElement() == 0x0 ); // Is there such thing as private and retired element ? //os << name; //os = PrintXML_char(os,name); } /* Public element */ else { //os << name; //os = PrintXML_char(os,name); } char c; for (; (*name)!='\0'; name++) { c = *name; if(c == '&') os << "&"; else if(c == '<') os << "<"; else if(c == '>') os << ">"; else if(c == '\'') os << "'"; else if(c == '\"') os << """; else os << c; } os << "\""; } else { if( t.IsPublic() ) { gdcmWarningMacro( "An unknown public element."); } // os << ""; // Special keyword } } os << ">\n"; #define StringFilterCase(type) \ case VR::type: \ { \ Element el; \ if( !de.IsEmpty() ) { \ el.Set( de.GetValue() ); \ if( el.GetLength() ) { \ os << "" ;os << "" << el.GetValue();os << "\n"; \ const uint32_t l = (uint32_t)el.GetLength(); \ for(uint32_t i = 1; i < l; ++i) \ { \ os << "" ;\ os << el.GetValue(i);os << "\n";} \ } \ else { if( de.IsEmpty() ) \ {} } } \ else { assert( de.IsEmpty()); } \ } break // Print Value now: //Handle PN first, acc. to Standard if(refvr == VR::PN) { if( bv ) { bv->PrintPNXML(os); //new function to print each value in new child tag } else { assert( de.IsEmpty() ); } } else if( refvr & VR::VRASCII ) { //assert( !sqi && !sqf); assert(!sqi); if( bv ) { bv->PrintASCIIXML(os); //new function to print each value in new child tag } else { assert( de.IsEmpty() ); } } else { assert( refvr & VR::VRBINARY || (vr == VR::INVALID && refvr == VR::INVALID) ); std::string s; switch(refvr) { StringFilterCase(AT); StringFilterCase(FL); StringFilterCase(FD); StringFilterCase(OD); StringFilterCase(OF); StringFilterCase(SL); StringFilterCase(SS); StringFilterCase(UL); StringFilterCase(US); StringFilterCase(SV); StringFilterCase(UV); case VR::OB: case VR::OW: case VR::OL: case VR::OV: case VR::OB_OW: case VR::UN: case VR::US_OW: case VR::US_SS_OW: { if ( bv ) { if(PrintStyle) { bv->PrintHexXML(os); } else { if(bv->GetLength()) { const char *suid = UIDgen.Generate(); os << "\n"; HandleBulkData( suid, ts, bv->GetPointer(), bv->GetLength() ); } } } else if ( sqf ) { assert( t == Tag(0x7fe0,0x0010) ); } else if ( sqi ) { gdcmErrorMacro( "Should not happen: VR=UN but contains a SQ" ); } else { assert( !sqi && !sqf ); assert( de.IsEmpty() ); } } break; case VR::US_SS: assert( refvr != VR::US_SS ); break; case VR::SQ://The below info need not be printed into the XML infoset acc. to the standard if( !sqi && !de.IsEmpty() && de.GetValue().GetLength() ) { } else { if( vl_read.IsUndefined() ) { //os << "(Sequence with undefined length)"; } else { //os << "(Sequence with defined length)"; } } break; case VR::INVALID: if( bv ) { if(PrintStyle) bv->PrintHexXML(os); else { if(bv->GetLength()) { const char *suid = UIDgen.Generate(); os << "\n"; HandleBulkData( suid, ts, bv->GetPointer(), bv->GetLength() ); } } } else { assert( !sqi && !sqf ); assert( de.IsEmpty() ); } break; /* ASCII are treated elsewhere but we do not want to use default: here to get warnings */ /* hopefully compiler is smart and remove dead switch/case */ case VR::AE: case VR::AS: case VR::CS: case VR::DA: case VR::DS: case VR::DT: case VR::IS: case VR::LO: case VR::LT: case VR::PN: case VR::SH: case VR::ST: case VR::TM: case VR::UC: case VR::UI: case VR::UR: case VR::UT: /* others */ case VR::VL16: case VR::VL32: case VR::VRASCII: case VR::VRBINARY: case VR::VR_VM1: case VR::VRALL: case VR::VR_END: assert(0 && "No Match! Impossible!!"); break; } } //os << "\n"; return refvr; } void XMLPrinter::PrintSQ(const SequenceOfItems *sqi, const TransferSyntax & ts, std::ostream & os) { if( !sqi ) return; int noItems = 1; SequenceOfItems::ItemVector::const_iterator it = sqi->Items.begin(); for(; it != sqi->Items.end(); ++it) { const Item &item = *it; const DataSet &ds = item.GetNestedDataSet(); //const DataElement &deitem = item; /* os << "> 8) << "\" "; os << " VR = \"UN\" keyword = "; if( deitem.GetVL().IsUndefined() ) { os << "\"ItemWithUndefinedLength\""; } else { os << "\"ItemWithDefinedLength\""; } os << ">\n"; */ os << "\n"; PrintDataSet(ds, ts, os); /* if( deitem.GetVL().IsUndefined() ) { os << "\n"; } os << "\n\n"; */ os << "\n"; } /* if( sqi->GetLength().IsUndefined() ) { os << "\n"; } */ } void XMLPrinter::PrintDataSet(const DataSet &ds, const TransferSyntax & ts, std::ostream &os) { const Global& g = GlobalInstance; const Dicts &dicts = g.GetDicts(); const Dict &d = dicts.GetPublicDict(); (void)d; DataSet::ConstIterator it = ds.Begin(); UUIDGenerator UIDgen; for( ; it != ds.End(); ++it ) { const DataElement &de = *it; const SequenceOfFragments *sqf = de.GetSequenceOfFragments(); os << " sqi2 = de.GetValueAsSQ(); PrintSQ(sqi2, ts, os); } else if ( sqf ) { /*I have appended all fragments into one by calling the GetBuffer method in gdcmSequenceOfFragments which does not write the Table to the buffer. It is slightly buggy as the size returns includes that of the table. Should I get the Table size and subtract it? Or should I append the table as well in the BulkData?? */ unsigned long size = sqf->ComputeByteLength(); char *bulkData = new char [size]; if(sqf->GetBuffer(bulkData, size)) { if(size) { const char *suid = UIDgen.Generate(); os << "\n"; HandleBulkData( suid, ts, bulkData, size); } } /* const BasicOffsetTable & table = sqf->GetTable(); const ByteValue *bv = table.GetByteValue(); if(bv->GetLength()) { const char *suid = UIDgen.Generate(); os << "\n"; HandleBulkData( suid, ts, bv->GetPointer(), bv->GetLength() ); } unsigned int numfrag = sqf->GetNumberOfFragments(); for(unsigned int i = 0; i < numfrag; i++) { const Fragment& frag = sqf->GetFragment(i); const ByteValue *bv = frag.GetByteValue(); if(bv->GetLength()) { const char *suid = UIDgen.Generate(); os << "\n"; HandleBulkData( suid, ts, bv->GetPointer(), bv->GetLength() ); } } */ delete[] bulkData; } else { // This is a byte value, so it should have been already treated. } os << "\n"; } } /*------------------------------------------------------------------------------------------------------------------------------------------------*/ void XMLPrinter::Print(std::ostream& os) { /* XML Meta Info */ const Tag CharacterEncoding(0x0008,0x0005); const DataSet &ds = F->GetDataSet(); const FileMetaInformation &header = F->GetHeader(); const TransferSyntax &ts = header.GetDataSetTransferSyntax(); os << R"( at; at.SetFromDataElement( de ); const char* EncodingFromFile = at.GetValue(0); if (!strcmp(EncodingFromFile,"ISO_IR 6")) os << "UTF-8"; else if (!strcmp(EncodingFromFile,"ISO_IR 192")) os << "UTF-8"; else if (!strcmp(EncodingFromFile,"ISO_IR 100")) os << "ISO-8859-1"; else if (!strcmp(EncodingFromFile,"ISO_IR 101")) os << "ISO-8859-2"; else if (!strcmp(EncodingFromFile,"ISO_IR 109")) os << "ISO-8859-3"; else if (!strcmp(EncodingFromFile,"ISO_IR 110")) os << "ISO-8859-4"; else if (!strcmp(EncodingFromFile,"ISO_IR 148")) os << "ISO-8859-9"; else if (!strcmp(EncodingFromFile,"ISO_IR 144")) os << "ISO-8859-5"; else if (!strcmp(EncodingFromFile,"ISO_IR 127")) os << "ISO-8859-6"; else if (!strcmp(EncodingFromFile,"ISO_IR 126")) os << "ISO-8859-7"; else if (!strcmp(EncodingFromFile,"ISO_IR 138")) os << "ISO-8859-8"; else os << "UTF-8"; os << "\"?>\n"; } else { os << "UTF-8\"?>\n\n"; } } else { os << "UTF-8\"?>\n\n"; } os << "\n"; PrintDataSet(ds, ts, os); os << ""; } // Drop BulkData by default. // Application programmer can override this mechanism. void XMLPrinter::HandleBulkData(const char *uuid, const TransferSyntax & ts, const char *bulkdata, size_t bulklen) { (void)ts; (void)uuid; (void)bulkdata; (void)bulklen; } }//end namespace gdcm