Edouard@2753: include yslt_noindent.yml2 Edouard@2779: Edouard@2779: // overrides yslt's output function to set CDATA Edouard@2798: decl output(method, cdata-section-elements="xhtml:script"); Edouard@2792: Edouard@2808: in xsl decl labels(*ptr, name="defs_by_labels") alias call-template { Edouard@2808: with "hmi_element", "$hmi_element"; Edouard@2810: with "labels"{text *ptr}; Edouard@2808: }; Edouard@2808: Edouard@2792: istylesheet Edouard@2753: /* From Inkscape */ Edouard@2753: xmlns:dc="http://purl.org/dc/elements/1.1/" Edouard@2753: xmlns:cc="http://creativecommons.org/ns#" Edouard@2753: xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" Edouard@2753: xmlns:svg="http://www.w3.org/2000/svg" Edouard@2753: xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" Edouard@2753: xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" Edouard@2798: xmlns:xhtml="http://www.w3.org/1999/xhtml" Edouard@2753: Edouard@2753: /* Our namespace to invoke python code */ Edouard@2753: xmlns:ns="beremiz" Edouard@2793: extension-element-prefixes="ns func" Edouard@2793: exclude-result-prefixes="ns str regexp exsl func" { Edouard@2792: Edouard@2792: /* This retrieves geometry obtained through "inkscape -S" Edouard@2792: * already parsed by python and presented as a list of Edouard@2753: * Edouard@2753: */ Edouard@2791: const "geometry", "ns:GetSVGGeometry()"; Edouard@2791: const "hmitree", "ns:GetHMITree()"; Edouard@2790: Edouard@2808: const "svg_root_id", "/svg:svg/@id"; Edouard@2797: const "hmi_elements", "//svg:*[starts-with(@inkscape:label, 'HMI:')]"; Edouard@2794: const "hmi_geometry", "$geometry[@Id = $hmi_elements/@id]"; Edouard@2794: Edouard@2794: const "hmi_pages", "$hmi_elements[func:parselabel(@inkscape:label)/widget/@type = 'Page']"; Edouard@2794: Edouard@2795: const "default_page" choose { Edouard@2795: when "count($hmi_pages) > 1" { Edouard@2795: const "Home_page", Edouard@2795: "$hmi_pages[func:parselabel(@inkscape:label)/widget/arg[1]/@value = 'Home']"; Edouard@2795: choose { Edouard@2795: when "$Home_page" > Home Edouard@2795: otherwise { Edouard@2795: error "No Home page defined!"; Edouard@2795: } Edouard@2795: } Edouard@2795: } Edouard@2795: when "count($hmi_pages) = 0" { Edouard@2795: error "No page defined!"; Edouard@2795: } Edouard@2795: otherwise > «func:parselabel($hmi_pages/@inkscape:label)/widget/arg[1]/@value» Edouard@2795: } Edouard@2795: Edouard@2791: const "_categories" { Edouard@2790: noindex > HMI_ROOT Edouard@2814: noindex > HMI_NODE Edouard@2790: noindex > HMI_PLC_STATUS Edouard@2790: noindex > HMI_CURRENT_PAGE Edouard@2790: } Edouard@2791: const "categories", "exsl:node-set($_categories)"; Edouard@2791: const "_indexed_hmitree" apply "$hmitree", mode="index"; Edouard@2791: const "indexed_hmitree", "exsl:node-set($_indexed_hmitree)"; Edouard@2790: Edouard@2794: template "*", mode="index" { Edouard@2790: param "index", "0"; Edouard@2791: param "parentpath", "''"; Edouard@2791: const "content" { Edouard@2791: const "path" Edouard@2791: choose { Edouard@2791: when "local-name() = 'HMI_ROOT'" > «$parentpath» Edouard@2791: otherwise > «$parentpath»/«@name» Edouard@2791: } Edouard@2790: choose { Edouard@2790: when "not(local-name() = $categories/noindex)" { Edouard@2790: xsl:copy { Edouard@2790: attrib "index" > «$index» Edouard@2791: attrib "hmipath" > «$path» Edouard@2790: foreach "@*" xsl:copy; Edouard@2790: } Edouard@2790: /* no node expected below value nodes */ Edouard@2790: } Edouard@2790: otherwise { Edouard@2790: apply "*[1]", mode="index"{ Edouard@2790: with "index", "$index"; Edouard@2791: with "parentpath" > «$path» Edouard@2790: } Edouard@2790: } Edouard@2790: } Edouard@2790: } Edouard@2790: Edouard@2790: copy "$content"; Edouard@2790: apply "following-sibling::*[1]", mode="index" { Edouard@2790: with "index", "$index + count(exsl:node-set($content)/*)"; Edouard@2791: with "parentpath" > «$parentpath» Edouard@2790: } Edouard@2790: } Edouard@2790: Edouard@2753: /* Identity template : Edouard@2753: * - copy every attributes Edouard@2753: * - copy every sub-elements Edouard@2753: */ Edouard@2808: template "@* | node()", mode="inline_svg" { Edouard@2753: /* use real xsl:copy instead copy-of alias from yslt.yml2 */ Edouard@2808: xsl:copy apply "@* | node()", mode="inline_svg"; Edouard@2808: } Edouard@2808: Edouard@2808: /* replaces inkscape's height and width hints. forces fit */ Edouard@2808: template "svg:svg/@width", mode="inline_svg"; Edouard@2808: template "svg:svg/@height", mode="inline_svg"; Edouard@2808: template "svg:svg", mode="inline_svg" xsl:copy { Edouard@2808: attrib "preserveAspectRatio" > none Edouard@2808: attrib "height" > 100vh Edouard@2808: attrib "width" > 100vw Edouard@2808: apply "@* | node()", mode="inline_svg"; Edouard@2753: } Edouard@2753: Edouard@2794: /*const "mark" > =HMI=\n*/ Edouard@2782: Edouard@2753: /* copy root node and add geometry as comment for a test */ Edouard@2792: template "/" { Edouard@2793: comment > Made with SVGHMI. https://beremiz.org Edouard@2814: /* DEBUG DATA */ Edouard@2808: comment { Edouard@2808: apply "$hmi_geometry", mode="testgeo"; Edouard@2808: } Edouard@2808: comment { Edouard@2808: apply "$hmitree", mode="testtree"; Edouard@2808: } Edouard@2808: comment { Edouard@2808: apply "$indexed_hmitree", mode="testtree"; Edouard@2808: } Edouard@2814: /**/ Edouard@2792: html xmlns="http://www.w3.org/1999/xhtml" { Edouard@2792: head; Edouard@2808: body style="margin:0;overflow:hidden;" { Edouard@2808: apply "svg:svg", mode="inline_svg"; Edouard@2792: script{ Edouard@2792: call "scripts"; Edouard@2792: } Edouard@2792: } Edouard@2792: } Edouard@2792: } Edouard@2792: Edouard@2810: /* Edouard@2810: Parses: Edouard@2810: "HMI:WidgetType:param1:param2@path1@path2" Edouard@2810: Edouard@2810: Into: Edouard@2810: widget type="WidgetType" { Edouard@2810: arg value="param1"; Edouard@2810: arg value="param2"; Edouard@2810: path value="path1"; Edouard@2810: path value="path2"; Edouard@2810: } Edouard@2810: */ Edouard@2810: Edouard@2793: func:function name="func:parselabel" { Edouard@2793: param "label"; Edouard@2793: const "description", "substring-after($label,'HMI:')"; Edouard@2793: Edouard@2793: const "_args", "substring-before($description,'@')"; Edouard@2793: const "args" choose { Edouard@2793: when "$_args" value "$_args"; Edouard@2793: otherwise value "$description"; Edouard@2793: } Edouard@2793: Edouard@2793: const "_type", "substring-before($args,':')"; Edouard@2793: const "type" choose { Edouard@2793: when "$_type" value "$_type"; Edouard@2793: otherwise value "$args"; Edouard@2793: } Edouard@2793: Edouard@2793: const "ast" if "$type" widget { Edouard@2793: attrib "type" > «$type» Edouard@2794: foreach "str:split(substring-after($args, ':'), ':')" { Edouard@2793: arg { Edouard@2793: attrib "value" > «.» Edouard@2793: } Edouard@2793: } Edouard@2793: const "paths", "substring-after($description,'@')"; Edouard@2793: foreach "str:split($paths, '@')" { Edouard@2793: path { Edouard@2793: attrib "value" > «.» Edouard@2793: } Edouard@2793: } Edouard@2793: } Edouard@2793: Edouard@2793: func:result select="exsl:node-set($ast)" Edouard@2793: } Edouard@2793: Edouard@2792: function "scripts" Edouard@2792: { Edouard@2799: | //(function(){ Edouard@2798: | Edouard@2797: | var hmi_hash = [«$hmitree/@hash»]; Edouard@2792: Edouard@2792: /* TODO re-enable Edouard@2792: || Edouard@2792: function evaluate_js_from_descriptions() { Edouard@2792: var Page; Edouard@2792: var Input; Edouard@2792: var Display; Edouard@2792: var res = []; Edouard@2792: || Edouard@2792: const "midmark" > \n«$mark» Edouard@2792: apply """//*[contains(child::svg:desc, $midmark) or \ Edouard@2792: starts-with(child::svg:desc, $mark)]""",2 Edouard@2792: mode="code_from_descs"; Edouard@2792: || Edouard@2792: return res; Edouard@2792: } Edouard@2792: || Edouard@2792: */ Edouard@2792: Edouard@2797: | var hmi_widgets = { Edouard@2797: foreach "$hmi_elements" { Edouard@2797: const "widget", "func:parselabel(@inkscape:label)/widget"; Edouard@2797: | «@id»: { Edouard@2797: | type: "«$widget/@type»", Edouard@2797: | args: [ Edouard@2797: foreach "$widget/arg" Edouard@2797: | "«@value»"`if "position()!=last()" > ,` Edouard@2797: | ], Edouard@2798: | indexes: [ Edouard@2797: foreach "$widget/path" { Edouard@2797: const "hmipath","@value"; Edouard@2797: const "hmitree_match","$indexed_hmitree/*[@hmipath = $hmipath]"; Edouard@2797: if "count($hmitree_match) = 0" Edouard@2814: error > No match for path "«$hmipath»" in HMI tree Edouard@2797: | «$hmitree_match/@index»`if "position()!=last()" > ,` Edouard@2797: } Edouard@2800: | ], Edouard@2800: | element: document.getElementById("«@id»"), Edouard@2800: apply "$widget", mode="widget_defs" with "hmi_element","."; Edouard@2797: | }`if "position()!=last()" > ,` Edouard@2797: } Edouard@2797: | } Edouard@2797: | Edouard@2822: | var heartbeat_index = «$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index»; Edouard@2822: | Edouard@2798: | var hmitree_types = [ Edouard@2798: Edouard@2792: foreach "$indexed_hmitree/*" { Edouard@2798: | /* «@index» «@hmipath» */ "«substring(local-name(), 5)»"`if "position()!=last()" > ,` Edouard@2792: } Edouard@2792: Edouard@2797: | ] Edouard@2792: | Edouard@2792: | var page_desc = { Edouard@2792: Edouard@2794: foreach "$hmi_pages" { Edouard@2794: const "desc", "func:parselabel(@inkscape:label)/widget"; Edouard@2797: const "page", "."; Edouard@2797: const "p", "$hmi_geometry[@Id = $page/@id]"; Edouard@2797: const "page_ids","""$hmi_geometry[@Id != $page/@id and Edouard@2797: @x >= $p/@x and @y >= $p/@y and Edouard@2797: @x+@w <= $p/@x+$p/@w and @y+@h <= $p/@y+$p/@h]/@Id"""; Edouard@2797: const "page_elements", "$hmi_elements[@id = $page_ids]"; Edouard@2794: | "«$desc/arg[1]/@value»": { Edouard@2795: | id: "«@id»", Edouard@2808: | bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»], Edouard@2794: | widgets: [ Edouard@2797: foreach "$page_ids" { Edouard@2798: | hmi_widgets.«.»`if "position()!=last()" > ,` Edouard@2797: } Edouard@2797: | ] Edouard@2797: | }`if "position()!=last()" > ,` Edouard@2792: } Edouard@2792: | } Edouard@2792: Edouard@2795: | Edouard@2795: | var default_page = "«$default_page»"; Edouard@2808: | var svg_root = document.getElementById("«$svg_root_id»"); Edouard@2792: include text svghmi.js Edouard@2799: | //})(); Edouard@2792: } Edouard@2792: Edouard@2810: // template "*", mode="code_from_descs" { Edouard@2810: // || Edouard@2810: // { Edouard@2810: // var path, role, name, priv; Edouard@2810: // var id = "«@id»"; Edouard@2810: // || Edouard@2810: Edouard@2810: // /* if label is used, use it as default name */ Edouard@2810: // if "@inkscape:label" Edouard@2810: // |> name = "«@inkscape:label»"; Edouard@2810: Edouard@2810: // | /* -------------- */ Edouard@2810: Edouard@2810: // // this breaks indent, but fixing indent could break string literals Edouard@2810: // value "substring-after(svg:desc, $mark)"; Edouard@2810: // // nobody reads generated code anyhow... Edouard@2810: Edouard@2810: // || Edouard@2810: Edouard@2810: // /* -------------- */ Edouard@2810: // res.push({ Edouard@2810: // path:path, Edouard@2810: // role:role, Edouard@2810: // name:name, Edouard@2810: // priv:priv Edouard@2810: // }) Edouard@2810: // } Edouard@2810: // || Edouard@2810: // } Edouard@2810: Edouard@2810: Edouard@2814: /**/ Edouard@2753: template "bbox", mode="testgeo"{ Edouard@2763: | ID: «@Id» x: «@x» y: «@y» w: «@w» h: «@h» Edouard@2763: } Edouard@2763: Edouard@2763: template "*", mode="testtree"{ Edouard@2763: param "indent", "''"; Edouard@2790: > «$indent» «local-name()» Edouard@2790: foreach "@*" > «local-name()»=«.» Edouard@2790: > \n Edouard@2763: apply "*", mode="testtree" { Edouard@2763: with "indent" value "concat($indent,'>')" Edouard@2763: }; Edouard@2753: } Edouard@2814: /**/ Edouard@2808: Edouard@2808: function "defs_by_labels" { Edouard@2808: param "labels","''"; Edouard@2808: param "mandatory","'yes'"; Edouard@2808: param "hmi_element"; Edouard@2808: foreach "str:split($labels)" { Edouard@2808: const "name","."; Edouard@2808: const "elt_id","$hmi_element//*[@inkscape:label=$name][1]/@id"; Edouard@2808: if "$mandatory='yes' and not($elt_id)" error > Meter widget must have a «$name» element Edouard@2808: | «$name»_elt: document.getElementById("«$elt_id»"), Edouard@2808: } Edouard@2808: } Edouard@2808: Edouard@2808: Edouard@2800: template "widget[@type='Display']", mode="widget_defs" { Edouard@2800: param "hmi_element"; Edouard@2800: | frequency: 5, Edouard@2800: | dispatch: function(value) { Edouard@2800: choose { Edouard@2800: when "$hmi_element[self::svg:text]"{ Edouard@2800: // TODO : care about ? Edouard@2800: | this.element.textContent = String(value); Edouard@2800: } Edouard@2800: otherwise { Edouard@2801: error > Display widget as a group not implemented Edouard@2800: } Edouard@2800: } Edouard@2800: | }, Edouard@2800: Edouard@2800: } Edouard@2800: template "widget[@type='Meter']", mode="widget_defs" { Edouard@2807: param "hmi_element"; Edouard@2801: | frequency: 10, Edouard@2808: labels("value min max needle range"); Edouard@2807: | dispatch: function(value) { Edouard@2807: | this.value_elt.textContent = String(value); Edouard@2807: | let [min,max,totallength] = this.range; Edouard@2807: | let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min))); Edouard@2807: | let tip = this.range_elt.getPointAtLength(length); Edouard@2810: // TODO : deal with transformations between needle and range Edouard@2807: | this.needle_elt.setAttribute('d', "M "+this.origin.x+","+this.origin.y+" "+tip.x+","+tip.y); Edouard@2807: | }, Edouard@2807: | origin: undefined, Edouard@2807: | range: undefined, Edouard@2807: | init: function() { Edouard@2807: | this.range = [Number(this.min_elt.textContent), Number(this.max_elt.textContent), this.range_elt.getTotalLength()] Edouard@2807: | this.origin = this.needle_elt.getPointAtLength(0); Edouard@2807: | }, Edouard@2807: } Edouard@2807: Edouard@2829: def "func:escape_quotes" { Edouard@2829: param "txt"; Edouard@2829: // have to use a python string to enter escaped quote Edouard@2829: const "frst", !"substring-before($txt,'\"')"!; Edouard@2829: const "frstln", "string-length($frst)"; Edouard@2829: choose { Edouard@2829: when "$frstln > 0 and string-length($txt) > $frstln" { Edouard@2829: result !"concat($frst,'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!; Edouard@2829: } Edouard@2829: otherwise { Edouard@2829: result "$txt"; Edouard@2829: } Edouard@2829: } Edouard@2829: } Edouard@2829: Edouard@2800: template "widget[@type='Input']", mode="widget_defs" { Edouard@2801: param "hmi_element"; Edouard@2801: | frequency: 5, Edouard@2808: labels("value"); Edouard@2801: | dispatch: function(value) { Edouard@2801: | this.value_elt.textContent = String(value); Edouard@2801: | }, Edouard@2801: const "edit_elt_id","$hmi_element/*[@inkscape:label='edit'][1]/@id"; Edouard@2801: | init: function() { Edouard@2801: if "$edit_elt_id" { Edouard@2801: | document.getElementById("«$edit_elt_id»").addEventListener( Edouard@2801: | "click", Edouard@2801: | evt => alert('XXX TODO : Edit value')); Edouard@2801: } Edouard@2829: foreach "$hmi_element/*[regexp:test(@inkscape:label,'^[=+\-].+')]" { Edouard@2801: | document.getElementById("«@id»").addEventListener( Edouard@2801: | "click", Edouard@2829: | evt => {let new_val = change_hmi_value(this.indexes[0], "«func:escape_quotes(@inkscape:label)»"); Edouard@2806: | this.value_elt.textContent = String(new_val);}); Edouard@2806: /* could gray out value until refreshed */ Edouard@2801: } Edouard@2801: | }, Edouard@2801: } Edouard@2801: template "widget[@type='Button']", mode="widget_defs" { Edouard@2801: } Edouard@2801: template "widget[@type='Toggle']", mode="widget_defs" { Edouard@2801: | frequency: 5, Edouard@2801: } Edouard@2801: template "widget[@type='Change']", mode="widget_defs" { Edouard@2800: | frequency: 5, Edouard@2800: } Edouard@2808: template "widget[@type='Jump']", mode="widget_defs" { Edouard@2808: | init: function() { Edouard@2808: | this.element.addEventListener( Edouard@2808: | "click", Edouard@2808: | evt => switch_page(this.args[0])); Edouard@2808: | }, Edouard@2808: } Edouard@2753: }