View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.internal.xml;
20  
21  import javax.xml.stream.XMLStreamException;
22  import javax.xml.stream.XMLStreamReader;
23  
24  import java.io.InputStream;
25  import java.io.Reader;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import com.ctc.wstx.stax.WstxInputFactory;
32  import org.apache.maven.api.xml.XmlNode;
33  
34  /**
35   * All methods in this class attempt to fully parse the XML.
36   * The caller is responsible for closing {@code InputStream} and {@code Reader} arguments.
37   */
38  public class XmlNodeStaxBuilder {
39      private static final boolean DEFAULT_TRIM = true;
40  
41      public static XmlNodeImpl build(InputStream stream, InputLocationBuilderStax locationBuilder)
42              throws XMLStreamException {
43          XMLStreamReader parser = WstxInputFactory.newFactory().createXMLStreamReader(stream);
44          return build(parser, DEFAULT_TRIM, locationBuilder);
45      }
46  
47      public static XmlNodeImpl build(Reader reader, InputLocationBuilderStax locationBuilder) throws XMLStreamException {
48          XMLStreamReader parser = WstxInputFactory.newFactory().createXMLStreamReader(reader);
49          return build(parser, DEFAULT_TRIM, locationBuilder);
50      }
51  
52      public static XmlNodeImpl build(XMLStreamReader parser) throws XMLStreamException {
53          return build(parser, DEFAULT_TRIM, null);
54      }
55  
56      public static XmlNodeImpl build(XMLStreamReader parser, InputLocationBuilderStax locationBuilder)
57              throws XMLStreamException {
58          return build(parser, DEFAULT_TRIM, locationBuilder);
59      }
60  
61      public static XmlNodeImpl build(XMLStreamReader parser, boolean trim, InputLocationBuilderStax locationBuilder)
62              throws XMLStreamException {
63          boolean spacePreserve = false;
64          String lPrefix = null;
65          String lNamespaceUri = null;
66          String lName = null;
67          String lValue = null;
68          Object location = null;
69          Map<String, String> attrs = null;
70          List<XmlNode> children = null;
71          int eventType = parser.getEventType();
72          int lastStartTag = -1;
73          while (eventType != XMLStreamReader.END_DOCUMENT) {
74              if (eventType == XMLStreamReader.START_ELEMENT) {
75                  lastStartTag = parser.getLocation().getLineNumber() * 1000
76                          + parser.getLocation().getColumnNumber();
77                  if (lName == null) {
78                      int namespacesSize = parser.getNamespaceCount();
79                      lPrefix = parser.getPrefix();
80                      lNamespaceUri = parser.getNamespaceURI();
81                      lName = parser.getLocalName();
82                      location = locationBuilder != null ? locationBuilder.toInputLocation(parser) : null;
83                      int attributesSize = parser.getAttributeCount();
84                      if (attributesSize > 0 || namespacesSize > 0) {
85                          attrs = new HashMap<>();
86                          for (int i = 0; i < namespacesSize; i++) {
87                              String nsPrefix = parser.getNamespacePrefix(i);
88                              String nsUri = parser.getNamespaceURI(i);
89                              attrs.put(nsPrefix != null && !nsPrefix.isEmpty() ? "xmlns:" + nsPrefix : "xmlns", nsUri);
90                          }
91                          for (int i = 0; i < attributesSize; i++) {
92                              String aName = parser.getAttributeLocalName(i);
93                              String aValue = parser.getAttributeValue(i);
94                              String aPrefix = parser.getAttributePrefix(i);
95                              if (aPrefix != null && !aPrefix.isEmpty()) {
96                                  aName = aPrefix + ":" + aName;
97                              }
98                              attrs.put(aName, aValue);
99                              spacePreserve = spacePreserve || ("xml:space".equals(aName) && "preserve".equals(aValue));
100                         }
101                     }
102                 } else {
103                     if (children == null) {
104                         children = new ArrayList<>();
105                     }
106                     XmlNode child = build(parser, trim, locationBuilder);
107                     children.add(child);
108                 }
109             } else if (eventType == XMLStreamReader.CHARACTERS || eventType == XMLStreamReader.CDATA) {
110                 String text = parser.getText();
111                 lValue = lValue != null ? lValue + text : text;
112             } else if (eventType == XMLStreamReader.END_ELEMENT) {
113                 boolean emptyTag = lastStartTag
114                         == parser.getLocation().getLineNumber() * 1000
115                                 + parser.getLocation().getColumnNumber();
116                 if (lValue != null && trim && !spacePreserve) {
117                     lValue = lValue.trim();
118                 }
119                 return new XmlNodeImpl(
120                         lPrefix,
121                         lNamespaceUri,
122                         lName,
123                         children == null ? (lValue != null ? lValue : emptyTag ? null : "") : null,
124                         attrs,
125                         children,
126                         location);
127             }
128             eventType = parser.next();
129         }
130         throw new IllegalStateException("End of document found before returning to 0 depth");
131     }
132 
133     /**
134      * Input location builder interface, to be implemented to choose how to store data.
135      *
136      * @since 3.2.0
137      */
138     public interface InputLocationBuilderStax {
139         Object toInputLocation(XMLStreamReader parser);
140     }
141 }