How to edit specific xml using xpath

2020-04-19 19:25发布

问题:

I have got below xml which is genrated using xslt function json-to-xml(). I need to update this xml as shown in Result xml using given xpaths. I need the solution in java or xslt. Any help would be much appreciated.

XML

 <?xml version="1.0" encoding="UTF-8"?>
    <map xmlns="http://www.w3.org/2005/xpath-functions">
        <map key="Response">
            <map key="Headers">
                <string key="server">Lisa</string>
                <string key="Status-Code">200</string>
                <string key="Content-Type">applicaton/json</string>
            </map>
            <map key="Payload">
                <map key="root">
                    <array key="cars">
                        <map>
                            <string key="company">ccc</string>
                            <string key="model">mmm</string>
                        </map>
                        <map>
                            <string key="strength">666</string>
                            <string key="Capacity">333</string>
                        </map>
                    </array>
                    <array key="bikes">
                        <map>
                            <string key="company">abc</string>
                            <string key="model">2018</string>
                        </map>
                    </array>
                </map>
            </map>
        </map>
    </map>

XPATHS

/Response/Payload/root/cars[2]/strength=999
/Response/Payload/root/bikes/model=2019
/Response/Headers/server=WebSphere
/Response/Headers/Content-Type=text
/Response/Payload/root/cars[2]/Capacity=555
/Response/Payload/root/cars[1]/model=mmm1
/Response/Payload/root/bikes/company=xyz
/Response/Payload/root/cars[1]/company=ccc1
/Response/Headers/Status-Code=400

UPDATED RESULT XML

<map xmlns="http://www.w3.org/2005/xpath-functions">
    <map key="Response">
        <map key="Headers">
            <string key="server">WebSphere</string>
            <string key="Status-Code">400</string>
            <string key="Content-Type">text</string>
        </map>
        <map key="Payload">
            <map key="root">
                <array key="cars">
                    <map>
                        <string key="company">ccc1</string>
                        <string key="model">mmm1</string>
                    </map>
                    <map>
                        <string key="strength">999</string>
                        <string key="Capacity">555</string>
                    </map>
                </array>
                <array key="bikes">
                    <map>
                        <string key="company">xyz</string>
                        <string key="model">2019</string>
                    </map>
                </array>
            </map>
        </map>
    </map>
</map>

My Try
I have tried converting this xml back to json using xslt function and then used com.jayway.jsonpath Jayway JsonPath library to parse/change values of json on given xpaths. And then finally changed that json again to xml using xslt function. This worked for me like charm !! But after using this library i started facing issues in my other part of application :( . All the post request with json request body started giving 400 error. The error was "Error 400 The request sent by the client was syntactically incorrect" . I couldn't figure out the issue and may be it was due to different jar conficts. So I am trying some other way to work. Any other library like Jayway JsonPath? Or any other solution/Suggestion will help alot.

回答1:

One approach given XSLT 3 might be to transform those paths you have into XSLT 3 match templates and then create a stylesheet you can execute with the transform function (https://www.w3.org/TR/xpath-functions/#func-transform):

<?xml version="1.0" encoding="UTF-8"?>
<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="xs"
    version="3.0">

    <xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>

    <xsl:output method="xml" indent="yes"/>

    <xsl:param name="paths" as="xs:string">/Response/Payload/root/cars[2]/strength=999
        /Response/Payload/root/bikes[1]/model=2019
        /Response/Headers/server=WebSphere
        /Response/Headers/Content-Type=text
        /Response/Payload/root/cars[2]/Capacity=555
        /Response/Payload/root/cars[1]/model=mmm1
        /Response/Payload/root/bikes[1]/company=xyz
        /Response/Payload/root/cars[1]/company=ccc1
        /Response/Headers/Status-Code=400</xsl:param>

    <xsl:param name="path-sequence" as="xs:string*" select="tokenize($paths, '\s+')!normalize-space()"/>

    <xsl:variable name="stylesheet">
        <axsl:stylesheet version="3.0">
            <axsl:mode on-no-match="shallow-copy"/>

            <xsl:for-each select="($path-sequence)">
                <xsl:variable name="steps" select="tokenize(., '/')"/>
                <axsl:template>
                    <xsl:attribute name="match">
                        <xsl:apply-templates select="tail($steps)" mode="step"/>
                    </xsl:attribute>
                    <axsl:copy>
                        <axsl:copy-of select="@*"/>
                        <xsl:value-of select="substring-after($steps[last()], '=')"/>
                    </axsl:copy>                   
                </axsl:template>
            </xsl:for-each>

        </axsl:stylesheet>
    </xsl:variable>

    <xsl:template match=".[not(contains(., '[')) and not(contains(., '='))]" mode="step">
        <xsl:if test="position() gt 1">/</xsl:if>
        <xsl:sequence select="'*[@key = ''' || . || ''']'"/>
    </xsl:template>

    <xsl:template match=".[contains(., '=')]" mode="step">
        <xsl:if test="position() gt 1">/</xsl:if>
        <xsl:sequence select="'*[@key = ''' || substring-before(., '=') || ''']'"/>
    </xsl:template>

    <xsl:template match=".[contains(., '[')]" mode="step">
        <xsl:if test="position() gt 1">/</xsl:if>
        <xsl:sequence select="'*[@key = ''' || substring-before(., '[') || ''']/*[' || replace(., '^[^\[]+\[([0-9]+)\]', '$1') || ']'"/>
    </xsl:template>

    <xsl:template match="/">
        <xsl:sequence select="transform(map {
            'source-node' : .,
            'stylesheet-node' : $stylesheet
          })?output"/>
    </xsl:template>

</xsl:stylesheet> 

At https://xsltfiddle.liberty-development.net/jyyiVhv/1 I get the wanted result

<?xml version="1.0" encoding="UTF-8"?>
<map xmlns="http://www.w3.org/2005/xpath-functions">
    <map key="Response">
        <map key="Headers">
            <string key="server">WebSphere</string>
            <string key="Status-Code">400</string>
            <string key="Content-Type">text</string>
        </map>
        <map key="Payload">
            <map key="root">
                <array key="cars">
                    <map>
                        <string key="company">ccc1</string>
                        <string key="model">mmm1</string>
                    </map>
                    <map>
                        <string key="strength">999</string>
                        <string key="Capacity">555</string>
                    </map>
                </array>
                <array key="bikes">
                    <map>
                        <string key="company">xyz</string>
                        <string key="model">2019</string>
                    </map>
                </array>
            </map>
        </map>
    </map>
</map>

that way although I had to adapt the paths including bikes to bikes[1].