Artifact b73068b828b41d3538a0f50eb31844f6d893657419c7fcea7667faa4fb1e52d1:
- File
src/OFXMLElement.m
— part of check-in
[6ce0093f8d]
at
2023-04-10 19:22:32
on branch trunk
— Remove OFSerialization
While the idea sounds nice that the tag name is the class, this means the
serialization includes whether something is mutable or immutable. This means
doing as much as making something immutable changes the serialization, which
can then cause issues after being deserialized. (user: js, size: 20708) [annotate] [blame] [check-ins using] [more...]
/* * Copyright (c) 2008-2023 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #define OF_XML_ELEMENT_M #include <stdlib.h> #include <string.h> #import "OFXMLElement.h" #import "OFArray.h" #import "OFData.h" #import "OFDictionary.h" #import "OFStream.h" #import "OFString.h" #import "OFXMLAttribute.h" #import "OFXMLCDATA.h" #import "OFXMLCharacters.h" #import "OFXMLElementBuilder.h" #import "OFXMLNode+Private.h" #import "OFXMLParser.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFMalformedXMLException.h" #import "OFUnboundNamespaceException.h" @interface OFXMLElementElementBuilderDelegate: OFObject <OFXMLElementBuilderDelegate> { @public OFXMLElement *_element; } @end @implementation OFXMLElementElementBuilderDelegate - (void)elementBuilder: (OFXMLElementBuilder *)builder didBuildElement: (OFXMLElement *)element { if (_element == nil) _element = [element retain]; } - (void)dealloc { [_element release]; [super dealloc]; } @end @implementation OFXMLElement @synthesize name = _name, namespace = _namespace; + (instancetype)elementWithName: (OFString *)name { return [[[self alloc] initWithName: name] autorelease]; } + (instancetype)elementWithName: (OFString *)name stringValue: (OFString *)stringValue { return [[[self alloc] initWithName: name stringValue: stringValue] autorelease]; } + (instancetype)elementWithName: (OFString *)name namespace: (OFString *)namespace { return [[[self alloc] initWithName: name namespace: namespace] autorelease]; } + (instancetype)elementWithName: (OFString *)name namespace: (OFString *)namespace stringValue: (OFString *)stringValue { return [[[self alloc] initWithName: name namespace: namespace stringValue: stringValue] autorelease]; } + (instancetype)elementWithXMLString: (OFString *)string { return [[[self alloc] initWithXMLString: string] autorelease]; } + (instancetype)elementWithStream: (OFStream *)stream { return [[[self alloc] initWithStream: stream] autorelease]; } - (instancetype)init { OF_INVALID_INIT_METHOD } - (instancetype)initWithName: (OFString *)name { return [self initWithName: name namespace: nil stringValue: nil]; } - (instancetype)initWithName: (OFString *)name stringValue: (OFString *)stringValue { return [self initWithName: name namespace: nil stringValue: stringValue]; } - (instancetype)initWithName: (OFString *)name namespace: (OFString *)namespace { self = [super of_init]; @try { if (name == nil) @throw [OFInvalidArgumentException exception]; _name = [name copy]; _namespace = [namespace copy]; _namespaces = [[OFMutableDictionary alloc] initWithKeysAndObjects: @"http://www.w3.org/XML/1998/namespace", @"xml", @"http://www.w3.org/2000/xmlns/", @"xmlns", nil]; } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)initWithName: (OFString *)name namespace: (OFString *)namespace stringValue: (OFString *)stringValue { self = [self initWithName: name namespace: namespace]; @try { if (stringValue != nil) self.stringValue = stringValue; } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)initWithXMLString: (OFString *)string { void *pool; OFXMLElement *element; @try { OFXMLParser *parser; OFXMLElementBuilder *builder; OFXMLElementElementBuilderDelegate *delegate; if (string == nil) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); parser = [OFXMLParser parser]; builder = [OFXMLElementBuilder builder]; delegate = [[[OFXMLElementElementBuilderDelegate alloc] init] autorelease]; parser.delegate = builder; builder.delegate = delegate; [parser parseString: string]; if (!parser.hasFinishedParsing) @throw [OFMalformedXMLException exceptionWithParser: parser]; element = delegate->_element; } @catch (id e) { [self release]; @throw e; } self = [self initWithName: element->_name namespace: element->_namespace]; @try { [_attributes release]; _attributes = [element->_attributes retain]; [_namespaces release]; _namespaces = [element->_namespaces retain]; [_children release]; _children = [element->_children retain]; objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)initWithStream: (OFStream *)stream { void *pool; OFXMLElement *element; @try { OFXMLParser *parser; OFXMLElementBuilder *builder; OFXMLElementElementBuilderDelegate *delegate; pool = objc_autoreleasePoolPush(); parser = [OFXMLParser parser]; builder = [OFXMLElementBuilder builder]; delegate = [[[OFXMLElementElementBuilderDelegate alloc] init] autorelease]; parser.delegate = builder; builder.delegate = delegate; [parser parseStream: stream]; if (!parser.hasFinishedParsing) @throw [OFMalformedXMLException exceptionWithParser: parser]; element = delegate->_element; } @catch (id e) { [self release]; @throw e; } self = [self initWithName: element->_name namespace: element->_namespace]; @try { [_attributes release]; _attributes = [element->_attributes retain]; [_namespaces release]; _namespaces = [element->_namespaces retain]; [_children release]; _children = [element->_children retain]; objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_name release]; [_namespace release]; [_attributes release]; [_namespaces release]; [_children release]; [super dealloc]; } - (OFArray *)attributes { return [[_attributes copy] autorelease]; } - (void)setChildren: (OFArray *)children { OFArray *old = _children; _children = [children mutableCopy]; [old release]; } - (OFArray *)children { return [[_children copy] autorelease]; } - (void)setStringValue: (OFString *)stringValue { void *pool = objc_autoreleasePoolPush(); self.children = [OFArray arrayWithObject: [OFXMLCharacters charactersWithString: stringValue]]; objc_autoreleasePoolPop(pool); } - (OFString *)stringValue { OFMutableString *ret; if (_children.count == 0) return @""; ret = [OFMutableString string]; for (OFXMLNode *child in _children) { void *pool = objc_autoreleasePoolPush(); [ret appendString: child.stringValue]; objc_autoreleasePoolPop(pool); } [ret makeImmutable]; return ret; } - (OFString *)of_XMLStringWithDefaultNS: (OFString *)defaultNS namespaces: (OFDictionary *)allNS indentation: (unsigned int)indentation level: (unsigned int)level OF_DIRECT { void *pool; char *cString; size_t length, i; OFString *prefix, *ret; pool = objc_autoreleasePoolPush(); /* Add the namespaces of the current element */ if (allNS != nil) { OFEnumerator *keyEnumerator = [_namespaces keyEnumerator]; OFEnumerator *objectEnumerator = [_namespaces objectEnumerator]; OFMutableDictionary *tmp; OFString *key, *object; tmp = [[allNS mutableCopy] autorelease]; while ((key = [keyEnumerator nextObject]) != nil && (object = [objectEnumerator nextObject]) != nil) [tmp setObject: object forKey: key]; allNS = tmp; } else allNS = _namespaces; prefix = [allNS objectForKey: (_namespace != nil ? _namespace : (OFString *)@"")]; i = 0; length = _name.UTF8StringLength + 3 + (level * indentation); cString = OFAllocMemory(length, 1); @try { memset(cString + i, ' ', level * indentation); i += level * indentation; /* Start of tag */ cString[i++] = '<'; if (prefix.length > 0) { length += prefix.UTF8StringLength + 1; cString = OFResizeMemory(cString, length, 1); memcpy(cString + i, prefix.UTF8String, prefix.UTF8StringLength); i += prefix.UTF8StringLength; cString[i++] = ':'; } memcpy(cString + i, _name.UTF8String, _name.UTF8StringLength); i += _name.UTF8StringLength; /* xmlns if necessary */ if (prefix.length == 0 && defaultNS != _namespace && ![defaultNS isEqual: _namespace]) { length += _namespace.UTF8StringLength + 9; cString = OFResizeMemory(cString, length, 1); memcpy(cString + i, " xmlns='", 8); i += 8; memcpy(cString + i, _namespace.UTF8String, _namespace.UTF8StringLength); i += _namespace.UTF8StringLength; cString[i++] = '\''; defaultNS = _namespace; } /* Attributes */ for (OFXMLAttribute *attribute in _attributes) { void *pool2 = objc_autoreleasePoolPush(); const char *attributeNameCString = attribute->_name.UTF8String; size_t attributeNameLength = attribute->_name.UTF8StringLength; OFString *attributePrefix = nil; OFString *tmp = attribute.stringValue.stringByXMLEscaping; char delimiter = (attribute->_useDoubleQuotes ? '"' : '\''); if (attribute->_namespace != nil && [(attributePrefix = [allNS objectForKey: attribute->_namespace]) length] == 0) @throw [OFUnboundNamespaceException exceptionWithNamespace: attribute.namespace element: self]; length += attributeNameLength + (attributePrefix != nil ? attributePrefix.UTF8StringLength + 1 : 0) + tmp.UTF8StringLength + 4; cString = OFResizeMemory(cString, length, 1); cString[i++] = ' '; if (attributePrefix != nil) { memcpy(cString + i, attributePrefix.UTF8String, attributePrefix.UTF8StringLength); i += attributePrefix.UTF8StringLength; cString[i++] = ':'; } memcpy(cString + i, attributeNameCString, attributeNameLength); i += attributeNameLength; cString[i++] = '='; cString[i++] = delimiter; memcpy(cString + i, tmp.UTF8String, tmp.UTF8StringLength); i += tmp.UTF8StringLength; cString[i++] = delimiter; objc_autoreleasePoolPop(pool2); } /* Children */ if (_children != nil) { OFMutableData *tmp = [OFMutableData data]; bool indent; if (indentation > 0) { indent = true; for (OFXMLNode *child in _children) { if ([child isKindOfClass: [OFXMLCharacters class]] || [child isKindOfClass: [OFXMLCDATA class]]) { indent = false; break; } } } else indent = false; for (OFXMLNode *child in _children) { OFString *childString; unsigned int ind = (indent ? indentation : 0); if (ind) [tmp addItem: "\n"]; if ([child isKindOfClass: [OFXMLElement class]]) childString = [(OFXMLElement *)child of_XMLStringWithDefaultNS: defaultNS namespaces: allNS indentation: ind level: level + 1]; else { childString = child.XMLString; for (unsigned int j = 0; j < ind * (level + 1); j++) [tmp addItem: " "]; } [tmp addItems: childString.UTF8String count: childString.UTF8StringLength]; } if (indent) [tmp addItem: "\n"]; length += tmp.count + _name.UTF8StringLength + 2 + (indent ? level * indentation : 0); cString = OFResizeMemory(cString, length, 1); cString[i++] = '>'; memcpy(cString + i, tmp.items, tmp.count); i += tmp.count; if (indent) { memset(cString + i, ' ', level * indentation); i += level * indentation; } cString[i++] = '<'; cString[i++] = '/'; if (prefix.length > 0) { length += prefix.UTF8StringLength + 1; cString = OFResizeMemory(cString, length, 1); memcpy(cString + i, prefix.UTF8String, prefix.UTF8StringLength); i += prefix.UTF8StringLength; cString[i++] = ':'; } memcpy(cString + i, _name.UTF8String, _name.UTF8StringLength); i += _name.UTF8StringLength; } else cString[i++] = '/'; cString[i++] = '>'; OFAssert(i == length); objc_autoreleasePoolPop(pool); ret = [OFString stringWithUTF8String: cString length: length]; } @finally { OFFreeMemory(cString); } return ret; } - (OFString *)XMLString { return [self of_XMLStringWithDefaultNS: nil namespaces: nil indentation: 0 level: 0]; } - (OFString *)XMLStringWithIndentation: (unsigned int)indentation { return [self of_XMLStringWithDefaultNS: nil namespaces: nil indentation: indentation level: 0]; } - (OFString *)XMLStringWithDefaultNamespace: (OFString *)defaultNS indentation: (unsigned int)indentation { return [self of_XMLStringWithDefaultNS: defaultNS namespaces: nil indentation: indentation level: 0]; } - (void)addAttribute: (OFXMLAttribute *)attribute { if (![attribute isKindOfClass: [OFXMLAttribute class]]) @throw [OFInvalidArgumentException exception]; if (_attributes == nil) _attributes = [[OFMutableArray alloc] init]; if ([self attributeForName: attribute->_name namespace: attribute->_namespace] == nil) [_attributes addObject: attribute]; } - (void)addAttributeWithName: (OFString *)name stringValue: (OFString *)stringValue { [self addAttributeWithName: name namespace: nil stringValue: stringValue]; } - (void)addAttributeWithName: (OFString *)name namespace: (OFString *)namespace stringValue: (OFString *)stringValue { void *pool = objc_autoreleasePoolPush(); [self addAttribute: [OFXMLAttribute attributeWithName: name namespace: namespace stringValue: stringValue]]; objc_autoreleasePoolPop(pool); } - (OFXMLAttribute *)attributeForName: (OFString *)attributeName { for (OFXMLAttribute *attribute in _attributes) if (attribute->_namespace == nil && [attribute->_name isEqual: attributeName]) return attribute; return nil; } - (OFXMLAttribute *)attributeForName: (OFString *)attributeName namespace: (OFString *)attributeNS { if (attributeNS == nil) return [self attributeForName: attributeName]; for (OFXMLAttribute *attribute in _attributes) if ([attribute->_namespace isEqual: attributeNS] && [attribute->_name isEqual: attributeName]) return attribute; return nil; } - (void)removeAttributeForName: (OFString *)attributeName { OFXMLAttribute *const *objects = _attributes.objects; size_t count = _attributes.count; for (size_t i = 0; i < count; i++) { if (objects[i]->_namespace == nil && [objects[i]->_name isEqual: attributeName]) { [_attributes removeObjectAtIndex: i]; return; } } } - (void)removeAttributeForName: (OFString *)attributeName namespace: (OFString *)attributeNS { OFXMLAttribute *const *objects; size_t count; if (attributeNS == nil) { [self removeAttributeForName: attributeName]; return; } objects = _attributes.objects; count = _attributes.count; for (size_t i = 0; i < count; i++) { if ([objects[i]->_namespace isEqual: attributeNS] && [objects[i]->_name isEqual: attributeName]) { [_attributes removeObjectAtIndex: i]; return; } } } - (void)setPrefix: (OFString *)prefix forNamespace: (OFString *)namespace { if (prefix.length == 0) @throw [OFInvalidArgumentException exception]; [_namespaces setObject: prefix forKey: namespace]; } - (void)bindPrefix: (OFString *)prefix forNamespace: (OFString *)namespace { [self setPrefix: prefix forNamespace: namespace]; [self addAttributeWithName: prefix namespace: @"http://www.w3.org/2000/xmlns/" stringValue: namespace]; } - (void)addChild: (OFXMLNode *)child { if ([child isKindOfClass: [OFXMLAttribute class]]) @throw [OFInvalidArgumentException exception]; if (_children == nil) _children = [[OFMutableArray alloc] init]; [_children addObject: child]; } - (void)insertChild: (OFXMLNode *)child atIndex: (size_t)idx { if ([child isKindOfClass: [OFXMLAttribute class]]) @throw [OFInvalidArgumentException exception]; if (_children == nil) _children = [[OFMutableArray alloc] init]; [_children insertObject: child atIndex: idx]; } - (void)insertChildren: (OFArray *)children atIndex: (size_t)idx { for (OFXMLNode *node in children) if ([node isKindOfClass: [OFXMLAttribute class]]) @throw [OFInvalidArgumentException exception]; [_children insertObjectsFromArray: children atIndex: idx]; } - (void)removeChild: (OFXMLNode *)child { if ([child isKindOfClass: [OFXMLAttribute class]]) @throw [OFInvalidArgumentException exception]; [_children removeObject: child]; } - (void)removeChildAtIndex: (size_t)idx { [_children removeObjectAtIndex: idx]; } - (void)replaceChild: (OFXMLNode *)child withNode: (OFXMLNode *)node { if ([node isKindOfClass: [OFXMLAttribute class]] || [child isKindOfClass: [OFXMLAttribute class]]) @throw [OFInvalidArgumentException exception]; [_children replaceObject: child withObject: node]; } - (void)replaceChildAtIndex: (size_t)idx withNode: (OFXMLNode *)node { if ([node isKindOfClass: [OFXMLAttribute class]]) @throw [OFInvalidArgumentException exception]; [_children replaceObjectAtIndex: idx withObject: node]; } - (OFXMLElement *)elementForName: (OFString *)elementName { return [self elementsForName: elementName].firstObject; } - (OFXMLElement *)elementForName: (OFString *)elementName namespace: (OFString *)elementNS { return [self elementsForName: elementName namespace: elementNS].firstObject; } - (OFArray *)elements { OFMutableArray OF_GENERIC(OFXMLElement *) *ret = [OFMutableArray array]; for (OFXMLNode *child in _children) if ([child isKindOfClass: [OFXMLElement class]]) [ret addObject: (OFXMLElement *)child]; [ret makeImmutable]; return ret; } - (OFArray *)elementsForName: (OFString *)elementName { OFMutableArray OF_GENERIC(OFXMLElement *) *ret = [OFMutableArray array]; for (OFXMLNode *child in _children) { if ([child isKindOfClass: [OFXMLElement class]]) { OFXMLElement *element = (OFXMLElement *)child; if (element->_namespace == nil && [element->_name isEqual: elementName]) [ret addObject: element]; } } [ret makeImmutable]; return ret; } - (OFArray *)elementsForNamespace: (OFString *)elementNS { OFMutableArray OF_GENERIC(OFXMLElement *) *ret = [OFMutableArray array]; for (OFXMLNode *child in _children) { if ([child isKindOfClass: [OFXMLElement class]]) { OFXMLElement *element = (OFXMLElement *)child; if (element->_name != nil && [element->_namespace isEqual: elementNS]) [ret addObject: element]; } } [ret makeImmutable]; return ret; } - (OFArray *)elementsForName: (OFString *)elementName namespace: (OFString *)elementNS { OFMutableArray OF_GENERIC(OFXMLElement *) *ret; if (elementNS == nil) return [self elementsForName: elementName]; ret = [OFMutableArray array]; for (OFXMLNode *child in _children) { if ([child isKindOfClass: [OFXMLElement class]]) { OFXMLElement *element = (OFXMLElement *)child; if ([element->_namespace isEqual: elementNS] && [element->_name isEqual: elementName]) [ret addObject: element]; } } [ret makeImmutable]; return ret; } - (bool)isEqual: (id)object { OFXMLElement *element; if (object == self) return true; if (![object isKindOfClass: [OFXMLElement class]]) return false; element = object; if (element->_name != _name && ![element->_name isEqual: _name]) return false; if (element->_namespace != _namespace && ![element->_namespace isEqual: _namespace]) return false; if (element->_attributes != _attributes && ![element->_attributes isEqual: _attributes]) return false; if (element->_namespaces != _namespaces && ![element->_namespaces isEqual: _namespaces]) return false; if (element->_children != _children && ![element->_children isEqual: _children]) return false; return true; } - (unsigned long)hash { unsigned long hash; OFHashInit(&hash); OFHashAddHash(&hash, _name.hash); OFHashAddHash(&hash, _namespace.hash); OFHashAddHash(&hash, _attributes.hash); OFHashAddHash(&hash, _namespaces.hash); OFHashAddHash(&hash, _children.hash); OFHashFinalize(&hash); return hash; } - (id)copy { OFXMLElement *copy = [[OFXMLElement alloc] of_init]; @try { copy->_name = [_name copy]; copy->_namespace = [_namespace copy]; copy->_attributes = [_attributes mutableCopy]; copy->_namespaces = [_namespaces mutableCopy]; copy->_children = [_children mutableCopy]; } @catch (id e) { [copy release]; @throw e; } return copy; } @end