• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
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 
17 package android.util;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.SystemProperties;
22 import android.system.ErrnoException;
23 import android.system.Os;
24 
25 import com.android.internal.util.BinaryXmlPullParser;
26 import com.android.internal.util.BinaryXmlSerializer;
27 import com.android.internal.util.FastXmlSerializer;
28 import com.android.internal.util.XmlUtils;
29 
30 import libcore.util.XmlObjectFactory;
31 
32 import org.xml.sax.ContentHandler;
33 import org.xml.sax.InputSource;
34 import org.xml.sax.SAXException;
35 import org.xml.sax.XMLReader;
36 import org.xmlpull.v1.XmlPullParser;
37 import org.xmlpull.v1.XmlPullParserException;
38 import org.xmlpull.v1.XmlSerializer;
39 
40 import java.io.BufferedInputStream;
41 import java.io.FileInputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.OutputStream;
45 import java.io.Reader;
46 import java.io.StringReader;
47 import java.io.UnsupportedEncodingException;
48 import java.nio.charset.StandardCharsets;
49 import java.util.Arrays;
50 
51 /**
52  * XML utility methods.
53  */
54 public class Xml {
Xml()55     private Xml() {}
56 
57     /**
58      * {@link org.xmlpull.v1.XmlPullParser} "relaxed" feature name.
59      *
60      * @see <a href="http://xmlpull.org/v1/doc/features.html#relaxed">
61      *  specification</a>
62      */
63     public static String FEATURE_RELAXED = "http://xmlpull.org/v1/doc/features.html#relaxed";
64 
65     /**
66      * Feature flag: when set, {@link #resolveSerializer(OutputStream)} will
67      * emit binary XML by default.
68      *
69      * @hide
70      */
71     public static final boolean ENABLE_BINARY_DEFAULT = SystemProperties
72             .getBoolean("persist.sys.binary_xml", true);
73 
74     /**
75      * Parses the given xml string and fires events on the given SAX handler.
76      */
parse(String xml, ContentHandler contentHandler)77     public static void parse(String xml, ContentHandler contentHandler)
78             throws SAXException {
79         try {
80             XMLReader reader = XmlObjectFactory.newXMLReader();
81             reader.setContentHandler(contentHandler);
82             reader.parse(new InputSource(new StringReader(xml)));
83         } catch (IOException e) {
84             throw new AssertionError(e);
85         }
86     }
87 
88     /**
89      * Parses xml from the given reader and fires events on the given SAX
90      * handler.
91      */
parse(Reader in, ContentHandler contentHandler)92     public static void parse(Reader in, ContentHandler contentHandler)
93             throws IOException, SAXException {
94         XMLReader reader = XmlObjectFactory.newXMLReader();
95         reader.setContentHandler(contentHandler);
96         reader.parse(new InputSource(in));
97     }
98 
99     /**
100      * Parses xml from the given input stream and fires events on the given SAX
101      * handler.
102      */
parse(InputStream in, Encoding encoding, ContentHandler contentHandler)103     public static void parse(InputStream in, Encoding encoding,
104             ContentHandler contentHandler) throws IOException, SAXException {
105         XMLReader reader = XmlObjectFactory.newXMLReader();
106         reader.setContentHandler(contentHandler);
107         InputSource source = new InputSource(in);
108         source.setEncoding(encoding.expatName);
109         reader.parse(source);
110     }
111 
112     /**
113      * Returns a new pull parser with namespace support.
114      */
newPullParser()115     public static XmlPullParser newPullParser() {
116         try {
117             XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
118             parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
119             parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
120             return parser;
121         } catch (XmlPullParserException e) {
122             throw new AssertionError();
123         }
124     }
125 
126     /**
127      * Creates a new {@link TypedXmlPullParser} which is optimized for use
128      * inside the system, typically by supporting only a basic set of features.
129      * <p>
130      * In particular, the returned parser does not support namespaces, prefixes,
131      * properties, or options.
132      *
133      * @hide
134      */
135     @SuppressWarnings("AndroidFrameworkEfficientXml")
newFastPullParser()136     public static @NonNull TypedXmlPullParser newFastPullParser() {
137         return XmlUtils.makeTyped(newPullParser());
138     }
139 
140     /**
141      * Creates a new {@link XmlPullParser} that reads XML documents using a
142      * custom binary wire protocol which benchmarking has shown to be 8.5x
143      * faster than {@code Xml.newFastPullParser()} for a typical
144      * {@code packages.xml}.
145      *
146      * @hide
147      */
newBinaryPullParser()148     public static @NonNull TypedXmlPullParser newBinaryPullParser() {
149         return new BinaryXmlPullParser();
150     }
151 
152     /**
153      * Creates a new {@link XmlPullParser} which is optimized for use inside the
154      * system, typically by supporting only a basic set of features.
155      * <p>
156      * This returned instance may be configured to read using an efficient
157      * binary format instead of a human-readable text format, depending on
158      * device feature flags.
159      * <p>
160      * To ensure that both formats are detected and transparently handled
161      * correctly, you must shift to using both {@link #resolveSerializer} and
162      * {@link #resolvePullParser}.
163      *
164      * @hide
165      */
resolvePullParser(@onNull InputStream in)166     public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
167             throws IOException {
168         final byte[] magic = new byte[4];
169         if (in instanceof FileInputStream) {
170             try {
171                 Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
172             } catch (ErrnoException e) {
173                 throw e.rethrowAsIOException();
174             }
175         } else {
176             if (!in.markSupported()) {
177                 in = new BufferedInputStream(in);
178             }
179             in.mark(8);
180             in.read(magic);
181             in.reset();
182         }
183 
184         final TypedXmlPullParser xml;
185         if (Arrays.equals(magic, BinaryXmlSerializer.PROTOCOL_MAGIC_VERSION_0)) {
186             xml = newBinaryPullParser();
187         } else {
188             xml = newFastPullParser();
189         }
190         try {
191             xml.setInput(in, StandardCharsets.UTF_8.name());
192         } catch (XmlPullParserException e) {
193             throw new IOException(e);
194         }
195         return xml;
196     }
197 
198     /**
199      * Creates a new xml serializer.
200      */
newSerializer()201     public static XmlSerializer newSerializer() {
202         return XmlObjectFactory.newXmlSerializer();
203     }
204 
205     /**
206      * Creates a new {@link XmlSerializer} which is optimized for use inside the
207      * system, typically by supporting only a basic set of features.
208      * <p>
209      * In particular, the returned parser does not support namespaces, prefixes,
210      * properties, or options.
211      *
212      * @hide
213      */
214     @SuppressWarnings("AndroidFrameworkEfficientXml")
newFastSerializer()215     public static @NonNull TypedXmlSerializer newFastSerializer() {
216         return XmlUtils.makeTyped(new FastXmlSerializer());
217     }
218 
219     /**
220      * Creates a new {@link XmlSerializer} that writes XML documents using a
221      * custom binary wire protocol which benchmarking has shown to be 4.4x
222      * faster and use 2.8x less disk space than {@code Xml.newFastSerializer()}
223      * for a typical {@code packages.xml}.
224      *
225      * @hide
226      */
newBinarySerializer()227     public static @NonNull TypedXmlSerializer newBinarySerializer() {
228         return new BinaryXmlSerializer();
229     }
230 
231     /**
232      * Creates a new {@link XmlSerializer} which is optimized for use inside the
233      * system, typically by supporting only a basic set of features.
234      * <p>
235      * This returned instance may be configured to write using an efficient
236      * binary format instead of a human-readable text format, depending on
237      * device feature flags.
238      * <p>
239      * To ensure that both formats are detected and transparently handled
240      * correctly, you must shift to using both {@link #resolveSerializer} and
241      * {@link #resolvePullParser}.
242      *
243      * @hide
244      */
resolveSerializer(@onNull OutputStream out)245     public static @NonNull TypedXmlSerializer resolveSerializer(@NonNull OutputStream out)
246             throws IOException {
247         final TypedXmlSerializer xml;
248         if (ENABLE_BINARY_DEFAULT) {
249             xml = newBinarySerializer();
250         } else {
251             xml = newFastSerializer();
252         }
253         xml.setOutput(out, StandardCharsets.UTF_8.name());
254         return xml;
255     }
256 
257     /**
258      * Copy the first XML document into the second document.
259      * <p>
260      * Implemented by reading all events from the given {@link XmlPullParser}
261      * and writing them directly to the given {@link XmlSerializer}. This can be
262      * useful for transparently converting between underlying wire protocols.
263      *
264      * @hide
265      */
copy(@onNull XmlPullParser in, @NonNull XmlSerializer out)266     public static void copy(@NonNull XmlPullParser in, @NonNull XmlSerializer out)
267             throws XmlPullParserException, IOException {
268         // Some parsers may have already consumed the event that starts the
269         // document, so we manually emit that event here for consistency
270         if (in.getEventType() == XmlPullParser.START_DOCUMENT) {
271             out.startDocument(in.getInputEncoding(), true);
272         }
273 
274         while (true) {
275             final int token = in.nextToken();
276             switch (token) {
277                 case XmlPullParser.START_DOCUMENT:
278                     out.startDocument(in.getInputEncoding(), true);
279                     break;
280                 case XmlPullParser.END_DOCUMENT:
281                     out.endDocument();
282                     return;
283                 case XmlPullParser.START_TAG:
284                     out.startTag(normalizeNamespace(in.getNamespace()), in.getName());
285                     for (int i = 0; i < in.getAttributeCount(); i++) {
286                         out.attribute(normalizeNamespace(in.getAttributeNamespace(i)),
287                                 in.getAttributeName(i), in.getAttributeValue(i));
288                     }
289                     break;
290                 case XmlPullParser.END_TAG:
291                     out.endTag(normalizeNamespace(in.getNamespace()), in.getName());
292                     break;
293                 case XmlPullParser.TEXT:
294                     out.text(in.getText());
295                     break;
296                 case XmlPullParser.CDSECT:
297                     out.cdsect(in.getText());
298                     break;
299                 case XmlPullParser.ENTITY_REF:
300                     out.entityRef(in.getName());
301                     break;
302                 case XmlPullParser.IGNORABLE_WHITESPACE:
303                     out.ignorableWhitespace(in.getText());
304                     break;
305                 case XmlPullParser.PROCESSING_INSTRUCTION:
306                     out.processingInstruction(in.getText());
307                     break;
308                 case XmlPullParser.COMMENT:
309                     out.comment(in.getText());
310                     break;
311                 case XmlPullParser.DOCDECL:
312                     out.docdecl(in.getText());
313                     break;
314                 default:
315                     throw new IllegalStateException("Unknown token " + token);
316             }
317         }
318     }
319 
320     /**
321      * Some parsers may return an empty string {@code ""} when a namespace in
322      * unsupported, which can confuse serializers. This method normalizes empty
323      * strings to be {@code null}.
324      */
normalizeNamespace(@ullable String namespace)325     private static @Nullable String normalizeNamespace(@Nullable String namespace) {
326         if (namespace == null || namespace.isEmpty()) {
327             return null;
328         } else {
329             return namespace;
330         }
331     }
332 
333     /**
334      * Supported character encodings.
335      */
336     public enum Encoding {
337 
338         US_ASCII("US-ASCII"),
339         UTF_8("UTF-8"),
340         UTF_16("UTF-16"),
341         ISO_8859_1("ISO-8859-1");
342 
343         final String expatName;
344 
Encoding(String expatName)345         Encoding(String expatName) {
346             this.expatName = expatName;
347         }
348     }
349 
350     /**
351      * Finds an encoding by name. Returns UTF-8 if you pass {@code null}.
352      */
findEncodingByName(String encodingName)353     public static Encoding findEncodingByName(String encodingName)
354             throws UnsupportedEncodingException {
355         if (encodingName == null) {
356             return Encoding.UTF_8;
357         }
358 
359         for (Encoding encoding : Encoding.values()) {
360             if (encoding.expatName.equalsIgnoreCase(encodingName))
361                 return encoding;
362         }
363         throw new UnsupportedEncodingException(encodingName);
364     }
365 
366     /**
367      * Return an AttributeSet interface for use with the given XmlPullParser.
368      * If the given parser itself implements AttributeSet, that implementation
369      * is simply returned.  Otherwise a wrapper class is
370      * instantiated on top of the XmlPullParser, as a proxy for retrieving its
371      * attributes, and returned to you.
372      *
373      * @param parser The existing parser for which you would like an
374      *               AttributeSet.
375      *
376      * @return An AttributeSet you can use to retrieve the
377      *         attribute values at each of the tags as the parser moves
378      *         through its XML document.
379      *
380      * @see AttributeSet
381      */
asAttributeSet(XmlPullParser parser)382     public static AttributeSet asAttributeSet(XmlPullParser parser) {
383         return (parser instanceof AttributeSet)
384                 ? (AttributeSet) parser
385                 : new XmlPullAttributes(parser);
386     }
387 }
388