--- a/svghmi/gen_index_xhtml.xslt Wed Aug 12 13:36:18 2020 +0200
+++ b/svghmi/gen_index_xhtml.xslt Wed Aug 12 15:24:02 2020 +0200
@@ -850,7 +850,7 @@
<xsl:variable name="args">
<xsl:for-each select="$widget/arg">
- <xsl:value-of select="@value"/>
+ <xsl:value-of select="func:escape_quotes(@value)"/> <xsl:if test="position()!=last()">
@@ -959,6 +959,34 @@
+ <xsl:text>const local_defaults = { + <xsl:for-each select="$parsed_widgets/widget[@type = 'VarInit']"> + <xsl:if test="count(path) != 1"> + <xsl:message terminate="yes"> + <xsl:text>VarInit </xsl:text> + <xsl:value-of select="@id"/> + <xsl:text> must have only one variable given.</xsl:text> + <xsl:if test="path/@type != 'PAGE_LOCAL' and path/@type != 'HMI_LOCAL'"> + <xsl:message terminate="yes"> + <xsl:text>VarInit </xsl:text> + <xsl:value-of select="@id"/> + <xsl:text> only applies to HMI variable.</xsl:text> + <xsl:value-of select="arg[0]"/> + <xsl:value-of select="path/@value"/> + <xsl:if test="position()!=last()"> <xsl:text>var cache = hmitree_types.map(_ignored => undefined);
@@ -975,20 +1003,12 @@
<xsl:text> hmi_locals[pagename] = {[varname]:new_index}
- <xsl:text> console.log("pagelocalindex insert",varname, pagename, new_index);
- <xsl:text> console.log("pagevars",pagevars);
<xsl:text> let result = pagevars[varname];
<xsl:text> if(result != undefined) {
- <xsl:text> console.log("pagelocalindex reuse",varname, pagename, result);
<xsl:text> return result;
@@ -999,12 +1019,8 @@
<xsl:text> pagevars[varname] = new_index;
- <xsl:text> console.log("pagelocalindex addwidget",varname, pagename, new_index);
- <xsl:text> cache[new_index] = "";
<xsl:text> return new_index;
@@ -1030,6 +1046,10 @@
+ <xsl:text>var pending_widget_animates = []; @@ -1038,6 +1058,10 @@
<xsl:text> unsubscribable = false;
+ <xsl:text> pending_animate = false; <xsl:text> constructor(elt_id,args,indexes,members){
<xsl:text> this.element_id = elt_id;
@@ -1108,11 +1132,11 @@
<xsl:text> apply_cache() {
- <xsl:text> if(!this.unsubscribable) for(let i = 0; i < this.indexes.length; i++) {
+ <xsl:text> if(!this.unsubscribable) for(let index of this.indexes){ <xsl:text> /* dispatch current cache in newly opened page widgets */
- <xsl:text> let realindex = this.get_variable_index(i);
+ <xsl:text> let realindex = this.get_variable_index(index); <xsl:text> let cached_val = cache[realindex];
@@ -1166,7 +1190,7 @@
<xsl:text> new_hmi_value(index, value, oldval) {
<xsl:text> // TODO avoid searching, store index at sub()
@@ -1206,11 +1230,39 @@
- <xsl:text> /* } catch(err) {
+ <xsl:text> } catch(err) { <xsl:text> console.log(err);
+ <xsl:text> this.animate(); + <xsl:text> this.pending_animate = false; + <xsl:text> request_animate(){ + <xsl:text> if(!this.pending_animate){ + <xsl:text> pending_widget_animates.push(this); + <xsl:text> this.pending_animate = true; + <xsl:text> requestHMIAnimation(); @@ -1246,8 +1298,8 @@
- <xsl:variable name="excluded_types" select="str:split('Page Lang')"/>
- <xsl:variable name="excluded_ids" select="$parsed_widgets/widget[not(@type = $excluded_types)]/@id"/>
+ <xsl:variable name="excluded_types" select="str:split('Page Lang VarInit')"/> + <xsl:variable name="included_ids" select="$parsed_widgets/widget[not(@type = $excluded_types)]/@id"/> <declarations:hmi-elements/>
<xsl:template match="declarations:hmi-elements">
@@ -1260,7 +1312,7 @@
<xsl:text>var hmi_widgets = {
- <xsl:apply-templates mode="hmi_widgets" select="$hmi_elements[@id = $excluded_ids]"/>
+ <xsl:apply-templates mode="hmi_widgets" select="$hmi_elements[@id = $included_ids]"/> @@ -1343,11 +1395,9 @@
<func:function name="func:escape_quotes">
- <xsl:variable name="frst" select="substring-before($txt,'"')"/>
- <xsl:variable name="frstln" select="string-length($frst)"/>
- <xsl:when test="$frstln > 0 and string-length($txt) > $frstln">
- <func:result select="concat($frst,'\"',func:escape_quotes(substring-after($txt,'"')))"/>
+ <xsl:when test="contains($txt,'"')"> + <func:result select="concat(substring-before($txt,'"'),'\"',func:escape_quotes(substring-after($txt,'"')))"/> <func:result select="$txt"/>
@@ -1380,6 +1430,78 @@
+ <xsl:template mode="widget_class" match="widget[@type='Button']"> + <xsl:text>class ButtonWidget extends Widget{ + <xsl:text> frequency = 5; + <xsl:text> active_style = undefined; + <xsl:text> inactive_style = undefined; + <xsl:text> on_mouse_down(evt) { + <xsl:text> if (this.active_style && this.inactive_style) { + <xsl:text> this.active_elt.setAttribute("style", this.active_style); + <xsl:text> this.inactive_elt.setAttribute("style", "display:none"); + <xsl:text> this.apply_hmi_value(0, 1); + <xsl:text> on_mouse_up(evt) { + <xsl:text> if (this.active_style && this.inactive_style) { + <xsl:text> this.active_elt.setAttribute("style", "display:none"); + <xsl:text> this.inactive_elt.setAttribute("style", this.inactive_style); + <xsl:text> this.apply_hmi_value(0, 0); + <xsl:text> this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined; + <xsl:text> this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined; + <xsl:text> if (this.active_style && this.inactive_style) { + <xsl:text> this.active_elt.setAttribute("style", "display:none"); + <xsl:text> this.inactive_elt.setAttribute("style", this.inactive_style); + <xsl:text> this.element.setAttribute("onmousedown", "hmi_widgets["+this.element_id+"].on_mouse_down(evt)"); + <xsl:text> this.element.setAttribute("onmouseup", "hmi_widgets["+this.element_id+"].on_mouse_up(evt)"); <xsl:template mode="widget_defs" match="widget[@type='Button']">
<xsl:param name="hmi_element"/>
<xsl:call-template name="defs_by_labels">
@@ -1389,63 +1511,7 @@
<xsl:with-param name="mandatory" select="'no'"/>
- <xsl:text>frequency: 5,
- <xsl:text>on_mouse_down: function(evt) {
- <xsl:text> if (this.active_style && this.inactive_style) {
- <xsl:text> this.active_elt.setAttribute("style", this.active_style);
- <xsl:text> this.inactive_elt.setAttribute("style", "display:none");
- <xsl:text> this.apply_hmi_value(0, 1);
- <xsl:text>on_mouse_up: function(evt) {
- <xsl:text> if (this.active_style && this.inactive_style) {
- <xsl:text> this.active_elt.setAttribute("style", "display:none");
- <xsl:text> this.inactive_elt.setAttribute("style", this.inactive_style);
- <xsl:text> this.apply_hmi_value(0, 0);
- <xsl:text>active_style: undefined,
- <xsl:text>inactive_style: undefined,
- <xsl:text>init: function() {
- <xsl:text> this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
- <xsl:text> this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
- <xsl:text> if (this.active_style && this.inactive_style) {
- <xsl:text> this.active_elt.setAttribute("style", "display:none");
- <xsl:text> this.inactive_elt.setAttribute("style", this.inactive_style);
- <xsl:text> this.element.setAttribute("onmousedown", "hmi_widgets['</xsl:text>
- <xsl:value-of select="$hmi_element/@id"/>
- <xsl:text>'].on_mouse_down(evt)");
- <xsl:text> this.element.setAttribute("onmouseup", "hmi_widgets['</xsl:text>
- <xsl:value-of select="$hmi_element/@id"/>
- <xsl:text>'].on_mouse_up(evt)");
<xsl:template mode="widget_defs" match="widget[@type='CircularBar']">
@@ -1544,6 +1610,320 @@
+ <xsl:template mode="widget_class" match="widget[@type='CircularSlider']"> + <xsl:text>class CircularSliderWidget extends Widget{ + <xsl:text> frequency = 5; + <xsl:text> range = undefined; + <xsl:text> circle = undefined; + <xsl:text> handle_pos = undefined; + <xsl:text> drag = false; + <xsl:text> enTimer = false; + <xsl:text> dispatch(value) { + <xsl:text> if(!this.drag){ + <xsl:text> if(this.value_elt) + <xsl:text> this.value_elt.textContent = String(value); + <xsl:text> this.handle_position(value); + <xsl:text> handle_position(value){ + <xsl:text> let [min,max,totalDistance] = this.range; + <xsl:text> let length = Math.max(0,Math.min((totalDistance),(Number(value)-min)/(max-min)*(totalDistance))); + <xsl:text> let tip = this.range_elt.getPointAtLength(length); + <xsl:text> this.handle_elt.setAttribute('transform',"translate("+(tip.x-this.handle_pos.x)+","+(tip.y-this.handle_pos.y)+")"); + <xsl:text> on_release(evt) { + <xsl:text> if(this.drag){ + <xsl:text> this.drag = false; + <xsl:text> update_position(evt){ + <xsl:text> if(this.drag && this.enTimer){ + <xsl:text> var svg_dist = 0; + <xsl:text> //calculate center of widget in html + <xsl:text> // --TODO maybe it would be better to bind this part to window change size event ??? + <xsl:text> let [xdest,ydest,svgWidth,svgHeight] = page_desc[current_visible_page].bbox; + <xsl:text> let [cX, cY,fiStart,fiEnd,minMax,x1,y1,width,height] = this.circle; + <xsl:text> let htmlCirc = this.range_elt.getBoundingClientRect(); + <xsl:text> let cxHtml = ((htmlCirc.right-htmlCirc.left)/(width)*(cX-x1))+htmlCirc.left; + <xsl:text> let cyHtml = ((htmlCirc.bottom-htmlCirc.top)/(height)*(cY-y1))+htmlCirc.top; + <xsl:text> //get mouse coordinates + <xsl:text> let mouseX = undefined; + <xsl:text> let mouseY = undefined; + <xsl:text> if (evt.type.startsWith("touch")){ + <xsl:text> mouseX = Math.ceil(evt.touches[0].clientX); + <xsl:text> mouseY = Math.ceil(evt.touches[0].clientY); + <xsl:text> mouseX = evt.pageX; + <xsl:text> mouseY = evt.pageY; + <xsl:text> //calculate angle + <xsl:text> let fi = Math.atan2(cyHtml-mouseY, mouseX-cxHtml); + <xsl:text> // transform from 0 to 2PI + <xsl:text> if (fi > 0){ + <xsl:text> fi = 2*Math.PI-fi; + <xsl:text> //offset it to 0 + <xsl:text> fi = fi - fiStart; + <xsl:text> if (fi < 0){ + <xsl:text> fi = fi + 2*Math.PI; + <xsl:text> //get handle distance from mouse position + <xsl:text> if(fi<fiEnd){ + <xsl:text> svg_dist=(fi)/(fiEnd)*(this.range[1]-this.range[0]); + <xsl:text> else if(fiEnd<fi && fi<fiEnd+minMax){ + <xsl:text> svg_dist = this.range[1]; + <xsl:text> svg_dist = this.range[0]; + <xsl:text> //redraw handle --TODO is it fast enough if I just call change_hmi_value??? + <xsl:text> this.handle_position(svg_dist); + <xsl:text> if(this.value_elt) + <xsl:text> this.value_elt.textContent = String(Math.ceil(svg_dist)); + <xsl:text> this.apply_hmi_value(0, Math.ceil(svg_dist)); + <xsl:text> //reset timer + <xsl:text> this.enTimer = false; + <xsl:text> setTimeout("{hmi_widgets['"+this.element_id+"'].enTimer = true;}", 100); + <xsl:text> on_select(evt){ + <xsl:text> this.drag = true; + <xsl:text> this.enTimer = true; + <xsl:text> this.update_position(evt); + <xsl:text> //get min max + <xsl:text> let min = this.min_elt ? + <xsl:text> Number(this.min_elt.textContent) : + <xsl:text> this.args.length >= 1 ? this.args[0] : 0; + <xsl:text> let max = this.max_elt ? + <xsl:text> Number(this.max_elt.textContent) : + <xsl:text> this.args.length >= 2 ? this.args[1] : 100; + <xsl:text> //fiStart ==> offset + <xsl:text> let fiStart = Number(this.range_elt.getAttribute('sodipodi:start')); + <xsl:text> let fiEnd = Number(this.range_elt.getAttribute('sodipodi:end')); + <xsl:text> fiEnd = fiEnd - fiStart; + <xsl:text> //fiEnd ==> size of angle + <xsl:text> if (fiEnd < 0){ + <xsl:text> fiEnd = 2*Math.PI + fiEnd; + <xsl:text> //min max barrier angle + <xsl:text> let minMax = (2*Math.PI - fiEnd)/2; + <xsl:text> //get parameters from svg + <xsl:text> let cX = Number(this.range_elt.getAttribute('sodipodi:cx')); + <xsl:text> let cY = Number(this.range_elt.getAttribute('sodipodi:cy')); + <xsl:text> this.range_elt.style.strokeMiterlimit="0"; //eliminates some weird border around html object + <xsl:text> this.range = [min, max,this.range_elt.getTotalLength()]; + <xsl:text> let cPos = this.range_elt.getBBox(); + <xsl:text> this.handle_pos = this.range_elt.getPointAtLength(0); + <xsl:text> this.circle = [cX, cY,fiStart,fiEnd,minMax,cPos.x,cPos.y,cPos.width,cPos.height]; + <xsl:text> //init events + <xsl:text> this.handle_elt.addEventListener("touchstart", hmi_widgets[this.element_id].on_select.bind(this)); + <xsl:text> this.handle_elt.addEventListener("mousedown", hmi_widgets[this.element_id].on_select.bind(this)); + <xsl:text> this.element.addEventListener("mousedown", hmi_widgets[this.element_id].on_select.bind(this)); + <xsl:text> window.addEventListener("touchmove", hmi_widgets[this.element_id].update_position.bind(this)); + <xsl:text> window.addEventListener("mousemove", hmi_widgets[this.element_id].update_position.bind(this)); + <xsl:text> window.addEventListener("mouseup", hmi_widgets[this.element_id].on_release.bind(this)) + <xsl:text> window.addEventListener("touchend", hmi_widgets[this.element_id].on_release.bind(this)); + <xsl:text> window.addEventListener("touchcancel", hmi_widgets[this.element_id].on_release.bind(this)); + <xsl:template mode="widget_defs" match="widget[@type='CircularSlider']"> + <xsl:param name="hmi_element"/> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>handle range</xsl:text> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>value min max</xsl:text> + <xsl:with-param name="mandatory" select="'no'"/> <xsl:template mode="widget_class" match="widget[@type='Display']">
<xsl:text>class DisplayWidget extends Widget{
@@ -2833,6 +3213,13 @@
<xsl:template mode="widget_defs" match="widget[@type='Input']">
<xsl:param name="hmi_element"/>
+ <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>key_pos</xsl:text> + <xsl:with-param name="mandatory" select="'no'"/> <xsl:variable name="value_elt">
<xsl:call-template name="defs_by_labels">
<xsl:with-param name="hmi_element" select="$hmi_element"/>
@@ -2891,11 +3278,13 @@
<xsl:text> on_edit_click: function(opstr) {
+ <xsl:text> var size = (typeof this.key_pos_elt !== 'undefined') ? this.key_pos_elt.getBBox() : undefined <xsl:text> edit_value("</xsl:text>
<xsl:value-of select="path/@value"/>
<xsl:text>", "</xsl:text>
<xsl:value-of select="path/@type"/>
- <xsl:text>", this, this.last_val);
+ <xsl:text>", this, this.last_val,size); @@ -3310,6 +3699,298 @@
+ <xsl:template mode="widget_class" match="widget[@type='Keypad']"> + <xsl:text>class KeypadWidget extends Widget{ + <xsl:text> moving = undefined; + <xsl:text> enTimer = undefined; + <xsl:text> offset = undefined; + <xsl:text> on_position_click(evt) { + <xsl:text> this.moving = true; + <xsl:text> this.enTimer = true; + <xsl:text> // get click position offset from widget x,y and save it to variable + <xsl:text> var keypad_borders = this.position_elt.getBoundingClientRect(); + <xsl:text> var clickX = undefined; + <xsl:text> var clickY = undefined; + <xsl:text> if (evt.type == "touchstart"){ + <xsl:text> clickX = Math.ceil(evt.touches[0].clientX); + <xsl:text> clickY = Math.ceil(evt.touches[0].clientY); + <xsl:text> clickX = evt.pageX; + <xsl:text> clickY = evt.pageY; + <xsl:text> this.offset=[clickX-keypad_borders.left,clickY-keypad_borders.top] + <xsl:text> off_position_click(evt) { + <xsl:text> if(this.moving) + <xsl:text> this.moving = false; + <xsl:text> on_move(evt) { + <xsl:text> if(this.moving && this.enTimer){ + <xsl:text> //get keyboard pos in html + <xsl:text> let [eltid, tmpgrp] = current_modal; + <xsl:text> let [xcoord,ycoord] = this.coordinates; + <xsl:text> let [xdest,ydest,svgWidth,svgHeight] = page_desc[current_visible_page].bbox; + <xsl:text> //get mouse coordinates + <xsl:text> var clickX = undefined; + <xsl:text> var clickY = undefined; + <xsl:text> if (evt.type == "touchmove"){ + <xsl:text> clickX = Math.ceil(evt.touches[0].clientX); + <xsl:text> clickY = Math.ceil(evt.touches[0].clientY); + <xsl:text> clickX = evt.pageX; + <xsl:text> clickY = evt.pageY; + <xsl:text> //translate keyboard position + <xsl:text> let mouseX = ((clickX-this.offset[0])/window.innerWidth)*svgWidth; + <xsl:text> let mouseY = ((clickY-this.offset[1])/window.innerHeight)*svgHeight; + <xsl:text> tmpgrp.setAttribute("transform","translate("+String(xdest-xcoord+mouseX)+","+String(ydest-ycoord+mouseY)+")"); + <xsl:text> //reset timer + <xsl:text> this.enTimer = false; + <xsl:text> setTimeout("{hmi_widgets['"+this.element_id+"'].enTimer = true;}", 100); + <xsl:text> on_key_click(symbols) { + <xsl:text> var syms = symbols.split(" "); + <xsl:text> this.shift |= this.caps; + <xsl:text> this.editstr += syms[this.shift?syms.length-1:0]; + <xsl:text> this.shift = false; + <xsl:text> this.update(); + <xsl:text> on_Esc_click() { + <xsl:text> end_modal.call(this); + <xsl:text> on_Enter_click() { + <xsl:text> end_modal.call(this); + <xsl:text> let callback_obj = this.result_callback_obj; + <xsl:text> callback_obj.edit_callback(this.editstr); + <xsl:text> on_BackSpace_click() { + <xsl:text> this.editstr = this.editstr.slice(0,this.editstr.length-1); + <xsl:text> this.update(); + <xsl:text> on_Sign_click() { + <xsl:text> if(this.editstr[0] == "-") + <xsl:text> this.editstr = this.editstr.slice(1,this.editstr.length); + <xsl:text> this.editstr = "-" + this.editstr; + <xsl:text> this.update(); + <xsl:text> on_NumDot_click() { + <xsl:text> if(this.editstr.indexOf(".") == "-1"){ + <xsl:text> this.editstr += "."; + <xsl:text> this.update(); + <xsl:text> on_Space_click() { + <xsl:text> this.editstr += " "; + <xsl:text> this.update(); + <xsl:text> caps = false; + <xsl:text> _caps = undefined; + <xsl:text> on_CapsLock_click() { + <xsl:text> this.caps = !this.caps; + <xsl:text> this.update(); + <xsl:text> shift = false; + <xsl:text> _shift = undefined; + <xsl:text> on_Shift_click() { + <xsl:text> this.shift = !this.shift; + <xsl:text> this.caps = false; + <xsl:text> this.update(); + <xsl:text> editstr = ""; + <xsl:text> _editstr = undefined; + <xsl:text> result_callback_obj = undefined; + <xsl:text> start_edit(info, valuetype, callback_obj, initial,size) { + <xsl:text> show_modal.call(this,size); + <xsl:text> this.editstr = initial; + <xsl:text> this.result_callback_obj = callback_obj; + <xsl:text> this.Info_elt.textContent = info; + <xsl:text> this.shift = false; + <xsl:text> this.caps = false; + <xsl:text> this.update(); + <xsl:text> if(this.editstr != this._editstr){ + <xsl:text> this._editstr = this.editstr; + <xsl:text> this.Value_elt.textContent = this.editstr; + <xsl:text> if(this.shift != this._shift){ + <xsl:text> this._shift = this.shift; + <xsl:text> (this.shift?widget_active_activable:widget_inactive_activable)(this.Shift_sub); + <xsl:text> if(this.caps != this._caps){ + <xsl:text> this._caps = this.caps; + <xsl:text> (this.caps?widget_active_activable:widget_inactive_activable)(this.CapsLock_sub); <xsl:template mode="widget_defs" match="widget[@type='Keypad']">
<xsl:param name="hmi_element"/>
<xsl:call-template name="defs_by_labels">
@@ -3321,7 +4002,7 @@
<xsl:call-template name="defs_by_labels">
<xsl:with-param name="hmi_element" select="$hmi_element"/>
<xsl:with-param name="labels">
- <xsl:text>Sign Space NumDot</xsl:text>
+ <xsl:text>Sign Space NumDot position</xsl:text> <xsl:with-param name="mandatory" select="'no'"/>
@@ -3359,105 +4040,27 @@
- <xsl:text> on_key_click: function(symbols) {
- <xsl:text> var syms = symbols.split(" ");
- <xsl:text> this.shift |= this.caps;
- <xsl:text> this.editstr += syms[this.shift?syms.length-1:0];
- <xsl:text> this.shift = false;
- <xsl:text> this.update();
- <xsl:text> on_Esc_click: function() {
- <xsl:text> end_modal.call(this);
- <xsl:text> on_Enter_click: function() {
- <xsl:text> end_modal.call(this);
- <xsl:text> callback_obj = this.result_callback_obj;
- <xsl:text> callback_obj.edit_callback(this.editstr);
- <xsl:text> on_BackSpace_click: function() {
- <xsl:text> this.editstr = this.editstr.slice(0,this.editstr.length-1);
- <xsl:text> this.update();
+ <xsl:text> if(this.position_elt){ + <xsl:text> this.position_elt.setAttribute("onmousedown", "hmi_widgets['"+this.element_id+"'].on_position_click(evt)"); + <xsl:text> this.position_elt.setAttribute("ontouchstart", "hmi_widgets['"+this.element_id+"'].on_position_click(evt)"); + <xsl:text> window.addEventListener("mouseup", hmi_widgets[this.element_id].off_position_click.bind(this)); + <xsl:text> window.addEventListener("touchend", hmi_widgets[this.element_id].off_position_click.bind(this)); + <xsl:text> window.addEventListener("touchcancel", hmi_widgets[this.element_id].off_position_click.bind(this)); + <xsl:text> window.addEventListener("mousemove", hmi_widgets[this.element_id].on_move.bind(this)); + <xsl:text> window.addEventListener("touchmove", hmi_widgets[this.element_id].on_move.bind(this)); - <xsl:text> on_Sign_click: function() {
- <xsl:text> if(this.editstr[0] == "-")
- <xsl:text> this.editstr = this.editstr.slice(1,this.editstr.length);
- <xsl:text> this.editstr = "-" + this.editstr;
- <xsl:text> this.update();
- <xsl:text> on_NumDot_click: function() {
- <xsl:text> if(this.editstr.indexOf(".") == "-1"){
- <xsl:text> this.editstr += ".";
- <xsl:text> this.update();
- <xsl:text> on_Space_click: function() {
- <xsl:text> this.editstr += " ";
- <xsl:text> this.update();
- <xsl:text> caps: false,
- <xsl:text> _caps: undefined,
- <xsl:text> on_CapsLock_click: function() {
- <xsl:text> this.caps = !this.caps;
- <xsl:text> this.update();
- <xsl:text> shift: false,
- <xsl:text> _shift: undefined,
- <xsl:text> on_Shift_click: function() {
- <xsl:text> this.shift = !this.shift;
- <xsl:text> this.caps = false;
- <xsl:text> this.update();
<xsl:variable name="g" select="$geometry[@Id = $hmi_element/@id]"/>
<xsl:text> coordinates: [</xsl:text>
@@ -3466,58 +4069,6 @@
<xsl:value-of select="$g/@y"/>
- <xsl:text> editstr: "",
- <xsl:text> _editstr: undefined,
- <xsl:text> result_callback_obj: undefined,
- <xsl:text> start_edit: function(info, valuetype, callback_obj, initial) {
- <xsl:text> show_modal.call(this);
- <xsl:text> this.editstr = initial;
- <xsl:text> this.result_callback_obj = callback_obj;
- <xsl:text> this.Info_elt.textContent = info;
- <xsl:text> this.shift = false;
- <xsl:text> this.caps = false;
- <xsl:text> this.update();
- <xsl:text> update: function() {
- <xsl:text> if(this.editstr != this._editstr){
- <xsl:text> this._editstr = this.editstr;
- <xsl:text> this.Value_elt.textContent = this.editstr;
- <xsl:text> if(this.shift != this._shift){
- <xsl:text> this._shift = this.shift;
- <xsl:text> (this.shift?widget_active_activable:widget_inactive_activable)(this.Shift_sub);
- <xsl:text> if(this.caps != this._caps){
- <xsl:text> this._caps = this.caps;
- <xsl:text> (this.caps?widget_active_activable:widget_inactive_activable)(this.CapsLock_sub);
<xsl:template mode="widget_defs" match="widget[@type='List']">
<xsl:param name="hmi_element"/>
@@ -3592,6 +4143,453 @@
+ <xsl:template mode="widget_class" match="widget[@type='MultiState']"> + <xsl:text>class MultiStateWidget extends Widget{ + <xsl:text> frequency = 5; + <xsl:text> dispatch(value) { + <xsl:text> this.state = value; + <xsl:text> for(let choice of this.choices){ + <xsl:text> if(this.state != choice.value){ + <xsl:text> choice.elt.setAttribute("style", "display:none"); + <xsl:text> choice.elt.setAttribute("style", choice.style); + <xsl:text> on_click(evt) { + <xsl:text> //get current selected value + <xsl:text> let next_ind; + <xsl:text> for(next_ind=0; next_ind<this.choices.length; next_ind++){ + <xsl:text> if(this.state == this.choices[next_ind].value){ + <xsl:text> next_ind = next_ind + 1; + <xsl:text> //get next selected value + <xsl:text> if(this.choices.length > next_ind){ + <xsl:text> this.state = this.choices[next_ind].value; + <xsl:text> this.state = this.choices[0].value; + <xsl:text> //post value to plc + <xsl:text> this.apply_hmi_value(0, this.state); + <xsl:text> this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)"); + <xsl:template mode="widget_defs" match="widget[@type='MultiState']"> + <xsl:param name="hmi_element"/> + <xsl:variable name="regex" select="'^("[^"].*"|\-?[0-9]+|false|true)(#.*)?$'"/> + <xsl:for-each select="$result_svg_ns//*[@id = $hmi_element/@id]//*[regexp:test(@inkscape:label,$regex)]"> + <xsl:variable name="literal" select="regexp:match(@inkscape:label,$regex)[2]"/> + <xsl:text> elt:id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text> style:"</xsl:text> + <xsl:value-of select="@style"/> + <xsl:text> value:</xsl:text> + <xsl:value-of select="$literal"/> + <xsl:text> }</xsl:text> + <xsl:if test="position()!=last()"> + <xsl:template mode="widget_class" match="widget[@type='Slider']"> + <xsl:text>class SliderWidget extends Widget{ + <xsl:text> frequency = 5; + <xsl:text> range = undefined; + <xsl:text> fi = undefined; + <xsl:text> drag = false; + <xsl:text> enTimer = false; + <xsl:text> dispatch(value) { + <xsl:text> if(this.value_elt) + <xsl:text> this.value_elt.textContent = String(value); + <xsl:text> this.update_DOM(value, this.handle_elt); + <xsl:text> last_drag = false; + <xsl:text> update_DOM(value, elt){ + <xsl:text> let [min,max,start,totallength] = this.range; + <xsl:text> let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min))); + <xsl:text> let tip = this.range_elt.getPointAtLength(length); + <xsl:text> elt.setAttribute('transform',"translate("+(tip.x-start.x)+","+(tip.y-start.y)+")"); + <xsl:text> if(this.setpoint_elt != undefined){ + <xsl:text> if(this.last_drag!= this.drag){ + <xsl:text> if(this.drag){ + <xsl:text> this.setpoint_elt.setAttribute("style", this.setpoint_style); + <xsl:text> this.setpoint_elt.setAttribute("style", "display:none"); + <xsl:text> this.last_drag = this.drag; + <xsl:text> on_release(evt) { + <xsl:text> window.removeEventListener("touchmove", this.on_bound_drag, true); + <xsl:text> window.removeEventListener("mousemove", this.on_bound_drag, true); + <xsl:text> window.removeEventListener("mouseup", this.bound_on_release, true) + <xsl:text> window.removeEventListener("touchend", this.bound_on_release, true); + <xsl:text> window.removeEventListener("touchcancel", this.bound_on_release, true); + <xsl:text> if(this.drag){ + <xsl:text> this.drag = false; + <xsl:text> this.update_position(evt); + <xsl:text> on_drag(evt){ + <xsl:text> if(this.enTimer && this.drag){ + <xsl:text> this.update_position(evt); + <xsl:text> //reset timer + <xsl:text> this.enTimer = false; + <xsl:text> setTimeout("{hmi_widgets['"+this.element_id+"'].enTimer = true;}", 100); + <xsl:text> update_position(evt){ + <xsl:text> var html_dist = 0; + <xsl:text> //calculate size of widget in html + <xsl:text> var range_borders = this.range_elt.getBoundingClientRect(); + <xsl:text> var range_length = Math.sqrt( range_borders.height*range_borders.height + range_borders.width*range_borders.width ); + <xsl:text> var [minX,minY,maxX,maxY] = [range_borders.left,range_borders.bottom,range_borders.right,range_borders.top]; + <xsl:text> //get range and mouse coordinates + <xsl:text> var mouseX = undefined; + <xsl:text> var mouseY = undefined; + <xsl:text> if (evt.type.startsWith("touch")){ + <xsl:text> mouseX = Math.ceil(evt.touches[0].clientX); + <xsl:text> mouseY = Math.ceil(evt.touches[0].clientY); + <xsl:text> mouseX = evt.pageX; + <xsl:text> mouseY = evt.pageY; + <xsl:text> //get handle distance from mouse position + <xsl:text> if (minX > mouseX && minY < mouseY){ + <xsl:text> html_dist = 0; + <xsl:text> else if (maxX < mouseX && maxY > mouseY){ + <xsl:text> html_dist = range_length; + <xsl:text> // calculate distace + <xsl:text> if(this.fi > 0.7){ + <xsl:text> html_dist = (minY - mouseY)/Math.sin(this.fi); + <xsl:text> html_dist = (mouseX - minX)/Math.cos(this.fi); + <xsl:text> //check if in range + <xsl:text> if (html_dist > range_length){ + <xsl:text> html_dist = range_length; + <xsl:text> else if (html_dist < 0){ + <xsl:text> html_dist = 0; + <xsl:text> this.svg_dist=Math.ceil((html_dist/range_length)*this.range[1]); + <xsl:text> this.apply_hmi_value(0, this.svg_dist); + <xsl:text> // update ghost cursor + <xsl:text> if(this.setpoint_elt != undefined){ + <xsl:text> this.request_animate(); + <xsl:text> this.update_DOM(this.svg_dist, this.setpoint_elt); + <xsl:text> on_select(evt){ + <xsl:text> this.drag = true; + <xsl:text> this.enTimer = true; + <xsl:text> window.addEventListener("touchmove", this.on_bound_drag, true); + <xsl:text> window.addEventListener("mousemove", this.on_bound_drag, true); + <xsl:text> window.addEventListener("mouseup", this.bound_on_release, true) + <xsl:text> window.addEventListener("touchend", this.bound_on_release, true); + <xsl:text> window.addEventListener("touchcancel", this.bound_on_release, true); + <xsl:text> this.update_position(evt); + <xsl:text> let min = this.min_elt ? + <xsl:text> Number(this.min_elt.textContent) : + <xsl:text> this.args.length >= 1 ? this.args[0] : 0; + <xsl:text> let max = this.max_elt ? + <xsl:text> Number(this.max_elt.textContent) : + <xsl:text> this.args.length >= 2 ? this.args[1] : 100; + <xsl:text> this.range = [min, max, this.range_elt.getPointAtLength(0),this.range_elt.getTotalLength()]; + <xsl:text> let start = this.range_elt.getPointAtLength(0); + <xsl:text> let end = this.range_elt.getPointAtLength(this.range_elt.getTotalLength()); + <xsl:text> this.fi = Math.atan2(start.y-end.y, end.x-start.x); + <xsl:text> this.bound_on_select = this.on_select.bind(this); + <xsl:text> this.bound_on_release = this.on_release.bind(this); + <xsl:text> this.on_bound_drag = this.on_drag.bind(this); + <xsl:text> this.element.addEventListener("mousedown", this.bound_on_select); + <xsl:text> this.element.addEventListener("touchstart", this.bound_on_select); + <xsl:text> if(this.setpoint_elt != undefined){ + <xsl:text> this.setpoint_style = this.setpoint_elt.getAttribute("style"); + <xsl:text> this.setpoint_elt.setAttribute("style", "display:none"); + <xsl:template mode="widget_defs" match="widget[@type='Slider']"> + <xsl:param name="hmi_element"/> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>handle range</xsl:text> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>value min max setpoint</xsl:text> + <xsl:with-param name="mandatory" select="'no'"/> <xsl:template mode="widget_class" match="widget[@type='Switch']">
<xsl:text>class SwitchWidget extends Widget{
@@ -3649,19 +4647,20 @@
- <xsl:template mode="widget_defs" match="widget[@type='ToggleButton']">
- <xsl:param name="hmi_element"/>
- <xsl:call-template name="defs_by_labels">
- <xsl:with-param name="hmi_element" select="$hmi_element"/>
- <xsl:with-param name="labels">
- <xsl:text>active inactive</xsl:text>
- <xsl:text> frequency: 5,
- <xsl:text> dispatch: function(value) {
+ <xsl:template mode="widget_class" match="widget[@type='ToggleButton']"> + <xsl:text>class ToggleButtonWidget extends Widget{ + <xsl:text> frequency = 5; + <xsl:text> active_style = undefined; + <xsl:text> inactive_style = undefined; + <xsl:text> dispatch(value) { <xsl:text> this.state = value;
@@ -3683,29 +4682,40 @@
- <xsl:text> on_click: function(evt) {
+ <xsl:text> on_click(evt) { <xsl:text> this.apply_hmi_value(0, this.state);
- <xsl:text> active_style: undefined,
- <xsl:text> inactive_style: undefined,
- <xsl:text> init: function() {
<xsl:text> this.active_style = this.active_elt.style.cssText;
<xsl:text> this.inactive_style = this.inactive_elt.style.cssText;
- <xsl:text> this.element.setAttribute("onclick", "hmi_widgets['</xsl:text>
- <xsl:value-of select="$hmi_element/@id"/>
- <xsl:text>'].on_click(evt)");
+ <xsl:text> this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)"); + <xsl:template mode="widget_defs" match="widget[@type='ToggleButton']"> + <xsl:param name="hmi_element"/> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>active inactive</xsl:text> @@ -3866,8 +4876,6 @@
<xsl:text> // -> pass Number(index) instead
- <xsl:text> console.log("apply updated local variable ",index, updates[index]);
<xsl:text> dispatch_value(Number(index), updates[index]);
<xsl:text> delete updates[index];
@@ -3908,6 +4916,14 @@
<xsl:text> apply_updates();
+ <xsl:text> pending_widget_animates.forEach(widget => widget._animate()); + <xsl:text> pending_widget_animates = []; <xsl:text> requestAnimationFrameID = null;
@@ -4576,7 +5592,7 @@
<xsl:text>var edit_callback;
- <xsl:text>function edit_value(path, valuetype, callback, initial) {
+ <xsl:text>function edit_value(path, valuetype, callback, initial, size) { @@ -4588,7 +5604,7 @@
<xsl:text> let widget = hmi_widgets[keypadid];
- <xsl:text> widget.start_edit(path, valuetype, callback, initial);
+ <xsl:text> widget.start_edit(path, valuetype, callback, initial, size); @@ -4598,7 +5614,7 @@
- <xsl:text>function show_modal() {
+ <xsl:text>function show_modal(size) { <xsl:text> let [element, parent] = detachable_elements[this.element.id];
@@ -4608,13 +5624,23 @@
<xsl:text> tmpgrpattr = document.createAttribute("transform");
<xsl:text> let [xcoord,ycoord] = this.coordinates;
<xsl:text> let [xdest,ydest] = page_desc[current_visible_page].bbox;
- <xsl:text> tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")";
+ <xsl:text> if (typeof size === 'undefined'){ + <xsl:text> tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")"; + <xsl:text> tmpgrpattr.value = "translate("+String(xdest-xcoord+size.x)+","+String(ydest-ycoord+size.y)+")"; <xsl:text> tmpgrp.setAttributeNode(tmpgrpattr);
--- a/svghmi/svghmi.js Wed Aug 12 13:36:18 2020 +0200
+++ b/svghmi/svghmi.js Wed Aug 12 15:24:02 2020 +0200
@@ -75,6 +75,10 @@
if(jumps_need_update) update_jumps();
+ pending_widget_animates.forEach(widget => widget._animate()); + pending_widget_animates = []; requestAnimationFrameID = null;
@@ -409,26 +413,31 @@
var xmlns = "http://www.w3.org/2000/svg";
-function edit_value(path, valuetype, callback, initial) {
+function edit_value(path, valuetype, callback, initial, size) { let [keypadid, xcoord, ycoord] = keypads[valuetype];
console.log('XXX TODO : Edit value', path, valuetype, callback, initial, keypadid);
edit_callback = callback;
let widget = hmi_widgets[keypadid];
- widget.start_edit(path, valuetype, callback, initial);
+ widget.start_edit(path, valuetype, callback, initial, size); var current_modal; /* TODO stack ?*/
+function show_modal(size) { let [element, parent] = detachable_elements[this.element.id];
tmpgrp = document.createElementNS(xmlns,"g");
tmpgrpattr = document.createAttribute("transform");
let [xcoord,ycoord] = this.coordinates;
let [xdest,ydest] = page_desc[current_visible_page].bbox;
- tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")";
+ if (typeof size === 'undefined'){ + tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")"; + tmpgrpattr.value = "translate("+String(xdest-xcoord+size.x)+","+String(ydest-ycoord+size.y)+")"; tmpgrp.setAttributeNode(tmpgrpattr);
tmpgrp.appendChild(element);
--- a/svghmi/widget_keypad.ysl2 Wed Aug 12 13:36:18 2020 +0200
+++ b/svghmi/widget_keypad.ysl2 Wed Aug 12 15:24:02 2020 +0200
@@ -13,10 +13,159 @@
+template "widget[@type='Keypad']", mode="widget_class" + class KeypadWidget extends Widget{ + on_position_click(evt) { + // get click position offset from widget x,y and save it to variable + var keypad_borders = this.position_elt.getBoundingClientRect(); + var clickX = undefined; + var clickY = undefined; + if (evt.type == "touchstart"){ + clickX = Math.ceil(evt.touches[0].clientX); + clickY = Math.ceil(evt.touches[0].clientY); + this.offset=[clickX-keypad_borders.left,clickY-keypad_borders.top] + off_position_click(evt) { + if(this.moving && this.enTimer){ + //get keyboard pos in html + let [eltid, tmpgrp] = current_modal; + let [xcoord,ycoord] = this.coordinates; + let [xdest,ydest,svgWidth,svgHeight] = page_desc[current_visible_page].bbox; + //get mouse coordinates + var clickX = undefined; + var clickY = undefined; + if (evt.type == "touchmove"){ + clickX = Math.ceil(evt.touches[0].clientX); + clickY = Math.ceil(evt.touches[0].clientY); + //translate keyboard position + let mouseX = ((clickX-this.offset[0])/window.innerWidth)*svgWidth; + let mouseY = ((clickY-this.offset[1])/window.innerHeight)*svgHeight; + tmpgrp.setAttribute("transform","translate("+String(xdest-xcoord+mouseX)+","+String(ydest-ycoord+mouseY)+")"); + setTimeout("{hmi_widgets['"+this.element_id+"'].enTimer = true;}", 100); + on_key_click(symbols) { + var syms = symbols.split(" "); + this.shift |= this.caps; + this.editstr += syms[this.shift?syms.length-1:0]; + let callback_obj = this.result_callback_obj; + callback_obj.edit_callback(this.editstr); + this.editstr = this.editstr.slice(0,this.editstr.length-1); + if(this.editstr[0] == "-") + this.editstr = this.editstr.slice(1,this.editstr.length); + this.editstr = "-" + this.editstr; + if(this.editstr.indexOf(".") == "-1"){ + this.caps = !this.caps; + this.shift = !this.shift; + result_callback_obj = undefined; + start_edit(info, valuetype, callback_obj, initial,size) { + show_modal.call(this,size); + this.editstr = initial; + this.result_callback_obj = callback_obj; + this.Info_elt.textContent = info; + if(this.editstr != this._editstr){ + this._editstr = this.editstr; + this.Value_elt.textContent = this.editstr; + if(this.shift != this._shift){ + this._shift = this.shift; + (this.shift?widget_active_activable:widget_inactive_activable)(this.Shift_sub); + if(this.caps != this._caps){ + this._caps = this.caps; + (this.caps?widget_active_activable:widget_inactive_activable)(this.CapsLock_sub); template "widget[@type='Keypad']", mode="widget_defs" {
labels("Esc Enter BackSpace Keys Info Value");
- optional_labels("Sign Space NumDot");
+ optional_labels("Sign Space NumDot position"); activable_labels("CapsLock Shift");
foreach "$hmi_element/*[@inkscape:label = 'Keys']/*" {
@@ -26,82 +175,19 @@
| this.«.»_elt.setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_«.»_click()");
- | on_key_click: function(symbols) {
- | var syms = symbols.split(" ");
- | this.shift |= this.caps;
- | this.editstr += syms[this.shift?syms.length-1:0];
- | on_Esc_click: function() {
- | end_modal.call(this);
- | on_Enter_click: function() {
- | end_modal.call(this);
- | callback_obj = this.result_callback_obj;
- | callback_obj.edit_callback(this.editstr);
- | on_BackSpace_click: function() {
- | this.editstr = this.editstr.slice(0,this.editstr.length-1);
+ | if(this.position_elt){ + | this.position_elt.setAttribute("onmousedown", "hmi_widgets['"+this.element_id+"'].on_position_click(evt)"); + | this.position_elt.setAttribute("ontouchstart", "hmi_widgets['"+this.element_id+"'].on_position_click(evt)"); + | window.addEventListener("mouseup", hmi_widgets[this.element_id].off_position_click.bind(this)); + | window.addEventListener("touchend", hmi_widgets[this.element_id].off_position_click.bind(this)); + | window.addEventListener("touchcancel", hmi_widgets[this.element_id].off_position_click.bind(this)); + | window.addEventListener("mousemove", hmi_widgets[this.element_id].on_move.bind(this)); + | window.addEventListener("touchmove", hmi_widgets[this.element_id].on_move.bind(this)); - | on_Sign_click: function() {
- | if(this.editstr[0] == "-")
- | this.editstr = this.editstr.slice(1,this.editstr.length);
- | this.editstr = "-" + this.editstr;
- | on_NumDot_click: function() {
- | if(this.editstr.indexOf(".") == "-1"){
- | on_Space_click: function() {
- | on_CapsLock_click: function() {
- | this.caps = !this.caps;
- | on_Shift_click: function() {
- | this.shift = !this.shift;
const "g", "$geometry[@Id = $hmi_element/@id]";
| coordinates: [«$g/@x», «$g/@y»],
- | result_callback_obj: undefined,
- | start_edit: function(info, valuetype, callback_obj, initial) {
- | show_modal.call(this);
- | this.editstr = initial;
- | this.result_callback_obj = callback_obj;
- | this.Info_elt.textContent = info;
- | if(this.editstr != this._editstr){
- | this._editstr = this.editstr;
- | this.Value_elt.textContent = this.editstr;
- | if(this.shift != this._shift){
- | this._shift = this.shift;
- | (this.shift?widget_active_activable:widget_inactive_activable)(this.Shift_sub);
- | if(this.caps != this._caps){
- | this._caps = this.caps;
- | (this.caps?widget_active_activable:widget_inactive_activable)(this.CapsLock_sub);
--- a/svghmi/widgets_common.ysl2 Wed Aug 12 13:36:18 2020 +0200
+++ b/svghmi/widgets_common.ysl2 Wed Aug 12 15:24:02 2020 +0200
@@ -24,7 +24,7 @@
template "svg:*", mode="hmi_widgets" {
const "widget", "func:widget(@id)";
- const "args" foreach "$widget/arg" > "«@value»"`if "position()!=last()" > ,`
+ const "args" foreach "$widget/arg" > "«func:escape_quotes(@value)»"`if "position()!=last()" > ,` const "indexes" foreach "$widget/path" {
@@ -92,7 +92,6 @@
new_index = next_available_index++;
pagevars[varname] = new_index;
@@ -104,10 +103,14 @@
emit "preamble:widget-base-class" {
+ var pending_widget_animates = []; frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */
+ pending_animate = false; constructor(elt_id,args,indexes,members){
this.element_id = elt_id;
this.element = id(elt_id);
@@ -143,9 +146,9 @@
- if(!this.unsubscribable) for(let i = 0; i < this.indexes.length; i++) {
+ if(!this.unsubscribable) for(let index of this.indexes){ /* dispatch current cache in newly opened page widgets */
- let realindex = this.get_variable_index(i);
+ let realindex = this.get_variable_index(index); let cached_val = cache[realindex];
if(cached_val != undefined)
this.new_hmi_value(realindex, cached_val, cached_val);
@@ -196,6 +199,20 @@
+ this.pending_animate = false; + if(!this.pending_animate){ + pending_widget_animates.push(this); + this.pending_animate = true; @@ -212,12 +229,12 @@
-const "excluded_types", "str:split('Page Lang')";
-const "excluded_ids","$parsed_widgets/widget[not(@type = $excluded_types)]/@id";
+const "excluded_types", "str:split('Page Lang VarInit')"; +const "included_ids","$parsed_widgets/widget[not(@type = $excluded_types)]/@id"; emit "declarations:hmi-elements" {
- apply "$hmi_elements[@id = $excluded_ids]", mode="hmi_widgets";
+ apply "$hmi_elements[@id = $included_ids]", mode="hmi_widgets"; @@ -266,11 +283,10 @@
def "func:escape_quotes" {
// have to use a python string to enter escaped quote
- const "frst", !"substring-before($txt,'\"')"!;
- const "frstln", "string-length($frst)";
+ // const "frstln", "string-length($frst)"; - when "$frstln > 0 and string-length($txt) > $frstln" {
- result !"concat($frst,'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!;
+ when !"contains($txt,'\"')"! { + result !"concat(substring-before($txt,'\"'),'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!;