View Javadoc

1   /*
2    * Copyright 2010-2013 smartics, Kronseder & Reiner GmbH
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 de.smartics.xml;
17  
18  import java.io.StringReader;
19  import java.util.Iterator;
20  import java.util.Stack;
21  
22  import javax.xml.stream.XMLEventReader;
23  import javax.xml.stream.XMLInputFactory;
24  import javax.xml.stream.XMLStreamException;
25  import javax.xml.stream.events.Attribute;
26  import javax.xml.stream.events.Characters;
27  import javax.xml.stream.events.StartElement;
28  import javax.xml.stream.events.XMLEvent;
29  
30  import org.apache.commons.lang.StringUtils;
31  import org.w3c.dom.Document;
32  import org.w3c.dom.Element;
33  import org.w3c.dom.Text;
34  
35  /**
36   * Copies XML information read from one source to another.
37   *
38   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
39   * @version $Revision:591 $
40   */
41  public class DomCopyReader
42  {
43    // ********************************* Fields *********************************
44  
45    // --- constants ------------------------------------------------------------
46  
47    /**
48     * The generic identifier (GI) of the artificial element that is added if the
49     * fragment is not enclosed in an element.
50     * <p>
51     * The value of this constant is {@value}.
52     */
53    private static final String ARTIFICIAL_GI = "root";
54  
55    /**
56     * The artificial start tag with the {@link #ARTIFICIAL_GI}.
57     * <p>
58     * The value of this constant is {@value}.
59     */
60    private static final String ARTIFICIAL_START_TAG = '<' + ARTIFICIAL_GI + '>';
61  
62    /**
63     * The artificial end tag with the {@link #ARTIFICIAL_GI}.
64     * <p>
65     * The value of this constant is {@value}.
66     */
67    private static final String ARTIFICIAL_END_TAG = "</" + ARTIFICIAL_GI + '>';
68  
69    // --- members --------------------------------------------------------------
70  
71    /**
72     * The document to create elements for.
73     */
74    private final Document document;
75  
76    /**
77     * The elements to write to.
78     */
79    private final Stack<Element> elements = new Stack<Element>();
80  
81    // ****************************** Initializer *******************************
82  
83    // ****************************** Constructors ******************************
84  
85    /**
86     * Default constructor.
87     *
88     * @param document the document to create elements for.
89     * @param element the element to write to.
90     */
91    public DomCopyReader(final Document document, final Element element)
92    {
93      this.document = document;
94      elements.push(element);
95    }
96  
97    // ****************************** Inner Classes *****************************
98  
99    // ********************************* Methods ********************************
100 
101   // --- init -----------------------------------------------------------------
102 
103   // --- get&set --------------------------------------------------------------
104 
105   // --- business -------------------------------------------------------------
106 
107   /**
108    * Copies the XML fragment to the writer.
109    *
110    * @param xmlFragment the XML fragment to read and write to the writer.
111    * @throws XMLStreamException on any problem reading from the fragment.
112    */
113   public void copy(final String xmlFragment) throws XMLStreamException
114   {
115     final XMLInputFactory factory = XMLInputFactory.newInstance();
116     final boolean isFragment = isFragment(xmlFragment);
117     final XMLEventReader eventReader;
118     if (isFragment)
119     {
120       eventReader =
121           factory.createXMLEventReader(new StringReader(ARTIFICIAL_START_TAG
122                                                         + xmlFragment
123                                                         + ARTIFICIAL_END_TAG));
124       boolean run = true;
125       while (run && eventReader.hasNext())
126       {
127         final XMLEvent event = eventReader.nextEvent();
128         if (event.getEventType() == XMLEvent.START_ELEMENT)
129         {
130           run = false;
131         }
132       }
133     }
134     else
135     {
136       eventReader = factory.createXMLEventReader(new StringReader(xmlFragment));
137     }
138     parse(eventReader);
139   }
140 
141   /**
142    * Checks if the given XML fragment is not enclosed within an element.
143    *
144    * @param xmlFragment the XML fragment to check.
145    * @return <code>true</code> if the fragment is not enclosed in an element,
146    *         <code>false</code> otherwise.
147    */
148   private static boolean isFragment(final String xmlFragment)
149   {
150     return xmlFragment.charAt(0) != '<';
151   }
152 
153   /**
154    * Parses the events from the given event reader.
155    *
156    * @param eventReader the event reader to read the events from.
157    * @throws XMLStreamException on any problem reading the events.
158    */
159   private void parse(final XMLEventReader eventReader)
160     throws XMLStreamException
161   {
162     int level = 0; // NOPMD
163     while (eventReader.hasNext())
164     {
165       final XMLEvent event = eventReader.nextEvent();
166       switch (event.getEventType())
167       {
168         case XMLEvent.START_ELEMENT:
169           level++; // NOPMD
170           handleStartElement(event);
171           break;
172         case XMLEvent.END_ELEMENT:
173           level--;
174           if (level >= 0)
175           {
176             elements.pop();
177           }
178           break;
179         case XMLEvent.CHARACTERS:
180           final Characters chars = event.asCharacters();
181           final String content = chars.getData();
182           final Text text = document.createTextNode(content);
183           elements.peek().appendChild(text);
184         default:
185           // Just skip...
186           break;
187       }
188     }
189   }
190 
191   /**
192    * Handles the start element with all of its attributes.
193    *
194    * @param event the event required to be a {@link StartElement}.
195    * @throws XMLStreamException on any parsing problem.
196    */
197   @SuppressWarnings("unchecked")
198   private void handleStartElement(final XMLEvent event)
199     throws XMLStreamException
200   {
201     final StartElement element = event.asStartElement();
202     final String gi = element.getName().getLocalPart();
203     final String prefix = element.getName().getPrefix();
204 
205     final Element currentElement;
206     if (StringUtils.isNotBlank(prefix))
207     {
208       final String uri = element.getNamespaceURI(prefix);
209       currentElement = document.createElementNS(uri, prefix + ':' + gi);
210     }
211     else
212     {
213       currentElement = document.createElement(gi);
214     }
215 
216     final Element parentElement = elements.peek();
217     parentElement.appendChild(currentElement);
218     elements.push(currentElement);
219 
220     for (final Iterator<Attribute> i = element.getAttributes(); i.hasNext();)
221     {
222       final Attribute attribute = i.next();
223       currentElement.setAttribute(attribute.getName().toString(),
224           attribute.getValue());
225     }
226   }
227 
228   // --- object basics --------------------------------------------------------
229 
230 }