I. Riahi
I. Riahi

Reputation: 15

How to generate XSLT 3.0 templates from a yaml file?

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>&#10;</xsl:text>
                <xsl:value-of select="$actionObj" />

                <Action>
                    <xsl:variable name="renameTag" select="ext:getTransformationAttribute('renameTag',$actionObj)"/>
                    <xsl:value-of select="$renameTag" />
                    <xsl:text>&#10;</xsl:text>
                    <xsl:variable name="removeElement" select="ext:getTransformationAttribute('removeElement',$actionObj)"/>
                    <xsl:value-of select="$removeElement" />
                    <xsl:text>&#10;</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

Answers (1)

Martin Honnen
Martin Honnen

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

Related Questions