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