KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > outerj > daisy > xmlutil > XmlSerializer


1 /*
2  * Copyright 2004 Outerthought bvba and Schaubroeck nv
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.outerj.daisy.xmlutil;
17
18 import org.xml.sax.SAXException JavaDoc;
19 import org.xml.sax.Attributes JavaDoc;
20 import org.xml.sax.ContentHandler JavaDoc;
21 import org.xml.sax.Locator JavaDoc;
22 import org.xml.sax.helpers.AttributesImpl JavaDoc;
23
24 import javax.xml.transform.sax.SAXTransformerFactory JavaDoc;
25 import javax.xml.transform.sax.TransformerHandler JavaDoc;
26 import javax.xml.transform.stream.StreamResult JavaDoc;
27 import javax.xml.transform.TransformerConfigurationException JavaDoc;
28 import javax.xml.transform.OutputKeys JavaDoc;
29 import java.util.List JavaDoc;
30 import java.util.ArrayList JavaDoc;
31 import java.util.Map JavaDoc;
32 import java.util.HashMap JavaDoc;
33 import java.io.StringWriter JavaDoc;
34 import java.io.OutputStream JavaDoc;
35
36 public class XmlSerializer implements ContentHandler JavaDoc {
37     private static boolean needsNamespaceFix = false;
38     private static boolean needsNamespaceFixInitialized = false;
39     private static final String JavaDoc XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
40
41     private ContentHandler JavaDoc nextHandler;
42
43     public XmlSerializer(OutputStream JavaDoc outputStream) throws Exception JavaDoc {
44         if (!needsNamespaceFixInitialized) {
45             synchronized(this) {
46                 // PS: I know that double checking does not work reliably, but
47
// that's not important here. It can't do harm if this is executed
48
// multiple times.
49
if (!needsNamespaceFixInitialized) {
50                     needsNamespaceFix = needsNamespacesAsAttributes();
51                     needsNamespaceFixInitialized = true;
52                 }
53             }
54         }
55
56         TransformerHandler JavaDoc serializer = getTransformerHandler();
57         serializer.getTransformer().setOutputProperty(OutputKeys.METHOD, "xml");
58         serializer.setResult(new StreamResult JavaDoc(outputStream));
59
60         if (needsNamespaceFix) {
61             nextHandler = new NamespaceAsAttributes(serializer);
62         } else {
63             nextHandler = serializer;
64         }
65     }
66
67     private TransformerHandler JavaDoc getTransformerHandler() throws TransformerConfigurationException JavaDoc {
68         SAXTransformerFactory JavaDoc transformerFactory = (SAXTransformerFactory JavaDoc) SAXTransformerFactory.newInstance();
69         return transformerFactory.newTransformerHandler();
70     }
71
72
73     /**
74      * Checks if the used Trax implementation correctly handles namespaces set using
75      * <code>startPrefixMapping()</code>, but wants them also as 'xmlns:' attributes.
76      * <p>
77      * The check consists in sending SAX events representing a minimal namespaced document
78      * with namespaces defined only with calls to <code>startPrefixMapping</code> (no
79      * xmlns:xxx attributes) and check if they are present in the resulting text.
80      *
81      * <p>THIS METHOD IS COPIED FROM COCOON'S AbstractTextSerializer with some modifications
82      */

83     private boolean needsNamespacesAsAttributes() throws Exception JavaDoc {
84
85             // Serialize a minimal document to check how namespaces are handled.
86
StringWriter JavaDoc writer = new StringWriter JavaDoc();
87
88             String JavaDoc uri = "namespaceuri";
89             String JavaDoc prefix = "nsp";
90             String JavaDoc check = "xmlns:" + prefix + "='" + uri + "'";
91
92             TransformerHandler JavaDoc handler = getTransformerHandler();
93
94             handler.setResult(new StreamResult JavaDoc(writer));
95
96             // Output a single element
97
handler.startDocument();
98             handler.startPrefixMapping(prefix, uri);
99             handler.startElement(uri, "element", "", new AttributesImpl JavaDoc());
100             handler.endPrefixMapping(prefix);
101             handler.endDocument();
102
103             String JavaDoc text = writer.toString();
104
105             // Check if the namespace is there (replace " by ' to be sure of what we search in)
106
boolean needsIt = (text.replace('"', '\'').indexOf(check) == -1);
107
108             // String msg = needsIt ? " needs namespace attributes (will be slower)." : " handles correctly namespaces.";
109
// logger.debug("Trax handler " + handler.getClass().getName() + msg);
110

111             return needsIt;
112     }
113
114     /**
115      * A pipe that ensures that all namespace prefixes are also present as
116      * 'xmlns:' attributes. This used to circumvent Xalan's serialization behaviour
117      * which is to ignore namespaces if they're not present as 'xmlns:xxx' attributes.
118      *
119      * <p>THIS CLASS IS COPIED FROM COCOON'S AbstractTextSerializer with some
120      * modifications
121      */

122     public class NamespaceAsAttributes implements ContentHandler JavaDoc {
123
124         /**
125          * The prefixes of startPreficMapping() declarations for the coming element.
126          */

127         private List JavaDoc prefixList = new ArrayList JavaDoc();
128
129         /**
130          * The URIs of startPrefixMapping() declarations for the coming element.
131          */

132         private List JavaDoc uriList = new ArrayList JavaDoc();
133
134         /**
135          * Maps of URI<->prefix mappings. Used to work around a bug in the Xalan
136          * serializer.
137          */

138         private Map JavaDoc uriToPrefixMap = new HashMap JavaDoc();
139         private Map JavaDoc prefixToUriMap = new HashMap JavaDoc();
140
141         /**
142          * True if there has been some startPrefixMapping() for the coming element.
143          */

144         private boolean hasMappings = false;
145
146         private ContentHandler JavaDoc nextHandler;
147
148         public NamespaceAsAttributes(ContentHandler JavaDoc nextHandler) {
149             this.nextHandler = nextHandler;
150         }
151
152         public void startDocument() throws SAXException JavaDoc {
153             // Cleanup
154
this.uriToPrefixMap.clear();
155             this.prefixToUriMap.clear();
156             clearMappings();
157             nextHandler.startDocument();
158         }
159
160         /**
161          * Track mappings to be able to add <code>xmlns:</code> attributes
162          * in <code>startElement()</code>.
163          */

164         public void startPrefixMapping(String JavaDoc prefix, String JavaDoc uri) throws SAXException JavaDoc {
165             // Store the mappings to reconstitute xmlns:attributes
166
// except prefixes starting with "xml": these are reserved
167
// VG: (uri != null) fixes NPE in startElement
168
if (uri != null && !prefix.startsWith("xml") && !this.prefixList.contains(prefix)) {
169                 this.hasMappings = true;
170                 this.prefixList.add(prefix);
171                 this.uriList.add(uri);
172
173                 // append the prefix colon now, in order to save concatenations later, but
174
// only for non-empty prefixes.
175
if (prefix.length() > 0) {
176                     this.uriToPrefixMap.put(uri, prefix + ":");
177                 } else {
178                     this.uriToPrefixMap.put(uri, prefix);
179                 }
180
181                 this.prefixToUriMap.put(prefix, uri);
182             }
183             nextHandler.startPrefixMapping(prefix, uri);
184         }
185
186         /**
187          * Ensure all namespace declarations are present as <code>xmlns:</code> attributes
188          * and add those needed before calling superclass. This is a workaround for a Xalan bug
189          * (at least in version 2.0.1) : <code>org.apache.xalan.serialize.SerializerToXML</code>
190          * ignores <code>start/endPrefixMapping()</code>.
191          */

192         public void startElement(String JavaDoc eltUri, String JavaDoc eltLocalName, String JavaDoc eltQName, Attributes JavaDoc attrs)
193                 throws SAXException JavaDoc {
194
195             // try to restore the qName. The map already contains the colon
196
if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) {
197                 eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName;
198             }
199             if (this.hasMappings) {
200                 // Add xmlns* attributes where needed
201

202                 // New Attributes if we have to add some.
203
AttributesImpl JavaDoc newAttrs = null;
204
205                 int mappingCount = this.prefixList.size();
206                 int attrCount = attrs.getLength();
207
208                 for (int mapping = 0; mapping < mappingCount; mapping++) {
209
210                     // Build infos for this namespace
211
String JavaDoc uri = (String JavaDoc) this.uriList.get(mapping);
212                     String JavaDoc prefix = (String JavaDoc) this.prefixList.get(mapping);
213                     String JavaDoc qName = prefix.equals("") ? "xmlns" : ("xmlns:" + prefix);
214
215                     // Search for the corresponding xmlns* attribute
216
boolean found = false;
217                     for (int attr = 0; attr < attrCount; attr++) {
218                         if (qName.equals(attrs.getQName(attr))) {
219                             // Check if mapping and attribute URI match
220
if (!uri.equals(attrs.getValue(attr))) {
221                                 System.err.println("XML serializer: URI in prefix mapping and attribute do not match : '"
222                                                   + uri + "' - '" + attrs.getURI(attr) + "'");
223                                 throw new SAXException JavaDoc("URI in prefix mapping and attribute do not match");
224                             }
225                             found = true;
226                             break;
227                         }
228                     }
229
230                     if (!found) {
231                         // Need to add this namespace
232
if (newAttrs == null) {
233                             // Need to test if attrs is empty or we go into an infinite loop...
234
// Well know SAX bug which I spent 3 hours to remind of :-(
235
if (attrCount == 0) {
236                                 newAttrs = new AttributesImpl JavaDoc();
237                             } else {
238                                 newAttrs = new AttributesImpl JavaDoc(attrs);
239                             }
240                         }
241
242                         if (prefix.equals("")) {
243                             newAttrs.addAttribute(XML_NAMESPACE_URI, "xmlns", "xmlns", "CDATA", uri);
244                         } else {
245                             newAttrs.addAttribute(XML_NAMESPACE_URI, prefix, qName, "CDATA", uri);
246                         }
247                     }
248                 } // end for mapping
249

250                 // Cleanup for the next element
251
clearMappings();
252
253                 // Start element with new attributes, if any
254
nextHandler.startElement(eltUri, eltLocalName, eltQName, newAttrs == null ? attrs : newAttrs);
255             } else {
256                 // Normal job
257
nextHandler.startElement(eltUri, eltLocalName, eltQName, attrs);
258             }
259         }
260
261
262         /**
263          * Receive notification of the end of an element.
264          * Try to restore the element qName.
265          */

266         public void endElement(String JavaDoc eltUri, String JavaDoc eltLocalName, String JavaDoc eltQName) throws SAXException JavaDoc {
267             // try to restore the qName. The map already contains the colon
268
if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) {
269                 eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName;
270             }
271             nextHandler.endElement(eltUri, eltLocalName, eltQName);
272         }
273
274         /**
275          * End the scope of a prefix-URI mapping:
276          * remove entry from mapping tables.
277          */

278         public void endPrefixMapping(String JavaDoc prefix) throws SAXException JavaDoc {
279             // remove mappings for xalan-bug-workaround.
280
// Unfortunately, we're not passed the uri, but the prefix here,
281
// so we need to maintain maps in both directions.
282
if (this.prefixToUriMap.containsKey(prefix)) {
283                 this.uriToPrefixMap.remove(this.prefixToUriMap.get(prefix));
284                 this.prefixToUriMap.remove(prefix);
285             }
286
287             if (hasMappings) {
288                 // most of the time, start/endPrefixMapping calls have an element event between them,
289
// which will clear the hasMapping flag and so this code will only be executed in the
290
// rather rare occasion when there are start/endPrefixMapping calls with no element
291
// event in between. If we wouldn't remove the items from the prefixList and uriList here,
292
// the namespace would be incorrectly declared on the next element following the
293
// endPrefixMapping call.
294
int pos = prefixList.lastIndexOf(prefix);
295                 if (pos != -1) {
296                     prefixList.remove(pos);
297                     uriList.remove(pos);
298                 }
299             }
300
301             nextHandler.endPrefixMapping(prefix);
302         }
303
304         /**
305          *
306          */

307         public void endDocument() throws SAXException JavaDoc {
308             // Cleanup
309
this.uriToPrefixMap.clear();
310             this.prefixToUriMap.clear();
311             clearMappings();
312             nextHandler.endDocument();
313         }
314
315         private void clearMappings() {
316             this.hasMappings = false;
317             this.prefixList.clear();
318             this.uriList.clear();
319         }
320
321         public void characters(char ch[], int start, int length) throws SAXException JavaDoc {
322             nextHandler.characters(ch, start, length);
323         }
324
325         public void ignorableWhitespace(char ch[], int start, int length) throws SAXException JavaDoc {
326             nextHandler.ignorableWhitespace(ch, start, length);
327         }
328
329         public void skippedEntity(String JavaDoc name) throws SAXException JavaDoc {
330             nextHandler.skippedEntity(name);
331         }
332
333         public void setDocumentLocator(Locator JavaDoc locator) {
334             nextHandler.setDocumentLocator(locator);
335         }
336
337         public void processingInstruction(String JavaDoc target, String JavaDoc data) throws SAXException JavaDoc {
338             nextHandler.processingInstruction(target, data);
339         }
340     }
341
342     public void endDocument() throws SAXException JavaDoc {
343         nextHandler.endDocument();
344     }
345
346     public void startDocument () throws SAXException JavaDoc {
347         nextHandler.startDocument();
348     }
349
350     public void characters (char ch[], int start, int length) throws SAXException JavaDoc {
351         nextHandler.characters(ch, start, length);
352     }
353
354     public void ignorableWhitespace (char ch[], int start, int length) throws SAXException JavaDoc {
355         nextHandler.ignorableWhitespace(ch, start, length);
356     }
357
358     public void endPrefixMapping (String JavaDoc prefix) throws SAXException JavaDoc {
359         nextHandler.endPrefixMapping(prefix);
360     }
361
362     public void skippedEntity (String JavaDoc name) throws SAXException JavaDoc {
363         nextHandler.skippedEntity(name);
364     }
365
366     public void setDocumentLocator (Locator JavaDoc locator) {
367         nextHandler.setDocumentLocator(locator);
368     }
369
370     public void processingInstruction (String JavaDoc target, String JavaDoc data) throws SAXException JavaDoc {
371         nextHandler.processingInstruction(target, data);
372     }
373
374     public void startPrefixMapping (String JavaDoc prefix, String JavaDoc uri) throws SAXException JavaDoc {
375         nextHandler.startPrefixMapping(prefix, uri);
376     }
377
378     public void endElement (String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName) throws SAXException JavaDoc {
379         nextHandler.endElement(namespaceURI, localName, qName);
380     }
381
382     public void startElement (String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName, Attributes JavaDoc atts) throws SAXException JavaDoc {
383         nextHandler.startElement(namespaceURI, localName, qName, atts);
384     }
385 }
386
Popular Tags