• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 com.android.providers.media.util;
18 
19 import android.util.Xml;
20 
21 import androidx.annotation.NonNull;
22 
23 import com.google.common.collect.Maps;
24 
25 import org.xmlpull.v1.XmlPullParser;
26 import org.xmlpull.v1.XmlPullParserException;
27 import org.xmlpull.v1.XmlSerializer;
28 
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.nio.charset.StandardCharsets;
33 import java.util.HashMap;
34 import java.util.Map;
35 import java.util.Objects;
36 
37 /**
38  * XML utility methods.
39  */
40 public class XmlUtils {
41     private static final String FEATURE_NAME =
42             "http://xmlpull.org/v1/doc/features.html#indent-output";
43     private static final String TAG_NAME_MAP_ENTRY = "map_entry";
44     private static final String ATTRIBUTE_NAME_MAP_ENTRY_KEY = "key";
45 
46     /**
47      * Read a {@link Map}<{@link String}, {@link String}> from an {@link InputStream} containing
48      * XML. The stream should previously have been written by
49      * {@link #writeMapXml(Map, OutputStream)}, else it may return a {@link Maps#newHashMap()},
50      * or throw a {@link RuntimeException}.
51      *
52      * @param in The {@link InputStream} from which to read.
53      *
54      * @return The resulting {@link Map}<{@link String}, {@link String}>.
55      *
56      * @see #writeMapXml(Map, OutputStream)
57      */
58     @NonNull
readMapXml(@onNull InputStream in)59     public static Map<String, String> readMapXml(@NonNull InputStream in)
60             throws IOException, XmlPullParserException {
61         final Map<String, String> map = new HashMap<>();
62 
63         final XmlPullParser parser = Xml.newPullParser();
64         parser.setInput(in, StandardCharsets.UTF_8.name());
65 
66         final StringBuilder mapEntryKey = new StringBuilder(), mapEntryValue = new StringBuilder();
67 
68         for (int eventType = parser.getEventType(); eventType != parser.END_DOCUMENT;
69                 eventType = parser.next()) {
70             if (eventType == parser.START_TAG) {
71                 final String tagName = parser.getName();
72                 if (!Objects.equals(tagName, TAG_NAME_MAP_ENTRY)) {
73                     throw new RuntimeException("Unexpected tag name: " + tagName);
74                 }
75 
76                 mapEntryKey.append(parser.getAttributeValue(
77                         /* namespace */ null, ATTRIBUTE_NAME_MAP_ENTRY_KEY));
78             } else if (eventType == parser.TEXT) {
79                 mapEntryValue.append(parser.getText());
80             } else if (eventType == parser.END_TAG) {
81                 if (mapEntryKey.length() > 0 && mapEntryValue.length() > 0) {
82                     map.put(mapEntryKey.toString(), mapEntryValue.toString());
83                 }
84 
85                 mapEntryKey.setLength(0);
86                 mapEntryValue.setLength(0);
87             }
88         }
89 
90         return map;
91     }
92 
93     /**
94      * Flatten a {@link Map}<{@link String}, {@link String}> into an {@link OutputStream} as XML.
95      * The map can later be read back with {@link #readMapXml(InputStream)}.
96      *
97      * @param map The {@link Map}<{@link String}, {@link String}> to be flattened.
98      * @param out The {@link OutputStream} where to write the XML data.
99      *
100      * @see #readMapXml(InputStream)
101      */
writeMapXml(@onNull Map<String, String> map, @NonNull OutputStream out)102     public static void writeMapXml(@NonNull Map<String, String> map, @NonNull OutputStream out)
103             throws IOException {
104         final XmlSerializer serializer = Xml.newSerializer();
105         serializer.setOutput(out, StandardCharsets.UTF_8.name());
106 
107         serializer.startDocument(/* encoding */ null, /* standalone */ true);
108         serializer.setFeature(FEATURE_NAME, /* state */ true);
109 
110         for (Map.Entry<String, String> e : map.entrySet()) {
111             serializer.startTag(/* namespace */ null, TAG_NAME_MAP_ENTRY);
112             serializer.attribute(/* namespace */ null, ATTRIBUTE_NAME_MAP_ENTRY_KEY, e.getKey());
113 
114             final String val = e.getValue();
115             if (val != null) {
116                 serializer.text(val);
117             }
118 
119             serializer.endTag(/* namespace */ null, TAG_NAME_MAP_ENTRY);
120         }
121 
122         serializer.endDocument();
123     }
124 }
125