1/*
2 * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.xml.internal.txw2.output;
27
28import org.w3c.dom.Document;
29import org.w3c.dom.Element;
30import org.w3c.dom.Node;
31import org.w3c.dom.Text;
32import org.xml.sax.Attributes;
33import org.xml.sax.ContentHandler;
34import org.xml.sax.Locator;
35import org.xml.sax.SAXException;
36import org.xml.sax.ext.LexicalHandler;
37
38import javax.xml.parsers.DocumentBuilder;
39import javax.xml.parsers.DocumentBuilderFactory;
40import javax.xml.parsers.ParserConfigurationException;
41import javax.xml.transform.dom.DOMResult;
42import java.util.ArrayList;
43import java.util.Stack;
44
45import com.sun.xml.internal.txw2.TxwException;
46
47/**
48 * {@link XmlSerializer} for {@link javax.xml.transform.dom.DOMResult} and {@link org.w3c.dom.Node}.
49 *
50 * @author Ryan.Shoemaker@Sun.COM
51 */
52public class DomSerializer implements XmlSerializer {
53
54    // delegate to SaxSerializer
55    private final SaxSerializer serializer;
56
57    public DomSerializer(Node node) {
58        Dom2SaxAdapter adapter = new Dom2SaxAdapter(node);
59        serializer = new SaxSerializer(adapter,adapter,false);
60    }
61
62    public DomSerializer(DOMResult domResult) {
63        Node node = domResult.getNode();
64
65        if (node == null) {
66            try {
67                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
68                dbf.setNamespaceAware(true);
69                DocumentBuilder db = dbf.newDocumentBuilder();
70                Document doc = db.newDocument();
71                domResult.setNode(doc);
72                serializer = new SaxSerializer(new Dom2SaxAdapter(doc),null,false);
73            } catch (ParserConfigurationException pce) {
74                throw new TxwException(pce);
75            }
76        } else {
77            serializer = new SaxSerializer(new Dom2SaxAdapter(node),null,false);
78        }
79    }
80
81    // XmlSerializer api's - delegate to SaxSerializer
82    public void startDocument() {
83        serializer.startDocument();
84    }
85
86    public void beginStartTag(String uri, String localName, String prefix) {
87        serializer.beginStartTag(uri, localName, prefix);
88    }
89
90    public void writeAttribute(String uri, String localName, String prefix, StringBuilder value) {
91        serializer.writeAttribute(uri, localName, prefix, value);
92    }
93
94    public void writeXmlns(String prefix, String uri) {
95        serializer.writeXmlns(prefix, uri);
96    }
97
98    public void endStartTag(String uri, String localName, String prefix) {
99        serializer.endStartTag(uri, localName, prefix);
100    }
101
102    public void endTag() {
103        serializer.endTag();
104    }
105
106    public void text(StringBuilder text) {
107        serializer.text(text);
108    }
109
110    public void cdata(StringBuilder text) {
111        serializer.cdata(text);
112    }
113
114    public void comment(StringBuilder comment) {
115        serializer.comment(comment);
116    }
117
118    public void endDocument() {
119        serializer.endDocument();
120    }
121
122    public void flush() {
123        // no flushing
124    }
125}
126
127
128
129
130/**
131 * Builds a DOM tree from SAX2 events.
132 *
133 * @author  Vivek Pandey
134 */
135class Dom2SaxAdapter implements ContentHandler, LexicalHandler {
136
137    private final Node _node;
138    private final Stack _nodeStk = new Stack();
139    private boolean inCDATA;
140
141    public final Element getCurrentElement() {
142        return (Element) _nodeStk.peek();
143    }
144
145    /**
146     * Document object that owns the specified node.
147     */
148    private final Document _document;
149
150    /**
151     * @param   node
152     *      Nodes will be created and added under this object.
153     */
154    public Dom2SaxAdapter(Node node)
155    {
156        _node = node;
157        _nodeStk.push(_node);
158
159        if( node instanceof Document )
160            this._document = (Document)node;
161        else
162            this._document = node.getOwnerDocument();
163    }
164
165    /**
166     * Creates a fresh empty DOM document and adds nodes under this document.
167     */
168    public Dom2SaxAdapter() throws ParserConfigurationException {
169        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
170        factory.setNamespaceAware(true);
171        factory.setValidating(false);
172
173        _document = factory.newDocumentBuilder().newDocument();
174        _node = _document;
175        _nodeStk.push( _document );
176    }
177
178    public Node getDOM() {
179        return _node;
180    }
181
182    public void startDocument() {
183    }
184
185    public void endDocument(){
186    }
187
188    public void startElement(String namespace, String localName, String qName, Attributes attrs){
189
190        // some broken DOM implementatino (we confirmed it with SAXON)
191        // return null from this method.
192        Element element = _document.createElementNS(namespace, qName);
193
194        if( element==null ) {
195            // if so, report an user-friendly error message,
196            // rather than dying mysteriously with NPE.
197            throw new TxwException("Your DOM provider doesn't support the createElementNS method properly");
198        }
199
200        // process namespace bindings
201        for( int i=0; i<unprocessedNamespaces.size(); i+=2 ) {
202            String prefix = (String)unprocessedNamespaces.get(i+0);
203            String uri = (String)unprocessedNamespaces.get(i+1);
204
205            String qname;
206            if( "".equals(prefix) || prefix==null )
207                qname = "xmlns";
208            else
209                qname = "xmlns:"+prefix;
210
211            // older version of Xerces (I confirmed that the bug is gone with Xerces 2.4.0)
212            // have a problem of re-setting the same namespace attribute twice.
213            // work around this bug removing it first.
214            if( element.hasAttributeNS("http://www.w3.org/2000/xmlns/",qname) ) {
215                // further workaround for an old Crimson bug where the removeAttribtueNS
216                // method throws NPE when the element doesn't have any attribute.
217                // to be on the safe side, check the existence of attributes before
218                // attempting to remove it.
219                // for details about this bug, see org.apache.crimson.tree.ElementNode2
220                // line 540 or the following message:
221                // https://jaxb.dev.java.net/servlets/ReadMsg?list=users&msgNo=2767
222                element.removeAttributeNS("http://www.w3.org/2000/xmlns/",qname);
223            }
224            // workaround until here
225
226            element.setAttributeNS("http://www.w3.org/2000/xmlns/",qname, uri);
227        }
228        unprocessedNamespaces.clear();
229
230
231        int length = attrs.getLength();
232        for(int i=0;i<length;i++){
233            String namespaceuri = attrs.getURI(i);
234            String value = attrs.getValue(i);
235            String qname = attrs.getQName(i);
236            element.setAttributeNS(namespaceuri, qname, value);
237        }
238        // append this new node onto current stack node
239        getParent().appendChild(element);
240        // push this node onto stack
241        _nodeStk.push(element);
242    }
243
244    private final Node getParent() {
245        return (Node) _nodeStk.peek();
246    }
247
248    public void endElement(String namespace, String localName, String qName){
249        _nodeStk.pop();
250    }
251
252
253    public void characters(char[] ch, int start, int length) {
254        Node text;
255        if(inCDATA)
256            text = _document.createCDATASection(new String(ch, start, length));
257        else
258            text = _document.createTextNode(new String(ch, start, length));
259        getParent().appendChild(text);
260    }
261
262    public void comment(char ch[], int start, int length) throws SAXException {
263        getParent().appendChild(_document.createComment(new String(ch,start,length)));
264    }
265
266
267
268    public void ignorableWhitespace(char[] ch, int start, int length) {
269    }
270
271    public void processingInstruction(String target, String data) throws org.xml.sax.SAXException{
272        Node node = _document.createProcessingInstruction(target, data);
273        getParent().appendChild(node);
274    }
275
276    public void setDocumentLocator(Locator locator) {
277    }
278
279    public void skippedEntity(String name) {
280    }
281
282    private ArrayList unprocessedNamespaces = new ArrayList();
283
284    public void startPrefixMapping(String prefix, String uri) {
285        unprocessedNamespaces.add(prefix);
286        unprocessedNamespaces.add(uri);
287    }
288
289    public void endPrefixMapping(String prefix) {
290    }
291
292    public void startDTD(String name, String publicId, String systemId) throws SAXException {
293    }
294
295    public void endDTD() throws SAXException {
296    }
297
298    public void startEntity(String name) throws SAXException {
299    }
300
301    public void endEntity(String name) throws SAXException {
302    }
303
304    public void startCDATA() throws SAXException {
305        inCDATA = true;
306    }
307
308    public void endCDATA() throws SAXException {
309        inCDATA = false;
310    }
311}
312