package de.eisfeldj.augendiagnosefx.tools; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; import java.nio.charset.Charset; import java.util.Collections; import java.util.Enumeration; import java.util.Properties; import java.util.TreeSet; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * Tool to convert Strings from Android app to Windows application. */ public final class StringConverter { /** * The global Strings file. */ private static final File[] XML_FILES_GLOBAL = {new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values/strings.xml"), new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values/strings_menu.xml"), new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values/strings_settings.xml"), new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values/strings_dialogs.xml")}; /** * The German String file. */ private static final File[] XML_FILES_DE = {new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values-de/strings.xml"), new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values-de/strings_menu.xml"), new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values-de/strings_settings.xml"), new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values-de/strings_dialogs.xml")}; /** * The Spanish String file. */ private static final File[] XML_FILES_ES = {new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values-es/strings.xml"), new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values-es/strings_menu.xml"), new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values-es/strings_settings.xml"), new File("../AugendiagnoseIdea/augendiagnoseLib/src/main/res/values-es/strings_dialogs.xml")}; /** * The global Properties file. */ private static final File PROP_FILE_GLOBAL = new File("resources/bundles/Strings.properties"); /** * The German Properties file. */ private static final File PROP_FILE_DE = new File("resources/bundles/Strings_de.properties"); /** * The Spanish Properties file. */ private static final File PROP_FILE_ES = new File("resources/bundles/Strings_es.properties"); /** * The class containing resource constants. */ private static final File RESOURCE_CLASS = new File("src/de/eisfeldj/augendiagnosefx/util/ResourceConstants.java"); /** * Suffix for file while writing. */ private static final String FILE_SUFFIX = ".tmp"; /** * An instance of this class. */ private static StringConverter mInstance; /** * The SAX parser factory used. */ private SAXParserFactory mFactory = SAXParserFactory.newInstance(); /** * Private constructor. */ private StringConverter() { // do nothing. } /** * Main method. * * @param args * The command line arguments. */ public static void main(final String[] args) { mInstance = new StringConverter(); mInstance.process(); } /** * Process the resource files and update valus from XML files, as well as ResourceConstants. */ private void process() { File tmpPropFile = new File(PROP_FILE_GLOBAL.getPath() + FILE_SUFFIX); File tmpPropFileDe = new File(PROP_FILE_DE.getPath() + FILE_SUFFIX); File tmpPropFileEs = new File(PROP_FILE_ES.getPath() + FILE_SUFFIX); PROP_FILE_GLOBAL.renameTo(tmpPropFile); PROP_FILE_DE.renameTo(tmpPropFileDe); PROP_FILE_ES.renameTo(tmpPropFileEs); try (InputStream reader = new FileInputStream(tmpPropFile); Writer writer = new FilteredFileWriter(PROP_FILE_GLOBAL); InputStream readerDe = new FileInputStream(tmpPropFileDe); Writer writerDe = new FilteredFileWriter(PROP_FILE_DE); InputStream readerEs = new FileInputStream(tmpPropFileEs); Writer writerEs = new FilteredFileWriter(PROP_FILE_ES)) { AlphabeticProperties props = new AlphabeticProperties(); props.load(reader); AlphabeticProperties propsDe = new AlphabeticProperties(); propsDe.load(readerDe); AlphabeticProperties propsEs = new AlphabeticProperties(); propsEs.load(readerEs); Enumeration<?> e = props.propertyNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); for (File xmlFile : XML_FILES_GLOBAL) { String xmlValue = getValueFromXml(xmlFile, key); if (xmlValue != null) { props.setProperty(key, xmlValue); } } for (File xmlFile : XML_FILES_DE) { String xmlValueDe = getValueFromXml(xmlFile, key); if (xmlValueDe != null) { propsDe.setProperty(key, xmlValueDe); } } for (File xmlFile : XML_FILES_ES) { String xmlValueEs = getValueFromXml(xmlFile, key); if (xmlValueEs != null) { propsEs.setProperty(key, xmlValueEs); } } } props.store(writer, "String resources"); propsDe.store(writerDe, "String resources DE"); propsEs.store(writerEs, "String resources ES"); createResourceConstants(props); } catch (IOException e) { e.printStackTrace(); } tmpPropFile.delete(); tmpPropFileDe.delete(); tmpPropFileEs.delete(); } /** * Create the resource constants class. * * @param props * The properties from which to create resource constants. */ private void createResourceConstants(final Properties props) { String header = "package de.eisfeldj.augendiagnosefx.util;\n\n" + "/**\n" + " * Constants for resource strings.\n" + " */\n" + "public final class ResourceConstants {\n\n" + " /**\n" + " * Private default constructor.\n" + " */\n" + " private ResourceConstants() {\n" + " // do nothing.\n" + " }\n\n" + " // JAVADOC:OFF\n"; String footer = " // JAVADOC:ON\n}"; File tmpResourceClass = new File(RESOURCE_CLASS.getAbsolutePath() + FILE_SUFFIX); RESOURCE_CLASS.renameTo(tmpResourceClass); try (PrintWriter writer = new PrintWriter(RESOURCE_CLASS)) { writer.print(header); Enumeration<?> e = props.keys(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); writer.println(" public static final String " + key.toUpperCase() + " = \"" + key + "\";"); } writer.println(footer); } catch (FileNotFoundException e) { e.printStackTrace(); } tmpResourceClass.delete(); } /** * Get a String value from an android String file. * * @param file * The file. * @param key * The key. * @return The String value. */ private String getValueFromXml(final File file, final String key) { String result = null; SAXParser parser; try { parser = mFactory.newSAXParser(); SaxHandler handler = new SaxHandler(key); parser.parse(file, handler); result = handler.getValue(); } catch (ParserConfigurationException | SAXException | IOException e) { e.printStackTrace(); } return result; } /** * Convert the non-ISO characters of an input String into its unicode representation. * * @param input * The input String * @return The converted String. */ private static String convertToUnicode(final String input) { StringBuffer result = new StringBuffer(); for (char character : input.toCharArray()) { String hexString = Integer.toHexString(character | 0x10000).substring(1); // MAGIC_NUMBER if (hexString.startsWith("00")) { result.append(character); } else { result.append("\\u" + hexString); } } return result.toString(); } /** * SAX handler to parse the strings.xml file. */ private static final class SaxHandler extends DefaultHandler { /** * Storage for the resource key. */ private String mKey = null; /** * Storage for the resource value. */ private String mValue = null; /** * The String to be looked for. */ private String mSearchKey = null; /** * Constructor passing a search key. * * @param searchKey * The String to be looked for. */ private SaxHandler(final String searchKey) { this.mSearchKey = searchKey; } @Override public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException { if ("string".equals(qName)) { mKey = attributes.getValue("name"); } } @Override public void characters(final char[] ch, final int start, final int length) throws SAXException { if (mKey != null && mKey.equals(mSearchKey)) { mValue = new String(ch, start, length); } } @Override public void endElement(final String uri, final String localName, final String qName) throws SAXException { if ("string".equals(qName)) { mKey = null; } } /** * Getter for the found value. * * @return The found value. */ public String getValue() { return mValue; } } /** * Variant of properties that stores values in alphabetical order. */ private static class AlphabeticProperties extends Properties { /** * The serial version UID. */ private static final long serialVersionUID = 1L; @Override public synchronized Enumeration<Object> keys() { return Collections.enumeration(new TreeSet<>(super.keySet())); } } /** * Utility writer to remove the date string from the Properties output and to convert non-iso Strings to unicode. */ private static class FilteredFileWriter extends BufferedWriter { /** * Constructor. * * @param file * The file to be written. * @throws IOException * thrown if there are issues writing the file. */ @SuppressWarnings("resource") protected FilteredFileWriter(final File file) throws IOException { super(new OutputStreamWriter(new FileOutputStream(file), Charset.forName("ISO8859-1").newEncoder())); } @Override public void write(final String str) throws IOException { if (!str.startsWith("#") || !str.matches("#.*[0-9][0-9]:[0-9][0-9]:[0-9][0-9].*")) { super.write(convertToUnicode(str)); } } } }