Search code examples
pythonxmlxsltaltova

xslt transformation works in Altova but not in python


I am trying to transform the one xml to another xml using xslt. Below is the xslt I am using

  <?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns="Apartments.AP.Mits20PropertyFinal"
                    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                    xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var"
                    exclude-result-prefixes="msxsl var userCSharp"
                    xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp"
                    >
      <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <xsl:key name="myKey" match="ILS_Unit/Units/Unit" use="./Identification[@IDType='FloorplanID']/@IDValue"/> 
      <xsl:template match="/">
        <PhysicalProperty>
    <!--      <xsl:apply-templates select="PhysicalProperty/Management"/>
    -->      <xsl:apply-templates select="PhysicalProperty/Property"/>
        </PhysicalProperty>
      </xsl:template>
      <xsl:template match="Property">
        <Property>      
          <PropertyInfo>

              <xsl:element name="MITSID">
                <xsl:value-of select="./PropertyID/Identification[@IDRank='secondary']/@IDValue"/>
              </xsl:element>
              <xsl:element name="MarketingName">
                <xsl:value-of select="./PropertyID/MarketingName"/>
              </xsl:element>
              <xsl:element name="TotalUnits">

              </xsl:element>
              <xsl:element name="AddressLine1">
                <xsl:value-of select="./PropertyID/Address/AddressLine1"/>
              </xsl:element>
              <xsl:element name="City">
                <xsl:value-of select="./PropertyID/Address/City"/>
              </xsl:element>
              <xsl:element name="State">
                <xsl:value-of select="./PropertyID/Address/State"/>
              </xsl:element>
              <xsl:element name="Zip">
                <xsl:value-of select="./PropertyID/Address/PostalCode"/>
              </xsl:element>

          </PropertyInfo>


           <xsl:for-each select="./Floorplan">
           <Floorplan>
              <FloorplanID><xsl:value-of select="./@IDValue"/></FloorplanID>
              <FloorplanName><xsl:value-of select="./Name"/></FloorplanName>
                <!--This Unit count is Totals no of Units per floorplan-->
              <UnitCount><xsl:value-of select="./UnitCount"/></UnitCount>
               <Units>

               <xsl:variable name="floorplanid" select="./@IDValue"/>
               <xsl:for-each select="key('myKey',$floorplanid)">
               <Unit>
                  <UnitNum>
                   <xsl:value-of select="./MarketingName"/>
                  </UnitNum>

                   <xsl:if test="./UnitLeasedStatus='Not_Available'">
                   <UnitLeasedStatus>Occupied</UnitLeasedStatus> 
                  </xsl:if>
                   <xsl:if test="./UnitLeasedStatus='On_Notice'">
                   <UnitLeasedStatus>On Notice</UnitLeasedStatus> 
                  </xsl:if>
                  <xsl:if test="./UnitLeasedStatus='Available'">
                   <UnitLeasedStatus>Available</UnitLeasedStatus> 
                  </xsl:if>


                 </Unit>
                </xsl:for-each>
      </Units>
     </Floorplan>
           </xsl:for-each>

        </Property>
      </xsl:template>
    </xsl:stylesheet>

And below is the sample xml file

  <?xml version="1.0" encoding="utf-8"?>
    <PhysicalProperty>
        <Property>
            <PropertyID>
                <Identification IDValue="183dbed4-0101-4a85-954c-a4e8042d2819" IDRank="primary"/>
                <Identification IDValue="6458174" IDRank="secondary"/>
                <MarketingName>Westmount at London Park</MarketingName>
                <Website>http://www.westmountatlondonpark.com</Website>
                <Address AddressType="property">
                    <AddressLine1>14545 Bammel North Houston Road</AddressLine1>
                    <AddressLine2/>
                    <City>Houston</City>
                    <State>TX</State>
                    <PostalCode>77014</PostalCode>
                </Address>
            </PropertyID>
            <ILS_Identification ILS_IdentificationType="Apartment" RentalType="Unspecified"/>
            <Floorplan IDValue="171ad0f2-da57-45c0-9cf5-c61cfa26dd63" IDType="FloorplanID" IDRank="primary">
                <FloorplanType>Internal</FloorplanType>
                <Name>A1  </Name>
                <Comment/>
                <UnitCount>22</UnitCount>
                <UnitsAvailable>3</UnitsAvailable>
                <DisplayedUnitsAvailable>5</DisplayedUnitsAvailable>
                <Room RoomType="Bedroom">
                    <Count>1</Count>
                </Room>
                <Room RoomType="Bathroom">
                    <Count>1</Count>
                </Room>
                <SquareFeet Min="602" Max="602"/>
                <EffectiveRent Min="810" Max="830"/>
                <Deposit DepositType="Security Deposit">
                    <Amount>
                        <ValueRange Min="150" Max="150"/>
                    </Amount>
                </Deposit>
                <File FileID="e114a438-ee82-497d-8e04-6a5a8b2381ef" active="true">
                    <FileType>Floorplan</FileType>
                    <Caption/>
                    <Src>https://apollostore.blob.core.windows.net/londonpark/uploads/images/floorplans/a1.7a75909c-3362-409c-82c7-aa6c959f9c99.jpg</Src>
                    <Rank>999</Rank>
                </File>
            </Floorplan>
            <ILS_Unit>
                <ILS_Unit IDValue="be827564-6460-4af1-9644-3f6ffa225557" IDType="UnitID" IDRank="primary">
                    <Units>
                        <Unit>
                            <Identification IDValue="be827564-6460-4af1-9644-3f6ffa225557" IDType="UnitID" IDRank="primary"/>
                            <Identification IDValue="171ad0f2-da57-45c0-9cf5-c61cfa26dd63" IDType="FloorplanID" IDRank="primary"/>
                            <MarketingName>1901</MarketingName>
                            <UnitBedrooms>1</UnitBedrooms>
                            <UnitBathrooms>1</UnitBathrooms>
                            <UnitRent>765</UnitRent>
                            <UnitLeasedStatus>Not_Available</UnitLeasedStatus>
                            <FloorplanName>A1  </FloorplanName>
                        </Unit>
                    </Units>
                    <Comment/>
                    <EffectiveRent Min="765" Max="765"/>
                    <Deposit DepositType="Security Deposit">
                        <Amount>
                            <ValueRange Min="150" Max="150"/>
                        </Amount>
                    </Deposit>
                </ILS_Unit>
            </ILS_Unit>
        </Property>
    </PhysicalProperty>

The transformation works fine using altova tool but does not work in python. Below is the python script I am using.

from lxml import etree
import os
import glob
xslt = etree.parse("fl.xslt")
dom = etree.parse("f.xml",)

transform = etree.XSLT(xslt)


try:
    newdom = transform(dom)
    root = etree.parse(newdom)
    properties = root.findall("./Property")
    print(properties)

except Exception as e:
    print (e)
    for error in transform.error_log:
        print(error.message, error.line)
print(etree.tostring(newdom, pretty_print=True))

I am trying to print the property nodes and I also tried to write the result to .xml file but it returns nothing. Could some one tell what the issue could be. Surprisingly it works fine in Altova.

Below are the errors that are thrown.

 line 53, in <module>
    root = etree.parse(newdom)
  File "src\lxml\etree.pyx", line 3519, in lxml.etree.parse
  File "src\lxml\parser.pxi", line 1862, in lxml.etree._parseDocument
TypeError: cannot parse from 'lxml.etree._XSLTResultTree'

Solution

  • Specific error has nothing to do with XSLT but your attempted parse of the result. Like Python's built-in xml.etree, the parse function of lxml.etree requires a file-like object. However, the result from an XSLT transformation in lxml is a ElementTree object that you can then directly run any XML DOM calls like findall, iterfind, etc.

    Therefore, simply remove the parse line. Additionally, because you have a default namespace, consider assigning a temporary prefix to access nodes. Also, consider xpath in lxml.

    newdom = transform(dom)
    
    properties = newdom.findall("./doc:Property", namespaces={'doc': 'Apartments.AP.Mits20PropertyFinal'})
    print(properties)
    # [<Element {Apartments.AP.Mits20PropertyFinal}Property at 0x136b2f94b08>]
    
    properties = newdom.xpath("./doc:Property", namespaces={'doc': 'Apartments.AP.Mits20PropertyFinal'})
    print(properties)
    # [<Element {Apartments.AP.Mits20PropertyFinal}Property at 0x136b2f94b08>]
    

    Output

    print(etree.tostring(newdom, pretty_print=True).decode("utf-8"))
    
    # <PhysicalProperty xmlns="Apartments.AP.Mits20PropertyFinal">
    #   <Property>
    #     <PropertyInfo>
    #       <MITSID>6458174</MITSID>
    #       <MarketingName>Westmount at London Park</MarketingName>
    #       <TotalUnits/>
    #       <AddressLine1>14545 Bammel North Houston Road</AddressLine1>
    #       <City>Houston</City>
    #       <State>TX</State>
    #       <Zip>77014</Zip>
    #     </PropertyInfo>
    #     <Floorplan>
    #       <FloorplanID>171ad0f2-da57-45c0-9cf5-c61cfa26dd63</FloorplanID>
    #       <FloorplanName>A1  </FloorplanName>
    #       <UnitCount>22</UnitCount>
    #       <Units>
    #        <Unit>
    #           <UnitNum>1901</UnitNum>
    #           <UnitLeasedStatus>Occupied</UnitLeasedStatus>
    #         </Unit>
    #       </Units>
    #     </Floorplan>
    #   </Property>
    # </PhysicalProperty>