298a55f528c55cc37814d91bfbc72b48a0dd16dc
[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 = m_AttrList.begin();
314
315   while ( i != m_AttrList.end() )
316     {
317       if ( i->name == std::string(name) )
318         m_AttrList.erase(i++);
319       else
320         ++i;
321     }
322 }
323
324 //
325 void
326 Kumu::XMLElement::DeleteChildren()
327 {
328   while ( ! m_ChildList.empty() )
329     {
330       delete m_ChildList.back();
331       m_ChildList.pop_back();
332     }
333 }
334
335 //
336 void
337 Kumu::XMLElement::DeleteChild(const XMLElement* element)
338 {
339   if ( element != 0 )
340     {
341       for ( ElementList::iterator i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
342         {
343           if ( *i == element )
344             {
345               delete *i;
346               m_ChildList.erase(i);
347               return;
348             }
349         }
350     }
351 }
352
353 //
354 void
355 Kumu::XMLElement::ForgetChild(const XMLElement* element)
356 {
357   if ( element != 0 )
358     {
359       for ( ElementList::iterator i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
360         {
361           if ( *i == element )
362             {
363               m_ChildList.erase(i);
364               return;
365             }
366         }
367     }
368 }
369
370
371 //----------------------------------------------------------------------------------------------------
372
373 #ifdef HAVE_EXPAT
374
375
376 class ExpatParseContext
377 {
378   KM_NO_COPY_CONSTRUCT(ExpatParseContext);
379   ExpatParseContext();
380 public:
381   ns_map*                  Namespaces;
382   std::stack<XMLElement*>  Scope;
383   XMLElement*              Root;
384
385   ExpatParseContext(XMLElement* root) : Root(root) {
386     Namespaces = new ns_map;
387     assert(Root);
388   }
389
390   ~ExpatParseContext() {}
391 };
392
393 // expat wrapper functions
394 // 
395 static void
396 xph_start(void* p, const XML_Char* name, const XML_Char** attrs)
397 {
398   assert(p);  assert(name);  assert(attrs);
399   ExpatParseContext* Ctx = (ExpatParseContext*)p;
400   XMLElement* Element;
401
402   const char* ns_root = name;
403   const char* local_name = strchr(name, '|');
404   if ( local_name != 0 )
405     name = local_name + 1;
406
407   if ( Ctx->Scope.empty() )
408     {
409       Ctx->Scope.push(Ctx->Root);
410     }
411   else
412     {
413       Element = Ctx->Scope.top();
414       Ctx->Scope.push(Element->AddChild(name));
415     }
416
417   Element = Ctx->Scope.top();
418   Element->SetName(name);
419
420   // map the namespace
421   std::string key;
422   if ( ns_root != name )
423     key.assign(ns_root, name - ns_root - 1);
424   
425   ns_map::iterator ni = Ctx->Namespaces->find(key);
426   if ( ni != Ctx->Namespaces->end() )
427     Element->SetNamespace(ni->second);
428
429   // set attributes
430   for ( int i = 0; attrs[i] != 0; i += 2 )
431     {
432       if ( ( local_name = strchr(attrs[i], '|') ) == 0 )
433         local_name = attrs[i];
434       else
435         local_name++;
436
437       Element->SetAttr(local_name, attrs[i+1]);
438     }
439 }
440
441 //
442 static void
443 xph_end(void* p, const XML_Char* name)
444 {
445   assert(p);  assert(name);
446   ExpatParseContext* Ctx = (ExpatParseContext*)p;
447   Ctx->Scope.pop();
448 }
449
450 //
451 static void
452 xph_char(void* p, const XML_Char* data, int len)
453 {
454   assert(p);  assert(data);
455   ExpatParseContext* Ctx = (ExpatParseContext*)p;
456
457   if ( len > 0 )
458     {
459       std::string tmp_str;
460       tmp_str.assign(data, len);
461       Ctx->Scope.top()->AppendBody(tmp_str);
462     }
463 }
464
465 //
466 void
467 xph_namespace_start(void* p, const XML_Char* ns_prefix, const XML_Char* ns_name)
468 {
469   assert(p);  assert(ns_name);
470   ExpatParseContext* Ctx = (ExpatParseContext*)p;
471   
472   if ( ns_prefix == 0 )
473     ns_prefix = "";
474
475   ns_map::iterator ni = Ctx->Namespaces->find(ns_name);
476
477   if  ( ni != Ctx->Namespaces->end() )
478     {
479       if ( ni->second->Name() != std::string(ns_name) )
480         {
481           DefaultLogSink().Error("Duplicate prefix: %s\n", ns_prefix);
482           return;
483         }
484     }
485   else
486     {
487       XMLNamespace* Namespace = new XMLNamespace(ns_prefix, ns_name);
488       Ctx->Namespaces->insert(ns_map::value_type(ns_name, Namespace));
489     }
490 }
491
492 //
493 bool
494 Kumu::XMLElement::ParseString(const std::string& document)
495 {
496   XML_Parser Parser = XML_ParserCreateNS("UTF-8", '|');
497
498   if ( Parser == 0 )
499     {
500       DefaultLogSink().Error("Error allocating memory for XML parser.\n");
501       return false;
502     }
503
504   ExpatParseContext Ctx(this);
505   XML_SetUserData(Parser, (void*)&Ctx);
506   XML_SetElementHandler(Parser, xph_start, xph_end);
507   XML_SetCharacterDataHandler(Parser, xph_char);
508   XML_SetStartNamespaceDeclHandler(Parser, xph_namespace_start);
509
510   if ( ! XML_Parse(Parser, document.c_str(), document.size(), 1) )
511     {
512       XML_ParserFree(Parser);
513       DefaultLogSink().Error("XML Parse error on line %d: %s\n",
514                              XML_GetCurrentLineNumber(Parser),
515                              XML_ErrorString(XML_GetErrorCode(Parser)));
516       return false;
517     }
518
519   XML_ParserFree(Parser);
520
521   if ( ! Ctx.Namespaces->empty() )
522     m_NamespaceOwner = (void*)Ctx.Namespaces;
523
524   return true;
525 }
526
527 //------------------------------------------------------------------------------------------
528
529 struct xph_test_wrapper
530 {
531   XML_Parser Parser;
532   bool  Status;
533
534   xph_test_wrapper(XML_Parser p) : Parser(p), Status(false) {}
535 };
536
537 // expat wrapper functions, map callbacks to IASAXHandler
538 // 
539 static void
540 xph_test_start(void* p, const XML_Char* name, const XML_Char** attrs)
541 {
542   assert(p);
543   xph_test_wrapper* Wrapper = (xph_test_wrapper*)p;
544
545   Wrapper->Status = true;
546   XML_StopParser(Wrapper->Parser, false);
547 }
548
549
550 //
551 bool
552 Kumu::StringIsXML(const char* document, ui32_t len)
553 {
554   if ( document == 0 )
555     return false;
556
557   if ( len == 0 )
558     len = strlen(document);
559
560   XML_Parser Parser = XML_ParserCreate("UTF-8");
561
562   if ( Parser == 0 )
563     {
564       DefaultLogSink().Error("Error allocating memory for XML parser.\n");
565       return false;
566     }
567
568   xph_test_wrapper Wrapper(Parser);
569   XML_SetUserData(Parser, (void*)&Wrapper);
570   XML_SetStartElementHandler(Parser, xph_test_start);
571
572   XML_Parse(Parser, document, len, 1);
573   XML_ParserFree(Parser);
574   return Wrapper.Status;
575 }
576
577 #endif
578
579 //----------------------------------------------------------------------------------------------------
580
581 #ifdef HAVE_XERCES_C
582
583 static Mutex sg_Lock;
584 static bool  sg_xml_init = false;
585
586
587 //
588 void
589 asdcp_init_xml_dom()
590 {
591   if ( ! sg_xml_init )
592     {
593       AutoMutex AL(sg_Lock);
594
595       if ( ! sg_xml_init )
596         {
597           try
598             {
599               XMLPlatformUtils::Initialize();
600               sg_xml_init = true;
601             }
602           catch (const XMLException &e)
603             {
604               DefaultLogSink().Error("Xerces initialization error: %s\n", e.getMessage());
605             }
606         }
607     }
608 }
609
610
611 //
612 class MyTreeHandler : public HandlerBase
613 {
614   ns_map*                  m_Namespaces;
615   std::stack<XMLElement*>  m_Scope;
616   XMLElement*              m_Root;
617
618 public:
619   MyTreeHandler(XMLElement* root) : m_Namespaces(0), m_Root(root) {
620     assert(m_Root);
621     m_Namespaces = new ns_map;
622   }
623
624   ~MyTreeHandler() {
625     delete m_Namespaces;
626   }
627
628   ns_map* TakeNamespaceMap() {
629     if ( m_Namespaces == 0 || m_Namespaces->empty() )
630       return 0;
631
632     ns_map* ret = m_Namespaces;
633     m_Namespaces = 0;
634     return ret;
635   }
636
637   //
638   void AddNamespace(const char* ns_prefix, const char* ns_name)
639   {
640     assert(ns_prefix);
641     assert(ns_name);
642
643     if ( ns_prefix[0] == ':' )
644       {
645         ns_prefix++;
646       }
647     else
648       {
649         assert(ns_prefix[0] == 0);
650         ns_prefix = "";
651       }
652
653     ns_map::iterator ni = m_Namespaces->find(ns_name);
654
655     if  ( ni != m_Namespaces->end() )
656       {
657         if ( ni->second->Name() != std::string(ns_name) )
658           {
659             DefaultLogSink().Error("Duplicate prefix: %s\n", ns_prefix);
660             return;
661           }
662       }
663     else
664       {
665         XMLNamespace* Namespace = new XMLNamespace(ns_prefix, ns_name);
666         m_Namespaces->insert(ns_map::value_type(ns_prefix, Namespace));
667       }
668
669     assert(!m_Namespaces->empty());
670   }
671
672   //
673   void startElement(const XMLCh* const x_name,
674                     XERCES_CPP_NAMESPACE::AttributeList& attributes)
675   {
676     assert(x_name);
677
678     const char* tx_name = XMLString::transcode(x_name);
679     const char* name = tx_name;
680     XMLElement* Element;
681     const char* ns_root = name;
682     const char* local_name = strchr(name, ':');
683
684     if ( local_name != 0 )
685       name = local_name + 1;
686
687     if ( m_Scope.empty() )
688       {
689         m_Scope.push(m_Root);
690       }
691     else
692       {
693         Element = m_Scope.top();
694         m_Scope.push(Element->AddChild(name));
695       }
696
697     Element = m_Scope.top();
698     Element->SetName(name);
699
700     // set attributes
701     ui32_t a_len = attributes.getLength();
702
703     for ( ui32_t i = 0; i < a_len; i++)
704       {
705         const XMLCh* aname = attributes.getName(i);
706         const XMLCh* value = attributes.getValue(i);
707         assert(aname);
708         assert(value);
709
710         char* x_aname = XMLString::transcode(aname);
711         char* x_value = XMLString::transcode(value);
712
713         if ( strncmp(x_aname, "xmlns", 5) == 0 )
714           AddNamespace(x_aname+5, x_value);
715
716         if ( ( local_name = strchr(x_aname, ':') ) == 0 )
717           local_name = x_aname;
718         else
719           local_name++;
720
721         Element->SetAttr(local_name, x_value);
722
723         XMLString::release(&x_aname);
724         XMLString::release(&x_value);
725       }
726
727     // map the namespace
728     std::string key;
729     if ( ns_root != name )
730       key.assign(ns_root, name - ns_root - 1);
731   
732     ns_map::iterator ni = m_Namespaces->find(key);
733     if ( ni != m_Namespaces->end() )
734       Element->SetNamespace(ni->second);
735
736     XMLString::release((char**)&tx_name);
737   }
738
739   void endElement(const XMLCh *const name) {
740     m_Scope.pop();
741   }
742
743   void characters(const XMLCh *const chars, const unsigned int length)
744   {
745     if ( length > 0 )
746       {
747         char* text = XMLString::transcode(chars);
748         m_Scope.top()->AppendBody(text);
749         XMLString::release(&text);
750       }
751   }
752 };
753
754 //
755 bool
756 Kumu::XMLElement::ParseString(const std::string& document)
757 {
758   if ( document.empty() )
759     return false;
760
761   asdcp_init_xml_dom();
762
763   int errorCount = 0;
764   SAXParser* parser = new SAXParser();
765
766 // #if XERCES_VERSION_MAJOR < 3
767 //   parser->setDoValidation(true);
768 // #else
769   parser->setValidationScheme(SAXParser::Val_Always);
770 // #endif
771
772   parser->setDoNamespaces(true);    // optional
773
774   MyTreeHandler* docHandler = new MyTreeHandler(this);
775   parser->setDocumentHandler(docHandler);
776   parser->setErrorHandler(docHandler);
777
778   try
779     {
780       MemBufInputSource xmlSource(reinterpret_cast<const XMLByte*>(document.c_str()),
781                                   static_cast<const unsigned int>(document.size()),
782                                   "pidc_rules_file");
783
784       parser->parse(xmlSource);
785     }
786   catch (const XMLException& e)
787     {
788       char* message = XMLString::transcode(e.getMessage());
789       DefaultLogSink().Error("Parser error: %s\n", message);
790       XMLString::release(&message);
791       errorCount++;
792     }
793   catch (const SAXParseException& e)
794     {
795       char* message = XMLString::transcode(e.getMessage());
796       DefaultLogSink().Error("Parser error: %s at line %d\n", message, e.getLineNumber());
797       XMLString::release(&message);
798       errorCount++;
799     }
800   catch (...)
801     {
802       DefaultLogSink().Error("Unexpected XML parser error\n");
803       errorCount++;
804     }
805   
806   if ( errorCount == 0 )
807     m_NamespaceOwner = (void*)docHandler->TakeNamespaceMap();
808
809   delete parser;
810   delete docHandler;
811
812   return errorCount > 0 ? false : true;
813 }
814
815 //
816 bool
817 Kumu::StringIsXML(const char* document, ui32_t len)
818 {
819   if ( document == 0 || *document == 0 )
820     return false;
821
822   asdcp_init_xml_dom();
823
824   if ( len == 0 )
825     len = strlen(document);
826
827   SAXParser parser;
828   XMLPScanToken token;
829   bool status = false;
830
831   try
832     {
833       MemBufInputSource xmlSource(reinterpret_cast<const XMLByte*>(document),
834                                   static_cast<const unsigned int>(len),
835                                   "pidc_rules_file");
836
837       if ( parser.parseFirst(xmlSource, token) )
838         {
839           if ( parser.parseNext(token) )
840             status = true;
841         }
842     }
843   catch (...)
844     {
845     }
846   
847   return status;
848 }
849
850
851 #endif
852
853 //----------------------------------------------------------------------------------------------------
854
855 #if ! defined(HAVE_EXPAT) && ! defined(HAVE_XERCES_C)
856
857 //
858 bool
859 Kumu::XMLElement::ParseString(const std::string& document)
860 {
861   DefaultLogSink().Error("Kumu compiled without XML parser support.\n");
862   return false;
863 }
864
865 //
866 bool
867 Kumu::StringIsXML(const char* document, ui32_t len)
868 {
869   DefaultLogSink().Error("Kumu compiled without XML parser support.\n");
870   return false;
871 }
872
873 #endif
874
875
876 //
877 // end KM_xml.cpp
878 //