oops
[asdcplib.git] / src / KM_xml.cpp
1 /*
2 Copyright (c) 2005-2006, 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 <stack>
35 #include <map>
36
37 #ifdef ASDCP_USE_EXPAT
38 #include <expat.h>
39 #endif
40
41 using namespace Kumu;
42
43
44 class ns_map : public std::map<std::string, XMLNamespace*>
45 {
46 public:
47   ns_map() {}
48   ~ns_map()
49   {
50     ns_map::iterator ni = begin();
51
52     while (ni != end() )
53       {
54         //      fprintf(stderr, "deleting namespace %s:%s\n", ni->second->Prefix().c_str(), ni->second->Name().c_str());
55         delete ni->second;
56         ni++;
57       }
58   }
59 };
60
61
62 Kumu::XMLElement::XMLElement(const char* name) : m_Namespace(0), m_NamespaceOwner(0)
63 {
64   m_Name = name;
65 }
66
67 Kumu::XMLElement::~XMLElement()
68 {
69   for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
70     delete *i;
71
72   if ( m_NamespaceOwner != 0 )
73     delete (ns_map*)m_NamespaceOwner;
74 }
75
76 //
77 void
78 Kumu::XMLElement::SetAttr(const char* name, const char* value)
79 {
80   NVPair TmpVal;
81   TmpVal.name = name;
82   TmpVal.value = value;
83
84   m_AttrList.push_back(TmpVal);
85 }
86
87 //
88 Kumu::XMLElement*
89 Kumu::XMLElement::AddChild(const char* name)
90 {
91   XMLElement* tmpE = new XMLElement(name);
92   m_ChildList.push_back(tmpE);
93   return tmpE;
94 }
95
96 //
97 Kumu::XMLElement*
98 Kumu::XMLElement::AddChildWithContent(const char* name, const std::string& value)
99 {
100   return AddChildWithContent(name, value.c_str());
101 }
102
103 //
104 void
105 Kumu::XMLElement::AppendBody(const std::string& value)
106 {
107   m_Body += value;
108 }
109
110 //
111 Kumu::XMLElement*
112 Kumu::XMLElement::AddChildWithContent(const char* name, const char* value)
113 {
114   assert(name);
115   assert(value);
116   XMLElement* tmpE = new XMLElement(name);
117   tmpE->m_Body = value;
118   m_ChildList.push_back(tmpE);
119   return tmpE;
120 }
121
122 //
123 Kumu::XMLElement*
124 Kumu::XMLElement::AddChildWithPrefixedContent(const char* name, const char* prefix, const char* value)
125 {
126   XMLElement* tmpE = new XMLElement(name);
127   tmpE->m_Body = prefix;
128   tmpE->m_Body += value;
129   m_ChildList.push_back(tmpE);
130   return tmpE;
131 }
132
133 //
134 void
135 Kumu::XMLElement::AddComment(const char* value)
136 {
137   m_Body += "  <!-- ";
138   m_Body += value;
139   m_Body += " -->\n";
140 }
141
142 //
143 void
144 Kumu::XMLElement::Render(std::string& outbuf) const
145 {
146   outbuf = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
147   RenderElement(outbuf, 0);
148 }
149
150 //
151 inline void
152 add_spacer(std::string& outbuf, i32_t depth)
153 {
154   while ( depth-- )
155     outbuf+= "  ";
156 }
157
158 //
159 void
160 Kumu::XMLElement::RenderElement(std::string& outbuf, ui32_t depth) const
161 {
162   add_spacer(outbuf, depth);
163
164   outbuf += "<";
165   outbuf += m_Name;
166
167   // render attributes
168   for ( Attr_i i = m_AttrList.begin(); i != m_AttrList.end(); i++ )
169     {
170       outbuf += " ";
171       outbuf += (*i).name;
172       outbuf += "=\"";
173       outbuf += (*i).value;
174       outbuf += "\"";
175     }
176
177   outbuf += ">";
178
179   // body contents and children
180   if ( ! m_ChildList.empty() )
181     {
182       outbuf += "\n";
183
184       // render body
185       if ( m_Body.length() > 0 )
186         outbuf += m_Body;
187
188       for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
189         (*i)->RenderElement(outbuf, depth + 1);
190
191       add_spacer(outbuf, depth);
192     }
193   else if ( m_Body.length() > 0 )
194     {
195       outbuf += m_Body;
196     }
197
198   outbuf += "</";
199   outbuf += m_Name;
200   outbuf += ">\n";
201 }
202
203 //
204 bool
205 Kumu::XMLElement::HasName(const char* name) const
206 {
207   if ( name == 0 || *name == 0 )
208     return false;
209
210   return (m_Name == name);
211 }
212
213
214 void
215 Kumu::XMLElement::SetName(const char* name)
216 {
217   if ( name != 0)
218     m_Name = name;
219 }
220
221 //
222 const char*
223 Kumu::XMLElement::GetAttrWithName(const char* name) const
224 {
225   for ( Attr_i i = m_AttrList.begin(); i != m_AttrList.end(); i++ )
226     {
227       if ( (*i).name == name )
228         return (*i).value.c_str();
229     }
230
231   return 0;
232 }
233
234 //
235 Kumu::XMLElement*
236 Kumu::XMLElement::GetChildWithName(const char* name) const
237 {
238   for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
239     {
240       if ( (*i)->HasName(name) )
241         return *i;
242     }
243
244   return 0;
245 }
246
247 //
248 const Kumu::ElementList&
249 Kumu::XMLElement::GetChildrenWithName(const char* name, ElementList& outList) const
250 {
251   assert(name);
252   for ( Elem_i i = m_ChildList.begin(); i != m_ChildList.end(); i++ )
253     {
254       if ( (*i)->HasName(name) )
255         outList.push_back(*i);
256
257       if ( ! (*i)->m_ChildList.empty() )
258         (*i)->GetChildrenWithName(name, outList);
259     }
260
261   return outList;
262 }
263
264 //----------------------------------------------------------------------------------------------------
265
266 #ifdef ASDCP_USE_EXPAT
267
268
269 class ExpatParseContext
270 {
271   KM_NO_COPY_CONSTRUCT(ExpatParseContext);
272   ExpatParseContext();
273 public:
274   ns_map*                  Namespaces;
275   std::stack<XMLElement*>  Scope;
276   XMLElement*              Root;
277
278   ExpatParseContext(XMLElement* root) : Root(root) {
279     Namespaces = new ns_map;
280     assert(Root);
281   }
282
283   ~ExpatParseContext() {}
284 };
285
286 // expat wrapper functions
287 // 
288 static void
289 xph_start(void* p, const XML_Char* name, const XML_Char** attrs)
290 {
291   assert(p);  assert(name);  assert(attrs);
292   ExpatParseContext* Ctx = (ExpatParseContext*)p;
293   XMLElement* Element;
294
295   const char* ns_root = name;
296   const char* local_name = strchr(name, '|');
297   if ( local_name != 0 )
298     name = local_name + 1;
299
300   if ( Ctx->Scope.empty() )
301     {
302       Ctx->Scope.push(Ctx->Root);
303     }
304   else
305     {
306       Element = Ctx->Scope.top();
307       Ctx->Scope.push(Element->AddChild(name));
308     }
309
310   Element = Ctx->Scope.top();
311   Element->SetName(name);
312
313   // map the namespace
314   std::string key;
315   if ( ns_root != name )
316     key.assign(ns_root, name - ns_root - 1);
317   
318   ns_map::iterator ni = Ctx->Namespaces->find(key);
319   if ( ni != Ctx->Namespaces->end() )
320     Element->SetNamespace(ni->second);
321
322   // set attributes
323   for ( int i = 0; attrs[i] != 0; i += 2 )
324     {
325       if ( ( local_name = strchr(attrs[i], '|') ) == 0 )
326         local_name = attrs[i];
327       else
328         local_name++;
329
330       Element->SetAttr(local_name, attrs[i+1]);
331     }
332 }
333
334 //
335 static void
336 xph_end(void* p, const XML_Char* name)
337 {
338   assert(p);  assert(name);
339   ExpatParseContext* Ctx = (ExpatParseContext*)p;
340   Ctx->Scope.pop();
341 }
342
343 //
344 static void
345 xph_char(void* p, const XML_Char* data, int len)
346 {
347   assert(p);  assert(data);
348   ExpatParseContext* Ctx = (ExpatParseContext*)p;
349
350   if ( len > 0 )
351     {
352       std::string tmp_str;
353       tmp_str.assign(data, len);
354       Ctx->Scope.top()->AppendBody(tmp_str);
355     }
356 }
357
358 //
359 void
360 xph_namespace_start(void* p, const XML_Char* ns_prefix, const XML_Char* ns_name)
361 {
362   assert(p);  assert(ns_name);
363   ExpatParseContext* Ctx = (ExpatParseContext*)p;
364   
365   if ( ns_prefix == 0 )
366     ns_prefix = "";
367
368   ns_map::iterator ni = Ctx->Namespaces->find(ns_name);
369
370   if  ( ni != Ctx->Namespaces->end() )
371     {
372       if ( ni->second->Name() != std::string(ns_name) )
373         {
374           DefaultLogSink().Error("Duplicate prefix: %s\n", ns_prefix);
375           return;
376         }
377     }
378   else
379     {
380       XMLNamespace* Namespace = new XMLNamespace(ns_prefix, ns_name);
381       Ctx->Namespaces->insert(ns_map::value_type(ns_name, Namespace));
382     }
383 }
384
385 //
386 bool
387 Kumu::XMLElement::ParseString(const std::string& document)
388 {
389   XML_Parser Parser = XML_ParserCreateNS("UTF-8", '|');
390
391   if ( Parser == 0 )
392     {
393       DefaultLogSink().Error("Error allocating memory for XML parser.\n");
394       return false;
395     }
396
397   ExpatParseContext Ctx(this);
398   XML_SetUserData(Parser, (void*)&Ctx);
399   XML_SetElementHandler(Parser, xph_start, xph_end);
400   XML_SetCharacterDataHandler(Parser, xph_char);
401   XML_SetStartNamespaceDeclHandler(Parser, xph_namespace_start);
402
403   if ( ! XML_Parse(Parser, document.c_str(), document.size(), 1) )
404     {
405       XML_ParserFree(Parser);
406       DefaultLogSink().Error("XML Parse error on line %d: %s\n",
407                              XML_GetCurrentLineNumber(Parser),
408                              XML_ErrorString(XML_GetErrorCode(Parser)));
409       return false;
410     }
411
412   XML_ParserFree(Parser);
413
414   if ( ! Ctx.Namespaces->empty() )
415     m_NamespaceOwner = (void*)Ctx.Namespaces;
416
417   return true;
418 }
419
420 //------------------------------------------------------------------------------------------
421
422 struct xph_test_wrapper
423 {
424   XML_Parser Parser;
425   bool  Status;
426
427   xph_test_wrapper(XML_Parser p) : Parser(p), Status(false) {}
428 };
429
430 // expat wrapper functions, map callbacks to IASAXHandler
431 // 
432 static void
433 xph_test_start(void* p, const XML_Char* name, const XML_Char** attrs)
434 {
435   assert(p);
436   xph_test_wrapper* Wrapper = (xph_test_wrapper*)p;
437
438   Wrapper->Status = true;
439   XML_StopParser(Wrapper->Parser, false);
440 }
441
442
443 //
444 bool
445 Kumu::StringIsXML(const char* document, ui32_t len)
446 {
447   if ( document == 0 )
448     return false;
449
450   if ( len == 0 )
451     len = strlen(document);
452
453   XML_Parser Parser = XML_ParserCreate("UTF-8");
454
455   if ( Parser == 0 )
456     {
457       DefaultLogSink().Error("Error allocating memory for XML parser.\n");
458       return false;
459     }
460
461   xph_test_wrapper Wrapper(Parser);
462   XML_SetUserData(Parser, (void*)&Wrapper);
463   XML_SetStartElementHandler(Parser, xph_test_start);
464
465   XML_Parse(Parser, document, len, 1);
466   XML_ParserFree(Parser);
467   return Wrapper.Status;
468 }
469
470 #else // no XML parser support
471
472 //
473 bool
474 Kumu::XMLElement::ParseString(const std::string& document)
475 {
476   DefaultLogSink().Error("asdcplib compiled without XML parser support.\n");
477   return false;
478 }
479
480 //
481 bool
482 Kumu::StringIsXML(const char* document, ui32_t len)
483 {
484   DefaultLogSink().Error("Kumu compiled without XML parser support.\n");
485   return false;
486 }
487
488 #endif
489
490
491 //
492 // end KM_xml.cpp
493 //