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