1

I am using Python and requests library to send POST requests with an XML file. My XML file looks like this:

property_name = """<wfs:Property>
  <wfs:Name>Adm2_NAME</wfs:Name>
  <wfs:Value>fff</wfs:Value>
</wfs:Property>"""

xml = """<wfs:Transaction service="WFS" version="1.0.0"
  xmlns:ogc="http://www.opengis.net/ogc"
  xmlns:wfs="http://www.opengis.net/wfs">
  <wfs:Update typeName="geonode:tjk_nhr_shockriskscore">
  """ + property_name + """
    <ogc:Filter>
      <ogc:FeatureId fid="tjk_nhr_shockriskscore.2"/>
    </ogc:Filter>
  </wfs:Update>
</wfs:Transaction>"""

As you can see there is a property_name variable which is actually will be generated by a function.

Right now I appended the property_name variable manually by concatenating the XML string. But as I will have many of these variables, I need to find a way to append them on the right position.

What is a proper way to do this with Python? Are there available libraries or recommended techniques to achieve this?

2
  • 1
    Have you tried using .format()? Commented Jul 17, 2016 at 8:48
  • What is your server XML data pattern. My idea don't use property definition cos got a update. Use <wfs:Name>Adm2_NAME</wfs:Name> <wfs:Value>fff</wfs:Value> under Update section. Maybe server not accepting your property, raise invalid key error. Your code haven't any error but need declare server side pattern. So name maybe primary key and not a property. Commented Jul 17, 2016 at 11:02

1 Answer 1

1

Consider XSLT, the special purpose language to transform XML files, to append parts of other XMLs into main XML file. Python's third-party module, lxml, can process XSLT 1.0 scripts. XSLT maintains the document() function, allowing querying across documents in same or sub directory. However, in order to run this approach, you must save the smaller XML strings to disk or file as well as XSLT script to handle the across document processing.

Another important requirement is the smaller XML strings must define its namespace in root tag <wfs:Property xmlns:wfs="http://www.opengis.net/wfs"> so concatenate that prior to output to file. To add other XML strings turned files in the XSLT, follow the property routine and add a line <xsl:copy-of select="document('OtherXML.xml')"> in xsl template matched to specified parent node.

import lxml.etree as et

# SAVE XML TO FILE
nmsp = 'xmlns:wfs="http://www.opengis.net/wfs"'
property_name = '''<wfs:Property {}>
  <wfs:Name>Adm2_NAME</wfs:Name>
  <wfs:Value>fff</wfs:Value>
</wfs:Property>'''
xmlfile = open('Property.xml','w')
xmlfile.write(property_name.format(nmsp))
xmlfile.close()

# SAVE XSL TO FILE
xslstr = '''<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ogc="http://www.opengis.net/ogc" xmlns:wfs="http://www.opengis.net/wfs">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>

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

  <xsl:template match="wfs:Update">
    <xsl:copy>
      <xsl:copy-of select="document('Property.xml')"/>
      <xsl:apply-templates />
    </xsl:copy>  
  </xsl:template>        
</xsl:transform>'''
xslfile = open('XSLTScript.xsl','w')
xslfile.write(xslstr)
xslfile.close()

# PARSE MAIN XML STRING
xml = '''<wfs:Transaction service="WFS" version="1.0.0"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:wfs="http://www.opengis.net/wfs">
<wfs:Update typeName="geonode:tjk_nhr_shockriskscore">        
  <ogc:Filter>
    <ogc:FeatureId fid="tjk_nhr_shockriskscore.2"/>
  </ogc:Filter>
</wfs:Update>
</wfs:Transaction>'''
dom = et.fromstring(xml)

# TRANSFORM XML
xsl = et.parse('XSLTScript.xsl')
transform = et.XSLT(xsl)
newdom = transform(dom)

print(newdom)
# <?xml version="1.0"?>
# <wfs:Transaction xmlns:ogc="http://www.opengis.net/ogc" 
#   xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0">
#   <wfs:Update>
#     <wfs:Property>
#       <wfs:Name>Adm2_NAME</wfs:Name>
#       <wfs:Value>fff</wfs:Value>
#     </wfs:Property>
#     <ogc:Filter>
#       <ogc:FeatureId fid="tjk_nhr_shockriskscore.2"/>
#     </ogc:Filter>
#   </wfs:Update>
# </wfs:Transaction>

# OUTPUT FINAL XML
xmlfile = open('Final.xml','wb')
xmlfile.write(newdom)
xmlfile.close()

Alternatively, still using XSLT, you can bypass any need for document() or saving individual strings to disk. In this approach, you simply concatenate the smaller XML strings to the template match of XSLT.

import lxml.etree as et

# XML STRING
property_name = '''<wfs:Property>
  <wfs:Name>Adm2_NAME</wfs:Name>
  <wfs:Value>fff</wfs:Value>
</wfs:Property>'''

# XSL STRING
xslstr = '''<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ogc="http://www.opengis.net/ogc" xmlns:wfs="http://www.opengis.net/wfs">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>

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

  <xsl:template match="wfs:Update">
    <xsl:copy>
      {}
      <xsl:apply-templates />
    </xsl:copy>  
  </xsl:template>    
</xsl:transform>'''.format(property_name)

# PARSE MAIN XML STRING
xmlstr = '''<wfs:Transaction service="WFS" version="1.0.0"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:wfs="http://www.opengis.net/wfs">
<wfs:Update typeName="geonode:tjk_nhr_shockriskscore">        
  <ogc:Filter>
    <ogc:FeatureId fid="tjk_nhr_shockriskscore.2"/>
  </ogc:Filter>
</wfs:Update>
</wfs:Transaction>'''
dom = et.fromstring(xmlstr)

# TRANSFORM XML
xsl = et.fromstring(xslstr)
transform = et.XSLT(xsl)
newdom = transform(dom)

print(newdom)
# <?xml version="1.0"?>
# <wfs:Transaction xmlns:ogc="http://www.opengis.net/ogc" 
# xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0">
#   <wfs:Update>
#     <wfs:Property>
#       <wfs:Name>Adm2_NAME</wfs:Name>
#       <wfs:Value>fff</wfs:Value>
#     </wfs:Property>
#     <ogc:Filter>
#       <ogc:FeatureId fid="tjk_nhr_shockriskscore.2"/>
#     </ogc:Filter>
#   </wfs:Update>
# </wfs:Transaction>

# OUTPUT FINAL XML
xmlfile = open('Final.xml','wb')
xmlfile.write(newdom)
xmlfile.close()
Sign up to request clarification or add additional context in comments.

7 Comments

I am a bit confused with the workflow. Why do you: PARSE MAIN XML STRING? Also I actually need to append multiple property_name to the initial xml. This code currently works in case I have only one property_name. Right?
Also the # XSL STRING contains elements which I don't have in my XML. Where all these elements come from?
For first type of document approach, add a copy line or each property (which I mention): <xsl:copy-of select="document('Property.xml')"/>. For second option string approach, add the other properties in same place as format() can take multiple elements but add corresponding {0}, {1}, ....
And the XSL string is a script file used to transform your original XML. Those elements will not show in final output.
Both XSLTs expect the property names variables to be known in advance. What you can do is concatenate (via a loop) all property vars into one long property XML string (add a root), output to .xml to be read one time in document(). Or concatenate large xml to XSLT string one time with format().
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.