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