user1179317
user1179317

Reputation: 2923

Python: Generate xml using xml & xslt file

I would like to generate an xml using an input xml and a xslt file. I have a sample input xml and xslt files here <https://xsltfiddle.liberty-development.net/aiyned/1>. And the final output is what I would like.

Basically the input.xml is:

<?xml version="1.0" encoding="utf-8" ?>
<root>
    <parent1 value="parent1">
        <inserthere value="parent1"/>
    </parent1>
    <parent2 value="parent2">
        <inserthere value="parent2"/>
    </parent2>
</root>

input.xslt is:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    
  <xsl:output method="xml"/>
  
  <!-- <xsl:variable name="childDoc" select="document('child.xml')"/> -->
  <xsl:variable name="childDoc">
    <root>
      <child1 value="child1"/>
      <child2 value="child2"/>
    </root>
  </xsl:variable>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="inserthere">
    <xsl:variable name="currentParent" select="."/>
      <xsl:for-each select="$childDoc/root/node()">
        <xsl:copy>
          <xsl:attribute name="value" select="concat($currentParent/@value,'_',@value)"/>
        </xsl:copy>
      </xsl:for-each>
  </xsl:template>
  
</xsl:stylesheet>

Output xml is:

<?xml version="1.0" encoding="UTF-8"?><root>
    <parent1 value="parent1">
        <child1 value="parent1_child1"/>
        <child2 value="parent1_child2"/>
    </parent1>
    <parent2 value="parent2">
        <child1 value="parent2_child1"/>
        <child2 value="parent2_child2"/>
    </parent2>
</root>

The problem is the site is using saxon engine, which I believe could require a license. I would like to generate the output xml using lxml or any python library that is free. Currently when I run

import lxml.etree as ET

dom = ET.parse("input.xml")
xslt = ET.parse("input.xslt")
transform = ET.XSLT(xslt)
newdom = transform(dom)
print(ET.tostring(newdom, pretty_print=True))

I get an error of:

    newdom = transform(dom)
  File "src/lxml/xslt.pxi", line 602, in lxml.etree.XSLT.__call__
lxml.etree.XSLTApplyError: Failed to evaluate the 'select' expression.

I think the problem is lxml only support version 1.0? There are some comments here how to use saxon use saxon with python, but I would like to prevent requiring java or other external applications. Is there a way to make the above work with just python? Or is there a way to update my xslt file so that it will work with just lxml transform function?

Upvotes: 0

Views: 1848

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167696

Saxon-C 1.2.1 is the latest release of Saxon-C https://www.saxonica.com/saxon-c/index.xml and has a Python API https://www.saxonica.com/saxon-c/doc/html/saxonc.html so you can download https://www.saxonica.com/download/c.xml, install https://www.saxonica.com/saxon-c/documentation/index.html#!starting/installing and run it https://www.saxonica.com/saxon-c/documentation/index.html#!samples/samples_python from Python if you think you need to use XSLT 3.

The HE edition does not require you to buy a license.

As for the error in XSLT, if you want to test XSLT 1.0 code with the xsltfiddle then choose XslCompiledTransform as the XSLT processor and you will get a similar error for your code and the easiest way, if you really declare your variable value inline as a result tree fragment, is to use exsl:node-set:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    exclude-result-prefixes="exsl"
    version="1.0">
    
  <xsl:output method="xml"/>
  
  <!-- <xsl:variable name="childDoc" select="document('child.xml')"/> -->
  <xsl:variable name="childDoc">
    <root>
      <child1 value="child1"/>
      <child2 value="child2"/>
    </root>
  </xsl:variable>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="inserthere">
    <xsl:variable name="currentParent" select="."/>
    <xsl:copy-of select="exsl:node-set($childDoc)/root/node()"/>
  </xsl:template>
  
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/aiyned/4

Or if you want to change values then use the template

  <xsl:template match="inserthere">
    <xsl:variable name="currentParent" select="."/>
      <xsl:for-each select="exsl:node-set($childDoc)/root/node()">
        <xsl:copy>
          <xsl:attribute name="value">
              <xsl:value-of select="concat($currentParent/@value,'_',@value)"/>
          </xsl:attribute>
        </xsl:copy>
      </xsl:for-each>
  </xsl:template>

https://xsltfiddle.liberty-development.net/aiyned/3

Upvotes: 3

Related Questions