Reputation: 15
I'm trying to find a way to generate xslt 3.0 templates from a yaml file and i'm looking for a way or a tool that can help me do it, the yaml file contains the list of transformations, here is the yaml file :
transformations:
- element: ABC
action:
removeElement: true
- element: BCD
action:
removeElement: true
- element: OldName1
action:
renameTag: NewName1
- element: OldName2
action:
renameTag: NewName2
changeAttributes:
- name: x
value: '@H'
- name: y
value: '@V'
- name: PI
value: 3.14
excludedAttributes:
- H
- V
i'm already parsing the yaml file to my java objects using snakeyaml
@Data
public class Transformations {
private List<Transformation> transformations;
}
@Data
public class Transformation {
private String element;
private Action action;
}
@Data
public class Action {
private String renameTag="";
private boolean removeElement=false;
private List<Attribute> changeAttributes= new ArrayList<>();
private boolean copyOtherAttributes=true;
private List<String> excludedAttributes= new ArrayList<>();
private String addChildElement="";
private String copyAttributesIn="";
}
@Data
public class Attribute {
private String name;
private String value;
}
i have passed the transformations list to my xslt 3.0 file
List<Transformation> transformationList = transformations.getTransformations();
transformer.setParameter("list", transformationList);
then i create a new class, implemented ExtensionFunction and used saxon and s9api in order to create all the getters for theses classes to use them in xslt
here is how i looped through the list and used the getters to print some attributes :
<xsl:template match="/">
<xsl:for-each select="$list">
<xsl:variable name="item" select="."/>
<Tranformation>
<xsl:variable name="element" select="ext:getTransformationAttribute('element',$item)"/>
<xsl:variable name="actionObj" select="ext:getTransformationAttribute('action',$item)"/>
<xsl:value-of select="$element" />
<xsl:text> </xsl:text>
<xsl:value-of select="$actionObj" />
<Action>
<xsl:variable name="renameTag" select="ext:getTransformationAttribute('renameTag',$actionObj)"/>
<xsl:value-of select="$renameTag" />
<xsl:text> </xsl:text>
<xsl:variable name="removeElement" select="ext:getTransformationAttribute('removeElement',$actionObj)"/>
<xsl:value-of select="$removeElement" />
<xsl:text> </xsl:text>
<xsl:variable name="changeAttributes" select="ext:getTransformationAttribute('changeAttributes',$actionObj)"/>
<xsl:for-each select="$changeAttributes">
<xsl:value-of select="." />
</xsl:for-each>
<xsl:variable name="addChildElement" select="ext:getTransformationAttribute('addChildElement',$actionObj)"/>
<xsl:value-of select="$addChildElement" />
</Action>
<!--
<xsl:value-of select="$item" />
-->
</Tranformation>
</xsl:for-each>
</xsl:template>
what i want to do now is to generate the xslt templates by theses attributes in order to transform my xml file using yaml, one problem is that i cannot call template inside another template or inside if tag or choose tag
example of templates which i need generate :
<xsl:template match="Element">
<NewName2 x="{@H}" y="{@V}" PI="3.14">
<xsl:apply-templates select="@*[not(name() = ('H', 'V'))]" />
<addedChild>
<xsl:apply-templates select="node()" />
</addedChild>
</NewName2>
</xsl:template>
in some transformations i have to add a child element, some should exclude attributes from copy, some don't have a renameTag action, some should have their attributes copied in the child tag and so on..
here is how it can be done with java for example :
private static void generateXSLT(String xsltPath, Transformations transformations) throws IOException {
FileWriter writer = new FileWriter(xsltPath);
StringBuilder xslt = new StringBuilder();
xslt.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n"
+ " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n"
+ " xmlns:math=\"http://www.w3.org/2005/xpath-functions/math\"\n"
+ " xmlns:xd=\"http://www.oxygenxml.com/ns/doc/xsl\"\n"
+ " exclude-result-prefixes=\"xs math xd\" version=\"3.0\">\n");
// strip-space is used for removing empty lines after deleting some elements
// template to copy everything
xslt.append("<xsl:strip-space elements=\"*\" />\r\n" + " \r\n" + " <xsl:template match=\"/\">\r\n"
+ " <xsl:apply-templates />\r\n" + " </xsl:template>\r\n" + " \r\n"
+ " <xsl:template match=\"*\">\r\n" + " <xsl:copy>\r\n"
+ " <xsl:apply-templates select=\"@* | node()\" />\r\n" + " </xsl:copy>\r\n"
+ " </xsl:template>\n\n");
List<Transformation> transformationList = transformations.getTransformations();
for (Transformation transformation : transformationList) {
if (action.isRemoveElement()) {
xslt.append("\t<xsl:template match=\"" + xpath + "\"/>\n");
} else if (action.getRenameTag() != null) {
xslt.append("\t<xsl:template match=\"" + xpath + "\">\n");
if (action.getChangeAttributes() != null) {
xslt.append("\t\t<" + action.getRenameTag());
for (Attribute attribute : action.getChangeAttributes()) {
xslt.append(" " + attribute.getName() + "=\"{" + attribute.getValue() + "}\"");
}
xslt.append(">\n");
} else {
xslt.append("\t\t<" + action.getRenameTag() + ">\n");
}
if (action.getExcludeAttributes() != null) {
xslt.append("\t\t\t<xsl:apply-templates select=\"@*[not(name() = (");
if (action.getExcludeAttributes() != null) {
xslt.append("\t\t\t<xsl:apply-templates select=\"@*[not(name() = (");
for (String excludedAttribute : action.getExcludeAttributes()) {
joiner.add("'" + excludedAttribute + "'");
}
xslt.append(joiner.toString() + "))]|node()\"/>\n");
} else {
xslt.append("\t\t\t<xsl:apply-templates select=\"@*|node()\"/>\n");
}
xslt.append("\t\t</" + action.getRenameTag() + ">\n");
xslt.append("\t</xsl:template>\n");
}
}
}
}
Upvotes: 0
Views: 426
Reputation: 167716
A simple example to transpile part of your YAML to XSLT and then run it in XSLT 3 is at https://xsltfiddle.liberty-development.net/eiZNCwk doing e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:axsl="http://www.w3.org/1999/XSL/Transform-alias"
exclude-result-prefixes="#all"
version="3.0">
<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="yaml-as-xslt" as="element(xsl:stylesheet)">
<axsl:stylesheet version="3.0">
<axsl:mode on-no-match="shallow-copy"/>
<xsl:for-each-group select="tokenize($yaml, '\n') => tail()" group-starting-with=".[matches(., '^\s+-\selement:')]">
<axsl:template match="{substring-after(., 'element: ')}">
<xsl:apply-templates select="current-group()[3]"/>
</axsl:template>
</xsl:for-each-group>
</axsl:stylesheet>
</xsl:variable>
<xsl:sequence
select="transform(map { 'source-node': ., 'stylesheet-node' : $yaml-as-xslt})?output"/>
</xsl:template>
<xsl:template match=".[matches(., '^\s+renameTag:\s')]">
<axsl:element name="{substring-after(., 'renameTag: ')}"/>
</xsl:template>
<xsl:template match=".[matches(., '^\s+removeElement:\strue')]"/>
<xsl:param name="yaml" as="xs:string" expand-text="no">transformations:
- element: ABC
action:
removeElement: true
- element: BCD
action:
removeElement: true
- element: OldName1
action:
renameTag: NewName1
- element: OldName2
action:
renameTag: NewName2</xsl:param>
</xsl:stylesheet>
to transform e.g.
<Root>
<ABC/>
<BCD/>
<OldName1/>
<OldName2/>
</Root>
based on the YAML rules into
<Root>
<NewName1/>
<NewName2/>
</Root>
Of course the full meaning of your YAML specifying XML transformations is not given and I have not tried to guess it for all of your sample nor have I tried to implement all of the sample, in the end, as said in the comment, an additional step first transforming the YAML into an intermediary XML or maps/json data structure to then generate XSLT and execute is might be necessary for a complex YAML syntax to define XML transformations.
Upvotes: 0