1/* 2 Copyright (C) 2000-2009 SKYRIX Software AG 3 4 This file is part of SOPE. 5 6 SOPE is free software; you can redistribute it and/or modify it under 7 the terms of the GNU Lesser General Public License as published by the 8 Free Software Foundation; either version 2, or (at your option) any 9 later version. 10 11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY 12 WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with SOPE; see the file COPYING. If not, write to the 18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 19 02111-1307, USA. 20*/ 21 22#include "DOMXMLOutputter.h" 23#include "DOMDocument.h" 24#include "DOMElement.h" 25#include "common.h" 26 27@interface DOMXMLOutputter(Privates) 28- (void)outputNode:(id<DOMNode>)_node to:(id)_target; 29- (void)outputNodeList:(id<DOMNodeList>)_nodeList to:(id)_target; 30@end 31 32@interface DOMXMLOutputter(PrefixStack) 33- (NSString *)topPrefix; 34- (NSString *)topNamespace; 35- (void)pushPrefix:(NSString *)_prefix namespace:(NSString *)_namespace; 36- (void)popPrefixAndNamespace; 37- (BOOL)isTagValidInStack:(id)_node; 38- (NSArray *)newAttributePrefixesAndNamespaces:(NSArray *)_attrs; 39@end /* DOMXMLOutputter(PrefixStack) */ 40 41 42@implementation DOMXMLOutputter 43 44- (id)init { 45 if ((self = [super init])) { 46 self->stack = [[NSMutableArray alloc] initWithCapacity:32]; 47 } 48 return self; 49} 50 51- (void)dealloc { 52 [self->stack release]; 53 [super dealloc]; 54} 55 56- (void)indentOn:(id)_target { 57 int i; 58 59 for (i = 0; i < (self->indent * 4); i++) { 60 if (_target) 61 [_target appendString:@" "]; 62 else 63 fputc(' ', stdout); 64 } 65} 66 67- (void)write:(NSString *)s to:(id)_target { 68 if (_target) 69 [_target appendString:s]; 70 else 71#ifndef __APPLE__ 72 printf("%s", [s cString]); 73#else 74 printf("%s", [s UTF8String]); 75#endif 76} 77- (BOOL)currentElementPreservesWhitespace { 78 return NO; 79} 80 81- (void)outputAttributeNode:(id<DOMAttr>)_attrNode 82 ofNode:(id<DOMNode>)_node 83 to:(id)_target 84{ 85 if ([[_attrNode prefix] length] > 0) { 86 [self write:[_attrNode prefix] to:_target]; 87 [self write:@":" to:_target]; 88 } 89 [self write:[_attrNode name] to:_target]; 90 91 if ([_attrNode hasChildNodes]) { 92 id children; 93 unsigned i, count; 94 95 [self write:@"=\"" to:_target]; 96 97 children = [_attrNode childNodes]; 98 for (i = 0, count = [children count]; i < count; i++) { 99 id child; 100 101 child = [children objectAtIndex:i]; 102 103 if ([child nodeType] == DOM_TEXT_NODE) 104 [self write:[(id<DOMText>)child data] to:_target]; 105 else 106 NSLog(@"WARNING: unsupported attribute value node %@", child); 107 } 108 109 [self write:@"\"" to:_target]; 110 } 111 else 112 NSLog(@"WARNING: attribute %@ has no content !", _attrNode); 113} 114 115- (void)outputAttributeNodes:(id<DOMNamedNodeMap>)_nodes 116 list:(NSArray *)_list to:(id)_target 117{ 118 unsigned i, count, count2; 119 120 if ((count = [_nodes length]) == 0) 121 return; 122 123 // append required prefix and namespaces 124 for (i = 0, count2 = [_list count]; i < count2; i = i + 2) { 125 [self write:@" xmlns:" to:_target]; 126 [self write:[_list objectAtIndex:i] to:_target]; 127 [self write:@"=\"" to:_target]; 128 [self write:[_list objectAtIndex:i+1] to:_target]; 129 [self write:@"\"" to:_target]; 130 } 131 132 for (i = 0; i < count; i++) { 133 id<DOMAttr> attrNode; 134 135 attrNode = [_nodes objectAtIndex:i]; 136 137 [self write:@" " to:_target]; 138 [self outputAttributeNode:attrNode ofNode:nil to:_target]; 139 } 140} 141 142- (void)outputTextNode:(id<DOMText>)_node to:(id)_target { 143 NSString *s; 144 unsigned len; 145 146 s = [_node data]; 147 if ((len = [s length]) == 0) 148 return; 149 150 if (![self currentElementPreservesWhitespace]) { 151 unsigned i; 152 153 for (i = 0; i < len; i++) { 154 if (!isspace([s characterAtIndex:i])) 155 break; 156 } 157 if (i == len) 158 /* only whitespace */ 159 return; 160 161 [self indentOn:_target]; 162 } 163 164 [self write:[_node data] to:_target]; 165 166 if (![self currentElementPreservesWhitespace]) 167 [self write:@"\n" to:_target]; 168} 169- (void)outputCommentNode:(id<DOMComment>)_node to:(id)_target { 170 [self write:@"<!-- " to:_target]; 171 [self write:[_node data] to:_target]; 172 [self write:@" -->" to:_target]; 173 174 if (![self currentElementPreservesWhitespace]) 175 [self write:@"\n" to:_target]; 176} 177 178- (void)outputElementNode:(id<DOMElement>)_node to:(id)_target { 179 NSArray *list; // new attribute prefixes and namespaces 180 NSString *tagName; 181 NSString *ns = nil; 182 NSString *tagURI; 183 NSString *tagPrefix; 184 BOOL isNodeValid; 185 unsigned i, count; 186 187 // getting new attributes prefixes and namespaces 188 list = (NSArray *)[_node attributes]; 189 list = [self newAttributePrefixesAndNamespaces:list]; 190 191 // push new attribute prefixes and namespaces to stack 192 for (i = 0, count = [list count]; i < count; i = i + 2) { 193 [self pushPrefix:[list objectAtIndex:i] 194 namespace:[list objectAtIndex:i+1]]; 195 } 196 197 tagURI = [_node namespaceURI]; 198 tagPrefix = [_node prefix]; 199 isNodeValid = [self isTagValidInStack:_node]; 200 if (!isNodeValid) [self pushPrefix:tagPrefix namespace:tagURI]; 201 202 /* needs to declare namespaces !!! */ 203 tagName = [_node tagName]; 204 if ([[_node prefix] length] > 0) { 205 NSString *p; 206 207 if (!isNodeValid) { 208 ns = [NSString stringWithFormat:@" xmlns:%@=\"%@\"", 209 tagPrefix, 210 tagURI]; 211 } 212 p = [_node prefix]; 213 p = [p stringByAppendingString:@":"]; 214 tagName = [p stringByAppendingString:tagName]; 215 } 216 else if ([tagURI length] > 0) { 217 id parent; 218 BOOL addNS; 219 220 addNS = YES; 221 if ((parent = [_node parentNode])) { 222 if ([parent nodeType] == DOM_ELEMENT_NODE) { 223 if ([[parent namespaceURI] isEqualToString:tagURI]) { 224 if ([[parent prefix] length] == 0) 225 addNS = NO; 226 } 227 } 228 } 229 else 230 addNS = YES; 231 232 if (addNS) 233 ns = [NSString stringWithFormat:@" xmlns=\"%@\"", [_node namespaceURI]]; 234 else 235 ns = nil; 236 } 237 else 238 ns = nil; 239 240 if ([_node hasChildNodes]) { 241 [self indentOn:_target]; 242 [self write:@"<" to:_target]; 243 [self write:tagName to:_target]; 244 if (ns) [self write:ns to:_target]; 245 246 [self outputAttributeNodes:[_node attributes] list:list to:_target]; 247 [self write:@">\n" to:_target]; 248 249 self->indent++; 250 [self outputNodeList:[_node childNodes] to:_target]; 251 self->indent--; 252 253 [self indentOn:_target]; 254 [self write:@"</" to:_target]; 255 [self write:tagName to:_target]; 256 [self write:@">\n" to:_target]; 257 } 258 else { 259 [self indentOn:_target]; 260 [self write:@"<" to:_target]; 261 [self write:tagName to:_target]; 262 [self outputAttributeNodes:[_node attributes] list:list to:_target]; 263 [self write:@"/>\n" to:_target]; 264 } 265 // pop attributes prefixes and namespaces from stack 266 for (i = 0; i < count; i = i + 2) { 267 [self popPrefixAndNamespace]; 268 } 269 if (!isNodeValid) [self popPrefixAndNamespace]; 270} 271 272- (void)outputCDATA:(id<DOMCharacterData>)_node to:(id)_target { 273 [self write:@"<![CDATA[" to:_target]; 274 [self outputNodeList:[_node childNodes] to:_target]; 275 [self write:@"]]>" to:_target]; 276} 277 278- (void)outputPI:(id<DOMProcessingInstruction>)_node to:(id)_target { 279 [self indentOn:_target]; 280 [self write:@"<?" to:_target]; 281 [self write:[_node target] to:_target]; 282 [self write:@" " to:_target]; 283 [self write:[_node data] to:_target]; 284 [self write:@"?>\n" to:_target]; 285} 286 287- (void)outputNode:(id<DOMNode>)_node to:(id)_target { 288 switch ([_node nodeType]) { 289 case DOM_ELEMENT_NODE: 290 [self outputElementNode:(id)_node to:_target]; 291 break; 292 case DOM_CDATA_SECTION_NODE: 293 [self outputCDATA:(id)_node to:_target]; 294 break; 295 case DOM_PROCESSING_INSTRUCTION_NODE: 296 [self outputPI:(id)_node to:_target]; 297 break; 298 case DOM_TEXT_NODE: 299 [self outputTextNode:(id)_node to:_target]; 300 break; 301 case DOM_COMMENT_NODE: 302 [self outputCommentNode:(id)_node to:_target]; 303 break; 304 305 default: 306 NSLog(@"cannot output node '%@'", _node); 307 break; 308 } 309} 310- (void)outputNodeList:(id<DOMNodeList>)_nodeList to:(id)_target { 311 id children; 312 unsigned i, count; 313 314 children = _nodeList; 315 316 for (i = 0, count = [children count]; i < count; i++) 317 [self outputNode:[children objectAtIndex:i] to:_target]; 318} 319 320- (void)outputDocument:(id)_document to:(id)_target { 321 if (![_document hasChildNodes]) { 322 NSLog(@"ERROR: document has no childnodes !"); 323 return; 324 } 325 326 [self write:@"<?xml version=\"1.0\"?>\n" to:_target]; 327 328 [self->stack removeAllObjects]; 329 [self outputNodeList:[_document childNodes] to:_target]; 330 331#if 0 332 NS_DURING { 333 } 334 NS_HANDLER 335 abort(); 336 NS_ENDHANDLER; 337#endif 338} 339 340@end /* DOMXMLOutputter */ 341 342 343@implementation DOMXMLOutputter(PrefixStack) 344 345- (void)_checkPrefixStack { 346 NSAssert2(([self->stack count] % 2 == 0), 347 @"%s: prefixStack is not valid (%@)!!!", 348 __PRETTY_FUNCTION__, 349 self->stack); 350} 351 352- (NSString *)topPrefix { 353 [self _checkPrefixStack]; 354 if ([self->stack count] == 0) return nil; 355 return [self->stack objectAtIndex:[self->stack count] -2]; 356} 357 358- (NSString *)topNamespace { 359 [self _checkPrefixStack]; 360 if ([self->stack count] == 0) return nil; 361 return [self->stack lastObject]; 362} 363 364- (void)pushPrefix:(NSString *)_prefix namespace:(NSString *)_namespace { 365 [self _checkPrefixStack]; 366 [self->stack addObject:(_prefix) ? _prefix : (NSString *)@""]; 367 [self->stack addObject:(_namespace) ? _namespace : (NSString *)@""]; 368} 369 370- (void)popPrefixAndNamespace { 371 [self _checkPrefixStack]; 372 NSAssert1(([self->stack count] > 0), @"%s: prefixStack.count == 0", 373 __PRETTY_FUNCTION__); 374 [self->stack removeLastObject]; // namespace 375 [self->stack removeLastObject]; // prefix 376} 377 378- (BOOL)isTagValidInStack:(id)_node { 379 NSString *nodeNamespace; 380 NSString *nodePrefix; 381 int i; 382 383 nodePrefix = [_node prefix]; 384 nodeNamespace = [_node namespaceURI]; 385 386 for (i = [self->stack count]; i >= 2; i = i - 2) { 387 NSString *namespace; 388 NSString *prefix; 389 390 prefix = [self->stack objectAtIndex:i-2]; 391 namespace = [self->stack objectAtIndex:i-1]; 392 if ([nodePrefix isEqualToString:prefix] && 393 [nodeNamespace isEqualToString:namespace]) 394 return YES; 395 } 396 return NO; 397} 398 399- (NSArray *)newAttributePrefixesAndNamespaces:(NSArray *)_attrs { 400 NSMutableArray *result; 401 int i, j, count; 402 403 count = [_attrs count]; 404 405 if (count == 0) return [NSArray array]; 406 407 result = [[NSMutableArray alloc] initWithCapacity:count]; 408 for (j = 0; j < count; j++) { 409 id attr; 410 NSString *attrNamespace; 411 NSString *attrPrefix; 412 BOOL didMatch = NO; 413 414 attr = [_attrs objectAtIndex:j]; 415 attrNamespace = [attr namespaceURI]; 416 attrPrefix = [attr prefix]; 417 attrNamespace = (attrNamespace) ? attrNamespace : (NSString *)@""; 418 attrPrefix = (attrPrefix) ? attrPrefix : (NSString *)@""; 419 420 if (([attrNamespace length] == 0 && [attrPrefix length] == 0)) continue; 421 422 for (i = [self->stack count]; i >= 2; i = i - 2) { 423 NSString *namespace; 424 NSString *prefix; 425 426 prefix = [self->stack objectAtIndex:i-2]; 427 namespace = [self->stack objectAtIndex:i-1]; 428 if ([attrPrefix isEqualToString:prefix] && 429 [attrNamespace isEqualToString:namespace]) { 430 didMatch = YES; 431 break; 432 } 433 } 434 if (didMatch == NO) { 435 [result addObject:attrPrefix]; 436 [result addObject:attrNamespace]; 437 } 438 } 439 return [result autorelease]; 440} 441 442@end /* DOMXMLOutputter(PrefixStack) */ 443