001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.xml;
014
015import javax.xml.stream.*;
016import javax.xml.stream.util.*;
017
018import org.apache.juneau.*;
019import org.apache.juneau.parser.*;
020
021/**
022 * Parses text generated by the {@link XmlSerializer} class back into a POJO model.
023 *
024 * <h5 class='topic'>Media types</h5>
025 *
026 * Handles <code>Content-Type</code> types:  <code><b>text/xml</b></code>
027 *
028 * <h5 class='topic'>Description</h5>
029 *
030 * See the {@link XmlSerializer} class for a description of Juneau-generated XML.
031 */
032public class XmlParser extends ReaderParser {
033
034   //-------------------------------------------------------------------------------------------------------------------
035   // Configurable properties
036   //-------------------------------------------------------------------------------------------------------------------
037
038   private static final String PREFIX = "XmlParser.";
039
040   /**
041    * Configuration property:  XML event allocator.
042    *
043    * <h5 class='section'>Property:</h5>
044    * <ul>
045    *    <li><b>Name:</b>  <js>"XmlParser.eventAllocator.c"</js>
046    *    <li><b>Data type:</b>  <code>Class&lt;? <jk>extends</jk> {@link XMLEventAllocator}&gt;</code>
047    *    <li><b>Default:</b>  <jk>null</jk>
048    *    <li><b>Session property:</b>  <jk>false</jk>
049    *    <li><b>Methods:</b>
050    *       <ul>
051    *          <li class='jm'>{@link XmlParserBuilder#eventAllocator(XMLEventAllocator)}
052    *       </ul>
053    * </ul>
054    *
055    * <h5 class='section'>Description:</h5>
056    * <p>
057    * Associates an {@link XMLEventAllocator} with this parser.
058    */
059   public static final String XML_eventAllocator = PREFIX + "eventAllocator.c";
060
061   /**
062    * Configuration property:  Preserve root element during generalized parsing.
063    *
064    * <h5 class='section'>Property:</h5>
065    * <ul>
066    *    <li><b>Name:</b>  <js>"XmlParser.preserveRootElement.b"</js>
067    *    <li><b>Data type:</b>  <code>Boolean</code>
068    *    <li><b>Default:</b>  <jk>false</jk>
069    *    <li><b>Session property:</b>  <jk>false</jk>
070    *    <li><b>Methods:</b>
071    *       <ul>
072    *          <li class='jm'>{@link XmlParserBuilder#preserveRootElement(boolean)}
073    *          <li class='jm'>{@link XmlParserBuilder#preserveRootElement()}
074    *       </ul>
075    * </ul>
076    *
077    * <h5 class='section'>Description:</h5>
078    * <p>
079    * If <jk>true</jk>, when parsing into a generic {@link ObjectMap}, the map will contain a single entry whose key
080    * is the root element name.
081    *
082    * <h5 class='section'>Example:</h5>
083    * <p class='bcode w800'>
084    *    <jc>// Parser with preserve-root-element.</jc>
085    *    ReaderParser p1 = XmlParser
086    *       .<jsm>create</jsm>()
087    *       .preserveRootElement(<jk>true</jk>)
088    *       .build();
089    *
090    *    <jc>// Parser without preserve-root-element (the default behavior).</jc>
091    *    ReaderParser p2 = XmlParser
092    *       .<jsm>create</jsm>()
093    *       .preserveRootElement(<jk>false</jk>)
094    *       .build();
095    *
096    *    String xml = <js>"&lt;root&gt;&lt;a&gt;foobar&lt;/a&gt;&lt;/root&gt;"</js>;
097    *
098    *    <jc>// Produces:  "{ root: { a:'foobar' }}"</jc>
099    *    ObjectMap m1 = p1.parse(xml, ObjectMap.<jk>class</jk>);
100    *
101    *    <jc>// Produces:  "{ a:'foobar' }"</jc>
102    *    ObjectMap m2 = p2.parse(xml, ObjectMap.<jk>class)</jk>;
103    * </p>
104    */
105   public static final String XML_preserveRootElement = PREFIX + "preserveRootElement.b";
106
107   /**
108    * Configuration property:  XML reporter.
109    *
110    * <h5 class='section'>Property:</h5>
111    * <ul>
112    *    <li><b>Name:</b>  <js>"XmlParser.reporter.c"</js>
113    *    <li><b>Data type:</b>  <code>Class&lt;? <jk>extends</jk> {@link XMLReporter}&gt;</code>
114    *    <li><b>Default:</b>  <jk>null</jk>
115    *    <li><b>Session property:</b>  <jk>false</jk>
116    *    <li><b>Methods:</b>
117    *       <ul>
118    *          <li class='jm'>{@link XmlParserBuilder#reporter(XMLReporter)}
119    *       </ul>
120    * </ul>
121    *
122    * <h5 class='section'>Description:</h5>
123    * <p>
124    * Associates an {@link XMLReporter} with this parser.
125    *
126    * <h5 class='section'>Notes:</h5>
127    * <ul class='spaced-list'>
128    *    <li>
129    *       Reporters are not copied to new parsers during a clone.
130    * </ul>
131    */
132   public static final String XML_reporter = PREFIX + "reporter.c";
133
134   /**
135    * Configuration property:  XML resolver.
136    *
137    * <h5 class='section'>Property:</h5>
138    * <ul>
139    *    <li><b>Name:</b>  <js>"XmlParser.resolver.c"</js>
140    *    <li><b>Data type:</b>  <code>Class&lt;? <jk>extends</jk> {@link XMLResolver}&gt;</code>
141    *    <li><b>Default:</b>  <jk>null</jk>
142    *    <li><b>Session property:</b>  <jk>false</jk>
143    *    <li><b>Methods:</b>
144    *       <ul>
145    *          <li class='jm'>{@link XmlParserBuilder#resolver(XMLResolver)}
146    *       </ul>
147    * </ul>
148    *
149    * <h5 class='section'>Description:</h5>
150    * <p>
151    * Associates an {@link XMLResolver} with this parser.
152    */
153   public static final String XML_resolver = PREFIX + "resolver.c";
154
155   /**
156    * Configuration property:  Enable validation.
157    *
158    * <h5 class='section'>Property:</h5>
159    * <ul>
160    *    <li><b>Name:</b>  <js>"XmlParser.validating.b"</js>
161    *    <li><b>Data type:</b>  <code>Boolean</code>
162    *    <li><b>Default:</b>  <jk>false</jk>
163    *    <li><b>Session property:</b>  <jk>false</jk>
164    *    <li><b>Methods:</b>
165    *       <ul>
166    *          <li class='jm'>{@link XmlParserBuilder#validating(boolean)}
167    *          <li class='jm'>{@link XmlParserBuilder#validating()}
168    *       </ul>
169    * </ul>
170    *
171    * <h5 class='section'>Description:</h5>
172    * <p>
173    * If <jk>true</jk>, XML document will be validated.
174    *
175    * <p>
176    * See {@link XMLInputFactory#IS_VALIDATING} for more info.
177    */
178   public static final String XML_validating = PREFIX + "validating.b";
179
180
181   //-------------------------------------------------------------------------------------------------------------------
182   // Predefined instances
183   //-------------------------------------------------------------------------------------------------------------------
184
185   /** Default parser, all default settings.*/
186   public static final XmlParser DEFAULT = new XmlParser(PropertyStore.DEFAULT);
187
188
189   //-------------------------------------------------------------------------------------------------------------------
190   // Instance
191   //-------------------------------------------------------------------------------------------------------------------
192
193   private final boolean
194      validating,
195      preserveRootElement;
196   private final XMLReporter reporter;
197   private final XMLResolver resolver;
198   private final XMLEventAllocator eventAllocator;
199
200   /**
201    * Constructor.
202    *
203    * @param ps
204    *    The property store containing all the settings for this object.
205    */
206   public XmlParser(PropertyStore ps) {
207      this(ps, "text/xml", "application/xml");
208   }
209
210   /**
211    * Constructor.
212    *
213    * @param ps
214    *    The property store containing all the settings for this object.
215    * @param consumes
216    *    The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*&#8203;/json"</js>).
217    */
218   public XmlParser(PropertyStore ps, String...consumes) {
219      super(ps, consumes);
220      validating = getBooleanProperty(XML_validating, false);
221      preserveRootElement = getBooleanProperty(XML_preserveRootElement, false);
222      reporter = getInstanceProperty(XML_reporter, XMLReporter.class, null);
223      resolver = getInstanceProperty(XML_resolver, XMLResolver.class, null);
224      eventAllocator = getInstanceProperty(XML_eventAllocator, XMLEventAllocator.class, null);
225   }
226
227   @Override /* Context */
228   public XmlParserBuilder builder() {
229      return new XmlParserBuilder(getPropertyStore());
230   }
231
232   /**
233    * Instantiates a new clean-slate {@link XmlParserBuilder} object.
234    *
235    * <p>
236    * This is equivalent to simply calling <code><jk>new</jk> XmlParserBuilder()</code>.
237    *
238    * <p>
239    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
240    * the settings of the object called on.
241    *
242    * @return A new {@link XmlParserBuilder} object.
243    */
244   public static XmlParserBuilder create() {
245      return new XmlParserBuilder();
246   }
247
248   @Override /* Parser */
249   public ReaderParserSession createSession(ParserSessionArgs args) {
250      return new XmlParserSession(this, args);
251   }
252
253   //-----------------------------------------------------------------------------------------------------------------
254   // Properties
255   //-----------------------------------------------------------------------------------------------------------------
256
257   /**
258    * Configuration property:  Enable validation.
259    *
260    * @see #XML_validating
261    * @return
262    *    <jk>true</jk> if XML document will be validated.
263    */
264   protected final boolean isValidating() {
265      return validating;
266   }
267
268   /**
269    * Configuration property:  Preserve root element during generalized parsing.
270    *
271    * @see #XML_preserveRootElement
272    * @return
273    *    <jk>true</jk> if when parsing into a generic {@link ObjectMap}, the map will contain a single entry whose key
274    *    is the root element name.
275    */
276   protected final boolean isPreserveRootElement() {
277      return preserveRootElement;
278   }
279
280   /**
281    * Configuration property:  XML reporter.
282    *
283    * @see #XML_reporter
284    * @return
285    *    The {@link XMLReporter} associated with this parser, or <jk>null</jk> if there isn't one.
286    */
287   protected final XMLReporter getReporter() {
288      return reporter;
289   }
290
291   /**
292    * Configuration property:  XML resolver.
293    *
294    * @see #XML_resolver
295    * @return
296    *    The {@link XMLResolver} associated with this parser, or <jk>null</jk> if there isn't one.
297    */
298   protected final XMLResolver getResolver() {
299      return resolver;
300   }
301
302   /**
303    * Configuration property:  XML event allocator.
304    *
305    * @see #XML_eventAllocator
306    * @return
307    *    The {@link XMLEventAllocator} associated with this parser, or <jk>null</jk> if there isn't one.
308    */
309   protected final XMLEventAllocator getEventAllocator() {
310      return eventAllocator;
311   }
312
313   @Override /* Context */
314   public ObjectMap asMap() {
315      return super.asMap()
316         .append("XmlParser", new ObjectMap()
317            .append("validating", validating)
318            .append("preserveRootElement", preserveRootElement)
319            .append("reporter", reporter)
320            .append("resolver", resolver)
321            .append("eventAllocator", eventAllocator)
322         );
323   }
324}