1 /* TransformerImpl.java --
2 Copyright (C) 2004,2005,2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package gnu.xml.transform;
40 import java.io.BufferedOutputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.io.OutputStream;
44 import java.io.UnsupportedEncodingException;
45 import java.io.Writer;
46 import java.net.MalformedURLException;
47 import java.net.UnknownServiceException;
49 import java.net.URLConnection;
50 import java.util.Collection;
51 import java.util.Iterator;
52 import java.util.LinkedList;
53 import java.util.List;
54 import java.util.Properties;
55 import java.util.StringTokenizer;
56 import javax.xml.namespace.QName;
57 import javax.xml.transform.ErrorListener;
58 import javax.xml.transform.OutputKeys;
59 import javax.xml.transform.Result;
60 import javax.xml.transform.Source;
61 import javax.xml.transform.Transformer;
62 import javax.xml.transform.TransformerConfigurationException;
63 import javax.xml.transform.TransformerException;
64 import javax.xml.transform.URIResolver;
65 import javax.xml.transform.dom.DOMSource;
66 import javax.xml.transform.dom.DOMResult;
67 import javax.xml.transform.sax.SAXResult;
68 import javax.xml.transform.stream.StreamResult;
69 import org.w3c.dom.Document;
70 import org.w3c.dom.DocumentType;
71 import org.w3c.dom.DOMImplementation;
72 import org.w3c.dom.Node;
73 import org.w3c.dom.Text;
74 import org.xml.sax.ContentHandler;
75 import org.xml.sax.SAXException;
76 import org.xml.sax.ext.LexicalHandler;
77 import gnu.xml.dom.DomDoctype;
78 import gnu.xml.dom.DomDocument;
79 import gnu.xml.dom.ls.WriterOutputStream;
82 * The transformation process for a given stylesheet.
84 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
90 final TransformerFactoryImpl factory;
91 final Stylesheet stylesheet;
92 URIResolver uriResolver;
93 ErrorListener errorListener;
94 Properties outputProperties;
96 TransformerImpl(TransformerFactoryImpl factory,
97 Stylesheet stylesheet,
98 Properties outputProperties)
99 throws TransformerConfigurationException
101 this.factory = factory;
102 uriResolver = factory.userResolver;
103 errorListener = factory.userListener;
104 this.stylesheet = stylesheet;
105 this.outputProperties = outputProperties;
106 if (stylesheet != null)
108 // Set up parameter context for this transformer
109 stylesheet.bindings.push(Bindings.PARAM);
113 public void transform(Source xmlSource, Result outputTarget)
114 throws TransformerException
116 // Get the source tree
118 synchronized (factory.resolver)
120 factory.resolver.setUserResolver(uriResolver);
121 factory.resolver.setUserListener(errorListener);
122 source = factory.resolver.resolveDOM(xmlSource, null, null);
124 Node context = source.getNode();
125 Document doc = (context instanceof Document) ? (Document) context :
126 context.getOwnerDocument();
127 if (doc instanceof DomDocument)
129 // Suppress mutation events
130 ((DomDocument) doc).setBuilding(true);
132 // Get the result tree
133 Node parent = null, nextSibling = null;
134 if (outputTarget instanceof DOMResult)
136 DOMResult dr = (DOMResult) outputTarget;
137 parent = dr.getNode();
138 nextSibling = dr.getNextSibling();
140 Document rdoc = (parent instanceof Document) ? (Document) parent :
141 parent.getOwnerDocument();
142 if (rdoc instanceof DomDocument)
144 // Suppress mutation events and allow multiple root elements
145 DomDocument drdoc = (DomDocument) rdoc;
146 drdoc.setBuilding(true);
147 drdoc.setCheckWellformedness(false);
150 boolean created = false;
152 if (stylesheet != null)
156 // Create a new document to hold the result
157 DomDocument resultDoc = new DomDocument();
158 resultDoc.setBuilding(true);
159 resultDoc.setCheckWellformedness(false);
163 // Make a copy of the source node, and strip it
164 context = context.cloneNode(true);
165 strip(stylesheet, context);
166 // XSLT transformation
169 // Set output properties in the underlying stylesheet
170 ((TransformerOutputProperties) outputProperties).apply();
171 stylesheet.initTopLevelVariables(context);
172 TemplateNode t = stylesheet.getTemplate(null, context, false);
175 stylesheet.current = context;
176 t.apply(stylesheet, null, context, 1, 1, parent, nextSibling);
179 catch (TransformerException e)
181 // Done transforming, reset document
182 if (doc instanceof DomDocument)
183 ((DomDocument) doc).setBuilding(false);
189 // Identity transform
190 Node clone = context.cloneNode(true);
191 if (context.getNodeType() != Node.DOCUMENT_NODE)
196 // Create a new document to hold the result
197 DomDocument rd = new DomDocument();
198 rd.setBuilding(true);
199 rd.setCheckWellformedness(false);
200 parent = resultDoc = rd;
205 resultDoc = (parent instanceof Document) ?
207 parent.getOwnerDocument();
209 Document sourceDoc = context.getOwnerDocument();
210 if (sourceDoc != resultDoc)
211 clone = resultDoc.adoptNode(clone);
212 if (nextSibling != null)
213 parent.insertBefore(clone, nextSibling);
215 parent.appendChild(clone);
219 // Cannot append document to another tree
224 String method = outputProperties.getProperty(OutputKeys.METHOD);
225 int outputMethod = "html".equals(method) ? Stylesheet.OUTPUT_HTML :
226 "text".equals(method) ? Stylesheet.OUTPUT_TEXT :
227 Stylesheet.OUTPUT_XML;
228 String encoding = outputProperties.getProperty(OutputKeys.ENCODING);
229 String publicId = outputProperties.getProperty(OutputKeys.DOCTYPE_PUBLIC);
230 String systemId = outputProperties.getProperty(OutputKeys.DOCTYPE_SYSTEM);
231 String version = outputProperties.getProperty(OutputKeys.VERSION);
232 boolean omitXmlDeclaration =
233 "yes".equals(outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION));
235 "yes".equals(outputProperties.getProperty(OutputKeys.STANDALONE));
236 String mediaType = outputProperties.getProperty(OutputKeys.MEDIA_TYPE);
237 String cdataSectionElements =
238 outputProperties.getProperty(OutputKeys.CDATA_SECTION_ELEMENTS);
240 "yes".equals(outputProperties.getProperty(OutputKeys.INDENT));
241 if (created && parent instanceof DomDocument)
243 // Discover document element
244 DomDocument resultDoc = (DomDocument) parent;
245 Node root = resultDoc.getDocumentElement();
246 // Add doctype if specified
247 if (publicId != null || systemId != null)
251 // We must know the name of the root element to
252 // create the document type
253 DocumentType doctype = new DomDoctype(resultDoc,
257 resultDoc.insertBefore(doctype, root);
260 resultDoc.setBuilding(false);
261 resultDoc.setCheckWellformedness(true);
263 else if (publicId != null || systemId != null)
265 switch (parent.getNodeType())
267 case Node.DOCUMENT_NODE:
268 case Node.DOCUMENT_FRAGMENT_NODE:
269 Document resultDoc = (parent instanceof Document) ?
271 parent.getOwnerDocument();
272 DOMImplementation impl = resultDoc.getImplementation();
273 Node root = resultDoc.getDocumentElement();
276 DocumentType doctype =
277 impl.createDocumentType(root.getNodeName(),
280 resultDoc.insertBefore(doctype, root);
285 parent.setUserData("version", version, stylesheet);
286 if (omitXmlDeclaration)
287 parent.setUserData("omit-xml-declaration", "yes", stylesheet);
289 parent.setUserData("standalone", "yes", stylesheet);
290 if (mediaType != null)
291 parent.setUserData("media-type", mediaType, stylesheet);
292 if (cdataSectionElements != null)
294 List list = new LinkedList();
295 StringTokenizer st = new StringTokenizer(cdataSectionElements);
296 while (st.hasMoreTokens())
298 String name = st.nextToken();
299 String localName = name;
301 String prefix = null;
302 int ci = name.indexOf(':');
305 // Use namespaces defined on xsl:output node to resolve
306 // namespaces for QName
307 prefix = name.substring(0, ci);
308 localName = name.substring(ci + 1);
309 uri = stylesheet.output.lookupNamespaceURI(prefix);
311 list.add(new QName(uri, localName, prefix));
315 Document resultDoc = (parent instanceof Document) ?
317 parent.getOwnerDocument();
318 convertCdataSectionElements(resultDoc, parent, list);
323 if (created && parent instanceof DomDocument)
325 DomDocument domDoc = (DomDocument) parent;
326 domDoc.setBuilding(true);
327 domDoc.setCheckWellformedness(false);
330 strip(stylesheet, parent);
331 Document resultDoc = (parent instanceof Document) ?
333 parent.getOwnerDocument();
334 reindent(resultDoc, parent, 0);
335 if (created && parent instanceof DomDocument)
337 DomDocument domDoc = (DomDocument) parent;
338 domDoc.setBuilding(false);
339 domDoc.setCheckWellformedness(true);
342 // Render result to the target device
343 if (outputTarget instanceof DOMResult)
347 DOMResult dr = (DOMResult) outputTarget;
349 dr.setNextSibling(null);
352 else if (outputTarget instanceof StreamResult)
354 StreamResult sr = (StreamResult) outputTarget;
355 IOException ex = null;
358 writeStreamResult(parent, sr, outputMethod, encoding);
360 catch (UnsupportedEncodingException e)
364 writeStreamResult(parent, sr, outputMethod, "UTF-8");
366 catch (IOException e2)
371 catch (IOException e)
377 if (errorListener != null)
378 errorListener.error(new TransformerException(ex));
380 ex.printStackTrace(System.err);
383 else if (outputTarget instanceof SAXResult)
385 SAXResult sr = (SAXResult) outputTarget;
388 ContentHandler ch = sr.getHandler();
389 LexicalHandler lh = sr.getLexicalHandler();
390 if (lh == null && ch instanceof LexicalHandler)
391 lh = (LexicalHandler) ch;
392 SAXSerializer serializer = new SAXSerializer();
393 serializer.serialize(parent, ch, lh);
395 catch (SAXException e)
397 if (errorListener != null)
398 errorListener.error(new TransformerException(e));
400 e.printStackTrace(System.err);
406 * Strip whitespace from the source tree.
408 static boolean strip(Stylesheet stylesheet, Node node)
409 throws TransformerConfigurationException
411 short nt = node.getNodeType();
412 if (nt == Node.ENTITY_REFERENCE_NODE)
414 // Replace entity reference with its content
415 Node parent = node.getParentNode();
416 Node nextSibling = node.getNextSibling();
417 Node child = node.getFirstChild();
418 while (child != null)
420 Node next = child.getNextSibling();
421 node.removeChild(child);
422 if (nextSibling != null)
423 parent.insertBefore(child, nextSibling);
425 parent.appendChild(child);
430 if (nt == Node.TEXT_NODE || nt == Node.CDATA_SECTION_NODE)
432 // Denormalize text into whitespace and non-whitespace nodes
433 String text = node.getNodeValue();
434 String[] tokens = tokenizeWhitespace(text);
435 if (tokens.length > 1)
437 node.setNodeValue(tokens[0]);
438 Node parent = node.getParentNode();
439 Node nextSibling = node.getNextSibling();
440 Document doc = node.getOwnerDocument();
441 for (int i = 1; i < tokens.length; i++)
443 Node newChild = (nt == Node.CDATA_SECTION_NODE) ?
444 doc.createCDATASection(tokens[i]) :
445 doc.createTextNode(tokens[i]);
446 if (nextSibling != null)
447 parent.insertBefore(newChild, nextSibling);
449 parent.appendChild(newChild);
452 return !stylesheet.isPreserved((Text) node, true);
456 Node child = node.getFirstChild();
457 while (child != null)
459 boolean remove = strip(stylesheet, child);
460 Node next = child.getNextSibling();
462 node.removeChild(child);
470 * Tokenize the specified text into contiguous whitespace-only and
471 * non-whitespace chunks.
473 private static String[] tokenizeWhitespace(String text)
475 int len = text.length();
476 int start = 0, end = len - 1;
477 // Find index of text start
478 for (int i = 0; i < len; i++)
480 char c = text.charAt(i);
481 boolean whitespace = (c == ' ' || c == '\n' || c == '\t' || c == '\r');
487 if (start == end) // all whitespace
488 return new String[] { text };
489 // Find index of text end
490 for (int i = end; i > start; i--)
492 char c = text.charAt(i);
493 boolean whitespace = (c == ' ' || c == '\n' || c == '\t' || c == '\r');
499 if (start == 0 && end == len - 1) // all non-whitespace
500 return new String[] { text };
501 // whitespace, then text, then whitespace
502 String[] ret = (start > 0 && end < len - 1) ?
503 new String[3] : new String[2];
506 ret[i++] = text.substring(0, start);
507 ret[i++] = text.substring(start, end + 1);
509 ret[i++] = text.substring(end + 1);
514 * Obtain a suitable output stream for writing the result to,
515 * and use the StreamSerializer to write the result tree to the stream.
517 void writeStreamResult(Node node, StreamResult sr, int outputMethod,
521 OutputStream out = null;
522 boolean created = false;
525 out = sr.getOutputStream();
528 Writer writer = sr.getWriter();
530 out = new WriterOutputStream(writer);
534 String systemId = sr.getSystemId();
537 URL url = new URL(systemId);
538 URLConnection connection = url.openConnection();
539 // We need to call setDoInput(false), because our
540 // implementation of the file protocol allows writing
541 // (unlike Sun), but it will fail with a FileNotFoundException
542 // if we also open the connection for input and the output
543 // file doesn't yet exist.
544 connection.setDoInput(false);
545 connection.setDoOutput(true);
546 out = connection.getOutputStream();
548 catch (MalformedURLException e)
550 out = new FileOutputStream(systemId);
552 catch (UnknownServiceException e)
554 URL url = new URL(systemId);
555 out = new FileOutputStream(url.getPath());
559 out = new BufferedOutputStream(out);
560 StreamSerializer serializer =
561 new StreamSerializer(outputMethod, encoding, null);
562 if (stylesheet != null)
564 Collection celem = stylesheet.outputCdataSectionElements;
565 serializer.setCdataSectionElements(celem);
567 serializer.serialize(node, out);
574 if (out != null && created)
577 catch (IOException e)
579 if (errorListener != null)
583 errorListener.error(new TransformerException(e));
585 catch (TransformerException e2)
587 e2.printStackTrace(System.err);
591 e.printStackTrace(System.err);
596 void copyChildren(Document dstDoc, Node src, Node dst)
598 Node srcChild = src.getFirstChild();
599 while (srcChild != null)
601 Node dstChild = dstDoc.adoptNode(srcChild);
602 dst.appendChild(dstChild);
603 srcChild = srcChild.getNextSibling();
607 public void setParameter(String name, Object value)
609 if (stylesheet != null)
610 stylesheet.bindings.set(new QName(null, name), value, Bindings.PARAM);
613 public Object getParameter(String name)
615 if (stylesheet != null)
616 return stylesheet.bindings.get(new QName(null, name), null, 1, 1);
620 public void clearParameters()
622 if (stylesheet != null)
624 stylesheet.bindings.pop(Bindings.PARAM);
625 stylesheet.bindings.push(Bindings.PARAM);
629 public void setURIResolver(URIResolver resolver)
631 uriResolver = resolver;
634 public URIResolver getURIResolver()
639 public void setOutputProperties(Properties oformat)
640 throws IllegalArgumentException
643 outputProperties.clear();
645 outputProperties.putAll(oformat);
648 public Properties getOutputProperties()
650 return (Properties) outputProperties.clone();
653 public void setOutputProperty(String name, String value)
654 throws IllegalArgumentException
656 outputProperties.put(name, value);
659 public String getOutputProperty(String name)
660 throws IllegalArgumentException
662 return outputProperties.getProperty(name);
665 public void setErrorListener(ErrorListener listener)
667 errorListener = listener;
670 public ErrorListener getErrorListener()
672 return errorListener;
675 static final String INDENT_WHITESPACE = " ";
678 * Apply indent formatting to the given tree.
680 void reindent(Document doc, Node node, int offset)
682 if (node.hasChildNodes())
684 boolean markupContent = false;
685 boolean textContent = false;
686 List children = new LinkedList();
687 Node ctx = node.getFirstChild();
690 switch (ctx.getNodeType())
692 case Node.ELEMENT_NODE:
693 case Node.PROCESSING_INSTRUCTION_NODE:
694 case Node.DOCUMENT_TYPE_NODE:
695 markupContent = true;
698 case Node.CDATA_SECTION_NODE:
699 case Node.ENTITY_REFERENCE_NODE:
700 case Node.COMMENT_NODE:
705 ctx = ctx.getNextSibling();
711 // XXX handle mixed content differently?
713 int nodeType = node.getNodeType();
714 if (nodeType == Node.DOCUMENT_NODE)
716 for (Iterator i = children.iterator(); i.hasNext(); )
718 ctx = (Node) i.next();
719 reindent(doc, ctx, offset);
724 StringBuffer buf = new StringBuffer();
726 for (int i = 0; i < offset + 1; i++)
727 buf.append(INDENT_WHITESPACE);
728 String ws = buf.toString();
729 for (Iterator i = children.iterator(); i.hasNext(); )
731 ctx = (Node) i.next();
732 node.insertBefore(doc.createTextNode(ws), ctx);
733 reindent(doc, ctx, offset + 1);
735 buf = new StringBuffer();
737 for (int i = 0; i < offset; i++)
738 buf.append(INDENT_WHITESPACE);
740 node.appendChild(doc.createTextNode(ws));
747 * Converts the text node children of any cdata-section-elements in the
748 * tree to CDATA section nodes.
750 void convertCdataSectionElements(Document doc, Node node, List list)
752 if (node.getNodeType() == Node.ELEMENT_NODE)
754 boolean match = false;
755 for (Iterator i = list.iterator(); i.hasNext(); )
757 QName qname = (QName) i.next();
758 if (match(qname, node))
766 Node ctx = node.getFirstChild();
769 if (ctx.getNodeType() == Node.TEXT_NODE)
771 Node cdata = doc.createCDATASection(ctx.getNodeValue());
772 node.replaceChild(cdata, ctx);
775 ctx = ctx.getNextSibling();
779 Node ctx = node.getFirstChild();
782 if (ctx.hasChildNodes())
783 convertCdataSectionElements(doc, ctx, list);
784 ctx = ctx.getNextSibling();
788 boolean match(QName qname, Node node)
790 String ln1 = qname.getLocalPart();
791 String ln2 = node.getLocalName();
793 return ln1.equals(node.getNodeName());
796 String uri1 = qname.getNamespaceURI();
797 String uri2 = node.getNamespaceURI();
798 return (uri1.equals(uri2) && ln1.equals(ln2));