View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2   // Copyright (c) 2018-2020 Saxonica Limited
3   // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4   // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
5   // This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
6   ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
7   
8   package net.sf.saxon.option.jdom2;
9   
10  import net.sf.saxon.event.PipelineConfiguration;
11  import net.sf.saxon.event.ReceiverOption;
12  import net.sf.saxon.lib.NamespaceConstant;
13  import net.sf.saxon.om.*;
14  import net.sf.saxon.s9api.Location;
15  import net.sf.saxon.trans.XPathException;
16  import net.sf.saxon.tree.util.FastStringBuffer;
17  import net.sf.saxon.type.SchemaType;
18  import net.sf.saxon.value.Whitespace;
19  import org.jdom2.*;
20  
21  import java.util.Stack;
22  
23  /**
24   * JDOMWriter is a Receiver that constructs a JDOM2 document from the stream of events
25   */
26  
27  public class JDOM2Writer extends net.sf.saxon.event.Builder {
28  
29      private Document document;
30      private Stack<Parent> ancestors = new Stack<>();
31      private boolean implicitDocumentNode = false;
32      private FastStringBuffer textBuffer = new FastStringBuffer(FastStringBuffer.C256);
33      private Stack<NamespaceMap> nsStack = new Stack<>();
34  
35      /**
36       * Create a JDOM2Writer using the default node factory
37       *
38       * @param pipe information about the Saxon pipeline
39       */
40  
41      public JDOM2Writer(PipelineConfiguration pipe) {
42          super(pipe);
43          nsStack.push(NamespaceMap.emptyMap());
44      }
45  
46      /**
47       * Notify an unparsed entity URI.
48       *
49       * @param name     The name of the unparsed entity
50       * @param systemID The system identifier of the unparsed entity
51       * @param publicID The public identifier of the unparsed entity
52       */
53  
54      @Override
55      public void setUnparsedEntity(String name, String systemID, String publicID) throws XPathException {
56          // no-op
57      }
58  
59      /**
60       * Start of the document.
61       */
62  
63      @Override
64      public void open() {
65      }
66  
67      /**
68       * End of the document.
69       */
70  
71      @Override
72      public void close() {
73      }
74  
75      /**
76       * Start of a document node.
77       * @param properties
78       */
79  
80      @Override
81      public void startDocument(int properties) throws XPathException {
82          document = new Document();
83          document.setBaseURI(systemId);
84          ancestors.push(document);
85          textBuffer.setLength(0);
86      }
87  
88      /**
89       * Notify the end of a document node
90       */
91  
92      @Override
93      public void endDocument() throws XPathException {
94          ancestors.pop();
95      }
96  
97      /**
98       * Start of an element.
99       */
100 
101     @Override
102     public void startElement(NodeName elemName, SchemaType type,
103                              AttributeMap attributes, NamespaceMap namespaces,
104                              Location location, int properties) throws XPathException {
105         flush();
106         String local = elemName.getLocalPart();
107         String uri = elemName.getURI();
108         String prefix = elemName.getPrefix();
109         Element element;
110         if (ancestors.isEmpty()) {
111             startDocument(ReceiverOption.NONE);
112             implicitDocumentNode = true;
113         }
114         element = new Element(local, prefix, uri);
115         if (ancestors.size() == 1) {
116             document.setRootElement(element);
117         } else {
118             ancestors.peek().addContent(element);
119         }
120         ancestors.push(element);
121 
122         NamespaceMap parentMap = nsStack.peek();
123         if (namespaces != parentMap) {
124             NamespaceBinding[] declarations = namespaces.getDifferences(parentMap, false);
125             for (NamespaceBinding ns : declarations) {
126                 namespace(element, ns);
127             }
128         }
129         nsStack.push(namespaces);
130 
131         for (AttributeInfo att : attributes) {
132             NodeName nameCode = att.getNodeName();
133             String attlocal = nameCode.getLocalPart();
134             String atturi = nameCode.getURI();
135             String attprefix = nameCode.getPrefix();
136             String value = att.getValue();
137             Namespace ns = attprefix.isEmpty() ?
138                     Namespace.getNamespace(atturi) :
139                     Namespace.getNamespace(attprefix, atturi);
140             boolean isXmlId = uri.equals(NamespaceConstant.XML) && attlocal.equals("id");
141             if (isXmlId) {
142                 value = Whitespace.trim(value);
143             }
144             Attribute attr = new Attribute(attlocal, value, ns);
145             if (isXmlId) {
146                 attr.setAttributeType(AttributeType.ID);
147             }
148             element.getAttributes().add(attr);
149         }
150     }
151 
152     private void namespace(Element element, NamespaceBinding namespaceBinding) throws XPathException {
153         String prefix = namespaceBinding.getPrefix();
154         String uri = namespaceBinding.getURI();
155         if (uri.isEmpty() && prefix.length() != 0) {
156             // ignore XML 1.1 namespace undeclarations because JDOM can't handle them
157             return;
158         }
159         Namespace ns = prefix.isEmpty() ?
160                 Namespace.getNamespace(uri) :
161                 Namespace.getNamespace(prefix, uri);
162         element.addNamespaceDeclaration(ns);
163     }
164 
165     /**
166      * End of an element.
167      */
168 
169     @Override
170     public void endElement() throws XPathException {
171         flush();
172         ancestors.pop();
173         nsStack.pop();
174         Object parent = ancestors.peek();
175         if (parent == document && implicitDocumentNode) {
176             endDocument();
177         }
178     }
179 
180     /**
181      * Character data.
182      */
183 
184     @Override
185     public void characters(CharSequence chars, Location locationId, int properties) throws XPathException {
186         textBuffer.cat(chars);
187     }
188 
189     private void flush() {
190         if (textBuffer.length() != 0) {
191             Text text = new Text(textBuffer.toString());
192             ancestors.peek().addContent(text);
193             textBuffer.setLength(0);
194         }
195     }
196 
197 
198     /**
199      * Handle a processing instruction.
200      */
201 
202     @Override
203     public void processingInstruction(String target, CharSequence data, Location locationId, int properties)
204             throws XPathException {
205         flush();
206         ProcessingInstruction pi = new ProcessingInstruction(target, data.toString());
207         ancestors.peek().addContent(pi);
208     }
209 
210     /**
211      * Handle a comment.
212      */
213 
214     @Override
215     public void comment(CharSequence chars, Location locationId, int properties) throws XPathException {
216         flush();
217         Comment comment = new Comment(chars.toString());
218         ancestors.peek().addContent(comment);
219     }
220 
221     /**
222      * Ask whether this Receiver (or the downstream pipeline) makes any use of the type annotations
223      * supplied on element and attribute events
224      *
225      * @return true if the Receiver makes any use of this information. If false, the caller
226      *         may supply untyped nodes instead of supplying the type annotation
227      */
228 
229     @Override
230     public boolean usesTypeAnnotations() {
231         return false;
232     }
233 
234     /**
235      * Get the constructed document node
236      *
237      * @return the document node of the constructed XOM tree
238      */
239 
240     public Document getDocument() {
241         return document;
242     }
243 
244     /**
245      * Get the current root node.
246      *
247      * @return a Saxon wrapper around the constructed XOM document node
248      */
249 
250     @Override
251     public NodeInfo getCurrentRoot() {
252         return new JDOM2DocumentWrapper(document, config).getRootNode();
253     }
254 }
255 
256 
257 // Original Code is Copyright (c) 2009-2020 Saxonica Limited