Framework/ROADSerialization/ROADSerialization/XML/RFAttributedXMLCoder.m (145 lines of code) (raw):

// // RFAttributedXMLCoder.m // ROADSerialization // // Copyright (c) 2014 EPAM Systems, Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or // other materials provided with the distribution. // Neither the name of the EPAM Systems, Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // See the NOTICE file and the LICENSE file distributed with this work // for additional information regarding copyright ownership and licensing #import "RFAttributedXMLCoder.h" #import <ROAD/ROADReflection.h> #import <ROAD/ROADCore.h> #import "RFSerializationAssistant.h" #import "RFXMLSerializable.h" #import "RFXMLSerializableCollection.h" #include <libxml/parser.h> static char *RFAttributedXMLCoderTagForClass(Class aClass); static NSString *xmlns = @"xmlns"; static char *RFAttributedXMLCoderTagForClass(Class aClass) { char *result = NULL; if ([aClass isSubclassOfClass:[NSArray class]]) result = "array"; else if ([aClass isSubclassOfClass:[NSDictionary class]]) result = "dictionary"; else if ([aClass isSubclassOfClass:[NSDate class]]) result = "date"; else if ([aClass isSubclassOfClass:[NSNumber class]]) result = "number"; else if ([aClass isSubclassOfClass:[NSString class]]) result = "string"; else result = "object"; return result; } static NSString* RFEncodeProperty(id serializedObject, RFPropertyInfo* propertyInfo, RFObjectPool* dateFormattersPool) { if ([serializedObject isKindOfClass:[NSDate class]]) { return RFSerializationEncodeDateForProperty(serializedObject, propertyInfo, dateFormattersPool); } return RFSerializationEncodeObjectForProperty(serializedObject, propertyInfo); } static BOOL isTagWithPrefix(NSString *key) { return (key && ([key rangeOfString:@":"].location != NSNotFound)); } static BOOL isDefineNamespace(NSString *key) { return (key && [key hasPrefix:[NSString stringWithFormat:@"%@:", xmlns]]); } static NSInteger sortedProperties(id oneProperty, id secondProperty, void *context) { NSInteger result = NSOrderedSame; RFXMLSerializable *oneXmlAttributes = [oneProperty attributeWithType:[RFXMLSerializable class]]; RFXMLSerializable *secondXmlAttributes = [secondProperty attributeWithType:[RFXMLSerializable class]]; if (oneXmlAttributes.isTagAttribute && isDefineNamespace(RFSerializationKeyForProperty((RFPropertyInfo *)oneProperty))) result = NSOrderedAscending; else if (secondXmlAttributes.isTagAttribute && isDefineNamespace(RFSerializationKeyForProperty((RFPropertyInfo *)secondProperty))) result = NSOrderedDescending; return result; } @interface RFAttributedXMLCoder () { xmlDocPtr _xmlDoc; RFObjectPool* _dateFormattersPool; } @end @implementation RFAttributedXMLCoder - (instancetype)init { self = [super init]; if ( self ) { _dateFormattersPool = RFCreateDateFormatterPool(); } return self; } - (NSString *)encodeRootObject:(id)rootObject { _xmlDoc = xmlNewDoc(BAD_CAST "1.0"); [self serializeObject:rootObject toXMLNode:NULL precreatedNode:NULL propertyInfo:nil serializationName:nil itemTag:nil]; xmlChar *xmlBuff = NULL; int xmlBufferSize = 0; xmlDocDumpFormatMemory(_xmlDoc, &xmlBuff, &xmlBufferSize, 1); NSString *result = @((char*)xmlBuff); xmlFree(xmlBuff); xmlFreeDoc(_xmlDoc); _xmlDoc = NULL; return result; } - (xmlNodePtr)serializeObject:(id)serializedObject toXMLNode:(xmlNodePtr)parentNode precreatedNode:(xmlNodePtr)precreatedNode propertyInfo:(RFPropertyInfo*)propertyInfo serializationName:(NSString *)serializationName itemTag:(NSString *)itemTag { Class class = [serializedObject class]; NSString *key = RFSerializationKeyForProperty(propertyInfo); xmlNodePtr result = precreatedNode ? precreatedNode : [self createXMLNodeWithName:(serializationName ? serializationName : key) parent:parentNode objectClass:class]; // Set here then to search by namespaces on the xml document if (!xmlDocGetRootElement(_xmlDoc)) { xmlDocSetRootElement(_xmlDoc, result); } // Check if we want CDATA if ([class isSubclassOfClass:[NSData class]]) { xmlNodePtr cdataPtr = xmlNewCDataBlock(_xmlDoc, [serializedObject bytes], (int)[serializedObject length]); xmlAddChild( result, cdataPtr ); } // Try to serialize as a container or object with defined properties. Assume it's simple value otherwise. else if (![self serializeObjectAsContainer:serializedObject toNode:result itemTag:itemTag] && ![self serializeObjectAsAttributed:serializedObject toNode:result]) { NSString *encodedString = RFEncodeProperty(serializedObject, propertyInfo, _dateFormattersPool); xmlNodeSetContent(result, BAD_CAST [encodedString UTF8String]); } // Check prefix among namespaces, that declared before if (isTagWithPrefix(key)) { NSArray *components = [key componentsSeparatedByString:@":"]; NSParameterAssert([components count] == 2); xmlNsPtr xmlNs = xmlSearchNs(_xmlDoc, result, BAD_CAST [components[0] UTF8String]); NSParameterAssert(xmlNs); xmlSetNs(result, xmlNs); xmlNodeSetName(result, BAD_CAST [components[1] UTF8String]); } return result; } #pragma mark - - (xmlNodePtr)createXMLNodeWithName:(NSString *)serializationName parent:(xmlNodePtr)parentNode objectClass:(Class)class { const char *serializationNameC = ([serializationName length] > 0) ? [serializationName UTF8String] : RFAttributedXMLCoderTagForClass(class); NSParameterAssert(serializationNameC != NULL); return parentNode ? xmlNewChild(parentNode, NULL, BAD_CAST serializationNameC, NULL) : xmlNewNode(NULL, BAD_CAST serializationNameC); } - (BOOL)serializeObjectAsContainer:(id)serializedObject toNode:(xmlNodePtr)xmlNode itemTag:(NSString *)itemTag { Class class = [serializedObject class]; BOOL isDictionary = [class isSubclassOfClass:[NSDictionary class]]; BOOL isArray = [class isSubclassOfClass:[NSArray class]]; BOOL result = isArray || isDictionary; if (result) { for (id item in serializedObject) { NSString *serializationName = [itemTag length] ? itemTag : ((isDictionary && [item isKindOfClass:[NSString class]]) ? item : nil); [self serializeObject:isArray ? item : serializedObject[item] toXMLNode:xmlNode precreatedNode:NULL propertyInfo:nil serializationName:serializationName itemTag:nil]; } } return result; } - (BOOL)serializeObjectAsAttributed:(id)serializedObject toNode:(xmlNodePtr)xmlNode { NSArray *properties = RFSerializationPropertiesForClass([serializedObject class]); BOOL result = ([properties count] > 0); if (result) { // Define namespaces before properties = [properties sortedArrayUsingFunction:sortedProperties context:nil]; for (RFPropertyInfo *property in properties) { RFXMLSerializable *xmlAttributes = [property attributeWithType:[RFXMLSerializable class]]; id propertyObject = [serializedObject valueForKey:property.propertyName]; if (xmlAttributes.isTagAttribute) { NSString *encodedString = RFEncodeProperty(propertyObject, property, _dateFormattersPool); if ([encodedString length]) { NSString *key = RFSerializationKeyForProperty(property); if (isDefineNamespace(key)) xmlNewNs(xmlNode, BAD_CAST [encodedString UTF8String], BAD_CAST [[key stringByReplacingOccurrencesOfString:@"xmlns:" withString:@"" ] UTF8String]); else if (isTagWithPrefix(key)){ NSArray *components = [key componentsSeparatedByString:@":"]; NSParameterAssert([components count] == 2); xmlNsPtr xmlNs = xmlSearchNs(_xmlDoc, xmlNode, BAD_CAST [components[0] UTF8String]); NSParameterAssert(xmlNs); xmlSetNsProp(xmlNode, xmlNs, BAD_CAST [components[1] UTF8String], BAD_CAST [encodedString UTF8String]); } else xmlNewProp(xmlNode, BAD_CAST [key UTF8String], BAD_CAST [encodedString UTF8String]); } } else { RFXMLSerializableCollection *collection = [property attributeWithType:[RFXMLSerializableCollection class]]; [self serializeObject:propertyObject toXMLNode:xmlNode precreatedNode:collection ? xmlNode : NULL propertyInfo:property serializationName:nil itemTag:collection.itemTag]; } } }; return result; } @end