p2p.wrox.com Forums

p2p.wrox.com Forums (http://p2p.wrox.com/index.php)
-   XSLT (http://p2p.wrox.com/forumdisplay.php?f=86)
-   -   Copy ALL elements except one (http://p2p.wrox.com/showthread.php?t=99878)

Neal May 24th, 2018 04:56 AM

Copy ALL elements except one
 
Hi,
I have an XML file that I need to update using XSLT. The update is on 1 field only (DocumentDestinationPartner). The rest of the XML should remain the same.

Original XML file

HTML Code:

<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
        <Header>
                <MessageId>ABCD</MessageId>
                <Action>http://blah/</Action>
                <DocumentDestinationDivision>CBA</DocumentDestinationDivision>
                <DocumentDestinationPartner>CBA</DocumentDestinationPartner>
                <DocumentSourceDivision>TEST</DocumentSourceDivision>
                <DocumentSourcePartner>AX12PPR</DocumentSourcePartner>
                <DocumentStandard>XXXX</DocumentStandard>
                <DocumentTimeStamp>20180522133623</DocumentTimeStamp>
                <DocumentType>Document1</DocumentType>
                <DocumentUniqueId>00012</DocumentUniqueId>
        </Header>
        <Body>
                <MessageParts xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
                        ...
                        ...
                </MessageParts>
        </Body>
</Envelope>


I have tried updating using the following XSLT technique (I ‘borrowed’ from the web). But it doesn’t update the specific field.


XSLT file

HTML Code:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <!--<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/>-->
        <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
        <!-- -->
    <!-- Identity template : copy all text nodes, elements and attributes -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <!-- -->
    <!-- 1. Append '#D' or 'D'-->
        <xsl:template match="DocumentDestinationPartner">
                <DocumentDestinationPartner>
                        <xsl:choose>
                                <xsl:when test="contains(//DocumentDestinationPartner, '#D')">
                                        <xsl:value-of select="concat(//DocumentDestinationPartner, 'D')"/>
                                </xsl:when>
                                <xsl:otherwise>
                                        <xsl:value-of select="concat(//DocumentDestinationPartner, '#D')"/>
                                </xsl:otherwise>
                        </xsl:choose>
                </DocumentDestinationPartner>
        </xsl:template>
        <!-- -->
    <xsl:template match="text()">
                <xsl:value-of select="normalize-space()" />
        </xsl:template>
        <!-- -->
</xsl:stylesheet>


In addition, the following attribute for the 'MessageParts' element is removed from the output (NOTE: it is the same as the Envelope attribute) which I wasn't expecting:

HTML Code:

xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"

Can anyone help please?


Thanks in advance,

mhkay May 24th, 2018 05:17 AM

Firstly, your source element is in a namespace, but you are matching elements in no namespace. Instead of match="DocumentDestinationPartner" you need match="p:DocumentDestinationPartner" where the prefix p is bound to the relevant namespace URI.

Secondly, within the template you should refer to the element currently being processed as ".", not as "//DocumentDestinationPartner". Using "//DocumentDestinationPartner" will search the whole document for elements of that name (in no namespace).

Finally, you need to create the new element in the right namespace. You can do that by writing <DocumentDestinationPartner xmlns="..."> in place of <DocumentDestinationPartner>, or simply by using xsl:copy.

Martin Honnen May 24th, 2018 05:22 AM

If you move to XSLT 2 or even 3 with Saxon 9 you can ease your task of matching elements in a namespace and of copying everything except the nodes for which you write templates with
Code:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xpath-default-namespace="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
    expand-text="yes"
        version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>
 
  <xsl:template match="DocumentDestinationPartner">
      <xsl:copy>{. || '#D'}</xsl:copy>
  </xsl:template>
 
  <xsl:template match="DocumentDestinationPartner[contains(., '#D')]">
      <xsl:copy>{. || 'D'}</xsl:copy>
  </xsl:template>
 
</xsl:stylesheet>

Online sample at https://xsltfiddle.liberty-development.net/pPqsHTg

Neal May 24th, 2018 06:36 AM

Thank you both...

You both helped me overcome the main problem. I still have an issue with the 'MessageParts' output. Please can either of you assist?

Michael - I have updated using your advise and the result worked great for the 'DocumentDestinationPartner'.

Martin - I am bound by company protocol and there is no plans to move to 2.0 in the near future but thanks for your help.


The updated XSLT file

HTML Code:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
        <!--<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/>-->
        <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
        <!-- -->
        <xsl:template match="/">
        <xsl:apply-templates />
    </xsl:template>

    <!-- Identity template : copy all text nodes, elements and attributes -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <!-- -->
    <!-- 1. Append '#D' or 'D'-->
        <xsl:template match="m:DocumentDestinationPartner">
                <xsl:copy>
                        <xsl:choose>
                                <xsl:when test="contains(., '#D')">
                                        <xsl:value-of select="concat(., 'D')"/>
                                </xsl:when>
                                <xsl:otherwise>
                                        <xsl:value-of select="concat(., '#D')"/>
                                </xsl:otherwise>
                        </xsl:choose>
                </xsl:copy>
        </xsl:template>
        <!-- -->
        <xsl:template match="text()">
                <xsl:value-of select="normalize-space()" />
        </xsl:template>
        <!-- -->
</xsl:stylesheet>

The MessageParts element is still outputting as
HTML Code:

<MessageParts>
i.e. it is missing the attribute. How can I resolve this to retain the original?
HTML Code:

<MessageParts xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">

Thanks in advance,

Martin Honnen May 24th, 2018 06:42 AM

There is nothing in your code as posted that should change the namespace of the "MessageParts" elements and indeed it is not changed, however in the input the namespace is duplicated, the XSLT serializer then eliminates the duplicated declaration. That is not something you can avoid when using XSLT.


All times are GMT -4. The time now is 11:19 AM.

Powered by vBulletin®
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.
© 2013 John Wiley & Sons, Inc.