Update copyright dates.
[asdcplib.git] / src / KM_xml.cpp
1 /*
2 Copyright (c) 2005-2009, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    KM_xml.cpp
28     \version $Id$
29     \brief   XML writer
30 */
31
32 #include <KM_xml.h>
33 #include <KM_log.h>
34 #include <KM_mutex.h>
35 #include <stack>
36 #include <map>
37
38 //#undef HAVE_EXPAT
39 //#define HAVE_XERCES_C
40
41 #ifdef HAVE_EXPAT
42 # ifdef HAVE_XERCES_C
43 #  error "Both HAVE_EXPAT and HAVE_XERCES_C defined"
44 # endif
45 #include <expat.h>
46 #endif
47
48 #ifdef HAVE_XERCES_C
49 # ifdef HAVE_EXPAT
50 #  error "Both HAVE_EXPAT and HAVE_XERCES_C defined"
51 # endif
52
53 #include <xercesc/util/PlatformUtils.hpp>
54 #include <xercesc/util/XMLString.hpp>
55 #include <xercesc/sax/AttributeList.hpp>
56 #include <xercesc/sax/HandlerBase.hpp>
57 #include <xercesc/sax/ErrorHandler.hpp>
58 #include <xercesc/sax/SAXParseException.hpp>
59 #include <xercesc/parsers/SAXParser.hpp>
60 #include <xercesc/framework/MemBufInputSource.hpp>
61 #include <xercesc/framework/XMLPScanToken.hpp>
62
63
64 XERCES_CPP_NAMESPACE_USE 
65 #endif
66
67 using namespace Kumu;
68
69
70 class ns_map : public std::map<std::string, XMLNamespace*>
71 {
72 public:
73   ~ns_map()
74   {
75     while ( ! empty() )
76       {
77         ns_map::iterator ni = begin();
78         delete ni->second;
79         erase(ni);
80       }
81   }
82 };
83
84
85 Kumu::XMLElement::XMLElement(const char* name) : m_Namespace(0), m_NamespaceOwner(0)
86 {
87   m_Name = name;
88 }
89
90 Kumu::XMLElement::~XMLElement()
91 {
92   for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
93     delete *i;
94
95   delete (ns_map*)m_NamespaceOwner;
96 }
97
98 //
99 void
100 Kumu::XMLElement::SetAttr(const char* name, const char* value)
101 {
102   NVPair TmpVal;
103   TmpVal.name = name;
104   TmpVal.value = value;
105
106   m_AttrList.push_back(TmpVal);
107 }
108
109 //
110 Kumu::XMLElement*
111 Kumu::XMLElement::AddChild(Kumu::XMLElement* element)
112 {
113   m_ChildList.push_back(element); // takes posession!
114   return element;
115 }
116
117 //
118 Kumu::XMLElement*
119 Kumu::XMLElement::AddChild(const char* name)
120 {
121   XMLElement* tmpE = new XMLElement(name);
122   m_ChildList.push_back(tmpE);
123   return tmpE;
124 }
125
126 //
127 Kumu::XMLElement*
128 Kumu::XMLElement::AddChildWithContent(const char* name, const std::string& value)
129 {
130   return AddChildWithContent(name, value.c_str());
131 }
132
133 //
134 void
135 Kumu::XMLElement::AppendBody(const std::string& value)
136 {
137   m_Body += value;
138 }
139
140 //
141 void
142 Kumu::XMLElement::SetBody(const std::string& value)
143 {
144   m_Body = value;
145 }
146
147 //
148 Kumu::XMLElement*
149 Kumu::XMLElement::AddChildWithContent(const char* name, const char* value)
150 {
151   assert(name);
152   assert(value);
153   XMLElement* tmpE = new XMLElement(name);
154   tmpE->m_Body = value;
155   m_ChildList.push_back(tmpE);
156   return tmpE;
157 }
158
159 //
160 Kumu::XMLElement*
161 Kumu::XMLElement::AddChildWithPrefixedContent(const char* name, const char* prefix, const char* value)
162 {
163   XMLElement* tmpE = new XMLElement(name);
164   tmpE->m_Body = prefix;
165   tmpE->m_Body += value;
166   m_ChildList.push_back(tmpE);
167   return tmpE;
168 }
169
170 //
171 void
172 Kumu::XMLElement::AddComment(const char* value)
173 {
174   m_Body += "  <!-- ";
175   m_Body += value;
176   m_Body += " -->\n";
177 }
178
179 //
180 void
181 Kumu::XMLElement::Render(std::string& outbuf) const
182 {
183   outbuf = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
184   RenderElement(outbuf, 0);
185 }
186
187 //
188 inline void
189 add_spacer(std::string& outbuf, i32_t depth)
190 {
191   while ( depth-- )
192     outbuf+= "  ";
193 }
194
195 //
196 void
197 Kumu::XMLElement::RenderElement(std::string& outbuf, ui32_t depth) const
198 {
199   add_spacer(outbuf, depth);
200
201   outbuf += "<";
202   outbuf += m_Name;
203
204   // render attributes
205   for ( Attr_i i = m_AttrList.begin(); i != m_AttrList.end(); i++ )
206     {
207       outbuf += " ";
208       outbuf += (*i).name;
209       outbuf += "=\"";
210       outbuf += (*i).value;
211       outbuf += "\"";
212     }
213
214   outbuf += ">";
215
216   // body contents and children
217   if ( ! m_ChildList.empty() )
218     {
219       outbuf += "\n";
220
221       // render body
222       if ( m_Body.length() > 0 )
223         outbuf += m_Body;
224
225       for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
226         (*i)->RenderElement(outbuf, depth + 1);
227
228       add_spacer(outbuf, depth);
229     }
230   else if ( m_Body.length() > 0 )
231     {
232       outbuf += m_Body;
233     }
234
235   outbuf += "</";
236   outbuf += m_Name;
237   outbuf += ">\n";
238 }
239
240 //
241 bool
242 Kumu::XMLElement::HasName(const char* name) const
243 {
244   if ( name == 0 || *name == 0 )
245     return false;
246
247   return (m_Name == name);
248 }
249
250
251 void
252 Kumu::XMLElement::SetName(const char* name)
253 {
254   if ( name != 0)
255     m_Name = name;
256 }
257
258 //
259 const char*
260 Kumu::XMLElement::GetAttrWithName(const char* name) const
261 {
262   for ( Attr_i i = m_AttrList.begin(); i != m_AttrList.end(); i++ )
263     {
264       if ( (*i).name == name )
265         return (*i).value.c_str();
266     }
267
268   return 0;
269 }
270
271 //
272 Kumu::XMLElement*
273 Kumu::XMLElement::GetChildWithName(const char* name) const
274 {
275   for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
276     {
277       if ( (*i)->HasName(name) )
278         return *i;
279     }
280
281   return 0;
282 }
283
284 //
285 const Kumu::ElementList&
286 Kumu::XMLElement::GetChildrenWithName(const char* name, ElementList& outList) const
287 {
288   assert(name);
289   for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
290     {
291       if ( (*i)->HasName(name) )
292         outList.push_back(*i);
293
294       if ( ! (*i)->m_ChildList.empty() )
295         (*i)->GetChildrenWithName(name, outList);
296     }
297
298   return outList;
299 }
300
301 //
302 void
303 Kumu::XMLElement::DeleteAttributes()
304 {
305   m_AttrList.clear();
306 }
307
308 //
309 void
310 Kumu::XMLElement::DeleteAttrWithName(const char* name)
311 {
312   assert(name);
313   AttributeList::iterator i;
314   for ( i = m_AttrList.begin(); i != m_AttrList.end(); i++ )
315     {
316       if ( i->name == std::string(name) )
317         m_AttrList.erase(i);
318     }
319 }
320
321 //
322 void
323 Kumu::XMLElement::DeleteChildren()
324 {
325   for ( ElementList::iterator i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
326     {
327       delete *i;
328       m_ChildList.erase(i);
329     }
330 }
331
332 //
333 void
334 Kumu::XMLElement::DeleteChild(const XMLElement* element)
335 {
336   if ( element != 0 )
337     {
338       for ( ElementList::iterator i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
339         {
340           if ( *i == element )
341             {
342               delete *i;
343               m_ChildList.erase(i);
344               return;
345             }
346         }
347     }
348 }
349
350 //
351 void
352 Kumu::XMLElement::ForgetChild(const XMLElement* element)
353 {
354   if ( element != 0 )
355     {
356       for ( ElementList::iterator i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
357         {
358           if ( *i == element )
359             {
360               m_ChildList.erase(i);
361               return;
362             }
363         }
364     }
365 }
366
367
368 //----------------------------------------------------------------------------------------------------
369
370 #ifdef HAVE_EXPAT
371
372
373 class ExpatParseContext
374 {
375   KM_NO_COPY_CONSTRUCT(ExpatParseContext);
376   ExpatParseContext();
377 public:
378   ns_map*                  Namespaces;
379   std::stack<XMLElement*>  Scope;
380   XMLElement*              Root;
381
382   ExpatParseContext(XMLElement* root) : Root(root) {
383     Namespaces = new ns_map;
384     assert(Root);
385   }
386
387   ~ExpatParseContext() {}
388 };
389
390 // expat wrapper functions
391 // 
392 static void
393 xph_start(void* p, const XML_Char* name, const XML_Char** attrs)
394 {
395   assert(p);  assert(name);  assert(attrs);
396   ExpatParseContext* Ctx = (ExpatParseContext*)p;
397   XMLElement* Element;
398
399   const char* ns_root = name;
400   const char* local_name = strchr(name, '|');
401   if ( local_name != 0 )
402     name = local_name + 1;
403
404   if ( Ctx->Scope.empty() )
405     {
406       Ctx->Scope.push(Ctx->Root);
407     }
408   else
409     {
410       Element = Ctx->Scope.top();
411       Ctx->Scope.push(Element->AddChild(name));
412     }
413
414   Element = Ctx->Scope.top();
415   Element->SetName(name);
416
417   // map the namespace
418   std::string key;
419   if ( ns_root != name )
420     key.assign(ns_root, name - ns_root - 1);
421   
422   ns_map::iterator ni = Ctx->Namespaces->find(key);
423   if ( ni != Ctx->Namespaces->end() )
424     Element->SetNamespace(ni->second);
425
426   // set attributes
427   for ( int i = 0; attrs[i] != 0; i += 2 )
428     {
429       if ( ( local_name = strchr(attrs[i], '|') ) == 0 )
430         local_name = attrs[i];
431       else
432         local_name++;
433
434       Element->SetAttr(local_name, attrs[i+1]);
435     }
436 }
437
438 //
439 static void
440 xph_end(void* p, const XML_Char* name)
441 {
442   assert(p);  assert(name);
443   ExpatParseContext* Ctx = (ExpatParseContext*)p;
444   Ctx->Scope.pop();
445 }
446
447 //
448 static void
449 xph_char(void* p, const XML_Char* data, int len)
450 {
451   assert(p);  assert(data);
452   ExpatParseContext* Ctx = (ExpatParseContext*)p;
453
454   if ( len > 0 )
455     {
456       std::string tmp_str;
457       tmp_str.assign(data, len);
458       Ctx->Scope.top()->AppendBody(tmp_str);
459     }
460 }
461
462 //
463 void
464 xph_namespace_start(void* p, const XML_Char* ns_prefix, const XML_Char* ns_name)
465 {
466   assert(p);  assert(ns_name);
467   ExpatParseContext* Ctx = (ExpatParseContext*)p;
468   
469   if ( ns_prefix == 0 )
470     ns_prefix = "";
471
472   ns_map::iterator ni = Ctx->Namespaces->find(ns_name);
473
474   if  ( ni != Ctx->Namespaces->end() )
475     {
476       if ( ni->second->Name() != std::string(ns_name) )
477         {
478           DefaultLogSink().Error("Duplicate prefix: %s\n", ns_prefix);
479           return;
480         }
481     }
482   else
483     {
484       XMLNamespace* Namespace = new XMLNamespace(ns_prefix, ns_name);
485       Ctx->Namespaces->insert(ns_map::value_type(ns_name, Namespace));
486     }
487 }
488
489 //
490 bool
491 Kumu::XMLElement::ParseString(const std::string& document)
492 {
493   XML_Parser Parser = XML_ParserCreateNS("UTF-8", '|');
494
495   if ( Parser == 0 )
496     {
497       DefaultLogSink().Error("Error allocating memory for XML parser.\n");
498       return false;
499     }
500
501   ExpatParseContext Ctx(this);
502   XML_SetUserData(Parser, (void*)&Ctx);
503   XML_SetElementHandler(Parser, xph_start, xph_end);
504   XML_SetCharacterDataHandler(Parser, xph_char);
505   XML_SetStartNamespaceDeclHandler(Parser, xph_namespace_start);
506
507   if ( ! XML_Parse(Parser, document.c_str(), document.size(), 1) )
508     {
509       XML_ParserFree(Parser);
510       DefaultLogSink().Error("XML Parse error on line %d: %s\n",
511                              XML_GetCurrentLineNumber(Parser),
512                              XML_ErrorString(XML_GetErrorCode(Parser)));
513       return false;
514     }
515
516   XML_ParserFree(Parser);
517
518   if ( ! Ctx.Namespaces->empty() )
519     m_NamespaceOwner = (void*)Ctx.Namespaces;
520
521   return true;
522 }
523
524 //------------------------------------------------------------------------------------------
525
526 struct xph_test_wrapper
527 {
528   XML_Parser Parser;
529   bool  Status;
530
531   xph_test_wrapper(XML_Parser p) : Parser(p), Status(false) {}
532 };
533
534 // expat wrapper functions, map callbacks to IASAXHandler
535 // 
536 static void
537 xph_test_start(void* p, const XML_Char* name, const XML_Char** attrs)
538 {
539   assert(p);
540   xph_test_wrapper* Wrapper = (xph_test_wrapper*)p;
541
542   Wrapper->Status = true;
543   XML_StopParser(Wrapper->Parser, false);
544 }
545
546
547 //
548 bool
549 Kumu::StringIsXML(const char* document, ui32_t len)
550 {
551   if ( document == 0 )
552     return false;
553
554   if ( len == 0 )
555     len = strlen(document);
556
557   XML_Parser Parser = XML_ParserCreate("UTF-8");
558
559   if ( Parser == 0 )
560     {
561       DefaultLogSink().Error("Error allocating memory for XML parser.\n");
562       return false;
563     }
564
565   xph_test_wrapper Wrapper(Parser);
566   XML_SetUserData(Parser, (void*)&Wrapper);
567   XML_SetStartElementHandler(Parser, xph_test_start);
568
569   XML_Parse(Parser, document, len, 1);
570   XML_ParserFree(Parser);
571   return Wrapper.Status;
572 }
573
574 #endif
575
576 //----------------------------------------------------------------------------------------------------
577
578 #ifdef HAVE_XERCES_C
579
580 static Mutex sg_Lock;
581 static bool  sg_xml_init = false;
582
583
584 //
585 void
586 asdcp_init_xml_dom()
587 {
588   if ( ! sg_xml_init )
589     {
590       AutoMutex AL(sg_Lock);
591
592       if ( ! sg_xml_init )
593         {
594           try
595             {
596               XMLPlatformUtils::Initialize();
597               sg_xml_init = true;
598             }
599           catch (const XMLException &e)
600             {
601               DefaultLogSink().Error("Xerces initialization error: %s\n", e.getMessage());
602             }
603         }
604     }
605 }
606
607
608 //
609 class MyTreeHandler : public HandlerBase
610 {
611   ns_map*                  m_Namespaces;
612   std::stack<XMLElement*>  m_Scope;
613   XMLElement*              m_Root;
614
615 public:
616   MyTreeHandler(XMLElement* root) : m_Namespaces(0), m_Root(root) {
617     assert(m_Root);
618     m_Namespaces = new ns_map;
619   }
620
621   ~MyTreeHandler() {
622     delete m_Namespaces;
623   }
624
625   ns_map* TakeNamespaceMap() {
626     if ( m_Namespaces == 0 || m_Namespaces->empty() )
627       return 0;
628
629     ns_map* ret = m_Namespaces;
630     m_Namespaces = 0;
631     return ret;
632   }
633
634   //
635   void AddNamespace(const char* ns_prefix, const char* ns_name)
636   {
637     assert(ns_prefix);
638     assert(ns_name);
639
640     if ( ns_prefix[0] == ':' )
641       {
642         ns_prefix++;
643       }
644     else
645       {
646         assert(ns_prefix[0] == 0);
647         ns_prefix = "";
648       }
649
650     ns_map::iterator ni = m_Namespaces->find(ns_name);
651
652     if  ( ni != m_Namespaces->end() )
653       {
654         if ( ni->second->Name() != std::string(ns_name) )
655           {
656             DefaultLogSink().Error("Duplicate prefix: %s\n", ns_prefix);
657             return;
658           }
659       }
660     else
661       {
662         XMLNamespace* Namespace = new XMLNamespace(ns_prefix, ns_name);
663         m_Namespaces->insert(ns_map::value_type(ns_prefix, Namespace));
664       }
665
666     assert(!m_Namespaces->empty());
667   }
668
669   //
670   void startElement(const XMLCh* const x_name,
671                     XERCES_CPP_NAMESPACE::AttributeList& attributes)
672   {
673     assert(x_name);
674
675     const char* tx_name = XMLString::transcode(x_name);
676     const char* name = tx_name;
677     XMLElement* Element;
678     const char* ns_root = name;
679     const char* local_name = strchr(name, ':');
680
681     if ( local_name != 0 )
682       name = local_name + 1;
683
684     if ( m_Scope.empty() )
685       {
686         m_Scope.push(m_Root);
687       }
688     else
689       {
690         Element = m_Scope.top();
691         m_Scope.push(Element->AddChild(name));
692       }
693
694     Element = m_Scope.top();
695     Element->SetName(name);
696
697     // set attributes
698     ui32_t a_len = attributes.getLength();
699
700     for ( ui32_t i = 0; i < a_len; i++)
701       {
702         const XMLCh* aname = attributes.getName(i);
703         const XMLCh* value = attributes.getValue(i);
704         assert(aname);
705         assert(value);
706
707         char* x_aname = XMLString::transcode(aname);
708         char* x_value = XMLString::transcode(value);
709
710         if ( strncmp(x_aname, "xmlns", 5) == 0 )
711           AddNamespace(x_aname+5, x_value);
712
713         if ( ( local_name = strchr(x_aname, ':') ) == 0 )
714           local_name = x_aname;
715         else
716           local_name++;
717
718         Element->SetAttr(local_name, x_value);
719
720         XMLString::release(&x_aname);
721         XMLString::release(&x_value);
722       }
723
724     // map the namespace
725     std::string key;
726     if ( ns_root != name )
727       key.assign(ns_root, name - ns_root - 1);
728   
729     ns_map::iterator ni = m_Namespaces->find(key);
730     if ( ni != m_Namespaces->end() )
731       Element->SetNamespace(ni->second);
732
733     XMLString::release((char**)&tx_name);
734   }
735
736   void endElement(const XMLCh *const name) {
737     m_Scope.pop();
738   }
739
740   void characters(const XMLCh *const chars, const unsigned int length)
741   {
742     if ( length > 0 )
743       {
744         char* text = XMLString::transcode(chars);
745         m_Scope.top()->AppendBody(text);
746         XMLString::release(&text);
747       }
748   }
749 };
750
751 //
752 bool
753 Kumu::XMLElement::ParseString(const std::string& document)
754 {
755   if ( document.empty() )
756     return false;
757
758   asdcp_init_xml_dom();
759
760   int errorCount = 0;
761   SAXParser* parser = new SAXParser();
762   parser->setDoValidation(true);
763   parser->setDoNamespaces(true);    // optional
764
765   MyTreeHandler* docHandler = new MyTreeHandler(this);
766   ErrorHandler* errHandler = (ErrorHandler*)docHandler;
767   parser->setDocumentHandler(docHandler);
768
769   try
770     {
771       MemBufInputSource xmlSource(reinterpret_cast<const XMLByte*>(document.c_str()),
772                                   static_cast<const unsigned int>(document.size()),
773                                   "pidc_rules_file");
774
775       parser->parse(xmlSource);
776     }
777   catch (const XMLException& e)
778     {
779       char* message = XMLString::transcode(e.getMessage());
780       DefaultLogSink().Error("Parser error: %s\n", message);
781       XMLString::release(&message);
782       errorCount++;
783     }
784   catch (const SAXParseException& e)
785     {
786       char* message = XMLString::transcode(e.getMessage());
787       DefaultLogSink().Error("Parser error: %s at line %d\n", message, e.getLineNumber());
788       XMLString::release(&message);
789       errorCount++;
790     }
791   catch (...)
792     {
793       DefaultLogSink().Error("Unexpected XML parser error\n");
794       errorCount++;
795     }
796   
797   if ( errorCount == 0 )
798     m_NamespaceOwner = (void*)docHandler->TakeNamespaceMap();
799
800   delete parser;
801   delete docHandler;
802
803   return errorCount > 0 ? false : true;
804 }
805
806 //
807 bool
808 Kumu::StringIsXML(const char* document, ui32_t len)
809 {
810   if ( document == 0 || *document == 0 )
811     return false;
812
813   asdcp_init_xml_dom();
814
815   if ( len == 0 )
816     len = strlen(document);
817
818   SAXParser parser;
819   XMLPScanToken token;
820   bool status = false;
821
822   try
823     {
824       MemBufInputSource xmlSource(reinterpret_cast<const XMLByte*>(document),
825                                   static_cast<const unsigned int>(len),
826                                   "pidc_rules_file");
827
828       if ( parser.parseFirst(xmlSource, token) )
829         {
830           if ( parser.parseNext(token) )
831             status = true;
832         }
833     }
834   catch (...)
835     {
836     }
837   
838   return status;
839 }
840
841
842 #endif
843
844 //----------------------------------------------------------------------------------------------------
845
846 #if ! defined(HAVE_EXPAT) && ! defined(HAVE_XERCES_C)
847
848 //
849 bool
850 Kumu::XMLElement::ParseString(const std::string& document)
851 {
852   DefaultLogSink().Error("asdcplib compiled without XML parser support.\n");
853   return false;
854 }
855
856 //
857 bool
858 Kumu::StringIsXML(const char* document, ui32_t len)
859 {
860   DefaultLogSink().Error("Kumu compiled without XML parser support.\n");
861   return false;
862 }
863
864 #endif
865
866
867 //
868 // end KM_xml.cpp
869 //