简体   繁体   中英

How to change XML attributes to elements then move them to different node?

I would like to convert the attributes of <Card> elements ( id , status , type ) to child elements of the associated <Information> element. I have implemented the attribute -> element change, but how can I give the resulting elements the correct parent?

Here is a sample input file:

<?xml version="1.0" encoding="UTF-8"?>
<Customers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" hasMore="false"
    recordCount="1" responseTime="2017-04-27T19:35:02" returnCode="SUCCESS">
<Customer>
<Information>
<FirstName>Brandon</FirstName>
<MiddleName/>
<LastName>Wrong</LastName>
<CustomerStatus>ACTIVE</CustomerStatus>
<Household>false</Household>
<EnrollmentDate xsi:nil="true"/>
<Address>100 Broadway Ave</Address>
<City>San Gabriel</City>
<State>CA</State>
<ZIP>91776-2348</ZIP>
<Country/>
<Phone>6268881212</Phone>
<MobilePhone/>
<Email>BWrong1@gmail.com</Email>
<DateOfBirth xsi:nil="true"/>
</Information>
<Cards>
<Card id="40012345601" status="ACTIVE" type="Customer card"/>
</Cards>
</Customer>
</Customers>

And here is my current stylesheet:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                >

  <xsl:output indent="yes" method="xml" encoding="utf-8" omit-xml-declaration="yes"/>

  <xsl:template match="*">
    <xsl:element name="{local-name()}">
      <xsl:apply-templates select="@* | node()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="Customers/@hasMore|Customers/@recordCount|Customers/@returnCode|@xsi:nil|@responseTime" />

    <xsl:template match="Card/@*">
    <xsl:element name="{local-name(.)}">
        <xsl:value-of select="."/> 
    </xsl:element>
  </xsl:template>


  <xsl:template match="@*">
    <xsl:attribute name="{local-name()}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>

  <xsl:template match="comment() | text() | processing-instruction()">
    <xsl:copy/>
  </xsl:template>

</xsl:stylesheet>

When you put the job to be done in terms of moving nodes around, you're thinking in an unhelpful way. XSL doesn't change (or move or etc.) anything. Rather, it defines a result document in terms of the characteristics of input documents.

Since part of defining an element of the output document is defining its content, it follows that an output element's child nodes must be defined in the context of the template for that output node. For your particular case, then, you need a template tailored more specifically to your <Information> elements than identity transform you are using now.

Before we continue with the specifics, however, I observe as an aside that the identity transform you define, spread as it is over three separate templates and explicitly using only elements' and attributes local names, is more verbose than the canonical identity transform for no apparent reason or advantage. Here's the usual identity transform, all in one template:

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

You seem, however, to be suppressing all attributes other than namespace declarations and the ones that you are transforming to elements. In that case, instead of providing a template that explicitly suppresses attributes, how about just avoiding transforming them in the first place, by using (only) this variation on the identity transform instead:

<xsl:template match="node()">
  <xsl:copy>
    <!-- transform child nodes, except attributes -->
    <xsl:apply-templates select="node()"/>
  </xsl:copy>
</xsl:template>

You do not need special provision for preserving namespace declarations with this approach, as xsl:copy automatically copies the copied node's namespace nodes, resulting in namespace declarations being provided were needed.

With that out of the way, the wanted template for <Information> elements can be written as a variation on the identity template:

<xsl:template match="Information">
  <xsl:copy>
    <!-- transform all the element's child nodes -->
    <xsl:apply-templates select="node()"/>
    <!-- include also transformations of the associated <Card> attributes -->
    <xsl:apply-templates select="../Cards/Card/@*" />
  </xsl:copy>
</xsl:template>

The template you already have for the <Card> attributes will probably do, though it's unclear why you're using local-name() instead of name() . It does seem, however, that you probably want to avoid emitting seemingly informationless <Cards> and <Card> elements into the output document. You can do that by providing a template for <Cards> that transforms it to nothing:

<xsl:template match="Cards"/>

Putting that all together, then, you might arrive at something like this:

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

  <!-- attributes are not matched or selected for transformation by this
       template: -->
  <xsl:template match="node()">
    <xsl:copy>
      <xsl:apply-templates select="node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- Information elements require a special template: -->
  <xsl:template match="Information">
    <xsl:copy>
      <!-- transform all the element's child nodes -->
      <xsl:apply-templates select="node()|@*"/>
      <!-- include also transformations of the associated <Card> attributes -->
      <xsl:apply-templates select="../Cards/Card/@*" />
    </xsl:copy>
  </xsl:template>

  <!-- transform <Cards> elements to nothing -->
  <xsl:template match="Cards"/>

  <!-- applied only where matching attribute nodes are selected for
       transformation: -->
  <xsl:template match="Card/@*">
    <xsl:element name="{name()}">
      <xsl:value-of select="."/> 
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM