Update build.
[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 static bool
512 init_xml()
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             }
524           catch (const XMLException &e)
525             {
526               DefaultLogSink().Error("Xerces initialization error: %s\n", e.getMessage());
527               return false;
528             }
529         }
530     }
531
532   return true;
533 }
534
535
536 //
537 class MyTreeHandler : public HandlerBase
538 {
539   ns_map*                  m_Namespaces;
540   std::stack<XMLElement*>  m_Scope;
541   XMLElement*              m_Root;
542
543 public:
544   MyTreeHandler(XMLElement* root) : m_Namespaces(0), m_Root(root) {
545     assert(m_Root);
546     m_Namespaces = new ns_map;
547   }
548
549   ~MyTreeHandler() {
550     delete m_Namespaces;
551   }
552
553   ns_map* TakeNamespaceMap() {
554     if ( m_Namespaces == 0 || m_Namespaces->empty() )
555       return 0;
556
557     ns_map* ret = m_Namespaces;
558     m_Namespaces = 0;
559     return ret;
560   }
561
562   //
563   void AddNamespace(const char* ns_prefix, const char* ns_name)
564   {
565     assert(ns_prefix);
566     assert(ns_name);
567
568     if ( ns_prefix[0] == ':' )
569       {
570         ns_prefix++;
571       }
572     else
573       {
574         assert(ns_prefix[0] == 0);
575         ns_prefix = "";
576       }
577
578     ns_map::iterator ni = m_Namespaces->find(ns_name);
579
580     if  ( ni != m_Namespaces->end() )
581       {
582         if ( ni->second->Name() != std::string(ns_name) )
583           {
584             DefaultLogSink().Error("Duplicate prefix: %s\n", ns_prefix);
585             return;
586           }
587       }
588     else
589       {
590         XMLNamespace* Namespace = new XMLNamespace(ns_prefix, ns_name);
591         m_Namespaces->insert(ns_map::value_type(ns_prefix, Namespace));
592       }
593
594     assert(!m_Namespaces->empty());
595   }
596
597   //
598   void startElement(const XMLCh* const x_name,
599                     XERCES_CPP_NAMESPACE::AttributeList& attributes)
600   {
601     assert(x_name);
602
603     const char* tx_name = XMLString::transcode(x_name);
604     const char* name = tx_name;
605     XMLElement* Element;
606     const char* ns_root = name;
607     const char* local_name = strchr(name, ':');
608
609     if ( local_name != 0 )
610       name = local_name + 1;
611
612     if ( m_Scope.empty() )
613       {
614         m_Scope.push(m_Root);
615       }
616     else
617       {
618         Element = m_Scope.top();
619         m_Scope.push(Element->AddChild(name));
620       }
621
622     Element = m_Scope.top();
623     Element->SetName(name);
624
625     // set attributes
626     ui32_t a_len = attributes.getLength();
627
628     for ( ui32_t i = 0; i < a_len; i++)
629       {
630         const XMLCh* aname = attributes.getName(i);
631         const XMLCh* value = attributes.getValue(i);
632         assert(aname);
633         assert(value);
634
635         char* x_aname = XMLString::transcode(aname);
636         char* x_value = XMLString::transcode(value);
637
638         if ( strncmp(x_aname, "xmlns", 5) == 0 )
639           AddNamespace(x_aname+5, x_value);
640
641         if ( ( local_name = strchr(x_aname, ':') ) == 0 )
642           local_name = x_aname;
643         else
644           local_name++;
645
646         Element->SetAttr(local_name, x_value);
647
648         XMLString::release(&x_aname);
649         XMLString::release(&x_value);
650       }
651
652     // map the namespace
653     std::string key;
654     if ( ns_root != name )
655       key.assign(ns_root, name - ns_root - 1);
656   
657     ns_map::iterator ni = m_Namespaces->find(key);
658     if ( ni != m_Namespaces->end() )
659       Element->SetNamespace(ni->second);
660
661     XMLString::release((char**)&tx_name);
662   }
663
664   void endElement(const XMLCh *const name) {
665     m_Scope.pop();
666   }
667
668   void characters(const XMLCh *const chars, const unsigned int length)
669   {
670     if ( length > 0 )
671       {
672         char* text = XMLString::transcode(chars);
673         m_Scope.top()->AppendBody(text);
674         XMLString::release(&text);
675       }
676   }
677 };
678
679 //
680 bool
681 Kumu::XMLElement::ParseString(const std::string& document)
682 {
683   if ( document.empty() )
684     return false;
685
686   if ( ! init_xml() )
687     return false;
688
689   SAXParser* parser = new SAXParser();
690   parser->setDoValidation(true);
691   parser->setDoNamespaces(true);    // optional
692
693   MyTreeHandler* docHandler = new MyTreeHandler(this);
694   ErrorHandler* errHandler = (ErrorHandler*)docHandler;
695   parser->setDocumentHandler(docHandler);
696
697   try
698     {
699       MemBufInputSource xmlSource(reinterpret_cast<const XMLByte*>(document.c_str()),
700                                   static_cast<const unsigned int>(document.size()),
701                                   "pidc_rules_file");
702
703       parser->parse(xmlSource);
704     }
705   catch (const XMLException& e)
706     {
707       char* message = XMLString::transcode(e.getMessage());
708       DefaultLogSink().Error("Parser error: %s\n", message);
709       XMLString::release(&message);
710       return false;
711     }
712   catch (const SAXParseException& e)
713     {
714       char* message = XMLString::transcode(e.getMessage());
715       DefaultLogSink().Error("Parser error: %s at line %d\n", message, e.getLineNumber());
716       XMLString::release(&message);
717       return false;
718     }
719   catch (...)
720     {
721       DefaultLogSink().Error("Unexpected XML parser error\n");
722       return false;
723     }
724   
725   m_NamespaceOwner = (void*)docHandler->TakeNamespaceMap();
726   delete parser;
727   delete docHandler;
728   return true;
729 }
730
731 //
732 bool
733 Kumu::StringIsXML(const char* document, ui32_t len)
734 {
735   if ( document == 0 || *document == 0 )
736     return false;
737
738   if ( ! init_xml() )
739     return false;
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 //