Objective-C++ Preprocessor  5.0 with ARC & GC
C++ classes to enhance syntax and manage reference counting.
objxml.h
Go to the documentation of this file.
1 /*
2  * objxml.h - OODictionary representation of XML
3  * ========
4  *
5  * Created by John Holdsworth on 01/04/2009.
6  * Copyright 2009 © John Holdsworth. All Rights Reserved.
7  *
8  * $Id: //depot/ObjCpp/objxml.h#32 $
9  * $DateTime: 2013/02/17 20:35:18 $
10  *
11  * C++ classes to wrap up XCode classes for operator overload of
12  * useful operations such as access to NSArrays and NSDictionary
13  * by subscript or NSString operators such as + for concatenation.
14  *
15  * This works as the Apple Objective-C compiler supports source
16  * which mixes C++ with objective C. To enable this: for each
17  * source file which will include/import this header file, select
18  * it in Xcode and open it's "Info". To enable mixed compilation,
19  * for the file's "File Type" select: "sourcecode.cpp.objcpp".
20  *
21  * For bugs or ommisions please email objcpp@johnholdsworth.com
22  *
23  * Home page for updates and docs: http://objcpp.johnholdsworth.com
24  *
25  * If you find it useful please send a donation via paypal to account
26  * objcpp@johnholdsworth.com. Thanks.
27  *
28  * License
29  * =======
30  *
31  * You may make commercial use of this source in applications without
32  * charge but not sell it as source nor can you remove this notice from
33  * this source if you redistribute. You can make any changes you like
34  * to this code before redistribution but you must annotate them below.
35  *
36  * For further details http://objcpp.johnholdsworth.com/license.html
37  *
38  * THIS CODE IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND EITHER
39  * EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
40  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
41  *
42  * IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
43  * WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
44  * THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING
45  * ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT
46  * OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
47  * TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED
48  * BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH
49  * ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN
50  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
51  *
52  */
53 
54 #ifndef _objxml_h_
55 #define _objxml_h_
56 #ifdef __cplusplus
57 #import "objcpp.h"
58 
59 /*************************************************************************/
60 /* Add $SDKROOT/usr/include/libxml2 to your project's header search path */
61 /*************************************************************************/
62 
63 #import <libxml/tree.h>
64 #import <libxml/xmlwriter.h>
65 
66 #define OONodeArray OOArray<OONode>
67 #define OONodes OONodeArray
68 
69 static NSString *kOOChildren = @".children", *kOONodeText = @".nodeText", *kOOTagName = @"@tagName", *kOOTagPrefix = @"@tagPrefix";
70 
71 /*=================================================================================*/
72 /*============ Parse NSData XML into OODictionary representation ==================*/
73 
80 
83  // not implemented...
86 };
87 
91 };
92 
93 class OONodeSub;
94 
95 /**
96  Subclass of OODictionary<OOString> to manipulate XML documents. On parsing as XML document
97  one OONode is created for each element in the document with the attribute values stored
98  as a NSString under a key: "@attributeName". Children of an element, be they pure text
99  or sub elements are accumulated in an NSMutableArray under the key @".children" as well
100  as in an array with the elements tagName (element name) as the key. This allows simple
101  xpath expressions such as document[@"root/child"] to be evaluated by breaking up the
102  path into the equivalent of document[@"root"][0][@"child"][0]. To gain efficient
103  access to the text inside an element it is also stores under the key @".nodeText"
104  in it's parent element.
105  */
106 
107 class OONode : public OODictionary<OOString> {
108 #ifdef __OBJC_GC__
109  NSMutableDictionary *ref2;
110 protected:
111  oo_inline virtual id rawset( NSMutableDictionary *val ) OO_RETURNS {
112  return OOReference<NSMutableDictionary *>::rawset( ref2 = val );
113  }
114 #endif
115 public:
117  [alloc() setObject:(id)kCFNull forKey:kOOTagName];
118  }
119  oo_inline OONode( CFNullRef obj ) {
120  set( OO_BRIDGE(id)obj );
121  }
122  oo_inline OONode( id node ) {
123  set( node );
124  }
125  oo_inline OONode( const OONode &node ) {
126  *this = node;
127  }
128  oo_inline OONode( NSString *tagName ) {
129  [alloc() setObject:tagName forKey:kOOTagName];
130  }
131  oo_inline OONode( cOOString tagName, cOOString nodeText = nil ) {
132  [alloc() setObject:tagName forKey:kOOTagName];
133  if ( nodeText ) {
134  [get() setObject:nodeText forKey:kOONodeText];
135  OODictionary<OOArray<OOString> > node = get();
136  NSMutableArray *children = node[kOOChildren].alloc( [NSMutableArray class] );
137  [children addObject:nodeText];
138  }
139  }
141  parseXML( xml, flags );
142  }
144  parseXML( xml, flags );
145  }
146  oo_inline OONode( const OONodeArraySub &sub );
147  oo_inline OONode( const OONodeSub &sub );
148 
149  oo_inline OONode &operator = ( const OONode &node ) { set( node.get() ); return *this; }
150  oo_inline OONode &operator = ( NSData *val ) { return parseXML( val ); }
151  oo_inline OONode &operator = ( const OONodeArraySub &sub );
152  oo_inline OONode &operator = ( const OONodeSub &sub );
153 
154  OONode &parseXML( NSData *xml, OOXMLParserOpts flags = OOXMLDefaultParser );
156 
157  oo_inline OOData data() const { return writeXML(); }
158  oo_inline operator OOData () const { return data(); }
159 
160  oo_inline OOString string() const { return OOString( data() ); }
161 
163  OODictionary<OONodeArray > node = get();
164  NSMutableArray *children = node[kOOChildren].alloc( [NSMutableArray class] );
165  [children addObject:val.get()];
166  NSString *tagName = [val objectForKey:kOOTagName];
167  if ( tagName && tagName != (id)kCFNull ) {
168  NSMutableArray *siblings = node[tagName].alloc( [NSMutableArray class] );
169  [siblings addObject:val.get()];
170  }
171  return *this;
172  }
173 
174  oo_inline OONodeSub operator [] ( id sub ) const;
175  oo_inline OONodeSub operator [] ( cOOString sub ) const;
176  oo_inline OONodeSub operator [] ( const char *sub ) const;
177  oo_inline OOArraySub<OONode> operator [] ( int sub ) const;
178 
180  return [get() objectForKey:kOOChildren];
181  }
182  oo_inline OONode child( int which = 0 ) const {
183  return children()[ which ];
184  }
185  oo_inline OOString text( int which = 0 ) const {
186  return [get() objectForKey:kOONodeText]; // (id)child( which ).get();
187  }
188 
189  oo_inline operator OOString () const { return text(); } ///
190 };
191 
192 /**
193  Internal class repesenting selecting of a node from an xpath selection.
194  */
195 
196 class OONodeArraySub : public OOArraySub<OOString> {
197  friend class OONodeSub;
198 
199  OONodeArraySub( OODictionarySub<NSMutableArray *> *ref, int sub ) : OOArraySub<OOString>( ref, sub ) { }
200 
201  OOArraySub<NSMutableDictionary *> *nodeReference( BOOL refer = YES ) const {
202  if ( refer && this->dref->aref->references )
203  this->dref->aref->references++;
204  return this->dref->aref;
205  }
206 
207 protected:
208  oo_inline virtual id set( id val ) const OO_RETURNS {
209  OOArraySub<OOString>::set( val );
210  if ( ![kOOChildren isEqualToString:dref->key] ) {
212  (NSMutableDictionary *)root->get() : dref->parent( YES );
213  OONodeArray children = node[kOOChildren].alloc( [NSMutableArray class] );
214  children += OONode( val );
215  if ( ![val objectForKey:kOOTagName] )
216  [val setObject:dref->key forKey:kOOTagName];
217  }
218  return val;
219  }
220 
221 public:
222  oo_inline OONode node() const {
223  return nodeReference( NO )->get();
224  }
225  oo_inline operator OONode () const {
226  return node();
227  }
228 
229  oo_inline OOString text() const {
230  return node().text();
231  }
232  oo_inline operator OOString () const {
233  return text();
234  }
235 
236  oo_inline OONodeSub operator [] ( id sub ) const;
237  oo_inline OONodeSub operator [] ( cOOString sub ) const;
238  oo_inline OONodeSub operator [] ( const char *sub ) const;
239 };
240 
241 /**
242  Internal class repesenting selection of node from XPath expression. Normally this
243  is the OOString value of the first node selected by the xpath expression but can
244  be cast to the first node itself or an array of all qualifying nodes.
245  */
246 
247 class OONodeSub : public OODictionarySub<OOString> {
248  friend class OONodeArraySub;
249  friend class OONode;
250 
251  void supportXPath() {
252  OOString Key = *key;
253 
254  unichar char0 = Key[0];
255  if ( char0 != '@' && char0 != '.' ) {
256  OOStringArray path = Key / @"/";
257 
258  NSInteger pmax = [path count]-1, firstCharOfLast = [*path[-1] length] ? (*path[-1])[0] : 0;
259 
260  if ( firstCharOfLast != '@' && firstCharOfLast != '.' ) { //// && firstCharOfLast != '*' ) {
261  path += kOONodeText;
262  pmax++;
263  }
264 
265  if ( pmax > 0 ) {
266  OODictionarySub<NSMutableArray *> *exp = root ?
267  new OODictionarySub<NSMutableArray *>( root, *path[0] ) :
268  new OODictionarySub<NSMutableArray *>( aref, *path[0] );
269  exp->references = 1;
270 
271  for ( int i=1 ; i<=pmax ; i++ ) {
272  OOString p = path[i];
273  int idx = 0;
274  if ( [p length] && iswdigit( p[0] ) ) {
275  idx = [p intValue];
276  i++;
277  }
278 
279  aref = (OOArraySub<NSMutableDictionary *> *)new OONodeArraySub( exp, idx );
280  aref->references = 1;
281  if ( i == pmax )
282  break;
283 
284  if ( (p = path[i]) == @"*" )
285  p = kOOChildren;
286  exp = new OODictionarySub<NSMutableArray *>( aref, p );
287  exp->references = 1;
288  }
289 
290  key = **path[pmax];
291  root = NULL;
292  dref = NULL;
293  }
294  }
295  }
296 
297  oo_inline OONodeSub( const OODictionary<OOString> *ref, id sub ) : OODictionarySub<OOString>( ref, sub ) {
298  supportXPath();
299  }
300  oo_inline OONodeSub( OOArraySub<NSMutableDictionary *> *ref, id sub ) : OODictionarySub<OOString>( ref, sub ) {
301  supportXPath();
302  }
303  oo_inline OONodeSub( OODictionarySub<NSMutableDictionary *> *ref, id sub ) : OODictionarySub<OOString>( ref, sub ) {
304  supportXPath();
305  }
306 
307  oo_inline virtual id set( id val ) const OO_RETURNS {
308  OODictionarySub<OOString>::set( val );
309  if ( [kOONodeText isEqualToString:this->key] ) {
310  OODictionary<OOArray<OOString> > textNode = this->parent( YES );
311  NSMutableArray *children = textNode[kOOChildren].alloc( [NSMutableArray class] );
312  [children addObject:val];
313  }
314  return val;
315  }
316 
317 public:
318  // assign and assign by mutable copy
319  oo_inline OONodeSub &operator = ( cOOString val ) { set( val ); return *this; }
320  oo_inline OONodeSub &operator = ( NSString *val ) { set( val ); return *this; }
321  oo_inline OONodeSub &operator = ( NSMutableString *val ) { set( val ); return *this; }
322  oo_inline OONodeSub &operator = ( const char *val ) { *this = OOString( val ); return *this; }
323  oo_inline OONodeSub &operator = ( const OOArraySub<OOString> &val ) { set( val.get() ); return *this; }
324  oo_inline OONodeSub &operator = ( const OODictionarySub<OOString> &val ) { set( val.get() ); return *this; }
325  oo_inline OONodeSub &operator = ( double val ) {
326  OO_RELEASE( set( [[NSString alloc] initWithFormat:@"%f", val] ) );
327  return *this;
328  }
329  oo_inline OONodeSub &operator = ( int val ) {
330  OO_RELEASE( set( [[NSString alloc] initWithFormat:@"%d", val] ) );
331  return *this;
332  }
333  oo_inline OONodeSub &operator = ( const OONode &val ) {
334  this->aref->set( val.get() ); return *this;
335  }
336  oo_inline OONodeSub &operator += ( const OONode &val ) {
337  OONode( this->parent(YES) ) += val; return *this;
338  }
339 
340  oo_inline OONodeSub operator [] ( id sub ) const {
341  if ( this->aref->references )
342  this->aref->references++;
343  return OONodeSub( this->aref, sub );
344  }
345  oo_inline OONodeSub operator [] ( cOOString sub ) const {
346  return (*this)[(id)sub];
347  }
348  oo_inline OONodeSub operator [] ( const char *sub ) const {
349  return (*this)[OOString(sub)];
350  }
351 
352  oo_inline OONodeArraySub operator [] ( int sub ) const {
353  if ( this->aref->dref->references )
354  this->aref->dref->references++;
355  OONodeArraySub *node = new OONodeArraySub( this->aref->dref, sub );
356  node->references = 1;
357  OONodeSub *children = new OONodeSub( (OOArraySub<NSMutableDictionary *> *)node, kOOChildren );
358  children->references = 1;
359  return OONodeArraySub( (OODictionarySub<NSMutableArray*> *)children, 0 );
360  }
361  oo_inline BOOL operator ! () {
362  return count() == 0;
363  }
364 
365  oo_inline OONodeArray nodes() const {
366  return this->aref ? this->aref->parent( NO ) : nil;
367  }
368  oo_inline operator OONodeArray () const {
369  return nodes();
370  }
371 
372  oo_inline OONode node( NSInteger which = NSNotFound ) const {
373  return nodes()[(int)(which == NSNotFound ? aref->idx : which)]; ///
374  }
375  oo_inline operator OONode () const {
376  return node();
377  }
378 
379  oo_inline OOString text() const {
380  return this->get( NO ); //[this->key characterAtIndex:0] == '@' ? OOString( this->get( NO ) ) : node().text();
381  }
382  oo_inline operator OOString () const {
383  return text();
384  }
385 
386  oo_inline NSInteger count() const {
387  return [nodes() count];
388  }
389  //oo_inline operator int () const {
390  // return count();
391  //}
392  //oo_inline operator BOOL () {
393  // return count() > 0;
394  //}
395 
396  oo_inline OONodeArray children() const {
397  return [this->parent( NO ) objectForKey:kOOChildren];
398  }
399  inline OONode child( int which = 0 ) const {
400  return children()[which];
401  }
402 
403  inline OOStringDictionaryArray dictionaries() const {
405  OONodeArray in = nodes();
406  for ( NSMutableDictionary *n in *in ) {
407  OOStringDictionary values;
408  OONode node = n;
409  for( NSString *key in [*node allKeys] ) {
410  unichar c0 = [key characterAtIndex:0];
411  if ( c0 == '.' || c0 == '@' )
412  continue;
413  values[key] = node[key];
414  }
415  out += values;
416  }
417  return out;
418  }
419  oo_inline operator OOStringDictionaryArray () const {
420  return dictionaries();
421  }
422 };
423 
424 inline OOArraySub<OONode> OONode::operator [] ( int sub ) const {
425  OODictionarySub<OONode> *step1 = new OODictionarySub<OONode>( (OODictionary<OONode> *)this, kOOChildren );
426  step1->references = 1;
427  return (*step1)[sub];
428 }
429 
430 inline OONodeSub OONode::operator [] ( id sub ) const {
431  return OONodeSub( this, sub );
432 }
433 
434 inline OONodeSub OONode::operator [] ( cOOString sub ) const {
435  return (*this)[(id)sub];
436 }
437 
438 inline OONodeSub OONode::operator [] ( const char *sub ) const {
439  return (*this)[OOString(sub)];
440 }
441 
442 inline OONodeSub OONodeArraySub::operator [] ( id sub ) const {
443  return OONodeSub( nodeReference(), sub );
444 }
445 
446 inline OONodeSub OONodeArraySub::operator [] ( cOOString sub ) const {
447  return (*this)[(id)sub];
448 }
449 
450 inline OONodeSub OONodeArraySub::operator [] ( const char *sub ) const {
451  return (*this)[OOString(sub)];
452 }
453 
454 template <typename ETYPE>
455 inline OOArray<ETYPE>::OOArray( const OONodeSub &sub ) { *this = sub; }
456 template <typename ETYPE>
457 inline OOArray<ETYPE> &OOArray<ETYPE>::operator = ( const OONodeSub &sub ) { return *this = sub.nodes(); }
458 
459 inline OOString::OOString( const OONode &sub ) { *this = sub.text(); }
460 inline OOString::OOString( const OONodeSub &sub ) { *this = sub.text(); }
461 inline OOString::OOString( const OONodeArraySub &sub ) { *this = sub.text(); }
462 
463 inline OOString &OOString::operator = ( const OONode &sub ) { return *this = sub.text(); }
464 inline OOString &OOString::operator = ( const OONodeSub &sub ) { return *this = sub.text(); }
465 inline OOString &OOString::operator = ( const OONodeArraySub &sub ) { return *this = sub.text(); }
466 
467 inline OONode::OONode( const OONodeSub &sub ) { *this = sub.node(); }
468 inline OONode::OONode( const OONodeArraySub &sub ) { *this = sub.node(); }
469 
470 inline OONode &OONode::operator = ( const OONodeSub &sub ) { return *this = sub.node(); }
471 inline OONode &OONode::operator = ( const OONodeArraySub &sub ) { return *this = sub.node(); }
472 
473 #if 1
474 inline BOOL operator == ( const OONodeSub &left, const OONode &val ) { return left.text() == val.text(); }
475 inline BOOL operator != ( const OONodeSub &left, const OONode &val ) { return !(left == val); }
476 //inline BOOL operator == ( const OONodeSub &left, cOOStringval ) { return left.text() == val; }
477 //inline BOOL operator != ( const OONodeSub &left, cOOStringval ) { return !(left == val); }
478 
479 inline BOOL operator == ( const OONodeArraySub &left, const OONode &val ) { return left.text() == val.text(); }
480 inline BOOL operator != ( const OONodeArraySub &left, const OONode &val ) { return !(left == val); }
481 //inline BOOL operator == ( const OONodeArraySub &left, cOOStringval ) { return left.text() == val; }
482 //inline BOOL operator != ( const OONodeArraySub &left, cOOStringval ) { return !(left == val); }
483 #endif
484 
485 /*=================================================================================*/
486 /*============ Parse XML NSData into OODictionary representation ==================*/
487 
488 
489 /**
490  SAX Parses an XML document into an OODictionary based represntation. See OONode class
491  for description of structure generated.
492  */
493 
495  xmlSAXHandler handlers;
497  xmlParserCtxtPtr context;
498 
499 public:
504 
506 
507  oo_inline NSMutableString *unique( const char *value ) {
508  OOString tagString = value, unique = tagCache[tagString];
509  if ( !unique )
510  unique = tagCache[tagString] = tagString;
511  return unique;
512  }
513 
514  oo_inline const char *normalize( const char *name, char *buff ) {
515  if ( flags & OOXMLStripNamespaces ) {
516  const char *colon = strchr( name, ':' );
517  if ( colon )
518  name = colon+1;
519  }
520  if ( flags & OOXMLStripUpperCase ) {
521  char *out = buff+10, *optr = out;
522  while ( *name )
523  *optr++ = tolower( *name++ );
524  *optr = '\000';
525  return out;
526  }
527  return name;
528  }
529 
530  oo_inline void addNode( id node, const char *utf8String = NULL, int len = 0 ) {
531  OONode parent = stack[-1];
532  if ( !children )
533  children = parent[kOOChildren].alloc( [NSMutableArray class] );
534 
535  if ( utf8String ) {
536  static OOPattern nonWhiteSpace( @"\\S" );
537  OOArray<OOString> textChildren = children;
538 #ifdef REG_STARTEND
539  regmatch_t match[] = {{0, len}};
540 #endif
541  OOString text = node;
542 
543  if ( children>0 && [*children[-1] isKindOfClass:[NSMutableString class]] )
544  *textChildren[-1] += node;
545 #ifdef REG_STARTEND
546  else if ( flags & OOXMLPreserveWhitespace || nonWhiteSpace.exec( utf8String, 0, match, REG_STARTEND ) ) {
547 #else
548  else if ( flags & OOXMLPreserveWhitespace || nonWhiteSpace.exec( text ) ) {
549 #endif
550  textChildren += text;
551  if ( ![*parent objectForKey:kOONodeText] )
552  [*parent setObject:node forKey:kOONodeText];
553  }
554  }
555  else
556  children += node;
557  }
558 
559  oo_inline int parse( NSData *chunk ) {
560  if ( !context ) {
561  OONode root;
562  if ( flags & OOXMLRecursive )
563  root[@"/"] = index = OONode();
564  stack = OONodeArray( root, nil );
565  context = xmlCreatePushParserCtxt( &handlers, this, NULL, 0, NULL);
566  children = 0;
567  }
568 
569  const char *bytes = (const char *)[chunk bytes];
570  int length = (int)[chunk length];
571  while ( length > 0 && bytes[length-1] == '\000' )
572  length--;
573  return xmlParseChunk(context, bytes, length, 0);
574  }
575 
576  oo_inline OONode rootNodeForXMLData( NSData *xml = nil ) {
577  OOPool pool;
578  if ( xml )
579  parse( xml );
580  xmlParseChunk(context, NULL, 0, 1);
581  xmlFreeParserCtxt(context);
582  context = NULL;
583  return --stack;
584  }
585 };
586 
587 static void objcppStartElement(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI,
588  int nb_namespaces, const xmlChar **namespaces,
589  int nb_attributes, int nb_defaulted, const xmlChar **attributes) {
590  char name[10000];
591  OOXMLSaxParser &sax = *(OOXMLSaxParser *)ctx;
592  OOString tagName = sax.unique( sax.normalize( (const char *)localname, name ) );
593  OONode element = OONode( tagName );
594 
595  if ( !(sax.flags & OOXMLStripNamespaces) ) {
596  if ( prefix )
597  [element setObject:sax.unique( (const char *)prefix ) forKey:kOOTagPrefix];
598 
599  for ( int ns=0 ; ns < nb_namespaces ; ns++ ) {
600  struct _ns { const char *prefix, *nsURI; } *nptr =
601  (struct _ns *)(namespaces + ns*sizeof *nptr/sizeof nptr->prefix);
602 
603  snprintf( name, sizeof name-1, nptr->prefix ? "@xmlns:%s" : "@xmlns%.0s", nptr->prefix );
604  [element setObject:sax.unique( nptr->nsURI ) forKey:sax.unique( name )];
605  }
606  }
607 
608  for ( int attr_no=0 ; attr_no < nb_attributes ; attr_no++ ) {
609  struct _attrs { const char *localName, *prefix, *uri, *value, *end; } *aptr =
610  (struct _attrs *)(attributes + attr_no*sizeof *aptr/sizeof aptr->localName);
611  NSInteger vlen = aptr->end-aptr->value;
612 
613  OOString value( aptr->value, vlen ), trouble = @"&#38;";
614  // attribute value fix required due to libxml2 bug...
615  if ( !!value[trouble] )
616  value[trouble] = @"&";
617 
618  snprintf( name, sizeof name-1, "@%s", sax.normalize( aptr->localName, name ) );
619  [element setObject:sax.unique( value ) forKey:sax.unique( name )];
620  }
621 
622  sax.stack += element;
623  sax.children = 0;
624 }
625 
626 static void objcppEndElement(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) {
627  OOXMLSaxParser &sax = *(OOXMLSaxParser *)ctx;
628  OONode element = sax.stack--;
629  OONode parent = sax.stack[-1];
630  parent += element;
631  if ( sax.flags & OOXMLRecursiveAtAnyLevel )
632  for ( NSDictionary *n in *sax.stack ) {
633  OONode node = n;
634  node[@"/"] += element;
635  }
636  else if ( sax.flags & OOXMLRecursive )
637  sax.index += element;
638  sax.children = 0;
639 }
640 
641 static void objcppCharacters(void *ctx, const xmlChar *ch, int len) {
642  OOXMLSaxParser &sax = *(OOXMLSaxParser *)ctx;
643  sax.addNode( OOString( (const char *)ch, len ).get(), (const char *)ch, len );
644 }
645 
646 static void objcppCData(void *ctx, const xmlChar *value, int len) {
647  OOXMLSaxParser &sax = *(OOXMLSaxParser *)ctx;
648  NSData *data = [[NSData alloc] initWithBytes:value length:len];
649  sax.addNode( data );
650  OO_RELEASE( data );
651 }
652 
653 static void objcppSAXError(void *ctx, const char *msg, ...) {
654  va_list argp; va_start(argp, msg);
655  NSLogv( "OOXMLSaxParser Error - "+OOString( msg ), argp );
656  va_end( argp );
657 }
658 
660  this->flags = flags;
661 
662  memset( &handlers, 0, sizeof handlers );
663  handlers.startElementNs = objcppStartElement;
664  handlers.endElementNs = objcppEndElement;
665  handlers.characters = objcppCharacters;
666  handlers.initialized = XML_SAX2_MAGIC;
667  handlers.error = objcppSAXError;
668 
669  handlers.cdataBlock = this->flags & OOXMLPreserveCData ? objcppCData : NULL;
670  context = NULL;
671 }
672 
673 inline OONode &OONode::parseXML( NSData *xml, OOXMLParserOpts flags ) {
674  return *this = OOXMLSaxParser( flags ).rootNodeForXMLData( xml );
675 }
676 
677 /*=================================================================================*/
678 /*======================== Convert Dictionary back to NSData XML ==================*/
679 
680 /**
681  Class to convert OODictionary representation of XML into an NSData structure to be written to the net.
682  */
683 
684 class OOXMLWriter {
686  int level;
687 
688 public:
689  xmlTextWriterPtr writer;
690  char name[1000], value[10000];
691 
692  oo_inline void checkrc( const char *what, int rc ) {
693  if ( rc < 0 )
694  OOWarn( @"Error code returned from %s(): %d", what, rc );
695  }
696 
697  oo_inline void indent() {
698  checkrc( "xmlTextWriterWriteFormatString", xmlTextWriterWriteFormatString( writer, "\n%*s", level*2, "" ) );
699  }
700 
701  void traverse( OONode node ) {
702  NSString *tagName = [*node objectForKey:kOOTagName];
703 
704  if ( tagName && tagName != (id)kCFNull ) {
705  if ( flags & OOXMLPrettyPrint && level )
706  indent();
707  level++;
708 
709  NSString *tagPrefix = [*node objectForKey:kOOTagPrefix];
710  if ( tagPrefix ) {
711  [tagPrefix getCString:name maxLength:sizeof name-1 encoding:NSUTF8StringEncoding];
712  strcat( name, ":" );
713  }
714  else
715  name[0] = '\000';
716 
717  [tagName getCString:name+strlen(name) maxLength:sizeof name-1-strlen(name) encoding:NSUTF8StringEncoding];
718  checkrc( "xmlTextWriterStartElement", xmlTextWriterStartElement( writer, (xmlChar *)name ) );
719 
720  for ( NSString *attr in [*node allKeys] ) {
721  if ( attr == kOOTagName || attr == kOOTagPrefix || [attr length] == 0 || [attr characterAtIndex:0] != '@' )
722  continue;
723  [attr getCString:name maxLength:sizeof name-1 encoding:NSUTF8StringEncoding];
724  [[*node objectForKey:attr] getCString:value maxLength:sizeof value-1 encoding:NSUTF8StringEncoding];
725  checkrc( "xmlTextWriterWriteAttribute", xmlTextWriterWriteAttribute( writer, (xmlChar *)name+1, (xmlChar *)value ) );
726  }
727  }
728 
729  BOOL hadChildElements = NO;
730  OONodeArray children = node.children();
731  for ( id child in *children )
732  if ( [child isKindOfClass:[NSString class]] )
733  checkrc( "xmlTextWriterWriteString", xmlTextWriterWriteString( writer, (xmlChar *)[child UTF8String] ) );
734  else if ( [child isKindOfClass:[NSData class]] )
735  checkrc( "xmlTextWriterWriteFormatCDATA", xmlTextWriterWriteFormatCDATA( writer, "%.*s", (int)[child length], (char *)[child bytes] ) );
736  else {
737  traverse( child );
738  hadChildElements = YES;
739  }
740 
741  if ( tagName != (id)kCFNull ) {
742  level--;
743  if ( flags & OOXMLPrettyPrint && hadChildElements )
744  indent();
745  checkrc( "xmlTextWriterEndElement", xmlTextWriterEndElement( writer ) );
746  }
747  }
748 
750  this->flags = flags;
751  level = 0;
752  }
753 
755  OOPool pool;
756  xmlBufferPtr buf = xmlBufferCreate();
757  writer = xmlNewTextWriterMemory(buf, 0);
758 
759  NSString *encoding = *node[@"@encoding"];
760  checkrc( "xmlTextWriterStartDocument", xmlTextWriterStartDocument(writer, NULL, encoding ? [encoding UTF8String] : "UTF-8", NULL) );
761 
762  traverse( node );
763 
764  xmlTextWriterEndDocument(writer);
765  xmlFreeTextWriter(writer);
766 
767  OOData data = [[NSData alloc] initWithBytes:buf->content length:strlen((const char *)buf->content)];
768  xmlBufferFree(buf);
769  OO_RELEASE( *data );
770  return data;
771  }
772 };
773 
774 inline OOData OONode::writeXML( OOXMLWriterOpts flags ) const {
775  return OOXMLWriter( flags ).dataForNode( *this );
776 }
777 
778 inline OONode OOURL::xml( int flags ) {
779  return OONode( *data(), (OOXMLParserOpts)flags );
780 }
781 
782 /**
783  Simple SOAP messaging interface.
784  */
785 
786 class OOSoap {
787  OOString url, action;
788 public:
789  oo_inline OOSoap( OOString url, OOString action = nil ) {
790  this->url = url;
791  this->action = action;
792  }
793  oo_inline OONode send( OONode body, int flags = OOXMLDefaultParser, OOString prefix = @"SOAP-ENV" ) {
794  OONode root;
795  root[prefix+@":Envelope/@"+prefix+@":encodingStyle"] = @"http://schemas.xmlsoap.org/soap/encoding/";
796  root[prefix+@":Envelope/@xmlns:"+prefix] = @"http://schemas.xmlsoap.org/soap/envelope/";
797  root[prefix+@":Envelope/@xmlns:SOAP-ENC"] = @"http://schemas.xmlsoap.org/soap/encoding/";
798  root[prefix+@":Envelope/@xmlns:xsi"] = @"http://www.w3.org/1999/XMLSchema-instance";
799  root[prefix+@":Envelope/@xmlns:xsd"] = @"http://www.w3.org/1999/XMLSchema";
800 
801  root[prefix+@":Envelope/"+prefix+@":Body"] += body;
802 
803  OORequest req( url );
804  [req setHTTPMethod:@"POST"];
805  if ( !!action )
806  req[@"SOAPAction"] = action;
807  req[@"Content-Type"] = @"text/xml";
808  [req setHTTPBody:root.data()];
809  //NSLog( @"soap: %@ %@ %@", *url, *action, *root.string() );
810  return OONode( req.data(), (OOXMLParserOpts)flags );
811  }
812 };
813 
814 #endif
815 #endif