added ostream for log, SAX parser improvements
[asdcplib.git] / src / KM_xml.cpp
1 /*
2 Copyright (c) 2005-2008, 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 Kumu::XMLElement*
142 Kumu::XMLElement::AddChildWithContent(const char* name, const char* value)
143 {
144   assert(name);
145   assert(value);
146   XMLElement* tmpE = new XMLElement(name);
147   tmpE->m_Body = value;
148   m_ChildList.push_back(tmpE);
149   return tmpE;
150 }
151
152 //
153 Kumu::XMLElement*
154 Kumu::XMLElement::AddChildWithPrefixedContent(const char* name, const char* prefix, const char* value)
155 {
156   XMLElement* tmpE = new XMLElement(name);
157   tmpE->m_Body = prefix;
158   tmpE->m_Body += value;
159   m_ChildList.push_back(tmpE);
160   return tmpE;
161 }
162
163 //
164 void
165 Kumu::XMLElement::AddComment(const char* value)
166 {
167   m_Body += "  <!-- ";
168   m_Body += value;
169   m_Body += " -->\n";
170 }
171
172 //
173 void
174 Kumu::XMLElement::Render(std::string& outbuf) const
175 {
176   outbuf = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
177   RenderElement(outbuf, 0);
178 }
179
180 //
181 inline void
182 add_spacer(std::string& outbuf, i32_t depth)
183 {
184   while ( depth-- )
185     outbuf+= "  ";
186 }
187
188 //
189 void
190 Kumu::XMLElement::RenderElement(std::string& outbuf, ui32_t depth) const
191 {
192   add_spacer(outbuf, depth);
193
194   outbuf += "<";
195   outbuf += m_Name;
196
197   // render attributes
198   for ( Attr_i i = m_AttrList.begin(); i != m_AttrList.end(); i++ )
199     {
200       outbuf += " ";
201       outbuf += (*i).name;
202       outbuf += "=\"";
203       outbuf += (*i).value;
204       outbuf += "\"";
205     }
206
207   outbuf += ">";
208
209   // body contents and children
210   if ( ! m_ChildList.empty() )
211     {
212       outbuf += "\n";
213
214       // render body
215       if ( m_Body.length() > 0 )
216         outbuf += m_Body;
217
218       for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
219         (*i)->RenderElement(outbuf, depth + 1);
220
221       add_spacer(outbuf, depth);
222     }
223   else if ( m_Body.length() > 0 )
224     {
225       outbuf += m_Body;
226     }
227
228   outbuf += "</";
229   outbuf += m_Name;
230   outbuf += ">\n";
231 }
232
233 //
234 bool
235 Kumu::XMLElement::HasName(const char* name) const
236 {
237   if ( name == 0 || *name == 0 )
238     return false;
239
240   return (m_Name == name);
241 }
242
243
244 void
245 Kumu::XMLElement::SetName(const char* name)
246 {
247   if ( name != 0)
248     m_Name = name;
249 }
250
251 //
252 const char*
253 Kumu::XMLElement::GetAttrWithName(const char* name) const
254 {
255   for ( Attr_i i = m_AttrList.begin(); i != m_AttrList.end(); i++ )
256     {
257       if ( (*i).name == name )
258         return (*i).value.c_str();
259     }
260
261   return 0;
262 }
263
264 //
265 Kumu::XMLElement*
266 Kumu::XMLElement::GetChildWithName(const char* name) const
267 {
268   for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
269     {
270       if ( (*i)->HasName(name) )
271         return *i;
272     }
273
274   return 0;
275 }
276
277 //
278 const Kumu::ElementList&
279 Kumu::XMLElement::GetChildrenWithName(const char* name, ElementList& outList) const
280 {
281   assert(name);
282   for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
283     {
284       if ( (*i)->HasName(name) )
285         outList.push_back(*i);
286
287       if ( ! (*i)->m_ChildList.empty() )
288         (*i)->GetChildrenWithName(name, outList);
289     }
290
291   return outList;
292 }
293
294 //----------------------------------------------------------------------------------------------------
295
296 #ifdef HAVE_EXPAT
297
298
299 class ExpatParseContext
300 {
301   KM_NO_COPY_CONSTRUCT(ExpatParseContext);
302   ExpatParseContext();
303 public:
304   ns_map*                  Namespaces;
305   std::stack<XMLElement*>  Scope;
306   XMLElement*              Root;
307
308   ExpatParseContext(XMLElement* root) : Root(root) {
309     Namespaces = new ns_map;
310     assert(Root);
311   }
312
313   ~ExpatParseContext() {}
314 };
315
316 // expat wrapper functions
317 // 
318 static void
319 xph_start(void* p, const XML_Char* name, const XML_Char** attrs)
320 {
321   assert(p);  assert(name);  assert(attrs);
322   ExpatParseContext* Ctx = (ExpatParseContext*)p;
323   XMLElement* Element;
324
325   const char* ns_root = name;
326   const char* local_name = strchr(name, '|');
327   if ( local_name != 0 )
328     name = local_name + 1;
329
330   if ( Ctx->Scope.empty() )
331     {
332       Ctx->Scope.push(Ctx->Root);
333     }
334   else
335     {
336       Element = Ctx->Scope.top();
337       Ctx->Scope.push(Element->AddChild(name));
338     }
339
340   Element = Ctx->Scope.top();
341   Element->SetName(name);
342
343   // map the namespace
344   std::string key;
345   if ( ns_root != name )
346     key.assign(ns_root, name - ns_root - 1);
347   
348   ns_map::iterator ni = Ctx->Namespaces->find(key);
349   if ( ni != Ctx->Namespaces->end() )
350     Element->SetNamespace(ni->second);
351
352   // set attributes
353   for ( int i = 0; attrs[i] != 0; i += 2 )
354     {
355       if ( ( local_name = strchr(attrs[i], '|') ) == 0 )
356         local_name = attrs[i];
357       else
358         local_name++;
359
360       Element->SetAttr(local_name, attrs[i+1]);
361     }
362 }
363
364 //
365 static void
366 xph_end(void* p, const XML_Char* name)
367 {
368   assert(p);  assert(name);
369   ExpatParseContext* Ctx = (ExpatParseContext*)p;
370   Ctx->Scope.pop();
371 }
372
373 //
374 static void
375 xph_char(void* p, const XML_Char* data, int len)
376 {
377   assert(p);  assert(data);
378   ExpatParseContext* Ctx = (ExpatParseContext*)p;
379
380   if ( len > 0 )
381     {
382       std::string tmp_str;
383       tmp_str.assign(data, len);
384       Ctx->Scope.top()->AppendBody(tmp_str);
385     }
386 }
387
388 //
389 void
390 xph_namespace_start(void* p, const XML_Char* ns_prefix, const XML_Char* ns_name)
391 {
392   assert(p);  assert(ns_name);
393   ExpatParseContext* Ctx = (ExpatParseContext*)p;
394   
395   if ( ns_prefix == 0 )
396     ns_prefix = "";
397
398   ns_map::iterator ni = Ctx->Namespaces->find(ns_name);
399
400   if  ( ni != Ctx->Namespaces->end() )
401     {
402       if ( ni->second->Name() != std::string(ns_name) )
403         {
404           DefaultLogSink().Error("Duplicate prefix: %s\n", ns_prefix);
405           return;
406         }
407     }
408   else
409     {
410       XMLNamespace* Namespace = new XMLNamespace(ns_prefix, ns_name);
411       Ctx->Namespaces->insert(ns_map::value_type(ns_name, Namespace));
412     }
413 }
414
415 //
416 bool
417 Kumu::XMLElement::ParseString(const std::string& document)
418 {
419   XML_Parser Parser = XML_ParserCreateNS("UTF-8", '|');
420
421   if ( Parser == 0 )
422     {
423       DefaultLogSink().Error("Error allocating memory for XML parser.\n");
424       return false;
425     }
426
427   ExpatParseContext Ctx(this);
428   XML_SetUserData(Parser, (void*)&Ctx);
429   XML_SetElementHandler(Parser, xph_start, xph_end);
430   XML_SetCharacterDataHandler(Parser, xph_char);
431   XML_SetStartNamespaceDeclHandler(Parser, xph_namespace_start);
432
433   if ( ! XML_Parse(Parser, document.c_str(), document.size(), 1) )
434     {
435       XML_ParserFree(Parser);
436       DefaultLogSink().Error("XML Parse error on line %d: %s\n",
437                              XML_GetCurrentLineNumber(Parser),
438                              XML_ErrorString(XML_GetErrorCode(Parser)));
439       return false;
440     }
441
442   XML_ParserFree(Parser);
443
444   if ( ! Ctx.Namespaces->empty() )
445     m_NamespaceOwner = (void*)Ctx.Namespaces;
446
447   return true;
448 }
449
450 //------------------------------------------------------------------------------------------
451
452 struct xph_test_wrapper
453 {
454   XML_Parser Parser;
455   bool  Status;
456
457   xph_test_wrapper(XML_Parser p) : Parser(p), Status(false) {}
458 };
459
460 // expat wrapper functions, map callbacks to IASAXHandler
461 // 
462 static void
463 xph_test_start(void* p, const XML_Char* name, const XML_Char** attrs)
464 {
465   assert(p);
466   xph_test_wrapper* Wrapper = (xph_test_wrapper*)p;
467
468   Wrapper->Status = true;
469   XML_StopParser(Wrapper->Parser, false);
470 }
471
472
473 //
474 bool
475 Kumu::StringIsXML(const char* document, ui32_t len)
476 {
477   if ( document == 0 )
478     return false;
479
480   if ( len == 0 )
481     len = strlen(document);
482
483   XML_Parser Parser = XML_ParserCreate("UTF-8");
484
485   if ( Parser == 0 )
486     {
487       DefaultLogSink().Error("Error allocating memory for XML parser.\n");
488       return false;
489     }
490
491   xph_test_wrapper Wrapper(Parser);
492   XML_SetUserData(Parser, (void*)&Wrapper);
493   XML_SetStartElementHandler(Parser, xph_test_start);
494
495   XML_Parse(Parser, document, len, 1);
496   XML_ParserFree(Parser);
497   return Wrapper.Status;
498 }
499
500 #endif
501
502 //----------------------------------------------------------------------------------------------------
503
504 #ifdef HAVE_XERCES_C
505
506 static Mutex sg_Lock;
507 static bool  sg_xml_init = false;
508
509
510 //
511 void
512 asdcp_init_xml_dom()
513 {
514   if ( ! sg_xml_init )
515     {
516       AutoMutex AL(sg_Lock);
517
518       if ( ! sg_xml_init )
519         {
520           try
521             {
522               XMLPlatformUtils::Initialize();
523               sg_xml_init = true;
524             }
525           catch (const XMLException &e)
526             {
527               DefaultLogSink().Error("Xerces initialization error: %s\n", e.getMessage());
528             }
529         }
530     }
531 }
532
533
534 //
535 class MyTreeHandler : public HandlerBase
536 {
537   ns_map*                  m_Namespaces;
538   std::stack<XMLElement*>  m_Scope;
539   XMLElement*              m_Root;
540
541 public:
542   MyTreeHandler(XMLElement* root) : m_Namespaces(0), m_Root(root) {
543     assert(m_Root);
544     m_Namespaces = new ns_map;
545   }
546
547   ~MyTreeHandler() {
548     delete m_Namespaces;
549   }
550
551   ns_map* TakeNamespaceMap() {
552     if ( m_Namespaces == 0 || m_Namespaces->empty() )
553       return 0;
554
555     ns_map* ret = m_Namespaces;
556     m_Namespaces = 0;
557     return ret;
558   }
559
560   //
561   void AddNamespace(const char* ns_prefix, const char* ns_name)
562   {
563     assert(ns_prefix);
564     assert(ns_name);
565
566     if ( ns_prefix[0] == ':' )
567       {
568         ns_prefix++;
569       }
570     else
571       {
572         assert(ns_prefix[0] == 0);
573         ns_prefix = "";
574       }
575
576     ns_map::iterator ni = m_Namespaces->find(ns_name);
577
578     if  ( ni != m_Namespaces->end() )
579       {
580         if ( ni->second->Name() != std::string(ns_name) )
581           {
582             DefaultLogSink().Error("Duplicate prefix: %s\n", ns_prefix);
583             return;
584           }
585       }
586     else
587       {
588         XMLNamespace* Namespace = new XMLNamespace(ns_prefix, ns_name);
589         m_Namespaces->insert(ns_map::value_type(ns_prefix, Namespace));
590       }
591
592     assert(!m_Namespaces->empty());
593   }
594
595   //
596   void startElement(const XMLCh* const x_name,
597                     XERCES_CPP_NAMESPACE::AttributeList& attributes)
598   {
599     assert(x_name);
600
601     const char* tx_name = XMLString::transcode(x_name);
602     const char* name = tx_name;
603     XMLElement* Element;
604     const char* ns_root = name;
605     const char* local_name = strchr(name, ':');
606
607     if ( local_name != 0 )
608       name = local_name + 1;
609
610     if ( m_Scope.empty() )
611       {
612         m_Scope.push(m_Root);
613       }
614     else
615       {
616         Element = m_Scope.top();
617         m_Scope.push(Element->AddChild(name));
618       }
619
620     Element = m_Scope.top();
621     Element->SetName(name);
622
623     // set attributes
624     ui32_t a_len = attributes.getLength();
625
626     for ( ui32_t i = 0; i < a_len; i++)
627       {
628         const XMLCh* aname = attributes.getName(i);
629         const XMLCh* value = attributes.getValue(i);
630         assert(aname);
631         assert(value);
632
633         char* x_aname = XMLString::transcode(aname);
634         char* x_value = XMLString::transcode(value);
635
636         if ( strncmp(x_aname, "xmlns", 5) == 0 )
637           AddNamespace(x_aname+5, x_value);
638
639         if ( ( local_name = strchr(x_aname, ':') ) == 0 )
640           local_name = x_aname;
641         else
642           local_name++;
643
644         Element->SetAttr(local_name, x_value);
645
646         XMLString::release(&x_aname);
647         XMLString::release(&x_value);
648       }
649
650     // map the namespace
651     std::string key;
652     if ( ns_root != name )
653       key.assign(ns_root, name - ns_root - 1);
654   
655     ns_map::iterator ni = m_Namespaces->find(key);
656     if ( ni != m_Namespaces->end() )
657       Element->SetNamespace(ni->second);
658
659     XMLString::release((char**)&tx_name);
660   }
661
662   void endElement(const XMLCh *const name) {
663     m_Scope.pop();
664   }
665
666   void characters(const XMLCh *const chars, const unsigned int length)
667   {
668     if ( length > 0 )
669       {
670         char* text = XMLString::transcode(chars);
671         m_Scope.top()->AppendBody(text);
672         XMLString::release(&text);
673       }
674   }
675 };
676
677 //
678 bool
679 Kumu::XMLElement::ParseString(const std::string& document)
680 {
681   if ( document.empty() )
682     return false;
683
684   asdcp_init_xml_dom();
685
686   int errorCount = 0;
687   SAXParser* parser = new SAXParser();
688   parser->setDoValidation(true);
689   parser->setDoNamespaces(true);    // optional
690
691   MyTreeHandler* docHandler = new MyTreeHandler(this);
692   ErrorHandler* errHandler = (ErrorHandler*)docHandler;
693   parser->setDocumentHandler(docHandler);
694
695   try
696     {
697       MemBufInputSource xmlSource(reinterpret_cast<const XMLByte*>(document.c_str()),
698                                   static_cast<const unsigned int>(document.size()),
699                                   "pidc_rules_file");
700
701       parser->parse(xmlSource);
702     }
703   catch (const XMLException& e)
704     {
705       char* message = XMLString::transcode(e.getMessage());
706       DefaultLogSink().Error("Parser error: %s\n", message);
707       XMLString::release(&message);
708       errorCount++;
709     }
710   catch (const SAXParseException& e)
711     {
712       char* message = XMLString::transcode(e.getMessage());
713       DefaultLogSink().Error("Parser error: %s at line %d\n", message, e.getLineNumber());
714       XMLString::release(&message);
715       errorCount++;
716     }
717   catch (...)
718     {
719       DefaultLogSink().Error("Unexpected XML parser error\n");
720       errorCount++;
721     }
722   
723   if ( errorCount == 0 )
724     m_NamespaceOwner = (void*)docHandler->TakeNamespaceMap();
725
726   delete parser;
727   delete docHandler;
728
729   return errorCount > 0 ? false : true;
730 }
731
732 //
733 bool
734 Kumu::StringIsXML(const char* document, ui32_t len)
735 {
736   if ( document == 0 || *document == 0 )
737     return false;
738
739   asdcp_init_xml_dom();
740
741   if ( len == 0 )
742     len = strlen(document);
743
744   SAXParser parser;
745   XMLPScanToken token;
746   bool status = false;
747
748   try
749     {
750       MemBufInputSource xmlSource(reinterpret_cast<const XMLByte*>(document),
751                                   static_cast<const unsigned int>(len),
752                                   "pidc_rules_file");
753
754       if ( parser.parseFirst(xmlSource, token) )
755         {
756           if ( parser.parseNext(token) )
757             status = true;
758         }
759     }
760   catch (...)
761     {
762     }
763   
764   return status;
765 }
766
767
768 #endif
769
770 //----------------------------------------------------------------------------------------------------
771
772 #if ! defined(HAVE_EXPAT) && ! defined(HAVE_XERCES_C)
773
774 //
775 bool
776 Kumu::XMLElement::ParseString(const std::string& document)
777 {
778   DefaultLogSink().Error("asdcplib compiled without XML parser support.\n");
779   return false;
780 }
781
782 //
783 bool
784 Kumu::StringIsXML(const char* document, ui32_t len)
785 {
786   DefaultLogSink().Error("Kumu compiled without XML parser support.\n");
787   return false;
788 }
789
790 #endif
791
792
793 //
794 // end KM_xml.cpp
795 //