svghmi/inline_svg.ysl2
author Edouard Tisserant
Thu, 04 Jun 2020 11:14:21 +0200
branchsvghmi
changeset 2980 2a21d6060d64
parent 2968 556ef07a7e31
child 2995 7769e7c7f925
permissions -rw-r--r--
SVGHMI: add "unsubscribable" property to widgets in order to generalize what already happens for jump buttons.
In most cases jump buttons do not really subscribe to pointed HMI variable, path is given as a relative page jump path. When widget.unsubscribable is set to true, no subscription is made on page switch, but still offset is updated.
This fixes bug happening on relative jump buttons without "disabled" element where offset did not change on relative page switch.
// inline_svg.ysl2
//
// Produce Inline SVG element of resulting XHTML page.

// Since stylesheet output namespace is xhtml, templates that output svg have to be explicitely declared as such 
in xsl decl svgtmpl(match, xmlns="http://www.w3.org/2000/svg") alias template;
in xsl decl svgfunc(name, xmlns="http://www.w3.org/2000/svg") alias template;


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

// replaces inkscape's height and width hints. forces fit
template "svg:svg/@width", mode="inline_svg";
template "svg:svg/@height", mode="inline_svg";
svgtmpl "svg:svg", mode="inline_svg" svg {
    attrib "preserveAspectRatio" > none
    attrib "height" > 100vh
    attrib "width" > 100vw
    apply "@* | node()", mode="inline_svg";
}
// ensure that coordinate in CSV file generated by inkscape are in default reference frame
template "svg:svg[@viewBox!=concat('0 0 ', @width, ' ', @height)]", mode="inline_svg" {
    error > ViewBox settings other than X=0, Y=0 and Scale=1 are not supported
}
// ensure that coordinate in CSV file generated by inkscape match svg default unit
template "sodipodi:namedview[@units!='px' or @inkscape:document-units!='px']", mode="inline_svg" {
    error > All units must be set to "px" in Inkscape's document properties
}

////// Clone unlinking
//
// svg:use (inkscape's clones) inside a widgets are
// replaced by real elements they refer in order to :
//  - allow finding "needle" element in "meter" widget,
//    even if "needle" is in a group refered by a svg use.
//  - if "needle" is visible through a svg:use for
//    each instance of the widget, then needle would show
//    the same position in all instances
//
// For now, clone unlinkink applies to descendants of all widget except HMI:Page
// TODO: narrow application of clone unlinking to active elements,
//       while keeping static decoration cloned
const "to_unlink", "$hmi_elements[not(@id = $hmi_pages)]/descendant-or-self::svg:use";
svgtmpl "svg:use", mode="inline_svg"
{
    choose {
        when "@id = $to_unlink/@id"
            call "unlink_clone";
        otherwise
            xsl:copy apply "@* | node()", mode="inline_svg";
    }
}

// to unlink a clone, an group containing a copy of target element is created
// that way, style and transforms can be preserved
const "_excluded_use_attrs" {
    name > href
    name > width
    name > height
    name > x
    name > y
}
const "excluded_use_attrs","exsl:node-set($_excluded_use_attrs)";

const "_merge_use_attrs" {
    name > transform
    name > style
}
const "merge_use_attrs","exsl:node-set($_merge_use_attrs)";

svgfunc "unlink_clone"{
    const "targetid","substring-after(@xlink:href,'#')";
    const "target", "//svg:*[@id = $targetid]";
    g{
        choose {
            when "$target[self::svg:g]" {
                foreach "@*[not(local-name() = $excluded_use_attrs/name | $merge_use_attrs)]"
                    attrib "{name()}" > «.»

                if "@style | $target/@style"
                    attrib "style" {
                        > «@style»
                        if "@style and $target/@style" > ;
                        > «$target/@style»
                    }

                if "@transform | $target/@transform"
                    attrib "transform" {
                        > «@transform»
                        if "@transform and $target/@transform" >  
                        > «$target/@transform»
                    }

                apply "$target/*", mode="unlink_clone"{
                    with "seed","@id";
                }
            }
            otherwise {
                // include non excluded attributes
                foreach "@*[not(local-name() = $excluded_use_attrs/name)]"
                    attrib "{name()}" > «.»

                apply "$target", mode="unlink_clone"{
                    with "seed","@id";
                }
            }
        }
    }
}

// clone unlinking is really similar to deep-copy
// all nodes are sytematically copied
svgtmpl "@id", mode="unlink_clone" {
    param "seed";
    attrib "id" > «$seed»_«.»
}

svgtmpl "@*", mode="unlink_clone" xsl:copy;

// copying widgets would have unwanted effect
// instead widget is refered through a svg:use.
svgtmpl "svg:*", mode="unlink_clone" {
    param "seed";
    choose {
        // node recursive copy ends when finding a widget
        when "@id = $hmi_elements/@id" {
            // place a clone instead of copying
            use{
                attrib "xlink:href" > «concat('#',@id)»
            }
        }
        otherwise {
            xsl:copy apply "@* | node()", mode="unlink_clone" {
                with "seed","$seed";
            }
        }
    }
}

const "result_svg" apply "/", mode="inline_svg";
const "result_svg_ns", "exsl:node-set($result_svg)";

emit "preamble:inline-svg" {
    | let id = document.getElementById.bind(document);
    | var svg_root = id("«$svg/@id»");
}

emit "debug:clone-unlinking" {
    |
    | Unlinked :
    foreach "$to_unlink"{
        | «@id»
    }
}