svghmi/gen_index_xhtml.ysl2
author Edouard Tisserant
Sun, 27 Oct 2019 22:28:51 +0100
branchsvghmi
changeset 2806 7d0e81cdedb0
parent 2801 390acff12755
child 2807 7fa21b3b5f9f
permissions -rw-r--r--
SVGHMI: Quicker update path for input widget when pressing on buttons, do not wait until data comes back, and simply update value text of the pressed widget. Updated PLC prog for more amimated value to display
include yslt_noindent.yml2

// overrides yslt's output function to set CDATA
decl output(method, cdata-section-elements="xhtml:script");

istylesheet
            /* From Inkscape */
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:cc="http://creativecommons.org/ns#"
            xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
            xmlns:svg="http://www.w3.org/2000/svg"
            xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
            xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
            xmlns:xhtml="http://www.w3.org/1999/xhtml"

            /* Our namespace to invoke python code */
            xmlns:ns="beremiz"
            extension-element-prefixes="ns func"
            exclude-result-prefixes="ns str regexp exsl func" {

    /* This retrieves geometry obtained through "inkscape -S"
     * already parsed by python and presented as a list of
     * <bbox x="0" y="0" w="42" h="42">
     */
    const "geometry", "ns:GetSVGGeometry()";
    const "hmitree", "ns:GetHMITree()";

    const "hmi_elements", "//svg:*[starts-with(@inkscape:label, 'HMI:')]";
    const "hmi_geometry", "$geometry[@Id = $hmi_elements/@id]";

    const "hmi_pages", "$hmi_elements[func:parselabel(@inkscape:label)/widget/@type = 'Page']";

    const "default_page" choose {
        when "count($hmi_pages) > 1" {
            const "Home_page", 
                "$hmi_pages[func:parselabel(@inkscape:label)/widget/arg[1]/@value = 'Home']";
            choose {
                when "$Home_page" > Home
                otherwise {
                    error "No Home page defined!";
                }
            }
        }
        when "count($hmi_pages) = 0" {
            error "No page defined!";
        }
        otherwise > «func:parselabel($hmi_pages/@inkscape:label)/widget/arg[1]/@value»
    }

    const "_categories" {
        noindex > HMI_ROOT
        noindex > HMI_LABEL
        noindex > HMI_CLASS
        noindex > HMI_PLC_STATUS
        noindex > HMI_CURRENT_PAGE
    }
    const "categories", "exsl:node-set($_categories)";
    const "_indexed_hmitree" apply "$hmitree", mode="index";
    const "indexed_hmitree", "exsl:node-set($_indexed_hmitree)";

    template "*", mode="index" {
        param "index", "0";
        param "parentpath", "''";
        const "content" {
            const "path"
                choose {
                    when "local-name() = 'HMI_ROOT'" > «$parentpath»
                    otherwise > «$parentpath»/«@name»
                }
            choose {
                when "not(local-name() = $categories/noindex)" {
                    xsl:copy {
                        attrib "index" > «$index»
                        attrib "hmipath" > «$path»
                        foreach "@*" xsl:copy;
                    }
                    /* no node expected below value nodes */
                }
                otherwise {
                    apply "*[1]", mode="index"{
                        with "index", "$index";
                        with "parentpath" > «$path»
                    }
                }
            }
        }

        copy "$content";
        apply "following-sibling::*[1]", mode="index" {
            with "index", "$index + count(exsl:node-set($content)/*)";
            with "parentpath" > «$parentpath»
        }
    }

    /* Identity template :
     *  - copy every attributes 
     *  - copy every sub-elements
     */
    template "@* | node()", mode="identity_svg" {
      /* use real xsl:copy instead copy-of alias from yslt.yml2 */
      xsl:copy apply "@* | node()", mode="identity_svg";
    }

    /*const "mark" > =HMI=\n*/

    /* copy root node and add geometry as comment for a test */
    template "/" { 
        comment > Made with SVGHMI. https://beremiz.org
        html xmlns="http://www.w3.org/1999/xhtml" {
            head;
            body style="margin:0;" {
                xsl:copy {
                    comment {
                        apply "$hmi_geometry", mode="testgeo";
                    }
                    comment {
                        apply "$hmitree", mode="testtree";
                    }
                    comment {
                        apply "$indexed_hmitree", mode="testtree";
                    }
                    apply "@* | node()", mode="identity_svg";
                }
                script{
                    call "scripts";
                }
            }
        }
    }

    func:function name="func:parselabel" {
        param "label";
        const "description", "substring-after($label,'HMI:')";

        const "_args", "substring-before($description,'@')";
        const "args" choose {
            when "$_args" value "$_args";
            otherwise value "$description";
        }

        const "_type", "substring-before($args,':')";
        const "type" choose {
            when "$_type" value "$_type";
            otherwise value "$args";
        }

        const "ast" if "$type" widget {
            attrib "type" > «$type»
            foreach "str:split(substring-after($args, ':'), ':')" {
                arg {
                    attrib "value" > «.»
                }
            }
            const "paths", "substring-after($description,'@')";
            foreach "str:split($paths, '@')" {
                path {
                    attrib "value" > «.»
                }
            }
        }

        func:result select="exsl:node-set($ast)"
    }

    function "scripts"
    {
        | //(function(){
        |
        | var hmi_hash = [«$hmitree/@hash»]; 

        /* TODO re-enable
        ||
        function evaluate_js_from_descriptions() {
            var Page;
            var Input;
            var Display;
            var res = [];
        ||
        const "midmark" > \n«$mark»
        apply """//*[contains(child::svg:desc, $midmark) or \
                     starts-with(child::svg:desc, $mark)]""",2 
              mode="code_from_descs";
        ||
            return res;
        }
        ||
        */

        | var hmi_widgets = {
        foreach "$hmi_elements" {
            const "widget", "func:parselabel(@inkscape:label)/widget";
            | «@id»: {
            |     type: "«$widget/@type»",
            |     args: [
            foreach "$widget/arg"
            |         "«@value»"`if "position()!=last()" > ,`
            |     ],
            |     indexes: [
            foreach "$widget/path" {
                const "hmipath","@value";
                const "hmitree_match","$indexed_hmitree/*[@hmipath = $hmipath]";
                if "count($hmitree_match) = 0"
                    error > No match for HMI «$hmipath»;
            |         «$hmitree_match/@index»`if "position()!=last()" > ,`
            }
            |     ],
            |     element: document.getElementById("«@id»"),
            apply "$widget", mode="widget_defs" with "hmi_element",".";
            | }`if "position()!=last()" > ,`
        }
        | }
        |
        | var hmitree_types = [

        foreach "$indexed_hmitree/*" {
            | /* «@index»  «@hmipath» */ "«substring(local-name(), 5)»"`if "position()!=last()" > ,`
        }

        | ]
        |
        | var page_desc = {

        foreach "$hmi_pages" {
            const "desc", "func:parselabel(@inkscape:label)/widget";
            const "page", "."; 
            const "p", "$hmi_geometry[@Id = $page/@id]"; 
            const "page_ids","""$hmi_geometry[@Id != $page/@id and 
                                @x >= $p/@x and @y >= $p/@y and 
                                @x+@w <= $p/@x+$p/@w and @y+@h <= $p/@y+$p/@h]/@Id""";
            const "page_elements", "$hmi_elements[@id = $page_ids]";
            |     "«$desc/arg[1]/@value»": {
            |         id: "«@id»",
            |         widgets: [
            foreach "$page_ids" {
            |             hmi_widgets.«.»`if "position()!=last()" > ,`
            }
            |         ]
            |     }`if "position()!=last()" > ,`
        }
        | }

        |
        | var default_page = "«$default_page»";

        include text svghmi.js
        | //})();
    }

    /*
        Parses:
        "HMI:WidgetType:param1:param2@path1@path2"

        Into:
        widget type="WidgetType" {
            arg value="param1";
            arg value="param2";
            path value="path1";
            path value="path2";
        }
    */

    template "*", mode="page_desc" {
    }

    template "*", mode="code_from_descs" {
        ||
        {
            var path, role, name, priv;
            var id = "«@id»";
        ||

        /* if label is used, use it as default name */
        if "@inkscape:label"
            |> name = "«@inkscape:label»";

        | /* -------------- */

        // this breaks indent, but fixing indent could break string literals
        value "substring-after(svg:desc, $mark)";
        // nobody reads generated code anyhow...

        ||

            /* -------------- */
            res.push({
                path:path,
                role:role,
                name:name,
                priv:priv
            })
        }
        ||
    }


    template "bbox", mode="testgeo"{
        | ID: «@Id» x: «@x» y: «@y» w: «@w» h: «@h»
    }

    template "*", mode="testtree"{
        param "indent", "''";
        > «$indent» «local-name()» 
        foreach "@*" > «local-name()»=«.» 
        > \n
        apply "*", mode="testtree" {
            with "indent" value "concat($indent,'>')"
        };
    }

    template "widget[@type='Display']", mode="widget_defs" {
        param "hmi_element";
        | frequency: 5,
        | dispatch: function(value) {
        choose {
            when "$hmi_element[self::svg:text]"{
            // TODO : care about <tspan> ?
        |   this.element.textContent = String(value);
            }
            otherwise {
                error > Display widget as a group not implemented
            }
        }
        | },

    }
    template "widget[@type='Meter']", mode="widget_defs" {
        | frequency: 10,
    }
    template "widget[@type='Input']", mode="widget_defs" {
        param "hmi_element";
        | frequency: 5,
        const "value_elt_id","$hmi_element//*[self::svg:text][@inkscape:label='value'][1]/@id";
        if "not($value_elt_id)" error > Input widget must have a text element
        | value_elt: document.getElementById("«$value_elt_id»"),
        | dispatch: function(value) {
        |     this.value_elt.textContent = String(value);
        | },
        const "edit_elt_id","$hmi_element/*[@inkscape:label='edit'][1]/@id";
        | init: function() {
        if "$edit_elt_id" {
        |     document.getElementById("«$edit_elt_id»").addEventListener(
        |         "click", 
        |         evt => alert('XXX TODO : Edit value'));
        }
        foreach "$hmi_element/*[regexp:test(@inkscape:label,'^[=+\-][0-9]+')]" {
        |     document.getElementById("«@id»").addEventListener(
        |         "click", 
        |         evt => {let new_val = change_hmi_value(this.indexes[0], "«@inkscape:label»");
        |                 this.value_elt.textContent = String(new_val);});
                          /* could gray out value until refreshed */
        }
        | },
    }
    template "widget[@type='Button']", mode="widget_defs" {
    }
    template "widget[@type='Toggle']", mode="widget_defs" {
        |     frequency: 5,
    }
    template "widget[@type='Change']", mode="widget_defs" {
        // HMI:Change:-10@/PUMP/VALUE 
        // HMI:Change:+1@/PUMP/VALUE 
        // HMI:Change:=42@/PUMP/VALUE 
        |     frequency: 5,
    }
    //    |     frequency: 10`apply ".", mode="refresh_frequency"`,
    // template "widget", mode="refresh_frequency" > 10
    /*
    template "widget[@type='Meter']", mode="refresh_frequency" > 10
    template "widget[@type='Display']", mode="refresh_frequency" > 5
    template "widget[@type='Input']", mode="refresh_frequency" > 5
    */
}