/** Implementation for GSXML classes Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. Written by: Michael Pakhantsov on behalf of Brainstorm computer solutions. Date: Jule 2000 Integration/updates/rewrites by: Richard Frith-Macdonald Date: Sep2000,Dec2001/Jan2002 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. The XML and HTML parsing system The XML and HTML parsing system

The GNUstep XML parser is a collection Objective-C classes wrapping the C XML parsing library (libxml).

The underlying C library handles high performance parsing, while the ObjectiveC classes provide ease of use/integration.

*/ #import "common.h" #import "GNUstepBase/Unicode.h" #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif #include #ifdef HAVE_LIBXML #import "GNUstepBase/GSObjCRuntime.h" #import "GNUstepBase/NSDebug+GNUstepBase.h" #import "GNUstepBase/NSObject+GNUstepBase.h" #import "GNUstepBase/GSMime.h" #import "GNUstepBase/GSXML.h" #import "Foundation/NSArray.h" #import "Foundation/NSCalendarDate.h" #import "Foundation/NSCharacterSet.h" #import "Foundation/NSData.h" #import "Foundation/NSDictionary.h" #import "Foundation/NSEnumerator.h" #import "Foundation/NSException.h" #import "Foundation/NSFileHandle.h" #import "Foundation/NSFileManager.h" #import "Foundation/NSRunLoop.h" #import "Foundation/NSString.h" #import "Foundation/NSThread.h" #import "Foundation/NSTimer.h" #import "Foundation/NSTimeZone.h" #import "Foundation/NSURL.h" #import "Foundation/NSURLHandle.h" #import "Foundation/NSValue.h" /* Avoid problems on systems where the xml headers use 'id' */ #define id GSXMLID /* libxml headers */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBXSLT #include #include #include #include #endif /* HAVE_LIBXSLT */ #undef id /* * optimization * */ static Class NSString_class; static id (*usImp)(id, SEL, const unsigned char*); static SEL usSel; static xmlExternalEntityLoader originalLoader = NULL; /* * Macro to cast results to correct type for libxml2 */ #define UTF8STRING(X) ((const unsigned char*)[X UTF8String]) inline static NSString* UTF8Str(const unsigned char *bytes) { // stringWithUTF8String: raises on OSX if you feed it with a NULL string. if (NULL == bytes) { return nil; } return (*usImp)(NSString_class, usSel, bytes); } inline static NSString* UTF8StrLen(const unsigned char *bytes, unsigned length) { NSString *str; str = [[NSString_class alloc] initWithBytes: bytes length: length encoding: NSUTF8StringEncoding]; return AUTORELEASE(str); } static BOOL cacheDone = NO; #ifdef NeXT_Foundation_LIBRARY @interface NSObject (MissingFromMacOSX) + (IMP) methodForSelector: (SEL)aSelector; @end #endif static char * xml_strdup(const char *from) { unsigned len; char *to; if (0 == from) from = ""; len = strlen(from) + 1; to = malloc(len); memcpy(to, from, len); return to; } @implementation NSObject (SetupForGSXML) + (void) _setupForGSXML { if (NO == cacheDone) { xmlInitParser(); xmlMemSetup(free, malloc, realloc, xml_strdup); xmlInitializeCatalog(); #if LIBXML_VERSION < 21000 xmlDefaultSAXHandlerInit(); #endif NSString_class = [NSString class]; usSel = @selector(stringWithUTF8String:); usImp = (id (*)(id, SEL, const unsigned char*)) [NSString_class methodForSelector: usSel]; cacheDone = YES; } } @end static void setupCache() { if (cacheDone == NO) { /* Setup of libxml2 must be done on main thread. */ [NSObject performSelectorOnMainThread: @selector(_setupForGSXML) withObject: nil waitUntilDone: YES]; } } static int xmlNSInputStreamReadCallback(void *context, char *buffer, int len) { NSInputStream *stream = (NSInputStream *)context; return [stream read: (uint8_t *)buffer maxLength: len]; } static int xmlNSInputStreamCloseCallback (void *context) { NSInputStream *stream = (NSInputStream *)context; [stream close]; return 0; } static xmlParserInputPtr loadEntityFunction(const unsigned char *url, const unsigned char *eid, void *ctx); static xmlParserInputPtr resolveEntityFunction(void *ctx, const unsigned char *eid, const unsigned char *url); static xmlEntityPtr getEntityIgnoreExternal(void *ctx, const xmlChar *name); static xmlEntityPtr getEntityResolveExternal(void *ctx, const xmlChar *name); @interface GSXPathObject(Private) + (id) _newWithNativePointer: (xmlXPathObject *)lib context: (GSXPathContext *)context; @end @interface GSXMLDocument (GSPrivate) - (id) _initFrom: (void*)data parent: (id)p ownsLib: (BOOL)f; @end @interface GSXMLNamespace (GSPrivate) - (id) _initFrom: (void*)data parent: (id)p; @end @interface GSXMLNode (GSPrivate) - (id) _initFrom: (void*)data parent: (id)p; @end @interface GSXMLParser (Private) - (BOOL) _initLibXML; - (NSMutableString*) _messages; - (void) _parseChunk: (NSData*)data; @end @interface GSSAXHandler (Private) - (BOOL) _initLibXML; - (void) _setParser: (GSXMLParser*)value; @end /** * A class wrapping attributes of an XML element node. Generally when * examining a GSXMLDocument, you need not concern yourself with * GSXMLAttribute objects as you will probably use the * [GSXMLNode-objectForKey:] method to return the string value of any * attribute you are interested in. */ @implementation GSXMLAttribute static NSMapTable *attrNames = 0; + (void) initialize { if (self == [GSXMLAttribute class]) { if (cacheDone == NO) setupCache(); attrNames = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 0); [[NSObject leakAt: &attrNames] release]; NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_CDATA, (void*)@"XML_ATTRIBUTE_CDATA"); NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_ID, (void*)@"XML_ATTRIBUTE_ID"); NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_IDREF, (void*)@"XML_ATTRIBUTE_IDREF"); NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_IDREFS, (void*)@"XML_ATTRIBUTE_IDREFS"); NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_ENTITY, (void*)@"XML_ATTRIBUTE_ENTITY"); NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_ENTITIES, (void*)@"XML_ATTRIBUTE_ENTITIES"); NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_NMTOKEN, (void*)@"XML_ATTRIBUTE_NMTOKEN"); NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_NMTOKENS, (void*)@"XML_ATTRIBUTE_NMTOKENS"); NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_ENUMERATION, (void*)@"XML_ATTRIBUTE_ENUMERATION"); NSMapInsert(attrNames, (void*)XML_ATTRIBUTE_NOTATION, (void*)@"XML_ATTRIBUTE_NOTATION"); } } + (NSInteger) typeFromDescription: (NSString*)desc { NSMapEnumerator enumerator; NSString *val; void *key; enumerator = NSEnumerateMapTable(attrNames); while (NSNextMapEnumeratorPair(&enumerator, &key, (void**)&val)) { if ([desc isEqual: val] == YES) { return (NSInteger)(intptr_t)key; } } return -1; } + (NSString*) descriptionFromType: (NSInteger)type { NSString *desc = (NSString*)NSMapGet(attrNames, (void*)(intptr_t)type); return desc; } - (NSInteger) type { return (NSInteger)((xmlAttrPtr)(lib))->atype; } - (NSString*) typeDescription { NSString *desc; desc = (NSString*)NSMapGet(attrNames, (void*)(intptr_t)[self type]); if (desc == nil) { desc = @"Unknown attribute type"; } return desc; } /** * Return the next sibling node ... another GSXMLAttribute instance. */ - (GSXMLNode*) next { if (((xmlAttrPtr)(lib))->next != NULL) { return AUTORELEASE([[GSXMLAttribute alloc] _initFrom: ((xmlAttrPtr)(lib))->next parent: self]); } else { return nil; } } /** * Return the previous sibling node ... another GSXMLAttribute instance. */ - (GSXMLNode*) previous { if (((xmlAttrPtr)(lib))->prev != NULL) { return AUTORELEASE([[GSXMLAttribute alloc] _initFrom: ((xmlAttrPtr)(lib))->prev parent: self]); } else { return nil; } } /** * Returns the string value of the attribute. */ - (NSString*) value { if (((xmlNodePtr)lib)->children != NULL && ((xmlNodePtr)lib)->children->content != NULL) { return UTF8Str(((xmlNodePtr)(lib))->children->content); } return nil; } @end /** * A GSXML document wraps the document structure of the underlying * libxml library. */ @implementation GSXMLDocument /** * Create a new document with the specified version. * * id d = [GSXMLDocument documentWithVersion: @"1.0"]; * * [d setRoot: [d makeNodeWithNamespace: nil name: @"plist" content: nil]]; * [[d root] setObject: @"0.9" forKey: @"version"]; * n1 = [[d root] makeChildWithNamespace: nil name: @"dict" content: nil]; * [n1 makeComment: @" this is a comment "]; * [n1 makePI: @"pi1" content: @"this is a process instruction"]; * [n1 makeChildWithNamespace: nil name: @"key" content: @"Year Of Birth"]; * [n1 makeChildWithNamespace: nil name: @"integer" content: @"65"]; * [n1 makeChildWithNamespace: nil name: @"key" content: @"Pets Names"]; * */ + (GSXMLDocument*) documentWithVersion: (NSString*)version { void *data = xmlNewDoc(UTF8STRING(version)); GSXMLDocument *document = nil; if (data == 0) { NSLog(@"Can't create GSXMLDocument object"); } else { document = [GSXMLDocument alloc]; document = [document _initFrom: data parent: nil ownsLib: YES]; } return AUTORELEASE(document); } + (void) initialize { if (cacheDone == NO) setupCache(); } - (id) copyWithZone: (NSZone*)z { return RETAIN(self); } - (void) dealloc { if (_ownsLib == YES && lib != NULL) { xmlFreeDoc(lib); } RELEASE(_parent); [super dealloc]; } /** * Returns a string representation of the document (ie the XML) * or nil if the document does not have reasonable contents. */ - (NSString*) description { NSString *string = nil; xmlChar *buf = NULL; int length; xmlDocDumpFormatMemoryEnc(lib, &buf, &length, "utf-8", 1); if (buf != 0 && length > 0) { string = UTF8StrLen(buf, length); free(buf); } return string; } /** * Returns the name of the encoding for this document. */ - (NSString*) encoding { return UTF8Str(((xmlDocPtr)(lib))->encoding); } - (NSUInteger) hash { return (((NSUInteger)(intptr_t)lib) >> 3); } - (id) init { NSLog(@"GSXMLDocument: calling -init is not legal"); DESTROY(self); return nil; } /** * Returns a pointer to the raw libxml data used by this document.
* Only for use by libxml experts! */ - (void*) lib { return lib; } /** * Creates a new node within the document. * * GSXMLNode *n1, *n2; * GSXMLDocument *d; * * d = [GSXMLDocument documentWithVersion: @"1.0"]; * [d setRoot: [d makeNodeWithNamespace: nil name: @"plist" content: nil]]; * [[d root] setObject: @"0.9" forKey: @"version"]; * n1 = [[d root] makeChildWithNamespace: nil name: @"dict" content: nil]; * */ - (GSXMLNode*) makeNodeWithNamespace: (GSXMLNamespace*)ns name: (NSString*)name content: (NSString*)content { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: xmlNewDocNode(lib, [ns lib], UTF8STRING(name), UTF8STRING(content)) parent: self]; return AUTORELEASE(n); } /** * Returns the root node of the document. */ - (GSXMLNode*) root { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: xmlDocGetRootElement(lib) parent: self]; return AUTORELEASE(n); } /** * Sets the root of the document.
* NB. The node must have been created as part of the receiving document * (eg. using the -makeNodeWithNamespace:name:content: method). */ - (GSXMLNode*) setRoot: (GSXMLNode*)node { xmlNodePtr nodeLib = (xmlNodePtr)[node lib]; xmlNodePtr selfLib = (xmlNodePtr)[self lib]; if (node == nil) { [NSException raise: NSInvalidArgumentException format: @"Attempt to set root of document to nil"]; } if (nodeLib->doc != selfLib->doc) { [NSException raise: NSInvalidArgumentException format: @"Attempt to set root to node from other document"]; } xmlDocSetRootElement(lib, nodeLib); return node; } /** * Returns the version string for this document. */ - (NSString*) version { return UTF8Str(((xmlDocPtr)(lib))->version); } /** * Uses the -description method to produce a string representation of * the document and writes that to filename. */ - (BOOL) writeToFile: (NSString*)filename atomically: (BOOL)useAuxilliaryFile { NSString *s = [self description]; if (s == nil) { return NO; } return [s writeToFile: filename atomically: useAuxilliaryFile]; } /** * Uses the -description method to produce a string representation of * the document and writes that to url. */ - (BOOL) writeToURL: (NSURL*)url atomically: (BOOL)useAuxilliaryFile { NSString *s = [self description]; if (s == nil) { return NO; } return [s writeToURL: url atomically: useAuxilliaryFile]; } @end @implementation GSXMLDocument (GSPrivate) /** * * Initialise a new document object using raw libxml data. * The resulting document does not 'own' the data, and will not free it. */ - (id) _initFrom: (void*)data parent: (id)p ownsLib: (BOOL)f { if (data == NULL) { NSLog(@"%@ - no data for initialization", NSStringFromClass([self class])); DESTROY(self); return nil; } lib = data; _ownsLib = f; ASSIGN(_parent, p); return self; } @end /** * A GSXMLNamespace object wraps part of the document structure of * the underlying libxml library. */ @implementation GSXMLNamespace static NSMapTable *nsNames = 0; /** * Return the string representation of the specified numeric type. */ + (NSString*) descriptionFromType: (NSInteger)type { NSString *desc = (NSString*)NSMapGet(nsNames, (void*)(intptr_t)type); return desc; } + (void) initialize { if (self == [GSXMLNamespace class]) { if (cacheDone == NO) setupCache(); nsNames = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 0); [[NSObject leakAt: &nsNames] release]; NSMapInsert(nsNames, (void*)XML_LOCAL_NAMESPACE, (void*)@"XML_LOCAL_NAMESPACE"); } } /** * Return the numeric constant value for the namespace * type named. This method is inefficient, so the returned * value should be saved for re-use later. The possible * values are - * * XML_LOCAL_NAMESPACE * */ + (NSInteger) typeFromDescription: (NSString*)desc { NSMapEnumerator enumerator; NSString *val; void *key; enumerator = NSEnumerateMapTable(nsNames); while (NSNextMapEnumeratorPair(&enumerator, &key, (void**)&val)) { if ([desc isEqual: val] == YES) { return (NSInteger)(intptr_t)key; } } return -1; } - (id) copyWithZone: (NSZone*)z { return RETAIN(self); } - (void) dealloc { RELEASE(_parent); [super dealloc]; } - (NSUInteger) hash { return (((NSUInteger)(intptr_t)lib) >> 3); } /** * Returns the namespace reference */ - (NSString*) href { return UTF8Str(((xmlNsPtr)(lib))->href); } - (id) init { NSLog(@"GSXMLNamespace: calling -init is not legal"); DESTROY(self); return nil; } - (BOOL) isEqual: (id)other { if ([other isKindOfClass: [self class]] == YES && [other lib] == lib) return YES; else return NO; } /** * Returns a pointer to the raw libxml data used by this document.
* Only for use by libxml experts! */ - (void*) lib { return lib; } /** * return the next namespace. */ - (GSXMLNamespace*) next { if (((xmlNsPtr)(lib))->next != NULL) { GSXMLNamespace *ns = [GSXMLNamespace alloc]; ns = [ns _initFrom: ((xmlNsPtr)(lib))->next parent: self]; return AUTORELEASE(ns); } else { return nil; } } /** * Return the namespace prefix. */ - (NSString*) prefix { return UTF8Str(((xmlNsPtr)(lib))->prefix); } /** * Return type of namespace */ - (NSInteger) type { return (NSInteger)((xmlNsPtr)(lib))->type; } /** * Return string representation of the type of the namespace. */ - (NSString*) typeDescription { NSString *desc; desc = (NSString*)NSMapGet(nsNames, (void*)(intptr_t)[self type]); if (desc == nil) { desc = @"Unknown namespace type"; } return desc; } @end @implementation GSXMLNamespace (GSPrivate) /** * Initialise a new namespace object using raw libxml data. * The resulting namespace does not 'own' the data, and will not free it. */ - (id) _initFrom: (void*)data parent: (id)p { if (data == NULL) { NSLog(@"%@ - no data for initialization", NSStringFromClass([self class])); DESTROY(self); return nil; } lib = data; ASSIGN(_parent, p); return self; } @end /** * A GSXMLNode object wraps part of the document structure of the * underlying libxml library. It may have a parent, siblings, and children. */ @implementation GSXMLNode static NSMapTable *nodeNames = 0; /** * Return the string constant value for the node type given. */ + (NSString*) descriptionFromType: (NSInteger)type { NSString *desc = (NSString*)NSMapGet(nodeNames, (void*)(intptr_t)type); return desc; } + (void) initialize { if (self == [GSXMLNode class]) { if (cacheDone == NO) setupCache(); nodeNames = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 0); [[NSObject leakAt: &nodeNames] release]; NSMapInsert(nodeNames, (void*)XML_ELEMENT_NODE, (void*)@"XML_ELEMENT_NODE"); NSMapInsert(nodeNames, (void*)XML_ATTRIBUTE_NODE, (void*)@"XML_ATTRIBUTE_NODE"); NSMapInsert(nodeNames, (void*)XML_TEXT_NODE, (void*)@"XML_TEXT_NODE"); NSMapInsert(nodeNames, (void*)XML_CDATA_SECTION_NODE, (void*)@"XML_CDATA_SECTION_NODE"); NSMapInsert(nodeNames, (void*)XML_ENTITY_REF_NODE, (void*)@"XML_ENTITY_REF_NODE"); NSMapInsert(nodeNames, (void*)XML_ENTITY_NODE, (void*)@"XML_ENTITY_NODE"); NSMapInsert(nodeNames, (void*)XML_PI_NODE, (void*)@"XML_PI_NODE"); NSMapInsert(nodeNames, (void*)XML_COMMENT_NODE, (void*)@"XML_COMMENT_NODE"); NSMapInsert(nodeNames, (void*)XML_DOCUMENT_NODE, (void*)@"XML_DOCUMENT_NODE"); NSMapInsert(nodeNames, (void*)XML_DOCUMENT_TYPE_NODE, (void*)@"XML_DOCUMENT_TYPE_NODE"); NSMapInsert(nodeNames, (void*)XML_DOCUMENT_FRAG_NODE, (void*)@"XML_DOCUMENT_FRAG_NODE"); NSMapInsert(nodeNames, (void*)XML_NOTATION_NODE, (void*)@"XML_NOTATION_NODE"); NSMapInsert(nodeNames, (void*)XML_HTML_DOCUMENT_NODE, (void*)@"XML_HTML_DOCUMENT_NODE"); NSMapInsert(nodeNames, (void*)XML_DTD_NODE, (void*)@"XML_DTD_NODE"); NSMapInsert(nodeNames, (void*)XML_ELEMENT_DECL, (void*)@"XML_ELEMENT_DECL"); NSMapInsert(nodeNames, (void*)XML_ATTRIBUTE_DECL, (void*)@"XML_ATTRIBUTE_DECL"); NSMapInsert(nodeNames, (void*)XML_ENTITY_DECL, (void*)@"XML_ENTITY_DECL"); } } /** *

Converts a node type string to a numeric constant which can be compared * with the result of the -type method to determine what sort of node an * instance is. Because this method is quite inefficient, you should cache * the numeric type returned and re-use the cached value. *

* The node type names are - * * XML_ELEMENT_NODE * XML_ATTRIBUTE_NODE * XML_TEXT_NODE * XML_CDATA_SECTION_NODE * XML_ENTITY_REF_NODE * XML_ENTITY_NODE * XML_PI_NODE * XML_COMMENT_NODE * XML_DOCUMENT_NODE * XML_DOCUMENT_TYPE_NODE * XML_DOCUMENT_FRAG_NODE * XML_NOTATION_NODE * XML_HTML_DOCUMENT_NODE * XML_DTD_NODE * XML_ELEMENT_DECL * XML_ATTRIBUTE_DECL * XML_ENTITY_DECL * */ + (NSInteger) typeFromDescription: (NSString*)desc { NSMapEnumerator enumerator; NSString *val; void *key; enumerator = NSEnumerateMapTable(nodeNames); while (NSNextMapEnumeratorPair(&enumerator, &key, (void**)&val)) { if ([desc isEqual: val] == YES) { return (NSInteger)(intptr_t)key; } } return -1; } /** *

* Return attributes and values as a dictionary *

*/ - (NSDictionary*) attributes { xmlAttrPtr prop; NSMutableDictionary *d = [NSMutableDictionary dictionary]; prop = ((xmlNodePtr)(lib))->properties; while (prop != NULL) { const void *name = prop->name; NSString *key = UTF8Str(name); NSString *value = @""; xmlNodePtr child = prop->children; while (child != NULL) { const void *content = child->content; value = [value stringByAppendingString: UTF8Str(content)]; child = child->next; } [d setObject: value forKey: key]; prop = prop->next; } return d; } - (id) copyWithZone: (NSZone*)z { return RETAIN(self); } /** * Return node content as a string. This should return meaningful * information for text nodes and for entity nodes containing only * text nodes.
* If entity substitution was not enabled during parsing, an * element containing text may actually contain both text nodes and * entity reference nodes, in this case you should not use this * method to get the content of the element, but should examine * the child nodes of the element individually and perform any * entity reference you need to do explicitly.
* NB. There are five standard entities which are automatically * substituted into the content text rather than appearing as * entity nodes in their own right. These are '<', '>', ''', * '"' and '&'. If you with to receive content in which these * characters are represented by the original entity escapes, you need * to use the -escapedContent method. */ - (NSString*) content { xmlNodePtr ptr = (xmlNodePtr)lib; if (ptr == NULL) { return nil; } if (ptr->content != NULL) { return UTF8Str(ptr->content); } if ((NSInteger)ptr->type == XML_TEXT_NODE) { return @""; } else if ((NSInteger)ptr->type == XML_ELEMENT_NODE) { ptr = ptr->children; if (ptr != NULL) { if (ptr->next == NULL) { if (ptr->content != NULL) { return UTF8Str(ptr->content); } } else { NSMutableString *m = [NSMutableString new]; while (ptr != NULL) { if (ptr->content != NULL) { [m appendString: UTF8Str(ptr->content)]; } ptr = ptr->next; } return AUTORELEASE(m); } } } return nil; } - (void) dealloc { RELEASE(_parent); [super dealloc]; } /** * Returns a string representation of the node and all its children * (ie the XML text) or nil if the node does not have reasonable contents. */ - (NSString*) description { NSString *string = nil; xmlOutputBufferPtr buf; buf = xmlAllocOutputBuffer(0); if (buf != 0) { xmlNodeDumpOutput(buf, ((xmlNodePtr)(lib))->doc, (xmlNodePtr)(lib), 1, 1, "utf-8"); if (xmlOutputBufferFlush(buf) < 0) { NSDebugMLog(@"Failed to flush XML description"); } #if LIBXML_VERSION < 20900 string = UTF8StrLen(buf->buffer->content, buf->buffer->use); #else string = UTF8StrLen(xmlBufContent(buf->buffer), xmlBufUse(buf->buffer)); #endif xmlOutputBufferClose(buf); } return string; } /** * Return the document in which this node exists. */ - (GSXMLDocument*) document { if (((xmlNodePtr)(lib))->doc != NULL) { GSXMLDocument *d = [GSXMLDocument alloc]; d = [d _initFrom: ((xmlNodePtr)(lib))->doc parent: self ownsLib: NO]; return AUTORELEASE(d); } else { return nil; } } /** * This performs the same function as the -content method, but retains * escaped character information (the standard five entities &lt;, * &gt;, &apos;, &quot;, and &amp;) which are normally * replaced with their standard equivalents * (<, >, ', ", and &). */ - (NSString*) escapedContent { NSString *str = [self content]; return [str stringByEscapingXML]; } /** * Return the first attribute in this node. */ - (GSXMLAttribute*) firstAttribute { if (((xmlNodePtr)(lib))->properties != NULL) { return AUTORELEASE([[GSXMLAttribute alloc] _initFrom: ((xmlNodePtr)(lib))->properties parent: self]); } else { return nil; } } /** * Return the first child element of this node. If you wish to step * through all children of the node (including non-element nodes) * you should use the -firstChild method instead. */ - (GSXMLNode*) firstChildElement { xmlNodePtr ptr = ((xmlNodePtr)lib)->children; while (ptr != NULL) { if (ptr->type == XML_ELEMENT_NODE) { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: ptr parent: self]; return AUTORELEASE(n); } ptr = ptr->next; } return nil; } /** * Return the first child node of this node. * * - (GSXMLNode*) elementRecursive: (GSXMLNode*)node * { * while (node != nil) * { * if ([node type] == XML_ELEMENT_NODE) * { * return node; * } * if ([node firstChild] != nil) * { * node = [self elementRecursive: [node firstChild]]; * } * else * { * node = [node next]; * } * } * return node; * } * */ - (GSXMLNode*) firstChild { if (((xmlNodePtr)(lib))->children != NULL) { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: ((xmlNodePtr)(lib))->children parent: self]; return AUTORELEASE(n); } else { return nil; } } - (NSUInteger) hash { return (((NSUInteger)(intptr_t)lib) >> 3); } - (id) init { NSLog(@"GSXMLNode: calling -init is not legal"); DESTROY(self); return nil; } /** * Convenience method, equivalent to calling -type and comparing it * with the result of passing "XML_ELEMENT_NODE" to * +typeFromDescription: (but faster). */ - (BOOL) isElement { if ((NSInteger)((xmlNodePtr)(lib))->type == XML_ELEMENT_NODE) { return YES; } return NO; } - (BOOL) isEqual: (id)other { if ([other isKindOfClass: [self class]] == YES && [other lib] == lib) { return YES; } else { return NO; } } /** * Convenience method, equivalent to calling -type and comparing it * with the result of passing "XML_TEXT_NODE" to * +typeFromDescription: (but faster). */ - (BOOL) isText { if ((NSInteger)((xmlNodePtr)(lib))->type == XML_TEXT_NODE) { return YES; } return NO; } /** * Returns a pointer to the raw libxml data used by this document.
* Only for use by libxml experts! */ - (void*) lib { return lib; } /** * Create and return an attribute (unless the named attribute already exists, * in which case we update them value of the existing attribute and return it. */ - (GSXMLAttribute*) makeAttributeWithName: (NSString*)name value: (NSString*)value { void *l; l = xmlNewProp((xmlNodePtr)[self lib], UTF8STRING(name), UTF8STRING(value)); return AUTORELEASE([[GSXMLAttribute alloc] _initFrom: l parent: self]); } /** *

* Creation of a new child element, added at the end of * parent children list. * ns and content parameters are optional (may be nil). * If content is non nil, a child list containing the * TEXTs and ENTITY_REFs node will be created. * Return previous node. *

* * * GSXMLNode *n1, *n2; * GSXMLDocument *d, *d1; * * d = [GSXMLDocument documentWithVersion: @"1.0"]; * [d setRoot: [d makeNodeWithNamespace: nil * name: @"plist" * content: nil]]; * [[d root] setObject: @"0.9" forKey: @"version"]; * n1 = [[d root] makeChildWithNamespace: nil * name: @"dict" * content: nil]; * [n1 makeChildWithNamespace: nil name: @"key" content: @"Year Of Birth"]; * [n1 makeChildWithNamespace: nil name: @"integer" content: @"65"]; * * [n1 makeChildWithNamespace: nil name: @"key" content: @"Pets Names"]; * [n1 makeChildWithNamespace: nil name: @"array" content: nil]; * * */ - (GSXMLNode*) makeChildWithNamespace: (GSXMLNamespace*)ns name: (NSString*)name content: (NSString*)content { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: xmlNewTextChild(lib, [ns lib], UTF8STRING(name), UTF8STRING(content)) parent: self]; return AUTORELEASE(n); } /** * Creation of a new text element, added at the end of * parent children list. * * d = [GSXMLDocument documentWithVersion: @"1.0"]; * * [d setRoot: [d makeNodeWithNamespace: nil name: @"plist" content: nil]]; * [[d root] setObject: @"0.9" forKey: @"version"]; * n1 = [[d root] makeChildWithNamespace: nil name: @"dict" content: nil]; * [n1 makeText: @" this is a text "]; * */ - (GSXMLNode*) makeText: (NSString*)content { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: xmlAddChild((xmlNodePtr)lib, xmlNewText(UTF8STRING(content))) parent: self]; return AUTORELEASE(n); } /** * Creation of a new comment element, added at the end of * parent children list. * * d = [GSXMLDocument documentWithVersion: @"1.0"]; * * [d setRoot: [d makeNodeWithNamespace: nil name: @"plist" content: nil]]; * [[d root] setObject: @"0.9" forKey: @"version"]; * n1 = [[d root] makeChildWithNamespace: nil name: @"dict" content: nil]; * [n1 makeComment: @" this is a comment "]; * */ - (GSXMLNode*) makeComment: (NSString*)content { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: xmlAddChild((xmlNodePtr)lib, xmlNewComment(UTF8STRING(content))) parent: self]; return AUTORELEASE(n); } /** * Create a namespace attached to this node. */ - (GSXMLNamespace*) makeNamespaceHref: (NSString*)href prefix: (NSString*)prefix { void *data; data = xmlNewNs((xmlNodePtr)lib, UTF8STRING(href), UTF8STRING(prefix)); if (data == NULL) { NSLog(@"Can't create GSXMLNamespace object"); return nil; } return AUTORELEASE([[GSXMLNamespace alloc] _initFrom: data parent: self]); } /** * Creation of a new process instruction element, * added at the end of parent children list. * * d = [GSXMLDocument documentWithVersion: @"1.0"]; * * [d setRoot: [d makeNodeWithNamespace: nil name: @"plist" content: nil]]; * [[d root] setObject: @"0.9" forKey: @"version"]; * n1 = [[d root] makeChildWithNamespace: nil name: @"dict" content: nil]; * [n1 makeComment: @" this is a comment "]; * [n1 makePI: @"pi1" content: @"this is a process instruction"]; * */ - (GSXMLNode*) makePI: (NSString*)name content: (NSString*)content { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: xmlAddChild((xmlNodePtr)lib, xmlNewPI(UTF8STRING(name), UTF8STRING(content))) parent: self]; return AUTORELEASE(n); } /** * Return the node-name */ - (NSString*) name { if (lib != NULL && ((xmlNodePtr)lib)->name!=NULL) { return UTF8Str(((xmlNodePtr)lib)->name); } else { return nil; } } /** * Return the next node at this level. This method can return any type * of node, and it may be more convenient to use the -nextElement node * if you are parsing a document where you wish to ignore non-element * nodes such as whitespace text separating elements. */ - (GSXMLNode*) next { if (((xmlNodePtr)(lib))->next != NULL) { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: ((xmlNodePtr)(lib))->next parent: _parent]; return AUTORELEASE(n); } else { return nil; } } /** * Returns the next element node, skipping past any other node types * (such as text nodes). If there is no element node to be returned, * this method returns nil.
* NB. This method is not available in java, as the method name conflicts * with that of java's Enumerator class. */ - (GSXMLNode*) nextElement { xmlNodePtr ptr = (xmlNodePtr)lib; while (ptr->next != NULL) { ptr = ptr->next; if (ptr->type == XML_ELEMENT_NODE) { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: ptr parent: _parent]; return AUTORELEASE(n); } } return nil; } /** * Return the namespace of the node. */ - (GSXMLNamespace*) namespace { if (lib != NULL && ((xmlNodePtr)(lib))->ns != NULL) { GSXMLNamespace *ns = [GSXMLNamespace alloc]; ns = [ns _initFrom: ((xmlNodePtr)(lib))->ns parent: self]; return AUTORELEASE(ns); } else { return nil; } } /** * Return namespace definitions for the node */ - (GSXMLNamespace*) namespaceDefinitions { if (lib != NULL && ((xmlNodePtr)lib)->nsDef != NULL) { GSXMLNamespace *ns = [GSXMLNamespace alloc]; ns = [ns _initFrom: ((xmlNodePtr)(lib))->nsDef parent: self]; return AUTORELEASE(ns); } else { return nil; } } /** * Return the attribute value for the specified key. */ - (NSString*) objectForKey: (NSString*)key { NSString *value = nil; const char *str = 0; xmlAttrPtr prop; prop = ((xmlNodePtr)(lib))->properties; while (prop != NULL) { const void *name = prop->name; if (0 == str) { str = [key UTF8String]; } if (strcmp(str, name) == 0) { xmlNodePtr child = prop->children; while (child != NULL) { const void *content = child->content; if (value == nil) { value = UTF8Str(content); } else { value = [value stringByAppendingString: UTF8Str(content)]; } child = child->next; } break; } prop = prop->next; } return value; } /** * Return the parent of this node. */ - (GSXMLNode*) parent { if (((xmlNodePtr)(lib))->parent != NULL) { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: ((xmlNodePtr)(lib))->parent parent: self]; return AUTORELEASE(n); } else { return nil; } } /** * Return the previous node at this level. */ - (GSXMLNode*) previous { if (((xmlNodePtr)(lib))->prev != NULL) { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: ((xmlNodePtr)(lib))->prev parent: _parent]; return AUTORELEASE(n); } else { return nil; } } /** * Return the previous element node at this level.
* NB. This method is not available in java, as the method name conflicts * with that of java's Enumerator class. */ - (GSXMLNode*) previousElement { xmlNodePtr ptr = (xmlNodePtr)lib; while (ptr->prev != NULL) { ptr = ptr->prev; if (ptr->type == XML_ELEMENT_NODE) { GSXMLNode *n = [GSXMLNode alloc]; n = [n _initFrom: ptr parent: _parent]; return AUTORELEASE(n); } } return nil; } /** *

* Return attributes and values as a dictionary, but applies * the specified selector to each key before adding the * key and value to the dictionary. The selector must be a * method of NSString taking no arguments and returning an * object suitable for use as a dictionary key. *

*

* This method exists for the use of GSWeb ... it is probably * not of much use elsewhere. *

*/ - (NSMutableDictionary*) propertiesAsDictionaryWithKeyTransformationSel: (SEL)keyTransformSel { xmlAttrPtr prop; NSMutableDictionary *d = [NSMutableDictionary dictionary]; prop = ((xmlNodePtr)(lib))->properties; while (prop != NULL) { xmlNodePtr child = prop->children; const void *name = prop->name; NSString *key = UTF8Str(name); NSString *value = @""; if (keyTransformSel != 0) { key = [key performSelector: keyTransformSel]; } while (child != NULL) { const void *content = child->content; value = [value stringByAppendingString: UTF8Str(content)]; child = child->next; } [d setObject: value forKey: key]; prop = prop->next; } return d; } /** * Set (or reset) an attribute carried by a node. * * [n1 setObject: @"prop1" forKey: @"name1"]; * [n1 setObject: @"prop2" forKey: @"name2"]; * [n1 setObject: @"prop3" forKey: @"name3"]; * */ - (void) setObject: (NSString*)value forKey: (NSString*)key { xmlSetProp(lib, UTF8STRING(key), UTF8STRING(value)); } /** * Return node-type. The most efficient way of testing node types is to * use this method and compare the return value with a value you previously * obtained using the +typeFromDescription: method. */ - (NSInteger) type { return (NSInteger)((xmlNodePtr)(lib))->type; } /** * Return node type as a string. */ - (NSString*) typeDescription { NSString *desc; desc = (NSString*)NSMapGet(nodeNames, (void*)(intptr_t)[self type]); if (desc == nil) { desc = @"Unknown node type"; } return desc; } /** * Sets the namespace of the receiver to the value specified.
* Supplying a nil namespace removes any namespace previously set * or any namespace that the node inherited from a parent when it * was created. */ - (void) setNamespace: (GSXMLNamespace *)space { xmlSetNs (lib, [space lib]); } @end @implementation GSXMLNode (GSPrivate) /** * Initialise from raw libxml data */ - (id) _initFrom: (void*)data parent: (id)p { if (data == NULL) { NSLog(@"%@ - no data for initialization", NSStringFromClass([self class])); DESTROY(self); return nil; } lib = data; ASSIGN(_parent, p); return self; } @end /** *

* The XML parser object is the pivotal part of parsing an XML * document - it will either build a tree representing the * document (if initialized without a GSSAXHandler), or will * cooperate with a GSSAXHandler object to provide parsing * without the overhead of building a tree. *

*

* The parser may be initialized with an input source (in which * case it will expect to be asked to parse the entire input in * a single operation), or without. If it is initialised without * an input source, incremental parsing can be done by feeding * successive parts of the XML document into the parser as * NSData objects. *

*/ @implementation GSXMLParser /* To override location for DTDs */ static NSString *dtdPath = nil; static NSString *endMarker = @"At end of incremental parse"; + (void) initialize { static BOOL beenHere = NO; if (beenHere == NO) { beenHere = YES; if (cacheDone == NO) setupCache(); /* Replace the default external entity loader with our own one which * looks for GNUstep DTDs in the correct location. */ if (NULL == originalLoader) { originalLoader = xmlGetExternalEntityLoader(); xmlSetExternalEntityLoader( (xmlExternalEntityLoader)loadEntityFunction); } } } /** *

* This method controls the loading of external entities into * the system. If it returns an empty string, the entity is not * loaded. If it returns a filename, the entity is loaded from * that file. If it returns nil, the default entity loading * mechanism is used. *

*

* The default entity loading mechanism is to construct a file * name from the locationURL, by replacing all path separators * with underscores, then attempt to locate that file in the DTDs * resource directory of the main bundle, and all the standard * system locations. *

*

* As a special case, the default loader examines the publicID * and if it is a GNUstep DTD, the loader constructs a special * name from the ID (by replacing dots with underscores and * spaces with hyphens) and looks for a file with that name * and a '.dtd' extension in the GNUstep bundles. *

*

* NB. This method will only be called if there is no SAX * handler in use, or if the corresponding method in the * SAX handler returns nil. *

*/ + (NSString*) loadEntity: (NSString*)publicId at: (NSString*)location { return nil; } /** * Creation of a new Parser (for incremental parsing) * by calling -initWithSAXHandler: */ + (GSXMLParser*) parser { return AUTORELEASE([[self alloc] initWithSAXHandler: nil]); } /** * Creation of a new Parser by calling * -initWithSAXHandler:withContentsOfFile: * * GSXMLParser *p = [GSXMLParser parserWithContentsOfFile: @"macos.xml"]; * * if ([p parse]) * { * [[p document] dump]; * } * else * { * printf("error parse file\n"); * } * */ + (GSXMLParser*) parserWithContentsOfFile: (NSString*)path { return AUTORELEASE([[self alloc] initWithSAXHandler: nil withContentsOfFile: path]); } /** * Creation of a new Parser by calling * -initWithSAXHandler:withContentsOfURL: */ + (GSXMLParser*) parserWithContentsOfURL: (NSURL*)url { return AUTORELEASE([[self alloc] initWithSAXHandler: nil withContentsOfURL: url]); } /** * Creation of a new Parser by calling * -initWithSAXHandler:withData: */ + (GSXMLParser*) parserWithData: (NSData*)data { return AUTORELEASE([[self alloc] initWithSAXHandler: nil withData: data]); } /** *

* Creation of a new Parser by calling -initWithSAXHandler: *

*

* If the handler object supplied is nil, the parser will build * a tree representing the parsed file rather than attempting * to get the handler to deal with the parsed elements and entities. *

*/ + (GSXMLParser*) parserWithSAXHandler: (GSSAXHandler*)handler { return AUTORELEASE([[self alloc] initWithSAXHandler: handler]); } /** * Creation of a new Parser by calling * -initWithSAXHandler:withContentsOfFile: * * NSAutoreleasePool *arp = [NSAutoreleasePool new]; * GSSAXHandler *h = [GSDebugSAXHandler handler]; * GSXMLParser *p = [GSXMLParser parserWithSAXHandler: h * withContentsOfFile: @"macos.xml"]; * if ([p parse]) * { * printf("ok\n"); * } * RELEASE(arp); * */ + (GSXMLParser*) parserWithSAXHandler: (GSSAXHandler*)handler withContentsOfFile: (NSString*)path { return AUTORELEASE([[self alloc] initWithSAXHandler: handler withContentsOfFile: path]); } /** * Creation of a new Parser by calling * -initWithSAXHandler:withContentsOfURL: */ + (GSXMLParser*) parserWithSAXHandler: (GSSAXHandler*)handler withContentsOfURL: (NSURL*)url { return AUTORELEASE([[self alloc] initWithSAXHandler: handler withContentsOfURL: url]); } /** * Creation of a new Parser by calling * -initWithSAXHandler:withData: */ + (GSXMLParser*) parserWithSAXHandler: (GSSAXHandler*)handler withData: (NSData*)data { return AUTORELEASE([[self alloc] initWithSAXHandler: handler withData: data]); } /** Sets a directory in which to look for DTDs when resolving external * references. Can be used whjen DTDs have not been installed in the * normal locatioons. */ + (void) setDTDs: (NSString*)aPath { ASSIGNCOPY(dtdPath, aPath); } /** * Return the name of the string encoding (for XML) to use for the * specified OpenStep encoding. */ + (NSString*) xmlEncodingStringForStringEncoding: (NSStringEncoding)encoding { NSString *xmlEncodingString = nil; switch (encoding) { case NSUnicodeStringEncoding: NSLog(@"NSUnicodeStringEncoding not supported for XML");//?? break; case NSNEXTSTEPStringEncoding: NSLog(@"NSNEXTSTEPStringEncoding not supported for XML");//?? break; case NSJapaneseEUCStringEncoding: xmlEncodingString = @"EUC-JP"; break; case NSShiftJISStringEncoding: xmlEncodingString = @"Shift-JIS"; break; case NSISO2022JPStringEncoding: xmlEncodingString = @"ISO-2022-JP"; break; case NSUTF8StringEncoding: xmlEncodingString = @"UTF-8"; break; case NSWindowsCP1251StringEncoding: NSLog(@"NSWindowsCP1251StringEncoding not supported for XML");//?? break; case NSWindowsCP1252StringEncoding: NSLog(@"NSWindowsCP1252StringEncoding not supported for XML");//?? break; case NSWindowsCP1253StringEncoding: NSLog(@"NSWindowsCP1253StringEncoding not supported for XML");//?? break; case NSWindowsCP1254StringEncoding: NSLog(@"NSWindowsCP1254StringEncoding not supported for XML");//?? break; case NSWindowsCP1250StringEncoding: NSLog(@"NSWindowsCP1250StringEncoding not supported for XML");//?? break; case NSISOLatin1StringEncoding: xmlEncodingString = @"ISO-8859-1"; break; case NSISOLatin2StringEncoding: xmlEncodingString = @"ISO-8859-2"; break; case NSSymbolStringEncoding: NSLog(@"NSSymbolStringEncoding not supported for XML");//?? break; default: NSLog(@"Encoding not supported for XML");//?? xmlEncodingString = nil; break; } return xmlEncodingString; } /** * If called by a SAX callback routine, this method will terminate * the parsiong process. */ - (void) abortParsing { if (lib != NULL) { xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)lib; // Stop SAX callbacks ctxt->disableSAX = 1; // Stop incoming data being parsed. ctxt->instate = XML_PARSER_EOF; // Pretend we are at end of file (nul byte). if (ctxt->input != NULL) ctxt->input->cur = (const unsigned char*)""; } } /** * If executed during a parse operation, returns the current column number. */ - (NSInteger) columnNumber { return xmlSAX2GetColumnNumber(lib); } - (void) dealloc { RELEASE(messages); RELEASE(src); RELEASE(saxHandler); if (lib != NULL) { xmlFreeDoc(((xmlParserCtxtPtr)lib)->myDoc); xmlFreeParserCtxt(lib); } [super dealloc]; } /** * Sets whether the document needs to be validated. */ - (BOOL) doValidityChecking: (BOOL)yesno { BOOL old; old = (((xmlParserCtxtPtr)lib)->validate) ? YES : NO; ((xmlParserCtxtPtr)lib)->validate = (yesno ? 1 : 0); return old; } /** * Return the document produced as a result of parsing data. */ - (GSXMLDocument*) document { GSXMLDocument *d = [GSXMLDocument alloc]; d = [d _initFrom: ((xmlParserCtxtPtr)lib)->myDoc parent: self ownsLib: NO]; return AUTORELEASE(d); } /** * Return error code for last parse operation. */ - (NSInteger) errNo { return ((xmlParserCtxtPtr)lib)->errNo; } /** * Sets whether warnings are generated. */ - (BOOL) getWarnings: (BOOL)yesno { BOOL old = (((xmlParserCtxtPtr)lib)->vctxt.warning) ? YES : NO; if (yesno == YES) { ((xmlParserCtxtPtr)lib)->vctxt.warning = xmlParserValidityWarning; } else { ((xmlParserCtxtPtr)lib)->vctxt.warning = 0; } return old; } /** * Initialises by calling -initWithSAXHandler: with a nil argument. */ - (id) init { return [self initWithSAXHandler: nil]; } /** *

* Initialisation of a new Parser with SAX handler. *

*

* If the handler object supplied is nil, the parser will use * an instance of [GSTreeSAXHandler] to build a tree representing * the parsed file. This tree will then be available (via the -document * method) as a [GSXMLDocument] on completion of parsing. *

*

* The source for the parsing process is not specified - so * parsing must be done incrementally by feeding data to the * parser. *

*/ - (id) initWithSAXHandler: (GSSAXHandler*)handler { if (handler == nil) { saxHandler = [GSTreeSAXHandler new]; } else if ([handler isKindOfClass: [GSSAXHandler class]] == YES) { saxHandler = RETAIN(handler); } else { NSLog(@"Bad GSSAXHandler object passed to GSXMLParser initialiser"); DESTROY(self); return nil; } [saxHandler _setParser: self]; if ([self _initLibXML] == NO) { DESTROY(self); return nil; } return self; } /** *

* Initialisation of a new Parser with SAX handler (if not nil) * by calling -initWithSAXHandler: *

*

* Sets the input source for the parser to be the specified file - * so parsing of the entire file will be performed rather than * incremental parsing. *

*/ - (id) initWithSAXHandler: (GSSAXHandler*)handler withContentsOfFile: (NSString*)path { if (path == nil || [path isKindOfClass: NSString_class] == NO) { NSLog(@"Bad file path passed to initialize GSXMLParser"); DESTROY(self); return nil; } src = [path copy]; self = [self initWithSAXHandler: handler]; return self; } /** *

* Initialisation of a new Parser with SAX handler (if not nil) * by calling -initWithSAXHandler: *

*

* Sets the input source for the parser to be the specified URL - * so parsing of the entire document will be performed rather than * incremental parsing. *

*/ - (id) initWithSAXHandler: (GSSAXHandler*)handler withContentsOfURL: (NSURL*)url { if (url == nil || [url isKindOfClass: [NSURL class]] == NO) { NSLog(@"Bad NSURL passed to initialize GSXMLParser"); DESTROY(self); return nil; } src = [url copy]; self = [self initWithSAXHandler: handler]; return self; } /** *

* Initialisation of a new Parser with SAX handler (if not nil) * by calling -initWithSAXHandler: *

*

* Sets the input source for the parser to be the specified data * object (which must contain an XML document), so parsing of the * entire document will be performed rather than incremental parsing. *

*/ - (id) initWithSAXHandler: (GSSAXHandler*)handler withData: (NSData*)data { if (nil == data) { NSLog(@"Nil NSData passed to initialize GSXMLParser"); DESTROY(self); return nil; } if ([data isKindOfClass: [NSData class]] == NO) { NSLog(@"Non NSData passed to initialize GSXMLParser; %@", data); DESTROY(self); return nil; } src = [data copy]; self = [self initWithSAXHandler: handler]; return self; } /** *

* Initialisation of a new Parser with SAX handler (if not nil) * by calling -initWithSAXHandler: *

*

* Sets the input source for the parser to be the specified input stream, * so parsing of the entire document will be performed rather than * incremental parsing. *

*/ - (id) initWithSAXHandler: (GSSAXHandler*)handler withInputStream: (NSInputStream*)stream { if (stream == nil || [stream isKindOfClass: [NSInputStream class]] == NO) { NSLog(@"Bad NSInputStream passed to initialize GSXMLParser"); DESTROY(self); return nil; } src = RETAIN(stream); self = [self initWithSAXHandler: handler]; return self; } /** * Set and return the previous value for blank text nodes support. * ignorableWhitespace nodes are only generated when running * the parser in validating mode and when the current element * doesn't allow CDATA or mixed content. */ - (BOOL) keepBlanks: (BOOL)yesno { BOOL old; old = (((xmlParserCtxtPtr)lib)->keepBlanks) ? YES : NO; ((xmlParserCtxtPtr)lib)->keepBlanks = (yesno ? 1 : 0); return old; } /** * If executed during a parse operation, returns the current line number. */ - (NSInteger) lineNumber { return xmlSAX2GetLineNumber(lib); } /** * Returns the string into which warning and error messages are saved, * or nil if they are being written to stderr. */ - (NSString*) messages { return messages; } /** * Parse source. Return YES if parsed as valid, otherwise NO. * If validation against a DTD is not enabled, the return value simply * indicates whether the xml was well formed.
* This method should be called once to parse the entire document. * * GSXMLParser *p = [GSXMLParser parserWithContentsOfFile:@"macos.xml"]; * * if ([p parse]) * { * [[p doc] dump]; * } * else * { * printf("error parse file\n"); * } * */ - (BOOL) parse { id tmp; if (src == endMarker) { NSLog(@"GSXMLParser -parse called on object that is already parsed"); return NO; } if (src == nil) { NSLog(@"GSXMLParser -parse called on object with no source"); return NO; } if ([src isKindOfClass: [NSData class]] || [src isKindOfClass: [NSInputStream class]]) { } else if ([src isKindOfClass: NSString_class]) { NSData *data = [NSData dataWithContentsOfFile: src]; if (data == nil) { NSLog(@"File to parse (%@) is not readable", src); return NO; } ASSIGN(src, data); } else if ([src isKindOfClass: [NSURL class]]) { NSData *data = [src resourceDataUsingCache: YES]; if (data == nil) { NSLog(@"URL to parse (%@) is not readable", src); return NO; } ASSIGN(src, data); } else { NSLog(@"Source for [-parse] must be NSString, NSData, NSInputStream, or" @" NSURL type"); return NO; } tmp = RETAIN(src); ASSIGN(src, endMarker); if ([tmp isKindOfClass: [NSInputStream class]]) { xmlParseDocument(lib); } else { [self _parseChunk: tmp]; [self _parseChunk: nil]; } RELEASE(tmp); if (((xmlParserCtxtPtr)lib)->wellFormed != 0 && (0 == ((xmlParserCtxtPtr)lib)->validate || ((xmlParserCtxtPtr)lib)->valid != 0)) { return YES; } return NO; } /** *

* Pass data to the parser for incremental parsing. This method * should be called many times, with each call passing another * block of data from the same document. After the whole of the * document has been parsed, the method should be called with * an empty or nil data object to indicate end of parsing. * On this final call, the return value indicates whether the * document was valid or not. If validation to a DTD is not enabled, * the return value simply indicates whether the xml was well formed. *

* * GSXMLParser *p = [GSXMLParser parserWithSAXHandler: nil source: nil]; * * while ((data = getMoreData()) != nil) * { * if ([p parse: data] == NO) * { * NSLog(@"parse error"); * } * } * // Do something with document parsed * [p parse: nil]; // Completed parsing of document. * */ - (BOOL) parse: (NSData*)data { if (src == endMarker) { NSLog(@"GSXMLParser -parse: called on object that is fully parsed"); return NO; } if (src != nil) { NSLog(@"XMLParser -parse: called for parser not initialised with nil"); return NO; } if (data == nil || [data length] == 0) { /* * At end of incremental parse. */ if (lib != NULL) { [self _parseChunk: nil]; src = endMarker; if (((xmlParserCtxtPtr)lib)->wellFormed != 0 && (0 == ((xmlParserCtxtPtr)lib)->validate || ((xmlParserCtxtPtr)lib)->valid != 0)) { return YES; } return NO; } else { NSLog(@"GSXMLParser -parse: terminated with no data"); return NO; } } else { [self _parseChunk: data]; return YES; } } /** * Return the public ID of the document being parsed. */ - (NSString*) publicID { return UTF8Str(xmlSAX2GetPublicId(lib)); } /** * Sets up (or removes) a mutable string to which error and warning * messages are saved. Using an argument of NO will cause these messages * to be written to stderr (the default).
* NB. A SAX handler which overrides the error and warning logging * messages may stop this mechanism operating. */ - (void) saveMessages: (BOOL)yesno { if (yesno == YES) { ASSIGN(messages, [NSMutableString stringWithCapacity: 256]); } else { DESTROY(messages); } } - (BOOL) resolveEntities: (BOOL)yesno { BOOL old; if (yesno) yesno = YES; if ((((xmlParserCtxtPtr)lib)->sax)->getEntity == (void*)getEntityIgnoreExternal) { old = NO; } else { old = YES; } if (YES == yesno) { (((xmlParserCtxtPtr)lib)->sax)->getEntity = (void*)getEntityResolveExternal; } else { (((xmlParserCtxtPtr)lib)->sax)->getEntity = (void*)getEntityIgnoreExternal; } return old; } /** * Set and return the previous value for entity support. * Initially the parser always keeps entity references instead * of substituting entity values in the output. */ - (BOOL) substituteEntities: (BOOL)yesno { BOOL old; if (yesno) yesno = YES; old = (((xmlParserCtxtPtr)lib)->replaceEntities) ? YES : NO; if (old != yesno) { ((xmlParserCtxtPtr)lib)->replaceEntities = (yesno ? 1 : 0); } return old; } /** * Return the system ID of the document being parsed. */ - (NSString*) systemID { return UTF8Str(xmlSAX2GetSystemId(lib)); } /* * Private methods - internal use only. */ - (BOOL) _initLibXML { const char *file; if ([src isKindOfClass: NSString_class]) { file = [src lossyCString]; } else if ([src isKindOfClass: [NSURL class]]) { file = [[src absoluteString] lossyCString]; } else { file = "."; } if ([src isKindOfClass: [NSInputStream class]]) { [(NSInputStream*)src open]; lib = (void*)xmlCreateIOParserCtxt([saxHandler lib], NULL, xmlNSInputStreamReadCallback, xmlNSInputStreamCloseCallback, (void*)src, XML_CHAR_ENCODING_NONE); } else { lib = (void*)xmlCreatePushParserCtxt([saxHandler lib], NULL, 0, 0, file); } if (lib == NULL) { NSLog(@"Failed to create libxml parser context"); return NO; } else { /* * Put saxHandler address in _private member, so we can retrieve * the GSSAXHandler to use in our SAX C Functions. */ ((xmlParserCtxtPtr)lib)->_private = saxHandler; /* * Set the entity loading function for this parser to be our one. */ ((xmlParserCtxtPtr)lib)->sax->resolveEntity = resolveEntityFunction; } return YES; } - (NSMutableString*) _messages { return messages; } // nil data allowed - (void) _parseChunk: (NSData*)data { if (lib == NULL || ((xmlParserCtxtPtr)lib)->disableSAX != 0) { return; // Parsing impossible or disabled. } xmlParseChunk(lib, [data bytes], [data length], data == nil); } @end /** * The GSHTMLParser class is a simple subclass of GSXMLParser which should * parse reasonably well formed HTML documents. If you wish to parse XHTML * documents, you should use GSXMLParser ... the GSHTMLParser class is for * older 'legacy' documents. */ @implementation GSHTMLParser - (BOOL) _initLibXML { lib = (void*)htmlCreatePushParserCtxt([saxHandler lib], NULL, 0, 0, ".", XML_CHAR_ENCODING_NONE); if (lib == NULL) { NSLog(@"Failed to create libxml parser context"); return NO; } else { /* * Put saxHandler address in _private member, so we can retrieve * the GSSAXHandler to use in our SAX C Functions. */ ((htmlParserCtxtPtr)lib)->_private = saxHandler; } return YES; } - (void) _parseChunk: (NSData*)data { htmlParseChunk(lib, [data bytes], [data length], data == nil); } @end /** *

XML SAX Handler.

*

* GSSAXHandler is a callback-based interface to the [GSXMLParser] * which operates in a similar (though not identical) manner to * SAX. *

*

* Each GSSAXHandler object is associated with a GSXMLParser * object. As parsing progresses, the methods of the GSSAXHandler * are invoked by the parser, so the handler is able to deal * with the elements and entities being parsed. *

*

* The callback methods in the GSSAXHandler class do nothing - it * is intended that you subclass GSSAXHandler and override them. *

*

* If you create a GSXMLParser passing nil as the GSSAXHandler, * the parser will parse data to create a [GSXMLDocument] instance * which you can then examine as a whole ... this is generally the * preferred mechanism for parsing as it permits the parser to * validate the parsed document againts a DTD, and your software * can then examine the document secure in the knowledge that it * contains the expected structure. Use of a GSSAXHandler is * preferred for very large documents with simple structure ... * in which case incremental parsing is more efficient. *

*/ @implementation GSSAXHandler + (void) initialize { static BOOL beenHere = NO; if (beenHere == NO) { beenHere = YES; if (cacheDone == NO) { setupCache(); } /* Replace the default external entity loader with our own one which * looks for GNUstep DTDs in the correct location. */ if (NULL == originalLoader) { originalLoader = xmlGetExternalEntityLoader(); xmlSetExternalEntityLoader( (xmlExternalEntityLoader)loadEntityFunction); } } } /* * The context is a xmlParserCtxtPtr or htmlParserCtxtPtr. * Its _private member contains the address of our Sax Handler Object. * We can use a (xmlParserCtxtPtr) cast because xmlParserCtxt and * htmlParserCtxt are the same structure (and will remain, cf libxml author). */ #define HANDLER ((GSSAXHandler*)(((xmlParserCtxtPtr)ctx)->_private)) static xmlEntityPtr getEntityDefault(void *ctx, const xmlChar *name, BOOL resolve) { xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) ctx; xmlEntityPtr ret = NULL; if (ctx != 0) { if (0 == ctxt->inSubset) { if ((ret = xmlGetPredefinedEntity(name)) != NULL) { return ret; } } if ((ctxt->myDoc != NULL) && (1 == ctxt->myDoc->standalone)) { if (2 == ctxt->inSubset) { ctxt->myDoc->standalone = 0; ret = xmlGetDocEntity(ctxt->myDoc, name); ctxt->myDoc->standalone = 1; } else { ret = xmlGetDocEntity(ctxt->myDoc, name); if (NULL == ret) { ctxt->myDoc->standalone = 0; ret = xmlGetDocEntity(ctxt->myDoc, name); if (ret != NULL) { ((((xmlParserCtxtPtr)ctxt)->sax)->fatalError)(ctxt, "Entity(%s) document marked standalone" " but requires external subset", name); xmlStopParser(ctxt); } ctxt->myDoc->standalone = 1; } } } else { ret = xmlGetDocEntity(ctxt->myDoc, name); } if ((ret != NULL) && ((ctxt->validate) || (ctxt->replaceEntities)) && (ret->children == NULL) && (ret->etype == XML_EXTERNAL_GENERAL_PARSED_ENTITY)) { if (YES == resolve) { xmlNodePtr children; int val; /* * for validation purposes we really need to fetch and * parse the external entity */ val = xmlParseCtxtExternalEntity(ctxt, ret->URI, ret->ExternalID, &children); if (val == 0) { xmlAddChildList((xmlNodePtr) ret, children); } else { ((((xmlParserCtxtPtr)ctxt)->sax)->fatalError)(ctxt, "Failure to process entity %s\n", name); xmlStopParser(ctxt); ctxt->validate = 0; return NULL; } ret->owner = 1; #if LIBXML_VERSION < 21100 if (ret->checked == 0) { ret->checked = 1; } #endif } } } return ret; } static xmlEntityPtr getEntityIgnoreExternal(void *ctx, const xmlChar *name) { return getEntityDefault(ctx, name, NO); } static xmlEntityPtr getEntityResolveExternal(void *ctx, const xmlChar *name) { return getEntityDefault(ctx, name, YES); } /* WARNING ... as far as I can tell libxml2 never uses the resolveEntity * callback, so this function is never called via that route. * We therefore also set this as the global default entity loading * function (in [GSXMLParser+initialize] and [GSSAXHandler+initialize]). * * To implement the -resolveEntities method we must permit/deny any attempt * to load an entity (before the function to resolve is even called), * We therefore intercept the getEntity callback (using getEntityDefault()), * re-implementing some of the code inside libxml2 to avoid attempts to * load/parse external entities unless we have specifically enabled it. */ static xmlParserInputPtr loadEntityFunction(const unsigned char *url, const unsigned char *eid, void *ctx) { NSString *file = nil; NSString *entityId; NSString *location; NSArray *components; NSMutableString *local; unsigned count; unsigned index; NSCAssert(ctx, @"No Context"); if (url == NULL) return NULL; entityId = (eid != NULL) ? (id)UTF8Str(eid) : nil; location = UTF8Str(url); components = [location pathComponents]; local = (NSMutableString *) [NSMutableString string]; /* * Build a local filename by replacing path separator characters with * something else. */ count = [components count]; if (count > 0) { count--; for (index = 0; index < count; index++) { [local appendString: [components objectAtIndex: index]]; [local appendString: @"_"]; } [local appendString: [components objectAtIndex: index]]; } /* Also replace ':' which isn't legal on some file systems */ [local replaceOccurrencesOfString: @":" withString: @"+" options: NSLiteralSearch range: NSMakeRange(0, [local length])]; if ([location rangeOfString: @"/DTDs/PropertyList"].length > 0) { file = [location substringFromIndex: 6]; if ([[NSFileManager defaultManager] fileExistsAtPath: file] == NO) { location = [NSBundle pathForLibraryResource: @"plist-0_9" ofType: @"dtd" inDirectory: @"DTDs"]; entityId = @"-//GNUstep//DTD plist 0.9//EN"; file = nil; } } if (file == nil && ((xmlParserCtxtPtr)ctx)->_private != NULL) { /* * Now ask the SAXHandler callback for the name of a local file */ file = [HANDLER loadEntity: entityId at: location]; if (file == nil) { file = [GSXMLParser loadEntity: entityId at: location]; } } if (file == nil) { /* * Special case - GNUstep DTDs - should be installed in the GNUstep * system bundle - so we look for them there. */ if ([entityId hasPrefix: @"-//GNUstep//DTD "] == YES) { NSCharacterSet *ws = [NSCharacterSet whitespaceCharacterSet]; NSString *found = nil; NSMutableString *name; unsigned len; NSRange r; /* * Extract the relevent DTD name */ name = AUTORELEASE([entityId mutableCopy]); r = NSMakeRange(0, 16); [name deleteCharactersInRange: r]; len = [name length]; r = [name rangeOfString: @"/" options: NSLiteralSearch]; if (r.length > 0) { r.length = len - r.location; [name deleteCharactersInRange: r]; len = [name length]; } /* * Convert dots to underscores. */ r = [name rangeOfString: @"." options: NSLiteralSearch]; while (r.length > 0) { [name replaceCharactersInRange: r withString: @"_"]; r.location++; r.length = len - r.location; r = [name rangeOfString: @"." options: NSLiteralSearch range: r]; } /* * Convert whitespace to hyphens. */ r = [name rangeOfCharacterFromSet: ws options: NSLiteralSearch]; while (r.length > 0) { [name replaceCharactersInRange: r withString: @"-"]; r.location++; r.length = len - r.location; r = [name rangeOfCharacterFromSet: ws options: NSLiteralSearch range: r]; } if (dtdPath != nil) { found = [dtdPath stringByAppendingPathComponent: name]; found = [found stringByAppendingPathExtension: @"dtd"]; if (![[NSFileManager defaultManager] fileExistsAtPath: found]) { found = nil; } } if (found == nil) { #ifndef NeXT_Foundation_LIBRARY found = [NSBundle pathForLibraryResource: name ofType: @"dtd" inDirectory: @"DTDs"]; #else found = [[NSBundle bundleForClass: [GSXMLNode class]] pathForResource: name ofType: @"dtd" inDirectory: @"DTDs"]; #endif } if (found == nil) { NSLog(@"unable to find GNUstep DTD - '%@' for '%s'", name, eid); } else { file = found; } } /* * DTD not found - so we look for it in standard locations. */ if (file == nil) { if (dtdPath != nil) { file = [dtdPath stringByAppendingPathComponent: local]; if (![[NSFileManager defaultManager] fileExistsAtPath: local]) { file = nil; } } if (file == nil) { file = [[NSBundle mainBundle] pathForResource: local ofType: @"" inDirectory: @"DTDs"]; } if (file == nil) { #ifndef NeXT_Foundation_LIBRARY file = [NSBundle pathForLibraryResource: local ofType: @"" inDirectory: @"DTDs"]; #else file = [[NSBundle bundleForClass: [GSXMLNode class]] pathForResource: local ofType: @"" inDirectory: @"DTDs"]; #endif } } } /* * If we found the DTD somewhere, add it to the catalog. */ if ([file length] > 0) { NSURL *theURL = [NSURL fileURLWithPath: file]; xmlCatalogAdd((const unsigned char*)"public", eid, UTF8STRING([theURL absoluteString])); } /* A local DTD will now be in the catalog: The builtin entity resolver can * take over. */ return (*originalLoader)((const char*)url, (const char*)eid, ctx); } static xmlParserInputPtr resolveEntityFunction(void *ctx, const unsigned char *eid, const unsigned char *url) { return loadEntityFunction(url, eid, ctx); } #define TREEFUN(NAME,ARGS) ((HANDLER->isHtmlHandler == YES) ? (*(htmlDefaultSAXHandler.NAME))ARGS : (*(xmlDefaultSAXHandler.NAME))ARGS) #define START(SELNAME, RET, ARGS) \ static SEL sel; \ static RET (*treeImp)ARGS = 0; \ RET (*imp)ARGS; \ \ NSCAssert(ctx,@"No Context"); \ \ if (treeImp == 0) \ { \ sel = @selector(SELNAME); \ treeImp = (RET (*)ARGS)[GSTreeSAXHandler instanceMethodForSelector: sel];\ } \ imp = (RET (*)ARGS)[HANDLER methodForSelector: sel] static void startDocumentFunction(void *ctx) { NSCAssert(ctx,@"No Context"); [HANDLER startDocument]; } static void endDocumentFunction(void *ctx) { NSCAssert(ctx,@"No Context"); [HANDLER endDocument]; } static int isStandaloneFunction(void *ctx) { NSCAssert(ctx,@"No Context"); return [HANDLER isStandalone]; } static int hasInternalSubsetFunction(void *ctx) { int has; NSCAssert(ctx,@"No Context"); has = [HANDLER hasInternalSubset]; if (has < 0) { has = TREEFUN(hasInternalSubset, (ctx)); } return has; } static int hasExternalSubsetFunction(void *ctx) { int has; NSCAssert(ctx,@"No Context"); has = [HANDLER hasExternalSubset]; if (has < 0) { has = TREEFUN(hasExternalSubset, (ctx)); } return has; } static void internalSubsetFunction(void *ctx, const unsigned char *name, const xmlChar *ExternalID, const xmlChar *SystemID) { NSCAssert(ctx,@"No Context"); [HANDLER internalSubset: UTF8Str(name) externalID: UTF8Str(ExternalID) systemID: UTF8Str(SystemID)]; } static void externalSubsetFunction(void *ctx, const unsigned char *name, const xmlChar *ExternalID, const xmlChar *SystemID) { NSCAssert(ctx,@"No Context"); [HANDLER externalSubset: UTF8Str(name) externalID: UTF8Str(ExternalID) systemID: UTF8Str(SystemID)]; } static xmlEntityPtr getEntityFunction(void *ctx, const unsigned char *name) { NSCAssert(ctx,@"No Context"); return [HANDLER getEntity: UTF8Str(name)]; } static xmlEntityPtr getParameterEntityFunction(void *ctx, const unsigned char *name) { NSCAssert(ctx,@"No Context"); return [HANDLER getParameterEntity: UTF8Str(name)]; } static void entityDeclFunction(void *ctx, const unsigned char *name, int type, const unsigned char *publicId, const unsigned char *systemId, unsigned char *content) { NSCAssert(ctx,@"No Context"); [HANDLER entityDecl: UTF8Str(name) type: type public: UTF8Str(publicId) system: UTF8Str(systemId) content: UTF8Str(content)]; } static void attributeDeclFunction(void *ctx, const unsigned char *elem, const unsigned char *name, int type, int def, const unsigned char *defaultValue, xmlEnumerationPtr tree) { NSCAssert(ctx,@"No Context"); [HANDLER attributeDecl: UTF8Str(elem) name: UTF8Str(name) type: type typeDefValue: def defaultValue: UTF8Str(defaultValue)]; } static void elementDeclFunction(void *ctx, const unsigned char *name, int type, xmlElementContentPtr content) { NSCAssert(ctx,@"No Context"); [HANDLER elementDecl: UTF8Str(name) type: type]; } static void notationDeclFunction(void *ctx, const unsigned char *name, const unsigned char *publicId, const unsigned char *systemId) { NSCAssert(ctx,@"No Context"); [HANDLER notationDecl: UTF8Str(name) public: UTF8Str(publicId) system: UTF8Str(systemId)]; } static void unparsedEntityDeclFunction(void *ctx, const unsigned char *name, const unsigned char *publicId, const unsigned char *systemId, const unsigned char *notationName) { NSCAssert(ctx,@"No Context"); [HANDLER unparsedEntityDecl: UTF8Str(name) public: UTF8Str(publicId) system: UTF8Str(systemId) notationName: UTF8Str(notationName)]; } static void startElementFunction(void *ctx, const unsigned char *name, const unsigned char **atts) { NSMutableDictionary *dict; NSCAssert(ctx,@"No Context"); dict = [NSMutableDictionary dictionary]; if (atts != NULL) { int i = 0; while (atts[i] != NULL) { NSString *key = UTF8Str(atts[i++]); NSString *obj; const unsigned char *val = atts[i++]; if (0 == val) { /* No value ... assume tis is a minimised attribute in html. */ obj = key; } else { obj = UTF8Str(val); } [dict setObject: obj forKey: key]; } } [HANDLER startElement: UTF8Str(name) attributes: dict]; } static void endElementFunction(void *ctx, const unsigned char *name) { [HANDLER endElement: UTF8Str(name)]; } static void startElementNsFunction(void *ctx, const unsigned char *name, const unsigned char *prefix, const unsigned char *href, int nb_namespaces, const unsigned char **namespaces, int nb_attributes, int nb_defaulted, const unsigned char **atts) { NSMutableDictionary *adict = nil; NSMutableDictionary *ndict = nil; NSString *elem; NSCAssert(ctx,@"No Context"); elem = UTF8Str(name); if (atts != NULL) { int i; int j; adict = [NSMutableDictionary dictionaryWithCapacity: nb_attributes]; for (i = j = 0; i < nb_attributes; i++, j += 5) { NSString *key = UTF8Str(atts[j]); NSString *obj = nil; // We need to append the namespace prefix if (atts[j+1] != NULL) { key = [[UTF8Str(atts[j+1]) stringByAppendingString: @":"] stringByAppendingString: key]; } obj = UTF8StrLen(atts[j+3], atts[j+4]-atts[j+3]); [adict setObject: obj forKey: key]; } } if (nb_namespaces > 0) { int i; int pos = 0; ndict = [NSMutableDictionary dictionaryWithCapacity: nb_namespaces]; for (i = 0; i < nb_namespaces; i++) { NSString *key; NSString *obj; if (namespaces[pos] == 0) { key = @""; } else { key = UTF8Str(namespaces[pos]); } pos++; if (namespaces[pos] == 0) { obj = @""; } else { obj = UTF8Str(namespaces[pos]); } pos++; [ndict setObject: obj forKey: key]; } } [HANDLER startElement: elem prefix: UTF8Str(prefix) href: UTF8Str(href) attributes: adict namespaces: ndict]; } static void endElementNsFunction(void *ctx, const unsigned char *name, const unsigned char *prefix, const unsigned char *href) { NSCAssert(ctx,@"No Context"); [HANDLER endElement: UTF8Str(name) prefix: UTF8Str(prefix) href: UTF8Str(href)]; } static void charactersFunction(void *ctx, const unsigned char *ch, int len) { NSCAssert(ctx,@"No Context"); [HANDLER characters: UTF8StrLen(ch, len)]; } static void referenceFunction(void *ctx, const unsigned char *name) { NSCAssert(ctx,@"No Context"); [HANDLER reference: UTF8Str(name)]; } static void ignorableWhitespaceFunction(void *ctx, const unsigned char *ch, int len) { NSCAssert(ctx,@"No Context"); [HANDLER ignoreWhitespace: UTF8StrLen(ch, len)]; } static void processingInstructionFunction(void *ctx, const unsigned char *target, const char *data) { NSCAssert(ctx,@"No Context"); [HANDLER processInstruction: UTF8Str(target) data: UTF8Str((const unsigned char*)data)]; } static void cdataBlockFunction(void *ctx, const unsigned char *value, int len) { NSCAssert(ctx,@"No Context"); [HANDLER cdataBlock: [NSData dataWithBytes: value length: len]]; } static void commentFunction(void *ctx, const unsigned char *value) { NSCAssert(ctx,@"No Context"); [HANDLER comment: UTF8Str(value)]; } static void warningFunction(void *ctx, const unsigned char *msg, ...) { NSString *estr; va_list args; int lineNumber = -1; int colNumber = -1; va_start(args, msg); estr = [[[NSString alloc] initWithFormat: UTF8Str(msg) arguments: args] autorelease]; va_end(args); NSCAssert(ctx,@"No Context"); lineNumber = xmlSAX2GetLineNumber(ctx); colNumber = xmlSAX2GetColumnNumber(ctx); [HANDLER warning: estr colNumber: colNumber lineNumber: lineNumber]; } static void errorFunction(void *ctx, const unsigned char *msg, ...) { NSString *estr; va_list args; int lineNumber = -1; int colNumber = -1; va_start(args, msg); estr = [[[NSString alloc] initWithFormat: UTF8Str(msg) arguments: args] autorelease]; va_end(args); NSCAssert(ctx,@"No Context"); lineNumber = xmlSAX2GetLineNumber(ctx); colNumber = xmlSAX2GetColumnNumber(ctx); [HANDLER error: estr colNumber: colNumber lineNumber: lineNumber]; } static void fatalErrorFunction(void *ctx, const unsigned char *msg, ...) { NSString *estr; va_list args; int lineNumber = -1; int colNumber = -1; va_start(args, msg); estr = [[[NSString alloc] initWithFormat: UTF8Str(msg) arguments: args] autorelease]; va_end(args); NSCAssert(ctx, @"No Context"); lineNumber = xmlSAX2GetLineNumber(ctx); colNumber = xmlSAX2GetColumnNumber(ctx); [HANDLER fatalError: estr colNumber: colNumber lineNumber: lineNumber]; } #undef HANDLER /** * Create a new SAX handler. */ + (GSSAXHandler*) handler { return AUTORELEASE([[self alloc] init]); } - (id) init { NSAssert(lib == 0, @"Already created lib"); self = [super init]; if (self != nil) { if ([self _initLibXML] == NO) { NSLog(@"GSSAXHandler: out of memory\n"); DESTROY(self); return nil; } } return self; } /** * Returns a pointer to the raw libxml data used by this document.
* Only for use by libxml experts! */ - (void*) lib { return lib; } /** * Return the parser object with which this handler is * associated. This may occasionally be useful. */ - (GSXMLParser*) parser { return parser; } - (void) dealloc { if (lib != NULL) { free(lib); } [super dealloc]; } /** * Called when the document starts being processed. */ - (void) startDocument { } /** * Called when the document end has been detected. */ - (void) endDocument { } /** * Called to detemrine if the document is standalone. */ - (NSInteger) isStandalone { return 1; } /** * Called when an opening tag has been processed. */ - (void) startElement: (NSString*)elementName attributes: (NSMutableDictionary*)elementAttributes { } - (void) startElement: (NSString*)elementName prefix: (NSString*)prefix href: (NSString*)href attributes: (NSMutableDictionary*)elementAttributes { [self startElement: elementName attributes: elementAttributes]; } - (void) startElement: (NSString*)elementName prefix: (NSString*)prefix href: (NSString*)href attributes: (NSMutableDictionary*)elementAttributes namespaces: (NSMutableDictionary*)elementNamespaces { [self startElement: elementName prefix: prefix href: href attributes: elementAttributes]; } /** * Called when a closing tag has been processed. */ - (void) endElement: (NSString*)elementName { } /** * Called when a closing tag has been processed. */ - (void) endElement: (NSString*)elementName prefix: (NSString*)prefix href: (NSString*)href { [self endElement: elementName]; } /** * Handle an attribute that has been read by the parser. */ - (void) attribute: (NSString*) name value: (NSString*)value { } /** * Receiving some chars from the parser. */ - (void) characters: (NSString*) name { } /** * Receiving some ignorable whitespaces from the parser. */ - (void) ignoreWhitespace: (NSString*) ch { } /** * A processing instruction has been parsed. */ - (void) processInstruction: (NSString*)targetName data: (NSString*)PIdata { } /** * A comment has been parsed. */ - (void) comment: (NSString*) value { } /** * Called when a cdata block has been parsed. */ - (void) cdataBlock: (NSData*)value { } /** * Called to return the filename from which an entity should be loaded. */ - (NSString*) loadEntity: (NSString*)publicId at: (NSString*)location { return nil; } /** * An old global namespace has been parsed. */ - (void) namespaceDecl: (NSString*)name href: (NSString*)href prefix: (NSString*)prefix { } /** * What to do when a notation declaration has been parsed. */ - (void) notationDecl: (NSString*)name public: (NSString*)publicId system: (NSString*)systemId { } /** * An entity definition has been parsed. */ - (void) entityDecl: (NSString*)name type: (NSInteger)type public: (NSString*)publicId system: (NSString*)systemId content: (NSString*)content { } /** * An attribute definition has been parsed. */ - (void) attributeDecl: (NSString*)nameElement name: (NSString*)name type: (NSInteger)type typeDefValue: (NSInteger)defType defaultValue: (NSString*)value { } /** * An element definition has been parsed. */ - (void) elementDecl: (NSString*)name type: (NSInteger)type { } /** * What to do when an unparsed entity declaration is parsed. */ - (void) unparsedEntityDecl: (NSString*)name public: (NSString*)publicId system: (NSString*)systemId notationName: (NSString*)notation { } /** * Called when an entity reference is detected. */ - (void) reference: (NSString*) name { } /** * An old global namespace has been parsed. */ - (void) globalNamespace: (NSString*)name href: (NSString*)href prefix: (NSString*)prefix { } /** * Called when a warning message needs to be output. */ - (void) warning: (NSString*)e { GSPrintf(stderr, @"%@", e); } /** * Called when an error message needs to be output. */ - (void) error: (NSString*)e { GSPrintf(stderr, @"%@", e); } /** * Called when a fatal error message needs to be output. */ - (void) fatalError: (NSString*)e { GSPrintf(stderr, @"%@", e); } /** * Called when a warning message needs to be output. */ - (void) warning: (NSString*)e colNumber: (NSInteger)colNumber lineNumber: (NSInteger)lineNumber { e = [NSString stringWithFormat: @"at line: %"PRIdPTR" column: %"PRIdPTR" ... %@", lineNumber, colNumber, e]; [self warning: e]; } /** * Called when an error message needs to be output. */ - (void) error: (NSString*)e colNumber: (NSInteger)colNumber lineNumber: (NSInteger)lineNumber { e = [NSString stringWithFormat: @"at line: %"PRIdPTR" column: %"PRIdPTR" ... %@", lineNumber, colNumber, e]; [self error: e]; } /** * Called when a fatal error message needs to be output. */ - (void) fatalError: (NSString*)e colNumber: (NSInteger)colNumber lineNumber: (NSInteger)lineNumber { e = [NSString stringWithFormat: @"at line: %"PRIdPTR" column: %"PRIdPTR" ... %@", lineNumber, colNumber, e]; [self fatalError: e]; } /** * Called to find out whether there is an internal subset. */ - (NSInteger) hasInternalSubset { return 0; } /** * Called to find out whether there is an internal subset. */ - (BOOL) internalSubset: (NSString*)name externalID: (NSString*)externalID systemID: (NSString*)systemID { return NO; } /** * Called to find out whether there is an external subset. */ - (NSInteger) hasExternalSubset { return 0; } /** * Called to find out whether there is an external subset. */ - (BOOL) externalSubset: (NSString*)name externalID: (NSString*)externalID systemID: (NSString*)systemID { return NO; } /** * get an entity by name */ - (void*) getEntity: (NSString*)name { return 0; } /** * get a parameter entity by name */ - (void*) getParameterEntity: (NSString*)name { return 0; } /* * Private methods - internal use only. */ - (BOOL) _initLibXML { lib = (xmlSAXHandler*)malloc(sizeof(xmlSAXHandler)); if (lib == NULL) { return NO; } else { xmlSAX2InitDefaultSAXHandler(lib, 0); #define LIB ((xmlSAXHandlerPtr)lib) /* * We must call xmlSAXVersion() BEFORE setting any functions as it * sets up default values and would trash our settings. */ xmlSAXVersion(LIB, 2); // Set SAX2 LIB->startElementNs = (void*) startElementNsFunction; LIB->endElementNs = (void*) endElementNsFunction; LIB->startElement = (void*) startElementFunction; LIB->endElement = (void*) endElementFunction; LIB->internalSubset = (void*) internalSubsetFunction; LIB->externalSubset = (void*) externalSubsetFunction; LIB->isStandalone = (void*) isStandaloneFunction; LIB->hasInternalSubset = (void*) hasInternalSubsetFunction; LIB->hasExternalSubset = (void*) hasExternalSubsetFunction; LIB->getEntity = (void*) getEntityIgnoreExternal; LIB->entityDecl = (void*) entityDeclFunction; LIB->notationDecl = (void*) notationDeclFunction; LIB->attributeDecl = (void*) attributeDeclFunction; LIB->elementDecl = (void*) elementDeclFunction; LIB->unparsedEntityDecl = (void*) unparsedEntityDeclFunction; LIB->startDocument = (void*) startDocumentFunction; LIB->endDocument = (void*) endDocumentFunction; LIB->reference = (void*) referenceFunction; LIB->characters = (void*) charactersFunction; LIB->ignorableWhitespace = (void*) ignorableWhitespaceFunction; LIB->processingInstruction = (void*) processingInstructionFunction; LIB->comment = (void*) commentFunction; LIB->warning = (void*) warningFunction; LIB->error = (void*) errorFunction; LIB->fatalError = (void*) fatalErrorFunction; LIB->getParameterEntity = (void*) getParameterEntityFunction; LIB->cdataBlock = (void*) cdataBlockFunction; LIB->resolveEntity = (void*) resolveEntityFunction; #undef LIB return YES; } } - (void) _setParser: (GSXMLParser*)value { parser = value; } @end /** * The default handler for parsing documents ... this will build a * GSXMLDocument for you. This handler may not currently be subclassed, * though that capability may be added at a later date. */ @implementation GSTreeSAXHandler /** * Called when a warning message needs to be output.
* See [GSXMLParser-setErrors:] for the mechanism implemented by this. */ - (void) warning: (NSString*)e { NSMutableString *m = [parser _messages]; if (m == nil) { GSPrintf(stderr, @"%@", e); } else { [m appendString: e]; } } /** * Called when an error message needs to be output.
* See [GSXMLParser-setErrors:] for the mechanism implemented by this. */ - (void) error: (NSString*)e { NSMutableString *m = [parser _messages]; if (m == nil) { GSPrintf(stderr, @"%@", e); } else { [m appendString: e]; } } /** * Called when a fatal error message needs to be output.
* See [GSXMLParser-setErrors:] for the mechanism implemented by this. */ - (void) fatalError: (NSString*)e { NSMutableString *m = [parser _messages]; if (m == nil) { GSPrintf(stderr, @"%@", e); } else { [m appendString: e]; } } - (BOOL) _initLibXML { lib = (xmlSAXHandler*)malloc(sizeof(xmlSAXHandler)); if (lib == NULL) { return NO; } else { xmlSAX2InitDefaultSAXHandler(lib, 0); #define LIB ((xmlSAXHandlerPtr)lib) #define SETCB(NAME,SEL) if ([self methodForSelector: @selector(SEL)] != [GSTreeSAXHandler instanceMethodForSelector: @selector(SEL)]) LIB->NAME = (void*)NAME ## Function /* * We must call xmlSAXVersion() BEFORE setting any functions as it * sets up default values and would trash our settings. */ xmlSAXVersion(LIB, 2); // Set SAX2 SETCB(startElementNs, startElement:prefix:href:attributes:); SETCB(endElementNs, endElement:prefix:href:); SETCB(startElement, startElement:attributes:); SETCB(endElement, endElement:); SETCB(internalSubset, internalSubset:externalID:systemID:); SETCB(externalSubset, externalSubset:externalID:systemID:); SETCB(isStandalone, isStandalone); SETCB(hasInternalSubset, hasInternalSubset); SETCB(hasExternalSubset, hasExternalSubset); SETCB(getEntity, getEntity:); if (LIB->getEntity != getEntityFunction) { LIB->getEntity = getEntityIgnoreExternal; } SETCB(entityDecl, entityDecl:type:public:system:content:); SETCB(notationDecl, notationDecl:public:system:); SETCB(attributeDecl, attributeDecl:name:type:typeDefValue:defaultValue:); SETCB(elementDecl, elementDecl:type:); SETCB(unparsedEntityDecl, unparsedEntityDecl:public:system:notationName:); SETCB(startDocument, startDocument); SETCB(endDocument, endDocument); SETCB(reference, reference:); SETCB(characters, characters:); SETCB(ignorableWhitespace, ignoreWhitespace:); SETCB(processingInstruction, processInstruction:data:); SETCB(comment, comment:); SETCB(getParameterEntity, getParameterEntity:); SETCB(cdataBlock, cdataBlock:); LIB->warning = (void*)warningFunction; LIB->error = (void*)errorFunction; LIB->fatalError = (void*)fatalErrorFunction; #undef LIB return YES; } } @end /** * You may create a subclass of this class to handle incremental parsing * of html documents ... this is provided for handling legacy documents, * as modern html documents should use xhtml, and for those you should * simply subclass [GSSAXHandler] */ @implementation GSHTMLSAXHandler - (BOOL) _initLibXML { isHtmlHandler = YES; lib = (xmlSAXHandler*)malloc(sizeof(htmlSAXHandler)); if (lib == NULL) { return NO; } else { memcpy(lib, &htmlDefaultSAXHandler, sizeof(htmlSAXHandler)); #define LIB ((htmlSAXHandlerPtr)lib) LIB->internalSubset = (void*)internalSubsetFunction; LIB->externalSubset = (void*)externalSubsetFunction; LIB->isStandalone = (void*)isStandaloneFunction; LIB->hasInternalSubset = (void*)hasInternalSubsetFunction; LIB->hasExternalSubset = (void*)hasExternalSubsetFunction; LIB->getEntity = (void*)getEntityFunction; LIB->entityDecl = (void*)entityDeclFunction; LIB->notationDecl = (void*)notationDeclFunction; LIB->attributeDecl = (void*)attributeDeclFunction; LIB->elementDecl = (void*)elementDeclFunction; LIB->unparsedEntityDecl = (void*)unparsedEntityDeclFunction; LIB->startDocument = (void*)startDocumentFunction; LIB->endDocument = (void*)endDocumentFunction; LIB->startElement = (void*)startElementFunction; LIB->endElement = (void*)endElementFunction; LIB->reference = (void*)referenceFunction; LIB->characters = (void*)charactersFunction; LIB->ignorableWhitespace = (void*)ignorableWhitespaceFunction; LIB->processingInstruction = (void*)processingInstructionFunction; LIB->comment = (void*)commentFunction; LIB->warning = (void*)warningFunction; LIB->error = (void*)errorFunction; LIB->fatalError = (void*)fatalErrorFunction; LIB->getParameterEntity = (void*)getParameterEntityFunction; LIB->cdataBlock = (void*)cdataBlockFunction; #undef LIB return YES; } } @end /** *

You don't create GSXPathObject instances, instead the XPATH system * creates them and returns them as the result of the * [GSXPathContext-evaluateExpression:] method. *

*/ @implementation GSXPathObject - (id) init { DESTROY(self); return nil; } /* Internal method. */ - (id) _initWithNativePointer: (xmlXPathObject *)lib context: (GSXPathContext *)context { _lib = lib; /* We RETAIN our context because we might be holding references to nodes * which belong to the document, and we must make sure the document is * not freed before we are. */ ASSIGN (_context, context); return self; } /* This method is called by GSXPathContext when creating a * GSXPathObject to wrap the results of a query. It assumes that lib * is a pointer created by xmlXPathEval (), and that we are now taking * on responsibility for freeing it. It then examines lib, and * replaces itself with an object of the appropriate subclass. */ + (id) _newWithNativePointer: (xmlXPathObject *)lib context: (GSXPathContext *)context { switch (lib->type) { case XPATH_NODESET: return [[GSXPathNodeSet alloc] _initWithNativePointer: lib context: context]; break; case XPATH_BOOLEAN: return [[GSXPathBoolean alloc] _initWithNativePointer: lib context: context]; break; case XPATH_NUMBER: return [[GSXPathNumber alloc] _initWithNativePointer: lib context: context]; break; case XPATH_STRING: return [[GSXPathString alloc] _initWithNativePointer: lib context: context]; break; default: /* This includes: case XPATH_UNDEFINED: case XPATH_POINT: case XPATH_RANGE: case XPATH_LOCATIONSET: case XPATH_USERS: case XPATH_XSLT_TREE: */ return [[self alloc] _initWithNativePointer: lib context: context]; } } - (void) dealloc { xmlXPathFreeObject (_lib); RELEASE (_context); [super dealloc]; } @end @implementation GSXPathBoolean /** * Returns the the value of the receiver ... YES/NO, true/false. */ - (BOOL) booleanValue { return ((xmlXPathObject*)_lib)->boolval; } - (NSString *) description { return ([self booleanValue] ? @"true" : @"false"); } @end @implementation GSXPathNumber /** * Returns the floating point (double) value of the receiver. */ - (double) doubleValue { return ((xmlXPathObject*)_lib)->floatval; } - (NSString *) description { return [NSString_class stringWithFormat: @"%f", [self doubleValue]]; } @end @implementation GSXPathString /** * Returns the string value of the receiver. */ - (NSString *) stringValue { xmlChar *string = ((xmlXPathObject*)_lib)->stringval; return [NSString_class stringWithUTF8String: (const char*)string]; } - (NSString *) description { return [NSString_class stringWithFormat: @"%@", [self stringValue]]; } @end /** * An XPATH node set is an ordered set of nodes returned as a result of * an expression. The order of the nodes in the set is the same as the * order in the xml document from which they were extracted. */ @implementation GSXPathNodeSet /** * Returns the number of nodes in the receiver. */ - (NSUInteger) count { if (xmlXPathNodeSetIsEmpty (((xmlXPathObject*)_lib)->nodesetval)) { return 0; } return xmlXPathNodeSetGetLength (((xmlXPathObject*)_lib)->nodesetval); } /** * Deprecated */ - (NSUInteger) length { if (xmlXPathNodeSetIsEmpty (((xmlXPathObject*)_lib)->nodesetval)) { return 0; } return xmlXPathNodeSetGetLength (((xmlXPathObject*)_lib)->nodesetval); } /** * Returns the node from the receiver at the specified index, or nil * if no such node exists. */ - (GSXMLNode *) nodeAtIndex: (NSUInteger)index { if (xmlXPathNodeSetIsEmpty (((xmlXPathObject*)_lib)->nodesetval)) { return nil; } else { xmlNode *node; GSXMLNode *n; node = xmlXPathNodeSetItem (((xmlXPathObject*)_lib)->nodesetval, (NSInteger)index); n = [GSXMLNode alloc]; n = [n _initFrom: node parent: self]; return AUTORELEASE(n); } } - (NSString *) description { return [NSString_class stringWithFormat: @"NodeSet (count %"PRIuPTR")", [self count]]; } @end /** *

Use of the GSXPathContext class is simple ... you just need to * look up xpath to learn the syntax of xpath expressions, then you * can apply those expressions to a context to retrieve data from a * document. *

* * GSXMLParser *p = [GSXMLParser parserWithContentsOfFile: @"xp.xml"]; * * if ([p parse]) * { * GSXMLDocument *d = [p document]; * GSXPathContext *c = [[GSXPathContext alloc] initWithDocument: document]; * GSXPathString *result = [c evaluateExpression: @"string(/body/text())"]; * * GSPrintf(stdout, @"Got %@", [result stringValue]); * } * else * { * GSPrintf(stderr, "error parsing file\n"); * } * */ @implementation GSXPathContext /** * Initialises the receiver as an xpath parser for the supplied document. */ - (id) initWithDocument: (GSXMLDocument *)d { ASSIGN (_document, d); _lib = xmlXPathNewContext ([_document lib]); ((xmlXPathContext*)_lib)->node = xmlDocGetRootElement ([_document lib]); return self; } /** * Evaluates the supplied expression and returns the resulting node or * node set. If the expression is invalid, returns nil. */ - (GSXPathObject *) evaluateExpression: (NSString *)XPathExpression { xmlXPathCompExpr *comp; xmlXPathObject *res; GSXPathObject *result; comp = xmlXPathCompile (UTF8STRING(XPathExpression)); if (comp == NULL) { /* Maybe an exception would be better ? */ return nil; } res = xmlXPathCompiledEval (comp, ((xmlXPathContext*)_lib)); if (res == NULL) { result = nil; } else { result = [GSXPathObject _newWithNativePointer: res context: self]; IF_NO_ARC ([result autorelease];) } xmlXPathFreeCompExpr (comp); return result; } - (BOOL) registerNamespaceWithPrefix: (NSString *)prefix href: (NSString *)href { if (xmlXPathRegisterNs (_lib, UTF8STRING(prefix), UTF8STRING(href)) != 0) { return NO; } else { return YES; } } - (void) dealloc { xmlXPathFreeContext (_lib); RELEASE (_document); [super dealloc]; } @end /* * need this to make the linker happy on Windows */ @interface GSXMLDummy : NSObject @end @implementation GSXMLDummy @end @implementation GSXMLNode (Deprecated) - (GSXMLNode*) childElement { static BOOL warned = NO; if (warned == NO) { warned = YES; NSLog(@"WARNING, use of deprecated method ... [%@ -%@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); } return [self firstChildElement]; } - (GSXMLNode*) children { static BOOL warned = NO; if (warned == NO) { warned = YES; NSLog(@"WARNING, use of deprecated method ... [%@ -%@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); } return [self firstChild]; } - (GSXMLDocument*) doc { static BOOL warned = NO; if (warned == NO) { warned = YES; NSLog(@"WARNING, use of deprecated method ... [%@ -%@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); } return [self document]; } - (GSXMLNamespace*) ns { static BOOL warned = NO; if (warned == NO) { warned = YES; NSLog(@"WARNING, use of deprecated method ... [%@ -%@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); } return [self namespace]; } - (GSXMLNamespace*) nsDefs { static BOOL warned = NO; if (warned == NO) { warned = YES; NSLog(@"WARNING, use of deprecated method ... [%@ -%@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); } return [self namespaceDefinitions]; } - (GSXMLNode*) prev { static BOOL warned = NO; if (warned == NO) { warned = YES; NSLog(@"WARNING, use of deprecated method ... [%@ -%@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); } return [self previous]; } - (NSMutableDictionary*) propertiesAsDictionary { static BOOL warned = NO; if (warned == NO) { warned = YES; NSLog(@"WARNING, use of deprecated method ... [%@ -%@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); } return [self propertiesAsDictionaryWithKeyTransformationSel: NULL]; } @end @implementation GSXMLParser (Deprecated) - (GSXMLDocument*) doc { static BOOL warned = NO; if (warned == NO) { warned = YES; NSLog(@"WARNING, use of deprecated method ... [%@ -%@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); } return [self document]; } @end @implementation GSXMLDocument (XSLT) #ifdef HAVE_LIBXSLT /** * Performs an XSLT transformation on the specified file using the * stylesheet provided.
* * Returns an autoreleased GSXMLDocument containing the transformed * XML, or nil on failure. */ + (GSXMLDocument*) xsltTransformFile: (NSString*)xmlFile stylesheet: (NSString*)xsltStylesheet { return [GSXMLDocument xsltTransformFile: xmlFile stylesheet: xsltStylesheet params: nil]; } /** * Performs an XSLT transformation on the specified file using the * stylesheet and parameters provided. See the libxslt documentation * for details of the supported parameters.
* * Returns an autoreleased GSXMLDocument containing the transformed * XML, or nil on failure. */ + (GSXMLDocument*) xsltTransformFile: (NSString*)xmlFile stylesheet: (NSString*)xsltStylesheet params: (NSDictionary*)params { GSXMLDocument *newdoc; NS_DURING { NSData *xml; NSData *ss; xml = [NSData dataWithContentsOfFile: xmlFile]; ss = [NSData dataWithContentsOfFile: xsltStylesheet]; if (xml == nil || ss == nil) { newdoc = nil; } else { newdoc = [GSXMLDocument xsltTransformXml: xml stylesheet: ss params: params]; } } NS_HANDLER { newdoc = nil; } NS_ENDHANDLER return newdoc; } /** * Performs an XSLT transformation on the specified file using the * stylesheet provided.
* * Returns an autoreleased GSXMLDocument containing the transformed * XML, or nil on failure. */ + (GSXMLDocument*) xsltTransformXml: (NSData*)xmlData stylesheet: (NSData*)xsltStylesheet { return [GSXMLDocument xsltTransformXml: xmlData stylesheet: xsltStylesheet params: nil]; } /** * Performs an XSLT transformation on the specified file using the * stylesheet and parameters provided.See the libxslt documentation * for details of the supported parameters.
* * Returns an autoreleased GSXMLDocument containing the transformed * XML, or nil on failure. */ + (GSXMLDocument*) xsltTransformXml: (NSData*)xmlData stylesheet: (NSData*)xsltStylesheet params: (NSDictionary*)params { GSXMLDocument *newdoc; NS_DURING { GSXMLParser *xmlParser; GSXMLDocument *xml; GSXMLParser *ssParser; GSXMLDocument *ss; newdoc = nil; xmlParser = [GSXMLParser parserWithData: xmlData]; if ([xmlParser parse] == YES) { xml = [xmlParser document]; ssParser = [GSXMLParser parserWithData: xsltStylesheet]; if ([ssParser parse] == YES) { ss = [ssParser document]; newdoc = [xml xsltTransform: ss params: params]; } } } NS_HANDLER { newdoc = nil; } NS_ENDHANDLER return newdoc; } /** * Performs an XSLT transformation on the current document using the * supplied stylesheet.
* * Returns an autoreleased GSXMLDocument containing the transformed * XML, or nil on failure. */ - (GSXMLDocument*) xsltTransform: (GSXMLDocument*)xsltStylesheet { return [self xsltTransform: xsltStylesheet params: nil]; } /** * Performs an XSLT transformation on the current document using the * supplied stylesheet and paramaters (params may be nil). * See the libxslt documentation for details of the supported parameters.
* * Returns an autoreleased GSXMLDocument containing the transformed * XML, or nil on failure. */ - (GSXMLDocument*) xsltTransform: (GSXMLDocument*)xsltStylesheet params: (NSDictionary*)params { GSXMLDocument *newdoc = nil; NS_DURING { xsltStylesheetPtr ss = NULL; xmlDocPtr ssXml = (xmlDocPtr)[xsltStylesheet lib]; int pSize = params == nil ? 1 : ([params count] * 2) + 1; int pNum = 0; const char *parameters[pSize]; if (params != nil) { NSEnumerator *keys = [params keyEnumerator]; if (keys != nil) { NSString *key = [keys nextObject]; while (key != nil) { NSString *value = [params objectForKey: key]; parameters[pNum++] = [key cString]; parameters[pNum++] = [value cString]; key = [keys nextObject]; } } } parameters[pNum] = NULL; ss = xsltParseStylesheetDoc(ssXml); if (xsltStylesheet != NULL) { xmlDocPtr res = NULL; res = xsltApplyStylesheet(ss, lib, parameters); if (res != NULL) { newdoc = [GSXMLDocument alloc]; newdoc = [newdoc _initFrom: res parent: self ownsLib: YES]; IF_NO_ARC([newdoc autorelease];) } } /* * N.B. We don't want to call xsltFreeStylesheet() to free the * stylesheet xmlDocPtr because that will destroy the lib which * is owned by the xsltStylesheet object. */ xsltCleanupGlobals(); } NS_HANDLER { newdoc= nil; } NS_ENDHANDLER return newdoc; } #else /* HAVE_LIBXSLT */ + (GSXMLDocument*) xsltTransformFile: (NSString*)xmlFile stylesheet: (NSString*)xsltStylesheet params: (NSDictionary*)params { NSLog(@"libxslt is not available"); return nil; } + (GSXMLDocument*) xsltTransformFile: (NSString*)xmlFile stylesheet: (NSString*)xsltStylesheet { NSLog(@"libxslt is not available"); return nil; } + (GSXMLDocument*) xsltTransformXml: (NSData*)xmlData stylesheet: (NSData*)xsltStylesheet params: (NSDictionary*)params { NSLog(@"libxslt is not available"); return nil; } + (GSXMLDocument*) xsltTransformXml: (NSData*)xmlData stylesheet: (NSData*)xsltStylesheet { NSLog(@"libxslt is not available"); return nil; } - (GSXMLDocument*) xsltTransform: (GSXMLDocument*)xsltStylesheet params: (NSDictionary*)params { NSLog(@"libxslt is not available"); return nil; } - (GSXMLDocument*) xsltTransform: (GSXMLDocument*)xsltStylesheet { NSLog(@"libxslt is not available"); return nil; } #endif /* HAVE_LIBXSLT */ @end #else #ifndef NeXT_Foundation_LIBRARY #import "Foundation/NSCoder.h" #import "Foundation/NSInvocation.h" #endif /* * Build dummy implementations of the classes if libxml is not available */ GS_EXPORT_CLASS @interface GSXMLDummy : NSObject @end GS_EXPORT_CLASS @interface GSXMLDocument : GSXMLDummy @end GS_EXPORT_CLASS @interface GSXMLNamespace : GSXMLDummy @end GS_EXPORT_CLASS @interface GSXMLNode : GSXMLDummy @end GS_EXPORT_CLASS @interface GSSAXHandler : GSXMLDummy @end GS_EXPORT_CLASS @interface GSXMLParser : GSXMLDummy @end GS_EXPORT_CLASS @interface GSXMLAttribute : GSXMLNode @end @implementation GSXMLDummy + (id) allocWithZone: (NSZone*)z { NSLog(@"Not built with libxml ... %@ unusable in %@", NSStringFromClass(self), NSStringFromSelector(_cmd)); return nil; } + (void) forwardInvocation: (NSInvocation*)anInvocation { NSLog(@"Not built with libxml ... %@ unusable in %@", NSStringFromClass([self class]), NSStringFromSelector([anInvocation selector])); return; } - (id) init { NSLog(@"Not built with libxml ... %@ unusable in %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); DESTROY(self); return nil; } - (id) initWithCoder: (NSCoder*)aCoder { NSLog(@"Not built with libxml ... %@ unusable in %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); DESTROY(self); return nil; } @end GS_EXPORT_CLASS @implementation GSXMLDocument @end GS_EXPORT_CLASS @implementation GSXMLNamespace @end GS_EXPORT_CLASS @implementation GSXMLNode @end GS_EXPORT_CLASS @implementation GSSAXHandler @end GS_EXPORT_CLASS @implementation GSXMLParser @end GS_EXPORT_CLASS @implementation GSXMLAttribute @end #endif @implementation NSString (GSXML) - (NSString*) stringByEscapingXML { unsigned length = [self length]; unsigned output = 0; unichar *from; unsigned i = 0; BOOL escape = NO; from = NSZoneMalloc (NSDefaultMallocZone(), sizeof(unichar) * length); [self getCharacters: from]; for (i = 0; i < length; i++) { unichar c = from[i]; if ((c >= 0x20 && c <= 0xd7ff) || c == 0x9 || c == 0xd || c == 0xa || (c >= 0xe000 && c <= 0xfffd)) { switch (c) { case '"': case '\'': output += 6; escape = YES; break; case '&': output += 5; escape = YES; break; case '<': case '>': output += 4; escape = YES; break; default: /* * For non-ascii characters, we can use &#nnnn; escapes */ if (c > 127) { output += 5; while (c >= 1000) { output++; c /= 10; } escape = YES; } output++; break; } } else { escape = YES; // Need to remove bad characters } } if (escape == YES) { unichar *to; unsigned j = 0; to = NSZoneMalloc (NSDefaultMallocZone(), sizeof(unichar) * output); for (i = 0; i < length; i++) { unichar c = from[i]; if ((c >= 0x20 && c <= 0xd7ff) || c == 0x9 || c == 0xd || c == 0xa || (c >= 0xe000 && c <= 0xfffd)) { switch (c) { case '"': to[j++] = '&'; to[j++] = 'q'; to[j++] = 'u'; to[j++] = 'o'; to[j++] = 't'; to[j++] = ';'; break; case '\'': to[j++] = '&'; to[j++] = 'a'; to[j++] = 'p'; to[j++] = 'o'; to[j++] = 's'; to[j++] = ';'; break; case '&': to[j++] = '&'; to[j++] = 'a'; to[j++] = 'm'; to[j++] = 'p'; to[j++] = ';'; break; case '<': to[j++] = '&'; to[j++] = 'l'; to[j++] = 't'; to[j++] = ';'; break; case '>': to[j++] = '&'; to[j++] = 'g'; to[j++] = 't'; to[j++] = ';'; break; default: if (c > 127) { char buf[12]; char *ptr = buf; to[j++] = '&'; to[j++] = '#'; snprintf(buf, sizeof(buf), "%u", c); while (*ptr != '\0') { to[j++] = *ptr++; } to[j++] = ';'; } else { to[j++] = c; } break; } } } self = [[NSString alloc] initWithCharacters: to length: output]; NSZoneFree (NSDefaultMallocZone (), to); IF_NO_ARC([self autorelease];) } else { self = AUTORELEASE([self copyWithZone: NSDefaultMallocZone()]); } NSZoneFree (NSDefaultMallocZone (), from); return self; } - (NSString*) stringByUnescapingXML { unsigned length = [self length]; NSRange r = NSMakeRange(0, length); r = [self rangeOfString: @"&" options: NSLiteralSearch range: r]; if (r.length > 0) { NSMutableString *m = [self mutableCopy]; while (r.length > 0) { NSRange e; unsigned s0 = NSMaxRange(r); e = [m rangeOfString: @";" options: NSLiteralSearch range: NSMakeRange(s0, length - s0)]; if (e.length > 0) { unsigned s1 = NSMaxRange(e); NSString *s = [m substringWithRange: NSMakeRange(s0, s1 - s0)]; if ([s hasPrefix: @"&#"] == YES) { unichar u; if ([s hasPrefix: @"&#x"] || [s hasPrefix: @"&#X"]) { unsigned val = 0; s = [s substringFromIndex: 3]; sscanf([s UTF8String], "%x", &val); u = val; } else if ([s hasPrefix: @"�x"] || [s hasPrefix: @"�X"]) { unsigned val = 0; s = [s substringFromIndex: 4]; sscanf([s UTF8String], "%x", &val); u = val; } else { s = [s substringFromIndex: 2]; u = [s intValue]; } if (u == 0) { u = ' '; } s = [[NSString alloc] initWithCharacters: &u length: 1]; s = AUTORELEASE(s); } else if ([s isEqualToString: @"amp"]) { s = @"&"; } else if ([s isEqualToString: @"apos"]) { s = @"'"; } else if ([s isEqualToString: @"quot"]) { s = @"\""; } else if ([s isEqualToString: @"lt"]) { s = @"<"; } else if ([s isEqualToString: @"gt"]) { s = @">"; } else { // Unknown escape ... don't change. s = [NSString stringWithFormat: @"&%@;", s]; } [m replaceCharactersInRange: NSMakeRange(s0, s1 - s0) withString: s]; r.length = [s length]; length += r.length - (s1 - s0); r.location = NSMaxRange(r); r.length = length - r.location; r = [m rangeOfString: @"&" options: NSLiteralSearch range: r]; } else { r.length = 0; } } self = AUTORELEASE(m); } else { self = AUTORELEASE([self copyWithZone: NSDefaultMallocZone()]); } return self; } @end #ifdef HAVE_LIBXML /* * Categories on other classes which are required for XMLRPC */ @interface NSArray (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc; @end @interface NSData (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc; @end @interface NSDate (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc; @end @interface NSDictionary (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc; @end @interface NSObject (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc; @end @interface NSNumber (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc; @end @interface NSString (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc; @end /* * A little code to handle indentation. */ static NSString *indentations[] = { @" ", @" ", @" ", @"\t", @"\t ", @"\t ", @"\t ", @"\t\t", @"\t\t ", @"\t\t ", @"\t\t ", @"\t\t\t", @"\t\t\t ", @"\t\t\t ", @"\t\t\t ", @"\t\t\t\t" }; static void indentation(unsigned level, NSMutableString *str) { if (level > 0) { if (level >= sizeof(indentations)/sizeof(*indentations)) { level = sizeof(indentations)/sizeof(*indentations) - 1; } [str appendString: indentations[level]]; } } #define INDENT(I) if (compact == NO) indentation(I, str) #define NL if (compact == NO) [str appendString: @"\n"] /* * Implementation of categories to output objects for XMLRPC */ @implementation NSArray (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc { unsigned i; unsigned c = [self count]; BOOL compact = [rpc compact]; INDENT(indent++); [str appendString: @""]; NL; INDENT(indent++); [str appendString: @""]; NL; for (i = 0; i < c; i++) { id value = [self objectAtIndex: i]; INDENT(indent++); [str appendString: @""]; NL; [value appendToXMLRPC: str indent: indent for: rpc]; NL; INDENT(--indent); [str appendString: @""]; NL; } INDENT(--indent); [str appendString: @""]; NL; INDENT(--indent); [str appendString: @""]; } @end @implementation NSData (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc { NSData *d; NSString *s; d = [GSMimeDocument encodeBase64: self]; s = [[NSString alloc] initWithData: d encoding: NSASCIIStringEncoding]; [str appendString: @""]; [str appendString: s]; [str appendString: @""]; RELEASE(s); } @end @implementation NSDate (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc { NSString *s; s = [self descriptionWithCalendarFormat: @"%Y%m%dT%H:%M:%S" timeZone: [rpc timeZone] locale: nil]; [str appendString: @""]; [str appendString: s]; [str appendString: @""]; } @end @implementation NSDictionary (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc { NSEnumerator *kEnum = [self keyEnumerator]; NSString *key; BOOL compact = [rpc compact]; INDENT(indent++); [str appendString: @""]; NL; while ((key = [kEnum nextObject])) { id value = [self objectForKey: key]; INDENT(indent++); [str appendString: @""]; NL; INDENT(indent); [str appendString: @""]; [str appendString: [[key description] stringByEscapingXML]]; [str appendString: @""]; NL; INDENT(indent++); [str appendString: @""]; NL; [value appendToXMLRPC: str indent: indent-- for: rpc]; NL; INDENT(indent--); [str appendString: @""]; NL; INDENT(indent); [str appendString: @""]; NL; } INDENT(--indent); [str appendString: @""]; } @end @implementation NSNumber (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc { const char *t = [self objCType]; BOOL compact = [rpc compact]; INDENT(indent); if (strchr("cCsSiIlLqQ", *t) != 0) { int64_t i = [self longLongValue]; if ((i & 0xffffffff) != i) { [NSException raise: NSInternalInconsistencyException format: @"Can't encode %"PRId64" as i4", i]; } if ((i == 0 || i == 1) && (*t == 'c' || *t == 'C')) { if (i == 0) { [str appendString: @"0"]; } else { [str appendString: @"1"]; } } else { [str appendFormat: @"%"PRId32"", (int32_t)i]; } } else { [str appendFormat: @"%f", [self doubleValue]]; } } @end @implementation NSObject (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc { [[self description] appendToXMLRPC: str indent: indent for: rpc]; } @end @implementation NSString (GSXMLRPC) - (void) appendToXMLRPC: (NSMutableString*)str indent: (NSUInteger)indent for: (GSXMLRPC*)rpc { BOOL compact = [rpc compact]; if (compact == YES) { [str appendString: [self stringByEscapingXML]]; } else { INDENT(indent); [str appendFormat: @"%@", [self stringByEscapingXML]]; } } @end /* * Convert incoming XMLRPC value to a normal Objective-C object. */ @interface GSXMLRPC (Private) - (id) _parseValue: (GSXMLNode*)node; @end @implementation GSXMLRPC (Private) - (id) _parseValue: (GSXMLNode*)node { NSString *name = [node name]; NSString *str; if ([name isEqualToString: @"value"]) { GSXMLNode *type = [node firstChildElement]; /* * A value with no type element is just a string. */ if (type == nil) { name = @"string"; } else { node = type; name = [node name]; } } if ([name length] == 0) { return nil; } if ([name isEqualToString: @"i4"] || [name isEqualToString: @"int"]) { str = [node content]; if (str == nil) { [NSException raise: NSInvalidArgumentException format: @"missing %@ value", name]; } return [NSNumber numberWithInt: [str intValue]]; } if ([name isEqualToString: @"string"]) { str = [node content]; if (str == nil) { str = @""; } return str; } if ([name isEqualToString: @"boolean"]) { char c; str = [node content]; if (str == nil) { [NSException raise: NSInvalidArgumentException format: @"missing %@ value", name]; } c = [str intValue]; return [NSNumber numberWithBool: c == 0 ? NO : YES]; } if ([name isEqualToString: @"double"]) { str = [node content]; if (str == nil) { [NSException raise: NSInvalidArgumentException format: @"missing %@ value", name]; } return [NSNumber numberWithDouble: [str doubleValue]]; } if ([name isEqualToString: @"base64"]) { NSData *d; str = [node content]; if (str == nil) { [NSException raise: NSInvalidArgumentException format: @"missing %@ value", name]; } d = [str dataUsingEncoding: NSASCIIStringEncoding]; return [GSMimeDocument decodeBase64: d]; } if ([name isEqualToString: @"dateTime.iso8601"]) { NSCalendarDate *d; const char *s; int year; int month; int day; int hour; int minute; int second; str = [node content]; if (str == nil) { [NSException raise: NSInvalidArgumentException format: @"missing %@ value", name]; } s = [str UTF8String]; if (sscanf(s, "%04d%02d%02dT%02d:%02d:%02d", &year, &month, &day, &hour, &minute, &second) != 6) { [NSException raise: NSInvalidArgumentException format: @"bad date/time format '%@'", str]; } d = [[NSCalendarDate alloc] initWithYear: year month: month day: day hour: hour minute: minute second: second timeZone: tz]; return AUTORELEASE(d); } if ([name isEqualToString: @"array"]) { NSMutableArray *arr = [NSMutableArray array]; node = [node firstChildElement]; while (node != nil && [[node name] isEqualToString: @"data"] == NO) { node = [node nextElement]; } if ([[node name] isEqualToString: @"data"] == YES) { node = [node firstChildElement]; while (node != nil) { if ([[node name] isEqualToString: @"value"] == YES) { id v; v = [self _parseValue: node]; if (v != nil) { [arr addObject: v]; } } node = [node nextElement]; } } return arr; } if ([name isEqualToString: @"struct"]) { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; node = [node firstChildElement]; while (node != nil) { if ([[node name] isEqualToString: @"member"] == YES) { GSXMLNode *member = [node firstChildElement]; NSString *key = nil; id val = nil; while (member != nil) { if ([[member name] isEqualToString: @"name"] == YES) { key = [member content]; } else if ([[member name] isEqualToString: @"value"] == YES) { val = [self _parseValue: member]; } if (key != nil && val != nil) { [dict setObject: val forKey: key]; break; } member = [member nextElement]; } } node = [node nextElement]; } return dict; } [NSException raise: NSInvalidArgumentException format: @"Unknown value type: %@", name]; return nil; } @end /* * And now, the actual GSXMLRPC class. */ @implementation GSXMLRPC - (NSData*) buildMethod: (NSString*)method params: (NSArray*)params { return [[self buildMethodCall: method params: params] dataUsingEncoding: NSUTF8StringEncoding]; } - (NSString*) buildMethodCall: (NSString*)method params: (NSArray*)params { NSMutableString *str = [NSMutableString stringWithCapacity: 1024]; unsigned c = [params count]; unsigned i; if ([method length] == 0) { return nil; } else { static NSCharacterSet *illegal = nil; NSRange r; if (illegal == nil) { NSMutableCharacterSet *tmp = [NSMutableCharacterSet new]; [tmp addCharactersInRange: NSMakeRange('0', 10)]; [tmp addCharactersInRange: NSMakeRange('a', 26)]; [tmp addCharactersInRange: NSMakeRange('A', 26)]; [tmp addCharactersInString: @"_.:/"]; [tmp invert]; illegal = [tmp copy]; RELEASE(tmp); } r = [method rangeOfCharacterFromSet: illegal]; if (r.length > 0) { return nil; // Bad method name. } } [str appendString: @"\n"]; [str appendString: @""]; NL; INDENT(1); [str appendFormat: @"%@", [method stringByEscapingXML]]; NL; if (c > 0) { INDENT(1); [str appendString: @""]; NL; for (i = 0; i < c; i++) { INDENT(2); [str appendString: @""]; NL; INDENT(3); [str appendString: @""]; NL; [[params objectAtIndex: i] appendToXMLRPC: str indent: 3 for: self]; NL; INDENT(3); [str appendString: @""]; NL; INDENT(2); [str appendString: @""]; NL; } INDENT(1); [str appendString: @""]; NL; } [str appendString: @""]; NL; return str; } - (NSString*) buildResponseWithFaultCode: (NSInteger)code andString: (NSString*)s { NSMutableString *str = [NSMutableString stringWithCapacity: 1024]; NSDictionary *fault; fault = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: code], @"faultCode", s, @"faultString", nil]; [str appendString: @"\n"]; [str appendString: @""]; NL; INDENT(1); [str appendString: @""]; NL; INDENT(2); [str appendString: @""]; NL; [fault appendToXMLRPC: str indent: 3 for: self]; NL; INDENT(2); [str appendString: @""]; NL; INDENT(1); [str appendString: @""]; NL; [str appendString: @""]; NL; return str; } - (NSString*) buildResponseWithParams: (NSArray*)params { NSMutableString *str = [NSMutableString stringWithCapacity: 1024]; unsigned c = [params count]; unsigned i; [str appendString: @"\n"]; [str appendString: @""]; NL; INDENT(1); [str appendString: @""]; NL; for (i = 0; i < c; i++) { INDENT(2); [str appendString: @""]; NL; INDENT(3); [str appendString: @""]; NL; [[params objectAtIndex: i] appendToXMLRPC: str indent: 3 for: self]; NL; INDENT(3); [str appendString: @""]; NL; INDENT(2); [str appendString: @""]; NL; } INDENT(1); [str appendString: @""]; NL; [str appendString: @""]; NL; return str; } - (BOOL) compact { return compact; } - (void) dealloc { RELEASE(tz); if (timer != nil) { [self timeout: nil]; // Treat as immediate timeout. } #ifdef GNUSTEP [handle removeClient: self]; #endif DESTROY(result); #ifdef GNUSTEP DESTROY(handle); #else if (connection) { [connection release]; } [response release]; [connectionURL release]; #endif [super dealloc]; } - (id) delegate { return delegate; } - (id) initWithURL: (NSString*)url { return [self initWithURL: url certificate: nil privateKey: nil password: nil]; } - (id) initWithURL: (NSString*)url certificate: (NSString*)cert privateKey: (NSString*)pKey password: (NSString*)pwd { if (url != nil) { NS_DURING { #ifdef GNUSTEP NSURL *u = [NSURL URLWithString: url]; handle = RETAIN([u URLHandleUsingCache: NO]); if (cert != nil && pKey != nil && pwd != nil) { [handle writeProperty: cert forKey: GSTLSCertificateFile]; [handle writeProperty: pKey forKey: GSTLSCertificateKeyFile]; [handle writeProperty: pwd forKey: GSTLSCertificateKeyPassword]; } #else connectionURL = [url copy]; connection = nil; response = [[NSMutableData alloc] init]; #endif } NS_HANDLER { DESTROY(self); } NS_ENDHANDLER } return self; } - (id) init { return [self initWithURL: nil certificate: nil privateKey: nil password: nil]; } - (id) makeMethodCall: (NSString*)method params: (NSArray*)params timeout: (NSInteger)seconds { NS_DURING { if ([self sendMethodCall: method params: params timeout: seconds] == YES) { NSDate *when = AUTORELEASE(RETAIN([timer fireDate])); while (timer != nil) { [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: when]; } } } NS_HANDLER { ASSIGN(result, [localException description]); } NS_ENDHANDLER return result; } - (NSString*) parseMethod: (NSData*)request params: (NSMutableArray*)params { GSXPathContext *ctx = nil; GSXPathNodeSet *ns = nil; GSXMLParser *parser = nil; NSString *method; [params removeAllObjects]; NS_DURING { GSXMLDocument *doc = nil; parser = [GSXMLParser parserWithData: request]; [parser substituteEntities: YES]; [parser saveMessages: YES]; if ([parser parse] == YES) { doc = [parser document]; ctx = AUTORELEASE([[GSXPathContext alloc] initWithDocument: doc]); } } NS_HANDLER { ctx = nil; } NS_ENDHANDLER if (ctx == nil) { [NSException raise: NSInvalidArgumentException format: @"Bad Request: parse failed (%@)", [parser messages]]; } ns = (GSXPathNodeSet*)[ctx evaluateExpression: @"//methodCall/methodName"]; if ([ns count] != 1) { [NSException raise: NSInvalidArgumentException format: @"Badly formatted methodCall"]; } method = [[ns nodeAtIndex: 0] content]; ns = (GSXPathNodeSet*)[ctx evaluateExpression: @"//methodCall/params/param/value"]; NS_DURING { int i; for (i = 0; i < [ns count]; i++) { GSXMLNode *node = [ns nodeAtIndex: i]; id value = [self _parseValue: node]; if (value != nil) { [params addObject: value]; } } } NS_HANDLER { [params removeAllObjects]; [localException raise]; } NS_ENDHANDLER return method; } - (NSDictionary*) parseResponse: (NSData*)resp params: (NSMutableArray*)params { GSXPathContext *ctx = nil; GSXPathNodeSet *ns = nil; GSXMLParser *parser = nil; id fault = nil; [params removeAllObjects]; NS_DURING { GSXMLDocument *doc = nil; parser = [GSXMLParser parserWithData: resp]; [parser substituteEntities: YES]; [parser saveMessages: YES]; if ([parser parse] == YES) { doc = [parser document]; ctx = AUTORELEASE([[GSXPathContext alloc] initWithDocument: doc]); } } NS_HANDLER { ctx = nil; } NS_ENDHANDLER if (ctx == nil) { [NSException raise: NSInvalidArgumentException format: @"Bad Request: parse failed (%@)", [parser messages]]; } ns = (GSXPathNodeSet*)[ctx evaluateExpression: @"//methodResponse/params/param/value"]; NS_DURING { int i; if ([ns count] > 0) { for (i = 0; i < [ns count]; i++) { GSXMLNode *node = [ns nodeAtIndex: i]; id value = [self _parseValue: node]; if (value != nil) { [params addObject: value]; } } } else { ns = (GSXPathNodeSet*)[ctx evaluateExpression: @"//methodResponse/fault/value/struct"]; if ([ns count] > 0) { fault = [self _parseValue: [ns nodeAtIndex: 0]]; } } } NS_HANDLER { [params removeAllObjects]; [localException raise]; } NS_ENDHANDLER return fault; } - (id) result { if (timer == nil) { return result; } else { return nil; } } - (BOOL) sendMethodCall: (NSString*)method params: (NSArray*)params timeout: (NSInteger)seconds { NSData *data; ASSIGN(result, @"unable to send"); #ifdef GNUSTEP if (handle == nil) { return NO; // Not initialised to send. } #endif if (timer != nil) { return NO; // Send already in progress. } data = [self buildMethod: method params: params]; if (data == nil) { return NO; } timer = [NSTimer scheduledTimerWithTimeInterval: seconds target: self selector: @selector(timeout:) userInfo: nil repeats: NO]; #ifdef GNUSTEP [handle addClient: self]; [handle writeProperty: @"POST" forKey: GSHTTPPropertyMethodKey]; [handle writeProperty: @"GSXMLRPC/1.0.0" forKey: @"User-Agent"]; [handle writeProperty: @"text/xml" forKey: @"Content-Type"]; [handle writeData: data]; [handle loadInBackground]; #else { NSMutableURLRequest *request; request = [NSMutableURLRequest alloc]; request = [request initWithURL: [NSURL URLWithString: connectionURL]]; [request setCachePolicy: NSURLRequestReloadIgnoringCacheData]; [request setHTTPMethod: @"POST"]; [request setValue: @"GSXMLRPC/1.0.0" forHTTPHeaderField: @"User-Agent"]; [request setValue: @"text/xml" forHTTPHeaderField: @"Content-Type"]; [request setHTTPBody: data]; connection = [NSURLConnection alloc]; connection = [connection initWithRequest: request delegate: self]; [request release]; } #endif return YES; } - (int) setDebug: (int)flag { #ifdef GNUSTEP if ([handle respondsToSelector: _cmd] == YES) { return [(id)handle setDebug: flag]; } #endif return NO; } - (void) setCompact: (BOOL)flag { compact = flag; } - (void) setDelegate: (id)aDelegate { delegate = aDelegate; } - (void) setTimeZone: (NSTimeZone*)timeZone { ASSIGN(tz, timeZone); } - (void) timeout: (NSTimer*)t { [timer invalidate]; timer = nil; #ifdef GNUSTEP [handle cancelLoadInBackground]; #else [connection cancel]; #endif } - (NSTimeZone*) timeZone { if (tz == nil) { tz = RETAIN([NSTimeZone timeZoneForSecondsFromGMT: 0]); } return tz; } #ifdef GNUSTEP - (void) URLHandle: (NSURLHandle*)sender resourceDataDidBecomeAvailable: (NSData*)newData { // Not interesting } - (void) URLHandle: (NSURLHandle*)sender resourceDidFailLoadingWithReason: (NSString*)reason { ASSIGN(result, reason); [timer invalidate]; timer = nil; #ifdef GNUSTEP [handle removeClient: self]; #endif if ([delegate respondsToSelector: @selector(completedXMLRPC:)]) { [delegate completedXMLRPC: self]; } } - (void) URLHandleResourceDidBeginLoading: (NSURLHandle*)sender { // Not interesting } - (void) URLHandleResourceDidCancelLoading: (NSURLHandle*)sender { NSString *str; [timer invalidate]; timer = nil; #ifdef GNUSTEP [handle removeClient: self]; #endif str = [handle propertyForKeyIfAvailable: NSHTTPPropertyStatusCodeKey]; if (str == nil) { str = @"timeout"; } else { str = [NSString stringWithFormat: @"HTTP status %@", str]; } ASSIGN(result, str); if ([delegate respondsToSelector: @selector(completedXMLRPC:)]) { [delegate completedXMLRPC: self]; } } - (void) URLHandleResourceDidFinishLoading: (NSURLHandle*)sender { NSMutableArray *params = [NSMutableArray array]; id fault = nil; int code; code = [[handle propertyForKey: NSHTTPPropertyStatusCodeKey] intValue]; if (code == 200) { NSData *resp = [handle availableResourceData]; NS_DURING { fault = [self parseResponse: resp params: params]; } NS_HANDLER { fault = [localException reason]; } NS_ENDHANDLER } else { fault = [NSString stringWithFormat: @"HTTP status %03d", code]; } if (fault == nil) { ASSIGN(result, params); } else { ASSIGN(result, fault); } [timer invalidate]; timer = nil; #ifdef GNUSTEP [handle removeClient: self]; #endif if ([delegate respondsToSelector: @selector(completedXMLRPC:)]) { [delegate completedXMLRPC: self]; } } #else /* !GNUSTEP */ - (void) connection: (NSURLConnection*)connection didCancelAuthenticationChallenge: (NSURLAuthenticationChallenge*)challenge { /* DO NOTHING */ } - (void) connection: (NSURLConnection*)connection didFailWithError: (NSError*)error { ASSIGN(result, [error localizedDescription]); [timer invalidate]; timer = nil; if ([delegate respondsToSelector: @selector(completedXMLRPC:)]) { [delegate completedXMLRPC: self]; } } - (void) connection: (NSURLConnection*)connection didReceiveAuthenticationChallenge: (NSURLAuthenticationChallenge*)challenge { } - (void) connection: (NSURLConnection*)connection didReceiveData: (NSData*)data { [response appendData: data]; } - (void) connection: (NSURLConnection*)connection didReceiveResponse: (NSURLResponse*)response { /* DO NOTHING */ } - (NSCachedURLResponse*) connection: (NSURLConnection*)connection willCacheResponse: (NSCachedURLResponse*)cachedResponse { return nil; } - (NSURLRequest*) connection: (NSURLConnection*)connection willSendRequest: (NSURLRequest*)request redirectResponse: (NSURLResponse*)redirectResponse { return nil; } -(void) connectionDidFinishLoading: (NSURLConnection*)connection { NSMutableArray *params = [NSMutableArray array]; id fault = nil; NS_DURING { fault = [self parseResponse: response params: params]; } NS_HANDLER { fault = [localException reason]; } NS_ENDHANDLER if (fault == nil) { ASSIGNCOPY(result, params); } else { ASSIGNCOPY(result, fault); } [timer invalidate]; timer = nil; if ([delegate respondsToSelector: @selector(completedXMLRPC:)]) { [delegate completedXMLRPC: self]; } } #endif @end @implementation GSXMLRPC (Delegate) - (void) completedXMLRPC: (GSXMLRPC*)sender { } @end #endif /* HAVE_LIBXML */