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