1 /* 2 * Copyright (C) 2020 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 import com.android.tools.layoutlib.annotations.Nullable; 18 19 import org.w3c.dom.Document; 20 import org.w3c.dom.Node; 21 import org.w3c.dom.NodeList; 22 23 import java.io.File; 24 import java.io.FileWriter; 25 import java.util.LinkedHashMap; 26 import java.util.Map; 27 import java.util.Map.Entry; 28 29 import javax.xml.parsers.DocumentBuilder; 30 import javax.xml.parsers.DocumentBuilderFactory; 31 32 33 public class ResourceConverter { 34 35 /** 36 * Convert the Android strings.xml into generic Java strings.properties. 37 */ main(String[] args)38 public static void main(String[] args) throws Exception { 39 System.out.println("Parsing input..."); 40 Map<String, String> map = loadStrings("./validator/resources/strings.xml"); 41 System.out.println("Writing to output..."); 42 writeStrings(map, "./validator/resources/strings.properties"); 43 System.out.println("Finished converting."); 44 } 45 46 /** 47 * Writes <name, value> pair to outputPath. 48 */ writeStrings(Map<String, String> map, String outputPath)49 private static void writeStrings(Map<String, String> map, String outputPath) throws Exception { 50 File output = new File(outputPath); 51 output.createNewFile(); 52 FileWriter writer = new FileWriter(output); 53 try { 54 writer.write(getCopyRight()); 55 writer.write("\n"); 56 for (Entry<String, String> entry : map.entrySet()) { 57 String name = entry.getKey(); 58 String value = entry.getValue(); 59 writer.write(name + " = " + value + "\n"); 60 } 61 } finally { 62 writer.close(); 63 } 64 } 65 66 /** 67 * Very hacky parser for Android-understood-values.xml. It parses <string> </string> 68 * tags, and retrieve the name and the value associated. 69 * 70 * @param path to .xml containing android strings 71 * @return Map containing name and values. 72 */ 73 @Nullable loadStrings(String path)74 private static Map<String, String> loadStrings(String path) 75 throws Exception { 76 // Use ordered map to minimize changes to strings.properties in git. 77 Map<String, String> toReturn = new LinkedHashMap<>(); 78 79 File file = new File(path); 80 if (!file.exists()) { 81 System.err.println("The input file "+ path + " does not exist. Terminating."); 82 return toReturn; 83 } 84 85 DocumentBuilder documentBuilder = DocumentBuilderFactory 86 .newInstance().newDocumentBuilder(); 87 Document document = documentBuilder.parse(file); 88 NodeList nodeList = document.getElementsByTagName("string"); 89 for (int i = 0; i < nodeList.getLength(); i++) { 90 Node node = nodeList.item(i); 91 String name = node.getAttributes().getNamedItem("name").getNodeValue(); 92 93 StringBuilder valueBuilder = new StringBuilder(); 94 try { 95 /** 96 * This is a very hacky way to bypass "ns1:g" tag in android's .xml. 97 * Ideally we'll read the tag from the parent and apply it here, but it being the 98 * deep node list I'm not currently sure how to parse it safely. Might need to look 99 * into IntelliJ PSI tree we have in Studio. But I didn't want to add unnecessary 100 * deps to LayoutLib. 101 * 102 * It also means resource namespaces are rendered useless after conversion. 103 */ 104 for (int j = 0; j < node.getChildNodes().getLength(); j++) { 105 Node child = node.getChildNodes().item(j); 106 String toAdd = null; 107 if ("ns1:g".equals(child.getNodeName())) { 108 toAdd = child.getFirstChild().getNodeValue(); 109 } else if ("xliff:g".equals(child.getNodeName())) { 110 toAdd = child.getFirstChild().getNodeValue(); 111 } else { 112 toAdd = child.getNodeValue(); 113 } 114 // Replace all tab, newline and multi indentations. 115 toAdd = toAdd.replaceAll("[\n\t]", ""); 116 toAdd = toAdd.replaceAll("[ ]+", " "); 117 valueBuilder.append(toAdd); 118 } 119 String finalString = valueBuilder.toString().trim(); 120 toReturn.put(name, finalString); 121 } catch (Exception e) { 122 e.printStackTrace(); 123 } 124 } 125 return toReturn; 126 } 127 getCopyRight()128 private static String getCopyRight() { 129 return "\n" + "#\n" + "# Copyright (C) 2020 The Android Open Source Project\n" + "#\n" + 130 "# Licensed under the Apache License, Version 2.0 (the \"License\");\n" + 131 "# you may not use this file except in compliance with the License.\n" + 132 "# You may obtain a copy of the License at\n" + "#\n" + 133 "# http://www.apache.org/licenses/LICENSE-2.0\n" + "#\n" + 134 "# Unless required by applicable law or agreed to in writing, software\n" + 135 "# distributed under the License is distributed on an \"AS IS\" BASIS,\n" + 136 "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + 137 "# See the License for the specific language governing permissions and\n" + 138 "# limitations under the License.\n" + "#"; 139 } 140 } 141