| /* | |
| * Copyright (C) 2007 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package android.util; | |
| import android.annotation.NonNull; | |
| import android.annotation.Nullable; | |
| import android.os.SystemProperties; | |
| import android.system.ErrnoException; | |
| import android.system.Os; | |
| import com.android.internal.util.ArtBinaryXmlPullParser; | |
| import com.android.internal.util.ArtBinaryXmlSerializer; | |
| import com.android.internal.util.FastXmlSerializer; | |
| import com.android.internal.util.XmlUtils; | |
| import com.android.modules.utils.BinaryXmlSerializer; | |
| import com.android.modules.utils.TypedXmlPullParser; | |
| import com.android.modules.utils.TypedXmlSerializer; | |
| import libcore.util.XmlObjectFactory; | |
| import org.xml.sax.ContentHandler; | |
| import org.xml.sax.InputSource; | |
| import org.xml.sax.SAXException; | |
| import org.xml.sax.XMLReader; | |
| import org.xmlpull.v1.XmlPullParser; | |
| import org.xmlpull.v1.XmlPullParserException; | |
| import org.xmlpull.v1.XmlSerializer; | |
| import java.io.BufferedInputStream; | |
| import java.io.FileInputStream; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.io.Reader; | |
| import java.io.StringReader; | |
| import java.io.UnsupportedEncodingException; | |
| import java.nio.charset.StandardCharsets; | |
| import java.util.Arrays; | |
| /** | |
| * XML utility methods. | |
| */ | |
| public class Xml { | |
| private Xml() {} | |
| /** | |
| * {@link org.xmlpull.v1.XmlPullParser} "relaxed" feature name. | |
| * | |
| * @see <a href="http://xmlpull.org/v1/doc/features.html#relaxed"> | |
| * specification</a> | |
| */ | |
| public static String FEATURE_RELAXED = "http://xmlpull.org/v1/doc/features.html#relaxed"; | |
| /** | |
| * Feature flag: when set, {@link #resolveSerializer(OutputStream)} will | |
| * emit binary XML by default. | |
| * | |
| * @hide | |
| */ | |
| public static final boolean ENABLE_BINARY_DEFAULT = SystemProperties | |
| .getBoolean("persist.sys.binary_xml", true); | |
| /** | |
| * Parses the given xml string and fires events on the given SAX handler. | |
| */ | |
| public static void parse(String xml, ContentHandler contentHandler) | |
| throws SAXException { | |
| try { | |
| XMLReader reader = XmlObjectFactory.newXMLReader(); | |
| reader.setContentHandler(contentHandler); | |
| reader.parse(new InputSource(new StringReader(xml))); | |
| } catch (IOException e) { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| /** | |
| * Parses xml from the given reader and fires events on the given SAX | |
| * handler. | |
| */ | |
| public static void parse(Reader in, ContentHandler contentHandler) | |
| throws IOException, SAXException { | |
| XMLReader reader = XmlObjectFactory.newXMLReader(); | |
| reader.setContentHandler(contentHandler); | |
| reader.parse(new InputSource(in)); | |
| } | |
| /** | |
| * Parses xml from the given input stream and fires events on the given SAX | |
| * handler. | |
| */ | |
| public static void parse(InputStream in, Encoding encoding, | |
| ContentHandler contentHandler) throws IOException, SAXException { | |
| XMLReader reader = XmlObjectFactory.newXMLReader(); | |
| reader.setContentHandler(contentHandler); | |
| InputSource source = new InputSource(in); | |
| source.setEncoding(encoding.expatName); | |
| reader.parse(source); | |
| } | |
| /** | |
| * Returns a new pull parser with namespace support. | |
| */ | |
| public static XmlPullParser newPullParser() { | |
| try { | |
| XmlPullParser parser = XmlObjectFactory.newXmlPullParser(); | |
| parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true); | |
| parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); | |
| return parser; | |
| } catch (XmlPullParserException e) { | |
| throw new AssertionError(); | |
| } | |
| } | |
| /** | |
| * Creates a new {@link TypedXmlPullParser} which is optimized for use | |
| * inside the system, typically by supporting only a basic set of features. | |
| * <p> | |
| * In particular, the returned parser does not support namespaces, prefixes, | |
| * properties, or options. | |
| * | |
| * @hide | |
| */ | |
| @SuppressWarnings("AndroidFrameworkEfficientXml") | |
| public static @NonNull TypedXmlPullParser newFastPullParser() { | |
| return XmlUtils.makeTyped(newPullParser()); | |
| } | |
| /** | |
| * Creates a new {@link XmlPullParser} that reads XML documents using a | |
| * custom binary wire protocol which benchmarking has shown to be 8.5x | |
| * faster than {@code Xml.newFastPullParser()} for a typical | |
| * {@code packages.xml}. | |
| * | |
| * @hide | |
| */ | |
| public static @NonNull TypedXmlPullParser newBinaryPullParser() { | |
| return new ArtBinaryXmlPullParser(); | |
| } | |
| /** | |
| * Creates a new {@link XmlPullParser} which is optimized for use inside the | |
| * system, typically by supporting only a basic set of features. | |
| * <p> | |
| * This returned instance may be configured to read using an efficient | |
| * binary format instead of a human-readable text format, depending on | |
| * device feature flags. | |
| * <p> | |
| * To ensure that both formats are detected and transparently handled | |
| * correctly, you must shift to using both {@link #resolveSerializer} and | |
| * {@link #resolvePullParser}. | |
| * | |
| * @hide | |
| */ | |
| public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in) | |
| throws IOException { | |
| final byte[] magic = new byte[4]; | |
| if (in instanceof FileInputStream) { | |
| try { | |
| Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0); | |
| } catch (ErrnoException e) { | |
| throw e.rethrowAsIOException(); | |
| } | |
| } else { | |
| if (!in.markSupported()) { | |
| in = new BufferedInputStream(in); | |
| } | |
| in.mark(8); | |
| in.read(magic); | |
| in.reset(); | |
| } | |
| final TypedXmlPullParser xml; | |
| if (Arrays.equals(magic, BinaryXmlSerializer.PROTOCOL_MAGIC_VERSION_0)) { | |
| xml = newBinaryPullParser(); | |
| } else { | |
| xml = newFastPullParser(); | |
| } | |
| try { | |
| xml.setInput(in, StandardCharsets.UTF_8.name()); | |
| } catch (XmlPullParserException e) { | |
| throw new IOException(e); | |
| } | |
| return xml; | |
| } | |
| /** | |
| * Creates a new xml serializer. | |
| */ | |
| public static XmlSerializer newSerializer() { | |
| return XmlObjectFactory.newXmlSerializer(); | |
| } | |
| /** | |
| * Creates a new {@link XmlSerializer} which is optimized for use inside the | |
| * system, typically by supporting only a basic set of features. | |
| * <p> | |
| * In particular, the returned parser does not support namespaces, prefixes, | |
| * properties, or options. | |
| * | |
| * @hide | |
| */ | |
| @SuppressWarnings("AndroidFrameworkEfficientXml") | |
| public static @NonNull TypedXmlSerializer newFastSerializer() { | |
| return XmlUtils.makeTyped(new FastXmlSerializer()); | |
| } | |
| /** | |
| * Creates a new {@link XmlSerializer} that writes XML documents using a | |
| * custom binary wire protocol which benchmarking has shown to be 4.4x | |
| * faster and use 2.8x less disk space than {@code Xml.newFastSerializer()} | |
| * for a typical {@code packages.xml}. | |
| * | |
| * @hide | |
| */ | |
| public static @NonNull TypedXmlSerializer newBinarySerializer() { | |
| return new ArtBinaryXmlSerializer(); | |
| } | |
| /** | |
| * Creates a new {@link XmlSerializer} which is optimized for use inside the | |
| * system, typically by supporting only a basic set of features. | |
| * <p> | |
| * This returned instance may be configured to write using an efficient | |
| * binary format instead of a human-readable text format, depending on | |
| * device feature flags. | |
| * <p> | |
| * To ensure that both formats are detected and transparently handled | |
| * correctly, you must shift to using both {@link #resolveSerializer} and | |
| * {@link #resolvePullParser}. | |
| * | |
| * @hide | |
| */ | |
| public static @NonNull TypedXmlSerializer resolveSerializer(@NonNull OutputStream out) | |
| throws IOException { | |
| final TypedXmlSerializer xml; | |
| if (ENABLE_BINARY_DEFAULT) { | |
| xml = newBinarySerializer(); | |
| } else { | |
| xml = newFastSerializer(); | |
| } | |
| xml.setOutput(out, StandardCharsets.UTF_8.name()); | |
| return xml; | |
| } | |
| /** | |
| * Copy the first XML document into the second document. | |
| * <p> | |
| * Implemented by reading all events from the given {@link XmlPullParser} | |
| * and writing them directly to the given {@link XmlSerializer}. This can be | |
| * useful for transparently converting between underlying wire protocols. | |
| * | |
| * @hide | |
| */ | |
| public static void copy(@NonNull XmlPullParser in, @NonNull XmlSerializer out) | |
| throws XmlPullParserException, IOException { | |
| // Some parsers may have already consumed the event that starts the | |
| // document, so we manually emit that event here for consistency | |
| if (in.getEventType() == XmlPullParser.START_DOCUMENT) { | |
| out.startDocument(in.getInputEncoding(), true); | |
| } | |
| while (true) { | |
| final int token = in.nextToken(); | |
| switch (token) { | |
| case XmlPullParser.START_DOCUMENT: | |
| out.startDocument(in.getInputEncoding(), true); | |
| break; | |
| case XmlPullParser.END_DOCUMENT: | |
| out.endDocument(); | |
| return; | |
| case XmlPullParser.START_TAG: | |
| out.startTag(normalizeNamespace(in.getNamespace()), in.getName()); | |
| for (int i = 0; i < in.getAttributeCount(); i++) { | |
| out.attribute(normalizeNamespace(in.getAttributeNamespace(i)), | |
| in.getAttributeName(i), in.getAttributeValue(i)); | |
| } | |
| break; | |
| case XmlPullParser.END_TAG: | |
| out.endTag(normalizeNamespace(in.getNamespace()), in.getName()); | |
| break; | |
| case XmlPullParser.TEXT: | |
| out.text(in.getText()); | |
| break; | |
| case XmlPullParser.CDSECT: | |
| out.cdsect(in.getText()); | |
| break; | |
| case XmlPullParser.ENTITY_REF: | |
| out.entityRef(in.getName()); | |
| break; | |
| case XmlPullParser.IGNORABLE_WHITESPACE: | |
| out.ignorableWhitespace(in.getText()); | |
| break; | |
| case XmlPullParser.PROCESSING_INSTRUCTION: | |
| out.processingInstruction(in.getText()); | |
| break; | |
| case XmlPullParser.COMMENT: | |
| out.comment(in.getText()); | |
| break; | |
| case XmlPullParser.DOCDECL: | |
| out.docdecl(in.getText()); | |
| break; | |
| default: | |
| throw new IllegalStateException("Unknown token " + token); | |
| } | |
| } | |
| } | |
| /** | |
| * Some parsers may return an empty string {@code ""} when a namespace in | |
| * unsupported, which can confuse serializers. This method normalizes empty | |
| * strings to be {@code null}. | |
| */ | |
| private static @Nullable String normalizeNamespace(@Nullable String namespace) { | |
| if (namespace == null || namespace.isEmpty()) { | |
| return null; | |
| } else { | |
| return namespace; | |
| } | |
| } | |
| /** | |
| * Supported character encodings. | |
| */ | |
| public enum Encoding { | |
| US_ASCII("US-ASCII"), | |
| UTF_8("UTF-8"), | |
| UTF_16("UTF-16"), | |
| ISO_8859_1("ISO-8859-1"); | |
| final String expatName; | |
| Encoding(String expatName) { | |
| this.expatName = expatName; | |
| } | |
| } | |
| /** | |
| * Finds an encoding by name. Returns UTF-8 if you pass {@code null}. | |
| */ | |
| public static Encoding findEncodingByName(String encodingName) | |
| throws UnsupportedEncodingException { | |
| if (encodingName == null) { | |
| return Encoding.UTF_8; | |
| } | |
| for (Encoding encoding : Encoding.values()) { | |
| if (encoding.expatName.equalsIgnoreCase(encodingName)) | |
| return encoding; | |
| } | |
| throw new UnsupportedEncodingException(encodingName); | |
| } | |
| /** | |
| * Return an AttributeSet interface for use with the given XmlPullParser. | |
| * If the given parser itself implements AttributeSet, that implementation | |
| * is simply returned. Otherwise a wrapper class is | |
| * instantiated on top of the XmlPullParser, as a proxy for retrieving its | |
| * attributes, and returned to you. | |
| * | |
| * @param parser The existing parser for which you would like an | |
| * AttributeSet. | |
| * | |
| * @return An AttributeSet you can use to retrieve the | |
| * attribute values at each of the tags as the parser moves | |
| * through its XML document. | |
| * | |
| * @see AttributeSet | |
| */ | |
| public static AttributeSet asAttributeSet(XmlPullParser parser) { | |
| return (parser instanceof AttributeSet) | |
| ? (AttributeSet) parser | |
| : new XmlPullAttributes(parser); | |
| } | |
| } |