tags:

views:

1488

answers:

2

I am trying something very simple, but for some reason it does not work. Basically, I need to rename some nodes in an XML document. Thus, I created an XSLT file to do the transformation.

Here is an example of the XML:

EDIT: Addresses and Address elements occur at many levels. This is what caused me to have to try and apply an XSLT. The Visual Studio typed dataset feature, which creates typed datasets from XSD files does not permit you to have nested references to the same table. Thus, having Businesses/Business/Addresses and Businesses/Business/Contact/Addresses causes the Load() to fail. This is a known issue, and all they tell you is something like "Don't have nested table references...edit your XSD to stop having that." Unfortunately, this means that we have to apply XSLT to make the XML conform to the "hacked" XSD, since the files are coming from a third party vendor.

So, we are very close with the help rendered here. The last couple of things are these:

1.) How can I use the namespace reference in the match attribute of the xsl:template in order to specify that I want to rename Businesses/Business/Addresses to BusinessAddresses, but rename Businesses/Business/Contacts/Contact/Addresses to ContactAddresses?

2.) How can I stop the XSLT from cluttering every new element with explicit namespace references? It is causing extreme bloat in the output.

I created a namespace called "steel", and was having good success with:

<xsl:template match="steel:Addresses>
  <xsl:element name="BusinessAddresses>
</xsl:template>

The obvious problem here is that it renames ALL of the Addresses elements to BusinessAddresses, even though I want some of them named ContactAddresses, and so on. The needless addition of explicit namespace references to all of the renamed nodes is also troublesome.

I tried this sort of thing, but as soon as I add slashes to the match attribute, it is a a syntax error in the XSLT, like so:

<xsl:template match="steel:/Businesses/Business/Addresses">

I feel very close, but need some guidance on how to mix both the namespace usage and a way to use the slashes to select ANY nodes under specific paths.

<?xml version="1.0"?>
<Businesses>
  <Business>
    <BusinessName>Steel Stretching</BusinessName>
    <Addresses>
      <Address>
        <City>Pittsburgh</City>
      </Address>
      <Address>
        <City>Philadelphia</City>
      </Address>
    </Addresses>
    <Contacts>
      <Contact>
        <FirstName>Paul</FirstName>
        <LastName>Jones</LastName>
        <Addresses>
          <Address>
            <City>Pittsburgh</City>
          </Address>
        </Addresses>
      </Contact>
    </Contacts>
  </Business>
  <Business>
    <BusinessName>Iron Works</BusinessName>
    <Addresses>
      <Address>
        <City>Harrisburg</City>
      </Address>
      <Address>
        <City>Lancaster</City>
      </Address>
    </Addresses>
  </Business>
</Businesses>

I need to rename Addresses to BusinessAddresses, and I need to rename Address to BusinessAddress, for every instance of Addresses and Address directly under a Business node. I also need to rename Addresses to ContactAddresses, and I need to rename Address to ContactAddress, for every instance of Addresses and Address directly under a Contact Node.

I have tried several solutions, but none seem to work. They all end up producing the same XML as the original file.

Here is an example of what I have tried:

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

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

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

This has been tried in at least 6 different flavors, complete with stepping through the XSLT debugger in VB.Net. It never executes the template match for Addresses.

Why?

+8  A: 

Why might an XSLT fail?

An XSLT will fail because of obvious things like typos. However, the most likely situation relates to namespace usage. If you declared a default namespace for your XML but don't include that in your XSLT, the XSLT won't match the templates as you might expect.

The following example adds the xmlns:business attribute which declares that items qualified by the business prefix belong to the namespace mynamespace.uri. I then used this prefix to qualify the Address and Addresses template matches. Of course, you will need to change the namespace URI to whatever matches the default namespace of your XML file.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                xmlns:business="mynamespace.uri"
                exclude-result-prefixes="msxsl">
  <xsl:template match="/">
    <xsl:apply-templates select="@*|node()"/>
  </xsl:template>

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

  <xsl:template match="business:Addresses">
    <xsl:element name="BusinessAddresses">
      <xsl:apply-templates select="@*|node()" />
    </xsl:element>
  </xsl:template>

  <xsl:template match="business:Address">
    <xsl:element name="BusinessAddress">
      <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

How do you match templates based on element location as well as name?

There are several ways to achieve this last part to your problem, BusinessAddress or ContactAddress, but the easiest is to modify the template match attributes to consider parent nodes. If you think of the match attribute as a path into the XML for a node, this option becomes clearer (contents of templates left out for brevity):

<xsl:template match="business:Business/business:Addresses>
</xsl:template>

<xsl:template match="business:Business/business:Addresses/business:Address">
</xsl:template>

<xsl:template match="business:Contact/business:Addresses">
</xsl:template>

<xsl:template match="business:Contact/business:Addresses/business:Address">
</xsl:template>

Other methods exist for achieving this if the match remains based on just the element name, but they're harder to implement, follow, and maintain as they involve the use of conditional checks on the parent node hierarchy of the element being processing, all within the template.

Jeff Yates
This is extremely close. I have it almost working now, but there are two more small hangups which I will update the case to show. Thank you!
Pittsburgh DBA
Okay, glad to be helping. I've updated the answer to cover your most recent issue.
Jeff Yates
This is terrific. Thank you. It is all good now.
Pittsburgh DBA
A: 

Maybe this, if the data you show is really like what you got to work with

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

<xsl:template match="Businesses">
 <Businesses>
  <xsl:apply-templates/>
 </Businesses>
</xsl:template>

<xsl:template match="*">
 <xsl:copy-of select="."/>
</xsl:template>

<xsl:template match="Addresses">
 <BusinessAddresses>
  <xsl:apply-templates/>
 </BusinessAddresses>
</xsl:template>

<xsl:template match="Addresses/Address">
 <BusinessAddress>
  <xsl:value-of select="."/>
 </BusinessAddress>
</xsl:template>

</xsl:stylesheet>
Pride Fallon