2

I need to add an additional namespace to an already namespaced XML file but only if a particular element does not exist.

My XML doc looks like:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<everyone xmlns="AAA" xmlns:ns2="BBB" xmlns:ns3="CCC" company="TestingCorp">
    <common>Stuff Here</common>
    <ns2:person id="123">
        <ns3:firstname>Billy</ns3:firstname>
        <ns2:lastname>Bobby</ns2:lastname>
    </ns2:person>
</everyone>

... and if there is no ns3:firstname element in the person element, I'd like to add a new namespace and (e.g. xmlns:frog="FFF") and also an additional element within person as shown below:

Desired Output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<everyone xmlns="AAA" xmlns:ns2="BBB" xmlns:ns3="CCC" xmlns:frog="FFF" company="TestingCorp">
    <common>Stuff Here</common>
    <ns2:person id="123">
        <frog:title>
            <Master/>
        </frog:title>
        <ns2:lastname>Bobby</ns2:lastname>
    </ns2:person>
</everyone>

My XSL doc currently is:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

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

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

</xsl:stylesheet>

.... unfortunately this does not work.

I've tried lots of different things but can't seem to achieve this using XSLT v1.0. Any help would be greatly appreciated.

1 Answer 1

2

First you need to declare the various namespaces in your stylesheet, as well as the "AAA" default namespace

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns="AAA"
    xmlns:frog="FFF"
    xmlns:ns2="BBB"
    xmlns:ns3="CCC">

    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

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

  <!-- for a Person with no firstname, add a frog:title -->
  <xsl:template match="ns2:person[not(ns3:firstname)]">
      <xsl:copy>
          <!-- must handle attributes before elements/text nodes -->
          <xsl:apply-templates select="@*" />
          <frog:title>
              <Master/>
          </frog:title>
          <xsl:apply-templates select="node()" />
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

This will produce

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<everyone xmlns="AAA" xmlns:ns2="BBB" xmlns:ns3="CCC" company="TestingCorp">
    <common>Stuff Here</common>
    <ns2:person id="123">
        <frog:title xmlns:frog="FFF">
            <Master/>
        </frog:title>
        <ns2:lastname>Bobby</ns2:lastname>
    </ns2:person>
</everyone>

If the xmlns:frog absolutely must be on the everyone element rather than on each frog:title then you could add another template

<xsl:template match="/*">
  <xsl:copy>
    <xsl:copy-of select="document('')/xsl:stylesheet/namespace::frog" />
    <xsl:apply-templates select="@*|node()" />
  </xsl:copy>
</xsl:template>

to copy the namespace declaration off the stylesheet element (though this would mean that every output document has an xmlns:frog declaration even if it doesn't involve any frog:* elements).


Edit: apparently Xalan doesn't like the copy-of namespaces from document(''), as an alternative, if you know that the document element will always have the same name then you can hard code that as a literal result element

<xsl:template match="/*">
  <everyone xmlns:frog="FFF">
    <xsl:copy-of select="namespace::*" />
    <xsl:apply-templates select="@*|node()" />
  </everyone>
</xsl:template>

(technically it will do what you want even without the explicit xmlns:frog in this template, since literal result elements always get the namespace declarations that are in scope at the point in the stylesheet where they are declared, but the intention is arguably clearer if you include it)

This mailing list post gives some possible insights into the reason for document('') not working as it should.

Sign up to request clarification or add additional context in comments.

8 Comments

Thank you for the fast response. I'm yet to test this but from my very limited knowledge of XSLT, I can't see how this will only add in the frog:title element if the ns3:firstname element in missing from the person.
@user2119994 the trick is in the match="ns2:person[not(ns3:firstname)]" - this template only applies to person elements that don't have a firstname, those that do have a firstname will use the identity template instead.
hi,Also, is there any way of achieving this by specifying only the new namespace I want to add to the root element and copy all existing ones across, without having to specify all the other ones too?
@user2119994 that's what it's doing. The xmlns:ns2 and xmlns:ns3 on the xsl:stylesheet are necessary to make the template match expression valid, and the xmlns= is to put the generated Master element in the correct namespace. You could do without the default namespace if you used <xsl:element name="Master" namespace="AAA" /> instead of <Master/> within the template, but the ns2 and 3 are definitely required.
I've just tried this and it works exactly as you said - thank you ever so much.
|

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.