]> err.no Git - sope/blob - sope-xml/DOM/DOMXMLOutputter.m
use %p for pointer formats
[sope] / sope-xml / DOM / DOMXMLOutputter.m
1 /*
2   Copyright (C) 2000-2005 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     printf("%s", [s cString]);
72 }
73 - (BOOL)currentElementPreservesWhitespace {
74   return NO;
75 }
76
77 - (void)outputAttributeNode:(id<DOMAttr>)_attrNode
78   ofNode:(id<DOMNode>)_node
79   to:(id)_target
80 {
81   if ([[_attrNode prefix] length] > 0) {
82     [self write:[_attrNode prefix] to:_target];
83     [self write:@":"               to:_target];
84   }
85   [self write:[_attrNode name] to:_target];
86   
87   if ([_attrNode hasChildNodes]) {
88     id children;
89     unsigned i, count;
90
91     [self write:@"=\"" to:_target];
92
93     children = [_attrNode childNodes];
94     for (i = 0, count = [children count]; i < count; i++) {
95       id child;
96       
97       child = [children objectAtIndex:i];
98       
99       if ([child nodeType] == DOM_TEXT_NODE)
100         [self write:[(id<DOMText>)child data] to:_target];
101       else
102         NSLog(@"WARNING: unsupported attribute value node %@", child);
103     }
104     
105     [self write:@"\"" to:_target];
106   }
107   else
108     NSLog(@"WARNING: attribute %@ has no content !", _attrNode);
109 }
110
111 - (void)outputAttributeNodes:(id<DOMNamedNodeMap>)_nodes
112   list:(NSArray *)_list to:(id)_target
113 {
114   unsigned i, count, count2;
115   
116   if ((count = [_nodes length]) == 0)
117     return;
118
119   // append required prefix and namespaces
120   for (i = 0, count2 = [_list count]; i < count2; i = i + 2) {
121     [self write:@" xmlns:" to:_target];
122     [self write:[_list objectAtIndex:i]   to:_target];
123     [self write:@"=\""     to:_target];
124     [self write:[_list objectAtIndex:i+1] to:_target];
125     [self write:@"\""      to:_target];
126   }
127   
128   for (i = 0; i < count; i++) {
129     id<DOMAttr> attrNode;
130     
131     attrNode = [_nodes objectAtIndex:i];
132     
133     [self write:@" " to:_target];
134     [self outputAttributeNode:attrNode ofNode:nil to:_target];
135   }
136 }
137
138 - (void)outputTextNode:(id<DOMText>)_node to:(id)_target {
139   NSString *s;
140   unsigned len;
141
142   s = [_node data];
143   if ((len = [s length]) == 0)
144     return;
145   
146   if (![self currentElementPreservesWhitespace]) {
147     unsigned i;
148     
149     for (i = 0; i < len; i++) {
150       if (!isspace([s characterAtIndex:i]))
151         break;
152     }
153     if (i == len)
154       /* only whitespace */
155       return;
156     
157     [self indentOn:_target];
158   }
159   
160   [self write:[_node data] to:_target];
161   
162   if (![self currentElementPreservesWhitespace])
163     [self write:@"\n" to:_target];
164 }
165 - (void)outputCommentNode:(id<DOMComment>)_node to:(id)_target {
166   [self write:@"<!-- "     to:_target];
167   [self write:[_node data] to:_target];
168   [self write:@" -->"      to:_target];
169   
170   if (![self currentElementPreservesWhitespace])
171     [self write:@"\n" to:_target];
172 }
173
174 - (void)outputElementNode:(id<DOMElement>)_node to:(id)_target {
175   NSArray  *list;  // new attribute prefixes and namespaces
176   NSString *tagName;
177   NSString *ns = nil;
178   NSString *tagURI;
179   NSString *tagPrefix;
180   BOOL     isNodeValid;
181   unsigned i, count;
182   
183   // getting new attributes prefixes and namespaces
184   list = (NSArray *)[_node attributes];
185   list = [self newAttributePrefixesAndNamespaces:list];
186
187   // push new attribute prefixes and namespaces to stack
188   for (i = 0, count = [list count]; i < count; i = i + 2) {
189     [self pushPrefix:[list objectAtIndex:i]
190           namespace:[list objectAtIndex:i+1]];
191   }
192   
193   tagURI       = [_node namespaceURI];
194   tagPrefix    = [_node prefix];
195   isNodeValid  = [self isTagValidInStack:_node];
196   if (!isNodeValid) [self pushPrefix:tagPrefix namespace:tagURI];
197
198   /* needs to declare namespaces !!! */
199   tagName = [_node tagName];
200   if ([[_node prefix] length] > 0) {
201     NSString *p;
202
203     if (!isNodeValid) {
204       ns = [NSString stringWithFormat:@" xmlns:%@=\"%@\"",
205                      tagPrefix,
206                      tagURI];
207     }
208     p       = [_node prefix];
209     p       = [p stringByAppendingString:@":"];
210     tagName = [p stringByAppendingString:tagName];
211   }
212   else if ([tagURI length] > 0) {
213     id   parent;
214     BOOL addNS;
215
216     addNS = YES;
217     if ((parent = [_node parentNode])) {
218       if ([parent nodeType] == DOM_ELEMENT_NODE) {
219         if ([[parent namespaceURI] isEqualToString:tagURI]) {
220           if ([[parent prefix] length] == 0)
221             addNS = NO;
222         }
223       }
224     }
225     else
226       addNS = YES;
227     
228     if (addNS)
229       ns = [NSString stringWithFormat:@" xmlns=\"%@\"", [_node namespaceURI]];
230     else
231       ns = nil;
232   }
233   else
234     ns = nil;
235   
236   if ([_node hasChildNodes]) {
237     [self indentOn:_target];
238     [self write:@"<"    to:_target];
239     [self write:tagName to:_target];
240     if (ns) [self write:ns to:_target];
241     
242     [self outputAttributeNodes:[_node attributes] list:list to:_target];
243     [self write:@">\n"  to:_target];
244
245     self->indent++;
246     [self outputNodeList:[_node childNodes] to:_target];
247     self->indent--;
248
249     [self indentOn:_target];
250     [self write:@"</"   to:_target];
251     [self write:tagName to:_target];
252     [self write:@">\n"  to:_target];
253   }
254   else {
255     [self indentOn:_target];
256     [self write:@"<"    to:_target];
257     [self write:tagName to:_target];
258     [self outputAttributeNodes:[_node attributes] list:list to:_target];
259     [self write:@"/>\n" to:_target];
260   }
261   // pop attributes prefixes and namespaces from stack
262   for (i = 0; i < count; i = i + 2) {
263     [self popPrefixAndNamespace];
264   }
265   if (!isNodeValid) [self popPrefixAndNamespace];
266 }
267
268 - (void)outputCDATA:(id<DOMCharacterData>)_node to:(id)_target {
269   [self write:@"<![CDATA[" to:_target];
270   [self outputNodeList:[_node childNodes] to:_target];
271   [self write:@"]]>" to:_target];
272 }
273
274 - (void)outputPI:(id<DOMProcessingInstruction>)_node to:(id)_target {
275   [self indentOn:_target];
276   [self write:@"<?"          to:_target];
277   [self write:[_node target] to:_target];
278   [self write:@" "           to:_target];
279   [self write:[_node data]   to:_target];
280   [self write:@"?>\n"        to:_target];
281 }
282
283 - (void)outputNode:(id<DOMNode>)_node to:(id)_target {
284   switch ([_node nodeType]) {
285     case DOM_ELEMENT_NODE:
286       [self outputElementNode:(id)_node to:_target];
287       break;
288     case DOM_CDATA_SECTION_NODE:
289       [self outputCDATA:(id)_node to:_target];
290       break;
291     case DOM_PROCESSING_INSTRUCTION_NODE:
292       [self outputPI:(id)_node to:_target];
293       break;
294     case DOM_TEXT_NODE:
295       [self outputTextNode:(id)_node to:_target];
296       break;
297     case DOM_COMMENT_NODE:
298       [self outputCommentNode:(id)_node to:_target];
299       break;
300       
301     default:
302       NSLog(@"cannot output node '%@'", _node);
303       break;
304   }
305 }
306 - (void)outputNodeList:(id<DOMNodeList>)_nodeList to:(id)_target {
307   id       children;
308   unsigned i, count;
309   
310   children = _nodeList;
311   
312   for (i = 0, count = [children count]; i < count; i++)
313     [self outputNode:[children objectAtIndex:i] to:_target];
314 }
315
316 - (void)outputDocument:(id)_document to:(id)_target {
317   if (![_document hasChildNodes]) {
318     NSLog(@"ERROR: document has no childnodes !");
319     return;
320   }
321   
322   [self write:@"<?xml version=\"1.0\"?>\n" to:_target];
323   
324   [self->stack removeAllObjects];
325   [self outputNodeList:[_document childNodes] to:_target];
326   
327 #if 0
328   NS_DURING {
329   }
330   NS_HANDLER
331     abort();
332   NS_ENDHANDLER;
333 #endif
334 }
335
336 @end /* DOMXMLOutputter */
337
338
339 @implementation DOMXMLOutputter(PrefixStack)
340
341 - (void)_checkPrefixStack {
342   NSAssert2(([self->stack count] % 2 == 0),
343             @"%s: prefixStack is not valid (%@)!!!",
344             __PRETTY_FUNCTION__,
345             self->stack);
346 }
347
348 - (NSString *)topPrefix {
349   [self _checkPrefixStack];
350   if ([self->stack count] == 0) return nil;
351   return [self->stack objectAtIndex:[self->stack count] -2];
352 }
353
354 - (NSString *)topNamespace {
355   [self _checkPrefixStack];
356   if ([self->stack count] == 0) return nil;
357   return [self->stack lastObject];
358 }
359
360 - (void)pushPrefix:(NSString *)_prefix namespace:(NSString *)_namespace {
361   [self _checkPrefixStack];
362   [self->stack addObject:(_prefix)    ? _prefix    : (NSString *)@""];
363   [self->stack addObject:(_namespace) ? _namespace : (NSString *)@""];
364 }
365
366 - (void)popPrefixAndNamespace {
367   [self _checkPrefixStack];
368   NSAssert1(([self->stack count] > 0), @"%s: prefixStack.count == 0",
369             __PRETTY_FUNCTION__);
370   [self->stack removeLastObject]; // namespace
371   [self->stack removeLastObject]; // prefix
372 }
373
374 - (BOOL)isTagValidInStack:(id)_node {
375   NSString *nodeNamespace;
376   NSString *nodePrefix;
377   int      i;
378
379   nodePrefix    = [_node prefix];
380   nodeNamespace = [_node namespaceURI];
381   
382   for (i = [self->stack count]; i >= 2; i = i - 2) {
383     NSString *namespace;
384     NSString *prefix;
385
386     prefix    = [self->stack objectAtIndex:i-2];
387     namespace = [self->stack objectAtIndex:i-1];
388     if ([nodePrefix isEqualToString:prefix] &&
389         [nodeNamespace isEqualToString:namespace])
390       return YES;
391   }
392   return NO;
393 }
394
395 - (NSArray *)newAttributePrefixesAndNamespaces:(NSArray *)_attrs {
396   NSMutableArray *result;
397   int            i, j, count;
398
399   count = [_attrs count];
400   
401   if (count == 0) return [NSArray array];
402
403   result = [[NSMutableArray alloc] initWithCapacity:count];
404   for (j = 0; j < count; j++) {
405     id       attr;
406     NSString *attrNamespace;
407     NSString *attrPrefix;
408     BOOL     didMatch = NO;
409
410     attr          = [_attrs objectAtIndex:j];
411     attrNamespace = [attr namespaceURI];
412     attrPrefix    = [attr prefix];
413     attrNamespace = (attrNamespace) ? attrNamespace : (NSString *)@"";
414     attrPrefix    = (attrPrefix)    ? attrPrefix    : (NSString *)@"";
415
416     if (([attrNamespace length] == 0 && [attrPrefix length] == 0)) continue;
417     
418     for (i = [self->stack count]; i >= 2; i = i - 2) {
419       NSString *namespace;
420       NSString *prefix;
421
422       prefix    = [self->stack objectAtIndex:i-2];
423       namespace = [self->stack objectAtIndex:i-1];
424       if ([attrPrefix isEqualToString:prefix] &&
425           [attrNamespace isEqualToString:namespace]) {
426         didMatch = YES;
427         break;
428       }
429     }
430     if (didMatch == NO) {
431       [result addObject:attrPrefix];
432       [result addObject:attrNamespace];
433     }
434   }
435   return [result autorelease];
436 }
437
438 @end /* DOMXMLOutputter(PrefixStack) */