in xsl decl labels(*ptr, name="defs_by_labels") alias call-template {
with "hmi_element", "$hmi_element";
with "labels"{text *ptr};
decl optional_labels(*ptr) alias - {
/* TODO add some per label xslt variable to check if exist */
decl activable_labels(*ptr) alias - {
with "subelements","'active inactive'";
template "svg:*", mode="hmi_widgets" {
const "widget", "func:widget(@id)";
const "args" foreach "$widget/arg" > "«@value»"`if "position()!=last()" > ,`
const "indexes" foreach "$widget/path" {
error > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree
when "@type = 'PAGE_LOCAL'"
> "«substring(1,@value)»"`if "position()!=last()" > ,`
when "@type = 'HMI_LOCAL'"
> hmi_local_index("«@value»")`if "position()!=last()" > ,`
> «@index»`if "position()!=last()" > ,`
| "«@id»": new «$widget/@type»Widget ("«@id»",[«$args»],[«$indexes»],{
apply "$widget", mode="widget_defs" with "hmi_element",".";
| })`if "position()!=last()" > ,`
def "func:unique_types" {
when "count($elts_with_type) > 1" {
const "prior_results","func:unique_types($elts_with_type[position()!=last()])";
when "$elts_with_type[last()][@type = $prior_results/@type]"{
result "$prior_results | $elts_with_type[last()]";
result "$elts_with_type";
emit "preamble:local-variable-indexes" {
var last_remote_index = hmitree_types.length - 1;
var next_available_index = hmitree_types.length;
function page_local_index(varname, pagename){
let pagevars = hmi_locals[pagename];
if(pagevars == undefined){
let new_index = next_available_index++;
hmi_locals[pagename] = {varname:new_index}
let result = pagevars[varname];
let new_index = next_available_index++;
pagevars[varname] = new_index;
function hmi_local_index(varname){
return page_local_index(varname, "HMI_LOCAL");
emit "preamble:widget-base-class" {
frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */
constructor(elt_id,args,indexes,members){
this.element_id = elt_id;
this.element = id(elt_id);
Object.keys(members).forEach(prop => this[prop]=members[prop]);
for(let i = 0; i < this.indexes.length; i++) {
let index = this.indexes[i];
subscribers[index].delete(this);
this.relativeness = undefined;
sub(new_offset=0, relativeness, container_id){
this.offset = new_offset;
this.relativeness = relativeness;
this.container_id = container_id ;
/* add this's subsribers */
for(let i = 0; i < this.indexes.length; i++) {
let index = this.get_variable_index(i);
if(index > last_remote_index) return;
subscribers[index].add(this);
need_cache_apply.push(this);
if(!this.unsubscribable) for(let i = 0; i < this.indexes.length; i++) {
/* dispatch current cache in newly opened page widgets */
let realindex = this.get_variable_index(i);
let cached_val = cache[realindex];
if(cached_val != undefined)
this.new_hmi_value(realindex, cached_val, cached_val);
get_variable_index(varnum) {
let index = this.indexes[varnum];
if(typeof(index) == "string"){
let page = this.relativeness[varnum];
index = page_local_index(index, this.container_id);
if(this.relativeness[varnum]){
change_hmi_value(index,opstr) {
return change_hmi_value(this.get_variable_index(index), opstr);
apply_hmi_value(index, new_val) {
return apply_hmi_value(this.get_variable_index(0), new_val);
new_hmi_value(index, value, oldval) {
// TODO avoid searching, store index at sub()
for(let i = 0; i < this.indexes.length; i++) {
let refindex = this.get_variable_index(i);
if(typeof(d) == "function"){
d.call(this, value, oldval, i);
else if(typeof(d) == "object"){
d[i].call(this, value, oldval);
/* else dispatch_0, ..., dispatch_n ? */
throw new Error("Dunno how to dispatch to widget at index = " + index);
emit "declarations:hmi-classes" {
const "used_widget_types", "func:unique_types($parsed_widgets/widget)";
apply "$used_widget_types", mode="widget_class";
template "widget", mode="widget_class"
class «@type»Widget extends Widget{
/* empty class, as «@type» widget didn't provide any */
const "excluded_types", "str:split('Page Lang')";
const "excluded_ids","$parsed_widgets/widget[not(@type = $excluded_types)]/@id";
emit "declarations:hmi-elements" {
apply "$hmi_elements[@id = $excluded_ids]", mode="hmi_widgets";
function "defs_by_labels" {
param "mandatory","'yes'";
param "subelements","/..";
const "widget_type","@type";
foreach "str:split($labels)" {
const "elt","$result_svg_ns//*[@id = $hmi_element/@id]//*[@inkscape:label=$name][1]";
error > «$widget_type» widget must have a «$name» element
// otherwise produce nothing
| «$name»_elt: id("«$elt/@id»"),
foreach "str:split($subelements)" {
const "subelt","$elt/*[@inkscape:label=$subname][1]";
when "not($subelt/@id)" {
error > «$widget_type» widget must have a «$name»/«$subname» element
| /* missing «$name»/«$subname» element */
| "«$subname»": id("«$subelt/@id»")`if "position()!=last()" > ,`
def "func:escape_quotes" {
// have to use a python string to enter escaped quote
const "frst", !"substring-before($txt,'\"')"!;
const "frstln", "string-length($frst)";
when "$frstln > 0 and string-length($txt) > $frstln" {
result !"concat($frst,'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!;