SVGHMI: Refactor the way JsonTable generate javascript code to access json data. Now support multiple assignments, used in the case of text to change both content and style on the same element. svghmi
authorEdouard Tisserant
Thu, 20 Aug 2020 13:56:21 +0200
branchsvghmi
changeset 3031 440d74319a74
parent 3030 2d13a4379e2f
child 3032 2f6dfb99d094
SVGHMI: Refactor the way JsonTable generate javascript code to access json data. Now support multiple assignments, used in the case of text to change both content and style on the same element.
svghmi/gen_index_xhtml.xslt
svghmi/widget_jsontable.ysl2
svghmi/widget_list.ysl2
tests/svghmi/py_ext_0@py_ext/pyfile.xml
tests/svghmi/svghmi_0@svghmi/svghmi.svg
--- a/svghmi/gen_index_xhtml.xslt	Thu Aug 20 13:52:00 2020 +0200
+++ b/svghmi/gen_index_xhtml.xslt	Thu Aug 20 13:56:21 2020 +0200
@@ -637,7 +637,10 @@
     <xsl:text>
 </xsl:text>
   </xsl:template>
-  <xsl:template mode="inline_svg" match="@* | node()">
+  <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="@*">
+    <xsl:copy/>
+  </xsl:template>
+  <xsl:template mode="inline_svg" match="node()">
     <xsl:if test="not(@id = $discardable_elements/@id)">
       <xsl:copy>
         <xsl:apply-templates mode="inline_svg" select="@* | node()"/>
@@ -672,19 +675,24 @@
   </xsl:template>
   <xsl:variable name="hmi_lists_descs" select="$parsed_widgets/widget[@type = 'List']"/>
   <xsl:variable name="hmi_lists" select="$hmi_elements[@id = $hmi_lists_descs/@id]"/>
-  <xsl:variable name="targets_not_to_unlink" select="$hmi_elements[@id = $hmi_lists/@id]/descendant::svg:*"/>
+  <xsl:variable name="targets_not_to_unlink" select="$hmi_lists/descendant-or-self::svg:*"/>
   <xsl:variable name="to_unlink" select="$hmi_elements[not(@id = $hmi_pages/@id)]/descendant-or-self::svg:use"/>
+  <func:function name="func:is_unlinkable">
+    <xsl:param name="targetid"/>
+    <xsl:param name="eltid"/>
+    <func:result select="$eltid = $to_unlink/@id and not($targetid = $targets_not_to_unlink/@id)"/>
+  </func:function>
   <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="svg:use">
     <xsl:variable name="targetid" select="substring-after(@xlink:href,'#')"/>
     <xsl:choose>
-      <xsl:when test="@id = $to_unlink/@id and not($targetid = $targets_not_to_unlink/@id)">
+      <xsl:when test="func:is_unlinkable($targetid, @id)">
         <xsl:call-template name="unlink_clone">
           <xsl:with-param name="targetid" select="$targetid"/>
         </xsl:call-template>
       </xsl:when>
       <xsl:otherwise>
         <xsl:copy>
-          <xsl:apply-templates mode="inline_svg" select="@* | node()"/>
+          <xsl:apply-templates mode="inline_svg" select="@*"/>
         </xsl:copy>
       </xsl:otherwise>
     </xsl:choose>
@@ -705,6 +713,9 @@
     <name>
       <xsl:text>y</xsl:text>
     </name>
+    <name>
+      <xsl:text>id</xsl:text>
+    </name>
   </xsl:variable>
   <xsl:variable name="excluded_use_attrs" select="exsl:node-set($_excluded_use_attrs)"/>
   <xsl:variable name="_merge_use_attrs">
@@ -718,8 +729,27 @@
   <xsl:variable name="merge_use_attrs" select="exsl:node-set($_merge_use_attrs)"/>
   <xsl:template xmlns="http://www.w3.org/2000/svg" name="unlink_clone">
     <xsl:param name="targetid"/>
+    <xsl:param name="seed" select="''"/>
     <xsl:variable name="target" select="//svg:*[@id = $targetid]"/>
+    <xsl:variable name="seeded_id">
+      <xsl:choose>
+        <xsl:when test="string-length($seed) &gt; 0">
+          <xsl:value-of select="$seed"/>
+          <xsl:text>_</xsl:text>
+          <xsl:value-of select="@id"/>
+        </xsl:when>
+        <xsl:otherwise>
+          <xsl:value-of select="@id"/>
+        </xsl:otherwise>
+      </xsl:choose>
+    </xsl:variable>
     <g>
+      <xsl:attribute name="id">
+        <xsl:value-of select="$seeded_id"/>
+      </xsl:attribute>
+      <xsl:attribute name="original">
+        <xsl:value-of select="@id"/>
+      </xsl:attribute>
       <xsl:choose>
         <xsl:when test="$target[self::svg:g]">
           <xsl:for-each select="@*[not(local-name() = $excluded_use_attrs/name | $merge_use_attrs)]">
@@ -746,7 +776,7 @@
             </xsl:attribute>
           </xsl:if>
           <xsl:apply-templates mode="unlink_clone" select="$target/*">
-            <xsl:with-param name="seed" select="@id"/>
+            <xsl:with-param name="seed" select="$seeded_id"/>
           </xsl:apply-templates>
         </xsl:when>
         <xsl:otherwise>
@@ -756,7 +786,7 @@
             </xsl:attribute>
           </xsl:for-each>
           <xsl:apply-templates mode="unlink_clone" select="$target">
-            <xsl:with-param name="seed" select="@id"/>
+            <xsl:with-param name="seed" select="$seeded_id"/>
           </xsl:apply-templates>
         </xsl:otherwise>
       </xsl:choose>
@@ -769,10 +799,32 @@
       <xsl:text>_</xsl:text>
       <xsl:value-of select="."/>
     </xsl:attribute>
+    <xsl:attribute name="original">
+      <xsl:value-of select="."/>
+    </xsl:attribute>
   </xsl:template>
   <xsl:template xmlns="http://www.w3.org/2000/svg" mode="unlink_clone" match="@*">
     <xsl:copy/>
   </xsl:template>
+  <xsl:template xmlns="http://www.w3.org/2000/svg" mode="unlink_clone" match="svg:use">
+    <xsl:param name="seed"/>
+    <xsl:variable name="targetid" select="substring-after(@xlink:href,'#')"/>
+    <xsl:choose>
+      <xsl:when test="func:is_unlinkable($targetid, @id)">
+        <xsl:call-template name="unlink_clone">
+          <xsl:with-param name="targetid" select="$targetid"/>
+          <xsl:with-param name="seed" select="$seed"/>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:copy>
+          <xsl:apply-templates mode="unlink_clone" select="@*">
+            <xsl:with-param name="seed" select="$seed"/>
+          </xsl:apply-templates>
+        </xsl:copy>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
   <xsl:template xmlns="http://www.w3.org/2000/svg" mode="unlink_clone" match="svg:*">
     <xsl:param name="seed"/>
     <xsl:choose>
@@ -1024,8 +1076,6 @@
 </xsl:text>
     <xsl:text>    let defaultval = local_defaults[varname];
 </xsl:text>
-    <xsl:text>    console.log("page_local_index creat local", varname, pagename, new_index, defaultval);
-</xsl:text>
     <xsl:text>    if(defaultval != undefined) 
 </xsl:text>
     <xsl:text>        cache[new_index] = defaultval; 
@@ -3365,60 +3415,177 @@
       <xsl:text>.</xsl:text>
     </xsl:message>
   </xsl:template>
+  <xsl:variable name="hmi_textstylelists_descs" select="$parsed_widgets/widget[@type = 'TextStyleList']"/>
+  <xsl:variable name="hmi_textstylelists" select="$hmi_elements[@id = $hmi_textstylelists_descs/@id]"/>
+  <xsl:variable name="textstylelist_related">
+    <xsl:for-each select="$hmi_textstylelists">
+      <list>
+        <xsl:attribute name="listid">
+          <xsl:value-of select="@id"/>
+        </xsl:attribute>
+        <xsl:for-each select="func:refered_elements(.)">
+          <elt>
+            <xsl:attribute name="eltid">
+              <xsl:value-of select="@id"/>
+            </xsl:attribute>
+          </elt>
+        </xsl:for-each>
+      </list>
+    </xsl:for-each>
+  </xsl:variable>
+  <xsl:variable name="textstylelist_related_ns" select="exsl:node-set($textstylelist_related)"/>
+  <func:function name="func:json_expressions">
+    <xsl:param name="expressions"/>
+    <xsl:param name="label"/>
+    <xsl:choose>
+      <xsl:when test="$label">
+        <xsl:variable name="suffixes" select="str:split($label)"/>
+        <xsl:variable name="res">
+          <xsl:for-each select="$suffixes">
+            <expression>
+              <xsl:variable name="suffix" select="."/>
+              <xsl:variable name="pos" select="position()"/>
+              <xsl:variable name="expr" select="$expressions[position() &lt;= $pos][last()]/expression"/>
+              <xsl:choose>
+                <xsl:when test="contains($suffix,'=')">
+                  <xsl:variable name="name" select="substring-before($suffix,'=')"/>
+                  <xsl:if test="$expr/@name[. != $name]">
+                    <xsl:message terminate="yes">
+                      <xsl:text>JsonTable : missplaced '=' or inconsistent names in Json data expressions.</xsl:text>
+                    </xsl:message>
+                  </xsl:if>
+                  <xsl:attribute name="name">
+                    <xsl:value-of select="$name"/>
+                  </xsl:attribute>
+                  <xsl:attribute name="content">
+                    <xsl:value-of select="$expr/@content"/>
+                    <xsl:value-of select="substring-after($suffix,'=')"/>
+                  </xsl:attribute>
+                </xsl:when>
+                <xsl:otherwise>
+                  <xsl:copy-of select="$expr/@name"/>
+                  <xsl:attribute name="content">
+                    <xsl:value-of select="$expr/@content"/>
+                    <xsl:value-of select="$suffix"/>
+                  </xsl:attribute>
+                </xsl:otherwise>
+              </xsl:choose>
+            </expression>
+          </xsl:for-each>
+        </xsl:variable>
+        <func:result select="exsl:node-set($res)"/>
+      </xsl:when>
+      <xsl:otherwise>
+        <func:result select="$expressions"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </func:function>
+  <xsl:variable name="initexpr">
+    <expression>
+      <xsl:attribute name="content">
+        <xsl:text>jdata</xsl:text>
+      </xsl:attribute>
+    </expression>
+  </xsl:variable>
+  <xsl:variable name="initexpr_ns" select="exsl:node-set($initexpr)"/>
   <xsl:template mode="json_table_elt_render" match="svg:use">
-    <xsl:param name="value_expr"/>
+    <xsl:param name="expressions"/>
     <xsl:variable name="targetid" select="substring-after(@xlink:href,'#')"/>
     <xsl:variable name="from_list" select="$hmi_lists[(@id | */@id) = $targetid]"/>
-    <xsl:if test="count($from_list) = 0">
-      <xsl:message terminate="yes">
-        <xsl:text>Clones (svg:use) in JsonTable Widget must point to a valid HMI:List widget or HMI:List item. Reference "</xsl:text>
-        <xsl:value-of select="@xlink:href"/>
-        <xsl:text>" is not valid.</xsl:text>
-      </xsl:message>
-    </xsl:if>
-    <xsl:text>        id("</xsl:text>
-    <xsl:value-of select="@id"/>
-    <xsl:text>").setAttribute("xlink:href", 
-</xsl:text>
-    <xsl:text>            "#"+hmi_widgets["</xsl:text>
-    <xsl:value-of select="$from_list/@id"/>
-    <xsl:text>"].items[</xsl:text>
-    <xsl:value-of select="$value_expr"/>
-    <xsl:text>]);
-</xsl:text>
+    <xsl:choose>
+      <xsl:when test="count($from_list) &gt; 0">
+        <xsl:text>        id("</xsl:text>
+        <xsl:value-of select="@id"/>
+        <xsl:text>").setAttribute("xlink:href", 
+</xsl:text>
+        <xsl:text>            "#"+hmi_widgets["</xsl:text>
+        <xsl:value-of select="$from_list/@id"/>
+        <xsl:text>"].items[</xsl:text>
+        <xsl:value-of select="$expressions/expression[1]/@content"/>
+        <xsl:text>]);
+</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:message terminate="no">
+          <xsl:text>Clones (svg:use) in JsonTable Widget must point to a valid HMI:List widget or item. Reference "</xsl:text>
+          <xsl:value-of select="@xlink:href"/>
+          <xsl:text>" is not valid and will not be updated.</xsl:text>
+        </xsl:message>
+      </xsl:otherwise>
+    </xsl:choose>
   </xsl:template>
   <xsl:template mode="json_table_elt_render" match="svg:text">
-    <xsl:param name="value_expr"/>
-    <xsl:text>        id("</xsl:text>
-    <xsl:value-of select="@id"/>
-    <xsl:text>").textContent = String(</xsl:text>
-    <xsl:value-of select="$value_expr"/>
-    <xsl:text>);
-</xsl:text>
-  </xsl:template>
+    <xsl:param name="expressions"/>
+    <xsl:variable name="value_expr" select="$expressions/expression[1]/@content"/>
+    <xsl:variable name="original" select="@original"/>
+    <xsl:variable name="from_textstylelist" select="$textstylelist_related_ns/list[elt/@eltid = $original]"/>
+    <xsl:choose>
+      <xsl:when test="count($from_textstylelist) &gt; 0">
+        <xsl:variable name="content_expr" select="$expressions/expression[2]/@content"/>
+        <xsl:if test="string-length($content_expr) = 0 or $expressions/expression[2]/@name != 'textContent'">
+          <xsl:message terminate="yes">
+            <xsl:text>Clones (svg:use) in JsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignement following value expression in label.</xsl:text>
+          </xsl:message>
+        </xsl:if>
+        <xsl:text>        {
+</xsl:text>
+        <xsl:text>          let elt = id("</xsl:text>
+        <xsl:value-of select="@id"/>
+        <xsl:text>");
+</xsl:text>
+        <xsl:text>          elt.textContent = String(</xsl:text>
+        <xsl:value-of select="$content_expr"/>
+        <xsl:text>);
+</xsl:text>
+        <xsl:text>          elt.style = hmi_widgets["</xsl:text>
+        <xsl:value-of select="$from_textstylelist/@listid"/>
+        <xsl:text>"].styles[</xsl:text>
+        <xsl:value-of select="$value_expr"/>
+        <xsl:text>];
+</xsl:text>
+        <xsl:text>        }
+</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:text>        id("</xsl:text>
+        <xsl:value-of select="@id"/>
+        <xsl:text>").textContent = String(</xsl:text>
+        <xsl:value-of select="$value_expr"/>
+        <xsl:text>);
+</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+  <func:function name="func:filter_non_widget_label">
+    <xsl:param name="elt"/>
+    <xsl:param name="widget_elts"/>
+    <xsl:variable name="eltid">
+      <xsl:choose>
+        <xsl:when test="$elt/@original">
+          <xsl:value-of select="$elt/@original"/>
+        </xsl:when>
+        <xsl:otherwise>
+          <xsl:value-of select="$elt/@id"/>
+        </xsl:otherwise>
+      </xsl:choose>
+    </xsl:variable>
+    <func:result select="$widget_elts[@id=$eltid]/@inkscape:label"/>
+  </func:function>
   <xsl:template mode="json_table_render" match="svg:*">
-    <xsl:param name="objname"/>
+    <xsl:param name="expressions"/>
+    <xsl:param name="widget_elts"/>
+    <xsl:variable name="label" select="func:filter_non_widget_label(., $widget_elts)"/>
     <xsl:apply-templates mode="json_table_elt_render" select=".">
-      <xsl:with-param name="value_expr">
-        <xsl:value-of select="$objname"/>
-        <xsl:value-of select="@inkscape:label"/>
-      </xsl:with-param>
+      <xsl:with-param name="expressions" select="func:json_expressions($expressions, $label)"/>
     </xsl:apply-templates>
   </xsl:template>
   <xsl:template mode="json_table_render" match="svg:g">
-    <xsl:param name="objname"/>
-    <xsl:text>        let obj_</xsl:text>
-    <xsl:value-of select="@id"/>
-    <xsl:text> = </xsl:text>
-    <xsl:value-of select="$objname"/>
-    <xsl:value-of select="@inkscape:label"/>
-    <xsl:text>;
-</xsl:text>
-    <xsl:apply-templates mode="json_table_render" select="*[@inkscape:label]">
-      <xsl:with-param name="objname">
-        <xsl:text>obj_</xsl:text>
-        <xsl:value-of select="@id"/>
-      </xsl:with-param>
+    <xsl:param name="expressions"/>
+    <xsl:param name="widget_elts"/>
+    <xsl:variable name="label" select="func:filter_non_widget_label(., $widget_elts)"/>
+    <xsl:apply-templates mode="json_table_render" select="*">
+      <xsl:with-param name="expressions" select="func:json_expressions($expressions, $label)"/>
+      <xsl:with-param name="widget_elts" select="$widget_elts"/>
     </xsl:apply-templates>
   </xsl:template>
   <xsl:template mode="widget_defs" match="widget[@type='JsonTable']">
@@ -3440,7 +3607,8 @@
     <xsl:text>    spread_json_data: function(jdata) {
 </xsl:text>
     <xsl:apply-templates mode="json_table_render" select="$data_elt/*">
-      <xsl:with-param name="objname" select="'jdata'"/>
+      <xsl:with-param name="expressions" select="$initexpr_ns"/>
+      <xsl:with-param name="widget_elts" select="$hmi_element/*[@inkscape:label = 'data']/descendant::svg:*"/>
     </xsl:apply-templates>
     <xsl:text>    }
 </xsl:text>
@@ -4079,7 +4247,7 @@
 </xsl:text>
     <xsl:for-each select="$hmi_element/*[@inkscape:label]">
       <xsl:text>        </xsl:text>
-      <xsl:value-of select="func:escape_quotes(@inkscape:label)"/>
+      <xsl:value-of select="@inkscape:label"/>
       <xsl:text>: "</xsl:text>
       <xsl:value-of select="@id"/>
       <xsl:text>",
@@ -4088,6 +4256,22 @@
     <xsl:text>    },
 </xsl:text>
   </xsl:template>
+  <xsl:template mode="widget_defs" match="widget[@type='TextStyleList']">
+    <xsl:param name="hmi_element"/>
+    <xsl:text>    styles: {
+</xsl:text>
+    <xsl:for-each select="$hmi_element/*[@inkscape:label]">
+      <xsl:variable name="style" select="func:refered_elements(.)[self::svg:text]/@style"/>
+      <xsl:text>        </xsl:text>
+      <xsl:value-of select="@inkscape:label"/>
+      <xsl:text>: "</xsl:text>
+      <xsl:value-of select="$style"/>
+      <xsl:text>",
+</xsl:text>
+    </xsl:for-each>
+    <xsl:text>    },
+</xsl:text>
+  </xsl:template>
   <xsl:template mode="widget_defs" match="widget[@type='Meter']">
     <xsl:param name="hmi_element"/>
     <xsl:text>    frequency: 10,
--- a/svghmi/widget_jsontable.ysl2	Thu Aug 20 13:52:00 2020 +0200
+++ b/svghmi/widget_jsontable.ysl2	Thu Aug 20 13:56:21 2020 +0200
@@ -41,44 +41,128 @@
 const "hmi_textstylelists_descs", "$parsed_widgets/widget[@type = 'TextStyleList']";
 const "hmi_textstylelists", "$hmi_elements[@id = $hmi_textstylelists_descs/@id]";
 
+const "textstylelist_related" foreach "$hmi_textstylelists" list {
+    attrib "listid" value "@id";
+    foreach "func:refered_elements(.)" elt {
+        attrib "eltid" value "@id";
+    }
+}
+const "textstylelist_related_ns", "exsl:node-set($textstylelist_related)";
+
+def "func:json_expressions" {
+    param "expressions";
+    param "label";
+
+    // compute javascript expressions to access JSON data
+    // desscribed in given svg element's "label"
+    // knowing that parent element already has given "expressions".
+
+    choose {
+        when "$label" {
+            const "suffixes", "str:split($label)";
+            const "res" foreach "$suffixes" expression {
+                const "suffix",".";
+                const "pos","position()";
+                // take last available expression (i.e can have more suffixes than expressions)
+                const "expr","$expressions[position() <= $pos][last()]/expression";
+                choose {
+                    when "contains($suffix,'=')" {
+                        const "name", "substring-before($suffix,'=')";
+                        if "$expr/@name[. != $name]" 
+                            error > JsonTable : missplaced '=' or inconsistent names in Json data expressions.
+                        attrib "name" value "$name";
+                        attrib "content" > «$expr/@content»«substring-after($suffix,'=')»
+                    }
+                    otherwise {
+                        copy "$expr/@name";
+                        attrib "content" > «$expr/@content»«$suffix»
+                    }
+                }
+            }
+            result "exsl:node-set($res)";
+        }
+        // Empty labels are ignored, expressions are then passed as-is.
+        otherwise result "$expressions";
+    }
+
+}
+
+const "initexpr" expression attrib "content" > jdata
+const "initexpr_ns", "exsl:node-set($initexpr)";
+
 template "svg:use", mode="json_table_elt_render" {
-    param "value_expr";
+    param "expressions";
     // cloned element must be part of a HMI:List
     const "targetid", "substring-after(@xlink:href,'#')";
     const "from_list", "$hmi_lists[(@id | */@id) = $targetid]";
-    const "from_textstylelist", "$hmi_textstylelists[(@id | */@id) = $targetid]";
 
     choose {
         when "count($from_list) > 0" {
             |         id("«@id»").setAttribute("xlink:href", 
             // obtain new target id from HMI:List widget
-            |             "#"+hmi_widgets["«$from_list/@id»"].items[«$value_expr»]);
-        }
-        when "count($from_textstylelist) > 0" {
-            |         console.log("from_textsylelist","«@id»", "«$value_expr»", «$value_expr»,
-            // obtain new style from HMI:TextStyleList widget
-            |             hmi_widgets["«$from_textstylelist/@id»"].items[«$value_expr»]);
+            |             "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»]);
         }
         otherwise
-            warning > Clones (svg:use) in JsonTable Widget must point to a valid HMI:List or HMI:TextStyleList widget or item. Reference "«@xlink:href»" is not valid and will not be updated.
+            warning > Clones (svg:use) in JsonTable Widget must point to a valid HMI:List widget or item. Reference "«@xlink:href»" is not valid and will not be updated.
     }
 }
 
 template "svg:text", mode="json_table_elt_render" {
-    param "value_expr";
-    |         id("«@id»").textContent = String(«$value_expr»);
+    param "expressions";
+    const "value_expr", "$expressions/expression[1]/@content";
+    const "original", "@original";
+    const "from_textstylelist", "$textstylelist_related_ns/list[elt/@eltid = $original]";
+    choose {
+
+        when "count($from_textstylelist) > 0" {
+            const "content_expr", "$expressions/expression[2]/@content";
+            if "string-length($content_expr) = 0 or $expressions/expression[2]/@name != 'textContent'"
+                error > Clones (svg:use) in JsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignement following value expression in label.
+            |         {
+            |           let elt = id("«@id»");
+            |           elt.textContent = String(«$content_expr»);
+            |           elt.style = hmi_widgets["«$from_textstylelist/@listid»"].styles[«$value_expr»];
+            |         }
+        }
+        otherwise {
+            |         id("«@id»").textContent = String(«$value_expr»);
+        }
+    }
+}
+
+
+// only labels comming from Json widget are counted in
+def "func:filter_non_widget_label" {
+    param "elt";
+    param "widget_elts";
+    const "eltid" choose {
+        when "$elt/@original" value "$elt/@original";
+        otherwise value "$elt/@id";
+    }
+    result "$widget_elts[@id=$eltid]/@inkscape:label";
 }
 
 template "svg:*", mode="json_table_render" {
-    param "objname";
-    apply ".", mode="json_table_elt_render" with "value_expr" > «$objname»«substring-before(@inkscape:label, ' ')»
+    param "expressions";
+    param "widget_elts";
+    const "label", "func:filter_non_widget_label(., $widget_elts)";
+    apply ".", mode="json_table_elt_render" {
+        with "expressions", "func:json_expressions($expressions, $label)";
+    }
 }
 
 template "svg:g", mode="json_table_render" {
-    param "objname";
-    |         let obj_«@id» = «$objname»«substring-before(@inkscape:label, ' ')»;
-    apply "*[@inkscape:label]", mode="json_table_render" 
-        with "objname" > obj_«@id»
+    param "expressions";
+    param "widget_elts";
+    /* TODO : use intermediate variables for optimization
+    foreach "$new_expressions"
+    |         let obj_«@id»_«position()» = «.»;
+    */
+    const "label", "func:filter_non_widget_label(., $widget_elts)";
+    apply "*", mode="json_table_render" {
+        with "expressions", "func:json_expressions($expressions, $label)";
+        with "widget_elts", "$widget_elts";
+    }
 }
 
 template "widget[@type='JsonTable']", mode="widget_defs" {
@@ -87,6 +171,9 @@
     optional_labels("forward backward cursor");
     const "data_elt", "$result_svg_ns//*[@id = $hmi_element/@id]/*[@inkscape:label = 'data']";
     |     spread_json_data: function(jdata) {
-    apply "$data_elt/*", mode="json_table_render" with "objname","'jdata'";
+    apply "$data_elt/*", mode="json_table_render" {
+        with "expressions","$initexpr_ns";
+        with "widget_elts","$hmi_element/*[@inkscape:label = 'data']/descendant::svg:*";
+    }
     |     }
 }
--- a/svghmi/widget_list.ysl2	Thu Aug 20 13:52:00 2020 +0200
+++ b/svghmi/widget_list.ysl2	Thu Aug 20 13:56:21 2020 +0200
@@ -1,10 +1,20 @@
 // widget_list.ysl2
 
-template "widget[@type='List' or @type='TextStyleList']", mode="widget_defs" {
+template "widget[@type='List']", mode="widget_defs" {
     param "hmi_element";
     |     items: {
     foreach "$hmi_element/*[@inkscape:label]" {
-    |         «func:escape_quotes(@inkscape:label)»: "«@id»",
+    |         «@inkscape:label»: "«@id»",
     }
     |     },
 }
+
+template "widget[@type='TextStyleList']", mode="widget_defs" {
+    param "hmi_element";
+    |     styles: {
+    foreach "$hmi_element/*[@inkscape:label]" {
+        const "style", "func:refered_elements(.)[self::svg:text]/@style";
+    |         «@inkscape:label»: "«$style»",
+    }
+    |     },
+}
--- a/tests/svghmi/py_ext_0@py_ext/pyfile.xml	Thu Aug 20 13:52:00 2020 +0200
+++ b/tests/svghmi/py_ext_0@py_ext/pyfile.xml	Thu Aug 20 13:56:21 2020 +0200
@@ -18,10 +18,10 @@
         newdata = request.content.getvalue()
         print newdata
         selected_alarms = [
-            {"name":"three", "sides":3},
-            {"name":"four", "sides":4},
-            {"name":"five", "sides":5},
-            {"name":"six", "sides":6},
+            {"name":"three", "sides":3, "textstyle":"alarm"},
+            {"name":"four", "sides":4, "textstyle":"ack"},
+            {"name":"five", "sides":5, "textstyle":"active"},
+            {"name":"six", "sides":6, "textstyle":"disabled"},
         ]
         return json.dumps(selected_alarms)
 
--- a/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Thu Aug 20 13:52:00 2020 +0200
+++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Thu Aug 20 13:56:21 2020 +0200
@@ -16,7 +16,7 @@
    version="1.1"
    id="hmi0"
    sodipodi:docname="svghmi.svg"
-   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
   <metadata
      id="metadata4542">
     <rdf:RDF>
@@ -182,17 +182,17 @@
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
      inkscape:document-units="px"
-     inkscape:current-layer="g907"
+     inkscape:current-layer="g1384"
      showgrid="false"
      units="px"
      inkscape:zoom="0.50000001"
-     inkscape:cx="632.83299"
-     inkscape:cy="-317.59408"
-     inkscape:window-width="2443"
-     inkscape:window-height="1567"
-     inkscape:window-x="816"
-     inkscape:window-y="103"
-     inkscape:window-maximized="0"
+     inkscape:cx="503.83301"
+     inkscape:cy="474.4059"
+     inkscape:window-width="1800"
+     inkscape:window-height="836"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
      showguides="true"
      inkscape:guide-bbox="true" />
   <rect
@@ -3236,7 +3236,7 @@
        inkscape:connector-curvature="0"
        id="path1320"
        d="M 130.96206,4.0725977 79.111776,-41.363223"
-       style="fill:none;fill-rule:evenodd;stroke:#ff3000;stroke-width:2.96333337;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0, 32.59666667;stroke-dashoffset:29.63333321;stroke-opacity:1;marker-end:url(#marker1656)" />
+       style="fill:none;fill-rule:evenodd;stroke:#ff3000;stroke-width:2.9633333;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:29.63333301;stroke-opacity:1;marker-end:url(#marker1656)" />
     <text
        xml:space="preserve"
        style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff6600;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"