// 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
svgtmpl "@*", mode="inline_svg" xsl:copy;
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
}
// remove i18n markers, so that defs_by_labels can find text elements
svgtmpl "svg:text/@inkscape:label[starts-with(., '_')]", mode="inline_svg" {
attrib "{name()}" > «substring(., 2)»
}
// remove "reference" and "frame" rectangles
svgtmpl "svg:rect[@inkscape:label='reference' or @inkscape:label='frame']", mode="inline_svg" {
// nothing
}
svgtmpl "svg:g[svg:rect/@inkscape:label='frame']", mode="inline_svg" {
const "reference_rect","(../svg:rect | ../svg:g/svg:rect)[@inkscape:label='reference']";
const "frame_rect","svg:rect[@inkscape:label='frame']";
const "offset","func:offset($frame_rect, $reference_rect)";
xsl:copy {
attrib "svghmi_x_offset" value "$offset/vector/@x";
attrib "svghmi_y_offset" value "$offset/vector/@y";
apply "@* | node()", mode="inline_svg";
}
}
////// 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 "targets_not_to_unlink", "$hmi_lists/descendant-or-self::svg:*";
const "to_unlink", "$hmi_widgets/descendant-or-self::svg:use";
def "func:is_unlinkable" {
param "targetid";
param "eltid";
result "$eltid = $to_unlink/@id and not($targetid = $targets_not_to_unlink/@id)";
}
svgtmpl "svg:use", mode="inline_svg"{
const "targetid","substring-after(@xlink:href,'#')";
choose {
when "func:is_unlinkable($targetid, @id)" {
call "unlink_clone" {
with "targetid", "$targetid";
}
}
otherwise xsl:copy apply "@*", 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
name > id
}
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"{
param "targetid";
param "seed","''";
const "target", "//svg:*[@id = $targetid]";
const "seeded_id" choose {
when "string-length($seed) > 0" > «$seed»_«@id»
otherwise value "@id";
}
g{
attrib "id" value "$seeded_id";
attrib "original" value "@id";
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","$seeded_id";
}
}
otherwise {
// include non excluded attributes
foreach "@*[not(local-name() = $excluded_use_attrs/name)]"
attrib "{name()}" > «.»
apply "$target", mode="unlink_clone"{
with "seed","$seeded_id";
}
}
}
}
}
// clone unlinking is really similar to deep-copy
// all nodes are sytematically copied
svgtmpl "@id", mode="unlink_clone" {
param "seed";
attrib "id" > «$seed»_«.»
attrib "original" > «.»
}
svgtmpl "@*", mode="unlink_clone" xsl:copy;
svgtmpl "svg:use", mode="unlink_clone" {
param "seed";
const "targetid","substring-after(@xlink:href,'#')";
choose {
when "func:is_unlinkable($targetid, @id)" {
call "unlink_clone" {
with "targetid", "$targetid";
with "seed","$seed";
}
}
otherwise xsl:copy apply "@*", mode="unlink_clone" {
with "seed","$seed";
}
}
}
// 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_widgets/@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" {
| const xmlns = "http://www.w3.org/2000/svg";
| let id = document.getElementById.bind(document);
| var svg_root = id("«$svg/@id»");
}
emit "debug:clone-unlinking" {
|
| Unlinked :
foreach "$to_unlink"{
| «@id»
}
| Not to unlink :
foreach "$targets_not_to_unlink"{
| «@id»
}
}