Home > Software engineering >  How to insert comment in desired position in XML using Python
How to insert comment in desired position in XML using Python

Time:10-21

I am finding elements from XML1 in XML2 and after that i want to insert one comment XML1 is:

<Price>
    <Amount>100</Amount>
    <Amount>102</Amount>
    <Amount>103</Amount>
</Price>

and XML2 is:

<List>
    <Item>
        <Price>
            <Amount>100</Amount>
            <Next_Item>
                <Name>Apple</Name>
            </Next_Item>
            <Next_Item>
                <Name>Orange</Name>
            </Next_Item>
        </Price>
         <Price>
            <Amount>200</Amount>
            <Next_Item>
                <Name>Apple</Name>
            </Next_Item>
            <Next_Item>
                <Name>Orange</Name>
            </Next_Item>
        </Price>
    </Item>
</List>

Output XML i want is following where I want to insert a comment above Price if the value is matching from price/amount from XML2:

<List>
    <Item>
      <!--important-->
        <Price>
            <Amount>100</Amount>
            <Next_Item>
                <Name>Apple</Name>
            </Next_Item>
            <Next_Item>
                <Name>Orange</Name>
            </Next_Item>
        </Price>
         <Price>
            <Amount>200</Amount>
            <Next_Item>
                <Name>Apple</Name>
            </Next_Item>
            <Next_Item>
                <Name>Orange</Name>
            </Next_Item>
        </Price>
    </Item>
</List>

After getting help from this forum I tried coding like this:

xml1 = etree.parse('C:/Python/XML1.xml')
xml2 = etree.parse('C:/Python/XML2.xml')
for am in xml1.xpath('//Price/Id/text()'):
    x = xml2.xpath(f'//List/Item/Price[./Amount/text()="{am}"]')
    if len(x)>0:
        root = xml2.find('.//List/Item/Price')
        root.insert(1, etree.Comment('important'))
        etree.dump(root)
with open('C:/Python/output.xml','w') as f:
    f.write(etree.Comment(root))

I am not able to get proper output xml I am getting error: AttributeError: 'NoneType' object has no attribute 'insert'

Grateful for any kind of help.

CodePudding user response:

Since you need to compare across documents and traverse multiple nodes with conditional logic to add comment, consider XSLT, the special-purpose language designed to transform XML files. Python's lxml library can run XSLT 1.0 scripts. Also, XSLT maintains the document function to parse from other documents.

Specifically, below XSLT runs the identity transform to copy document as is and then conditionally adds comment before <Price> element. Notice XSLT references the First_Document.xml and Python references the Second_Document.xml. Be sure file in document() is in same location to second document.

XSLT (save as .xsl file, a special .xml file)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" omit-xml-declaration="yes" encoding="utf-8" indent="yes" />
    <xsl:strip-space elements="*"/>

    <!-- IDENTITY TRANSFORM -->
    <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
    </xsl:template>

    <!-- UPDATE Item NODES -->
    <xsl:template match="Item">
     <xsl:copy>
       <!-- MAP THROUGH Price NODES -->
       <xsl:for-each select="Price">
         <!-- CONDITIONALLY ADD COMMENT -->
         <xsl:variable name="curr_amount" select="Amount"/>
         <xsl:if test="document('First_Document.xml')/Price/Amount = $curr_amount">
           <xsl:comment>important</xsl:comment>
         </xsl:if>
         <!-- ALWAYS RE-WRITE Price NODES -->         
         <xsl:copy>
             <xsl:apply-templates select="*"/>
         </xsl:copy>
       </xsl:for-each>
     </xsl:copy>
    </xsl:template> 
</xsl:stylesheet>

Python

from lxml import etree

# LOAD XML AND XSLT
xml = etree.parse("Second_Document.xml")
xsl = etree.parse("My_StyleSheet.xsl")

# CONFIGURE AND RUN TRANSFORMER
transformer = etree.XSLT(xsl)
result = transformer(xml)

# SAVE TRANSFORMATION TO FILE
result.write_output("Output.xml")

Output

<List>
  <Item>
    <!--important-->
    <Price>
      <Amount>100</Amount>
      <Next_Item>
        <Name>Apple</Name>
      </Next_Item>
      <Next_Item>
        <Name>Orange</Name>
      </Next_Item>
    </Price>
    <Price>
      <Amount>200</Amount>
      <Next_Item>
        <Name>Apple</Name>
      </Next_Item>
      <Next_Item>
        <Name>Orange</Name>
      </Next_Item>
    </Price>
  </Item>
</List>

CodePudding user response:

Try the below

import xml.etree.ElementTree as ET


xml1 = '''<Price>
    <Amount>100</Amount>
    <Amount>102</Amount>
    <Amount>103</Amount>
</Price>'''

xml2 = ''' <List>
    <Item>
        <Price>
            <Amount>100</Amount>
            <Next_Item>
                <Name>Apple</Name>
            </Next_Item>
            <Next_Item>
                <Name>Orange</Name>
            </Next_Item>
        </Price>
         <Price>
            <Amount>200</Amount>
            <Next_Item>
                <Name>Apple</Name>
            </Next_Item>
            <Next_Item>
                <Name>Orange</Name>
            </Next_Item>
        </Price>
    </Item>
</List>
'''

root1 = ET.fromstring(xml1)
root2 = ET.fromstring(xml2)
amounts = set(e.text for e in root1.findall('.//Amount'))
for item in root2.findall('.//Item'):
    prices = item.findall('.//Price')
    for price in prices:
        amount = price.find('Amount').text
        if amount in amounts:
            item.insert(0, ET.Comment('Important'))
ET.dump(root2)

output

<List>
    <Item>
        <!--Important-->
        <Price>
            <Amount>100</Amount>
            <Next_Item>
                <Name>Apple</Name>
            </Next_Item>
            <Next_Item>
                <Name>Orange</Name>
            </Next_Item>
        </Price>
         <Price>
            <Amount>200</Amount>
            <Next_Item>
                <Name>Apple</Name>
            </Next_Item>
            <Next_Item>
                <Name>Orange</Name>
            </Next_Item>
        </Price>
    </Item>
</List>
  • Related