Changeset 35996


Ignore:
Timestamp:
2022-01-13T12:57:05+13:00 (2 years ago)
Author:
cstephen
Message:

Fix the implementation of XSLTUtil#getInterfaceStringsAsJavascript(...). Root objects are now initialized properly, the code has been refactored and the JS output has been cleaned up.

Location:
main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/Dictionary.java

    r33994 r35996  
    122122    }
    123123
    124     public Enumeration getKeys()
     124    public Enumeration<String> getKeys()
    125125    {
    126126        if (this.raw != null)
  • main/trunk/greenstone3/src/java/org/greenstone/gsdl3/util/XSLTUtil.java

    r35745 r35996  
    822822    }
    823823
    824     public static String getInterfaceStringsAsJavascript(String interface_name, String lang, String prefix)
     824    public static String getInterfaceStringsAsJavascript(String interfaceName, String lang, String prefix)
    825825    {
    826826        String prependToPrefix = "gs.text";
    827         return XSLTUtil.getInterfaceStringsAsJavascript(interface_name, lang, prefix, prependToPrefix);
     827        return XSLTUtil.getInterfaceStringsAsJavascript(interfaceName, lang, prefix, prependToPrefix);
    828828    }
    829829
    830830    /**
    831      * Generates a Javascript object graph to store language strings. Leaf nodes contain the string and the previous node, the key.
    832      * Further preceding nodes denote the prefix of the string (i.e. the gs3 language strings object, which interface they belong to etc.).
    833      * Accessing a language string on a well-formed JS object graph will be similar to this: const myString = gs.text.atea.asr.Title;
    834      * @param interface_name The name of the interface to retrieve language strings for.
     831     * Generates a Javascript object graph to store language strings. Leaf nodes are the key, and their value the string.
     832     * Further preceding nodes denote the prefix of the string within the language strings property file.
     833     * Accessing a language string from the object graph can be done as such: 'const myString = {prependToPrefix}.{prefix}.{key};'
     834     * @param interfaceName The name of the interface to retrieve language strings for.
    835835     * @param lang The language to retrieve.
    836      * @param prefix The prefix to place the language strings under (e.g. atea.asr).
    837      * @param prependToPrefix
    838      * @return Javascript code that will generate the language string object graph.
     836     * @param prefix The prefix to to retrieve strings under. E.g. a value of 'atea.macroniser' will only retrieve strings prefixed with that value.
     837     * @param prependToPrefix An accessor string to prepend to the generated JS object graph.
     838     * @return Stringified Javascript code that will generate the language string object graph.
    839839     */
    840     public static String getInterfaceStringsAsJavascript(String interface_name, String lang, String prefix, String prependToPrefix)
     840    public static String getInterfaceStringsAsJavascript(String interfaceName, String lang, String prefix, String prependToPrefix)
    841841    {
    842842        // now we allow looking for files in the interface's resources folder
    843         CustomClassLoader my_loader = new CustomClassLoader(XSLTUtil.class.getClassLoader(),
    844                 GSFile.interfaceResourceDir(GlobalProperties.getGSDL3Home(), interface_name));
    845         String prefixwithdot = prefix + ".";
     843        CustomClassLoader my_loader = new CustomClassLoader(
     844            XSLTUtil.class.getClassLoader(),
     845            GSFile.interfaceResourceDir(GlobalProperties.getGSDL3Home(), interfaceName)
     846        );
    846847
    847848        StringBuffer outputStr = new StringBuffer();
    848         HashSet<String> initialisedDepths = new HashSet<>();
    849 
    850         // Initialise the root prefix object
    851         outputStr.append("if(!" + prependToPrefix + ") { ");
    852         outputStr.append(prependToPrefix + " = {}; ");
    853         outputStr.append("}\n");
    854         initialisedDepths.add(prependToPrefix);
    855 
    856         // Initialise the prefix object structure, handling '.' characters.
    857         // JS identifies these as property accessors, hence requiring a new object for each 'level'.
    858         String currentDepth = prependToPrefix;
    859         String[] prefixComponents = prefix.split("\\.");
    860 
    861         for (String element : prefixComponents) {
    862             currentDepth += "." + element;
    863 
    864             outputStr.append("if(!" + currentDepth + ") { ");
    865             outputStr.append(currentDepth + " = {}; ");
    866             outputStr.append("}\n");
    867 
    868             initialisedDepths.add(currentDepth);
    869         }
    870 
    871         for (String dictName : new String[] { "interface_" + interface_name, "interface_default",
    872                 "interface_default2" }) {
    873             // get all the keys from the english dictionary as this is a complete set
     849        HashSet<String> initialisedNodes = new HashSet<>();
     850
     851        // The dictionaries to pull keys from
     852        String[] dictionaries = new String[] {
     853            "interface_" + interfaceName,
     854            "interface_default",
     855            "interface_default2"
     856        };
     857
     858        for (String dictName : dictionaries)
     859        {
     860            // get all the *keys* from the english dictionary as this is a complete set
    874861            Dictionary dict = new Dictionary(dictName, "en", my_loader);
    875             Enumeration keys = dict.getKeys();
     862            Enumeration<String> keys = dict.getKeys();
    876863            if (keys == null) {
    877864                continue;
     
    880867            // Get all properties in the language-specific dictionary with the given key prefix
    881868            // Create Javascript strings of the form:
    882             // prefixPath.key= "value";\n
    883             while (keys.hasMoreElements()) {
    884                 String key = (String) keys.nextElement();
    885                 if (key.startsWith(prefixwithdot)) {
    886                     String[] keyComponents = key.split("\\.");
    887                     currentDepth = prependToPrefix + "." + prefix;
    888 
    889                     for (int i = prefixComponents.length; i < keyComponents.length - 1; i++) {
    890                         currentDepth += "." + keyComponents[i];
    891 
    892                         if (initialisedDepths.contains(currentDepth)) {
    893                             continue;
    894                         }
    895 
    896                         outputStr.append("if(!" + currentDepth + ") { ");
    897                         outputStr.append(currentDepth + " = {}; ");
    898                         outputStr.append("}\n");
    899 
    900                         initialisedDepths.add(currentDepth);
    901                     }
     869            // prependToPrefix.key="value";\n
     870            while (keys.hasMoreElements())
     871            {
     872                String key = keys.nextElement();
     873                if (key.startsWith(prefix))
     874                {
     875                    // Builds the JS object structure we need to access the key.
     876                    // Also has the side effect of ensuring that any '.' characters in
     877                    // the key are valid once parsed by the JS engine.
     878                    buildJSObjectGraph(
     879                        outputStr,
     880                        prependToPrefix + "." + key.substring(0, key.lastIndexOf(".")), // Strip the actual key from the path
     881                        initialisedNodes
     882                    );
    902883
    903884                    // get the language dependent value for the key. This will return the english if no value found for the given lang
    904                     String value = getInterfaceText(interface_name, dictName, lang, key, null);
    905 
    906                     outputStr.append(prependToPrefix);
    907                     outputStr.append(".");
    908                     outputStr.append(key);
    909                     outputStr.append("=\"");
    910                     outputStr.append(value);
    911                     outputStr.append("\";\n");
     885                    String value = getInterfaceText(interfaceName, dictName, lang, key, null);
     886                    outputStr.append(prependToPrefix + "." + key + "=\"" + value + "\";\n");
    912887                }
    913888            }
     
    915890
    916891        return outputStr.toString();
     892    }
     893   
     894    /**
     895     * Builds a string that will initialize an empty javascript object.
     896     * I.e. 'gs.text??={};'.
     897     * @param buffer The buffer to append the string to.
     898     * @param objectPath The path to the JS object to initialize. E.g. gs.text.atea.
     899     * @param isRootObject A value indicating whether the object is a root object. If not, a logical nullish assignment statement will be built in order to produce cleaner javascript.
     900     */
     901    private static void buildJSObjectInitializer(
     902        StringBuffer buffer,
     903        String objectPath,
     904        Boolean isRootObject
     905    )
     906    {
     907        if (!isRootObject) {
     908            buffer.append(objectPath + "??={};\n");
     909            return;
     910        }
     911
     912        buffer.append("if(typeof " + objectPath + "===\"undefined\"){" + objectPath + "={};}\n");
     913    }
     914
     915    /**
     916     * Builds a string that will initialize a javascript object graph.
     917     * I.e. the structure required to access the final property on the object graph 'gs.text.atea.asr'.
     918     * @param buffer The buffer to append the string to.
     919     * @param objectGraph The object graph to build.
     920     * @param visitedNodes A map of previously built nodes. Updated to add nodes that are produced in this function.
     921     */
     922    private static void buildJSObjectGraph(
     923        StringBuffer buffer,
     924        String objectGraph,
     925        HashSet<String> preBuiltNodes
     926    )
     927    {
     928        if (objectGraph == null) {
     929            return;
     930        }
     931
     932        String[] nodes = objectGraph.split("\\.");
     933
     934        if (!preBuiltNodes.contains(nodes[0])) {
     935            buildJSObjectInitializer(buffer, nodes[0], true);
     936            preBuiltNodes.add(nodes[0]);
     937        }
     938
     939        if (nodes.length == 1) {
     940            return;
     941        }
     942
     943        String currentDepth = nodes[0];
     944        for (int i = 1; i < nodes.length; i++) {
     945            currentDepth += "." + nodes[i];
     946            if (preBuiltNodes.contains(currentDepth)) {
     947                continue;
     948            }
     949
     950            buildJSObjectInitializer(buffer, currentDepth, false);
     951            preBuiltNodes.add(currentDepth);
     952        }
    917953    }
    918954
Note: See TracChangeset for help on using the changeset viewer.