Application Development and Automation Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 
Read only

Transform XML to a different namespace prefix

matt
Active Contributor
7,192

I'm generating the XML for a ZUGFeRD document. It's all working great, except some validators are rejecting my XML due to the namespace prefix.

I've used the ABAP Proxy framework to generate a wsdl from the XSD for ZUGFeRD 2.0 XML, and from that generated an ABAP proxy to get get all the structures and types defined.

I can then use

        DATA(with_default_ns) = cl_proxy_xml_transform=>abap_to_xml_xstring(
             abap_data  = input
             ddic_type  = 'ZFB_MESSAGE1'
             xml_header = 'full' ).

to get the XML. My input is the type that the ABAP Proxy has generated.

The problem is that this creates names spaces like this:

<?xml version="1.0" encoding="utf-8"?>
<n0:CrossIndustryInvoice xmlns:n0="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
                         xmlns:prx="urn:sap.com:proxy:DEV:/1SAI/TASE493CC7A2F13F4EF8BCD:750"
                         xmlns:n1="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
                         xmlns:n2="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
                         xmlns:n3="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
	<n0:ExchangedDocumentContext>
		<n2:GuidelineSpecifiedDocumentContextParameter>
			<n2:ID>urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0</n2:ID>
		</n2:GuidelineSpecifiedDocumentContextParameter>
	</n0:ExchangedDocumentContext>
	<n0:ExchangedDocument>
		<n2:ID>0090000702</n2:ID>
		<n2:TypeCode>380</n2:TypeCode>
		<n2:IssueDateTime>
			<n3:DateTimeString format="102">20190829</n3:DateTimeString>
		</n2:IssueDateTime>
		<n2:IncludedNote>

But to be completely correct, I need to convert all those nm: prefixes to ZUGFeRD prefixes - specifically

n0: to rsm:, n1: to qdt:, n2: to ram:, n3: to udt:
I've tried to use an XSLT stylesheet to transform the name prefixes. I've tried various form like this:

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

and

  <xsl:template match="n0:*">
    <xsl:element name="rsm:{local-name()}" namespace="http://sample.com/rsm">
      <xsl:value-of select="."/>
    </xsl:element>
  </xsl:template>

But just can't get it to work. Judging from the fact that when I set a breakpoint in it at the line after the template match it doesn't stop, it seems it just isn't finding a match.

Anyone have any ideas?

1 ACCEPTED SOLUTION
Read only

Sandra_Rossi
Active Contributor
5,797

I can rename the namespace prefixes (source XML with 2 namespace prefixes n0 and n2, result XML with rsm and ram) (NB: your link is very good https://stackoverflow.com/questions/10124887/how-to-replace-namespace-prefix-in-element-and-attribut...

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sap="http://www.sap.com/sapxsl" xmlns:n0="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:n2=
"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" version="1.0">

  <xsl:strip-space elements="*"/>

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

  <xsl:template match="n0:*">
    <xsl:element name="rsm:{local-name()}" namespace="http://sample.com/rsm">
      <xsl:apply-templates select="*|text()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="n2:*">
    <xsl:element name="ram:{local-name()}" namespace="http://sample.com/ram">
      <xsl:apply-templates select="*|text()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="text()">
    <xsl:value-of select="."/>
  </xsl:template>

</xsl:transform>

I tried various things to get the XSLT to work - but getting nowhere. You'd have thought it was quite a simple thing to do! I was using this as my base https://stackoverflow.com/questions/10124887/how-to-replace-namespace-prefix-in-element-and-attribut...

However, it's all moot, since we're going with "the namespaces don't matter; your validator needs work". I think it's perhaps a shortcut they've taken, as the error message is

"Cannot find the declaration of element 'n0:CrossIndustryInvoice'".

Now using this validator (needs registration, but currently free). https://portal3.gefeg.com/invoice/validation

8 REPLIES 8
Read only

Sandra_Rossi
Active Contributor
5,797

Could you please name this fake XML validator that is unable to interpret correctly namespace prefixes!!?? I'd say that it's none of your business to spend time because of their buggy validator. The namespace prefixes are not important, what is important is the URI. n0 or rsm (or whatever) pointing to the same URI are completely equivalent.

Concerning the XSLT question:

  1. I think the way you did it is correct i.e.
<xsl:element name="rsm:{local-name()}" namespace="http://sample.com/rsm">
  1. For the match, did you declare xmlns:n0="..." at an upper level in the XSLT?
Read only

matt
Active Contributor
5,797

It's not an XML validator, it's a ZUGFeRD validator. E.g. https://www.zugferd-community.net/de/open_community/validation.

Given that ZUGFeRD is a standard use by the German bureaucracy, I wouldn't be surprised if you have to get the namespace right...

Point 2: You mean the XSLT of my transformation. No... I'll give that a go! thanks.

Read only

sergei-u-niq
Active Contributor
5,797

I agree with sandra.rossi - ZUGFeRD is a certain XML format and general XML rules apply. If the validator rejects generated files, the validator may well be buggy.

you can get the specs here: https://www.ferd-net.de/standards/zugferd-2.1.1/index.html - they contain XSD files that define the various levels of ZUGFeRD definition (minimal, basic, etc) - but you have the XSD, since you generated the proxy from te XSD.

Just use a generic XML validator that validates your generated XML against the XSD files provided by the spec. If your file is fine, raise a ticket with whoever is responsible for the validator that rejects your file.

Read only

matt
Active Contributor
0 Likes
5,797

I tried various things to get the XSLT to work - but getting nowhere. You'd have thought it was quite a simple thing to do! I was using this as my base https://stackoverflow.com/questions/10124887/how-to-replace-namespace-prefix-in-element-and-attribut...

However, it's all moot, since we're going with "the namespaces don't matter; your validator needs work". I think it's perhaps a shortcut they've taken, as the error message is

"Cannot find the declaration of element 'n0:CrossIndustryInvoice'".

Now using this validator (needs registration, but currently free). https://portal3.gefeg.com/invoice/validation

Read only

Sandra_Rossi
Active Contributor
5,798

I can rename the namespace prefixes (source XML with 2 namespace prefixes n0 and n2, result XML with rsm and ram) (NB: your link is very good https://stackoverflow.com/questions/10124887/how-to-replace-namespace-prefix-in-element-and-attribut...

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sap="http://www.sap.com/sapxsl" xmlns:n0="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:n2=
"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" version="1.0">

  <xsl:strip-space elements="*"/>

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

  <xsl:template match="n0:*">
    <xsl:element name="rsm:{local-name()}" namespace="http://sample.com/rsm">
      <xsl:apply-templates select="*|text()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="n2:*">
    <xsl:element name="ram:{local-name()}" namespace="http://sample.com/ram">
      <xsl:apply-templates select="*|text()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="text()">
    <xsl:value-of select="."/>
  </xsl:template>

</xsl:transform>
Read only

matt
Active Contributor
5,797

I had to add the other namespaces, so I've now got:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:n0="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
                xmlns:n1="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
                xmlns:n2="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
                xmlns:n3="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
                xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
                xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
                xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
                xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
                version="2.0">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:strip-space elements="*"/> <xsl:template match="/"> <xsl:apply-templates select="*|text()"/> </xsl:template> <!-- change the namespace declaration for n0 to rsm --> <xsl:template match="n0:*"> <xsl:element name="rsm:{local-name()}"> <xsl:apply-templates select="*|text()"/> </xsl:element> </xsl:template> <!-- change the namespace declaration for n1 to qdt --> <xsl:template match="n1:*"> <xsl:element name="qdt:{local-name()}"> <xsl:apply-templates select="*|text()"/> </xsl:element> </xsl:template> <!-- change the namespace declaration for n2 to ram --> <xsl:template match="n2:*"> <xsl:element name="ram:{local-name()}"> <xsl:apply-templates select="*|text()"/> </xsl:element> </xsl:template> <!-- change the namespace declaration for n3 to udt --> <xsl:template match="n3:*"> <xsl:element name="udt:{local-name()}"> <xsl:apply-templates select="*|text()"/> </xsl:element> </xsl:template> <xsl:template match="text()"> <xsl:value-of select="."/> </xsl:template> </xsl:stylesheet>

And it's nearly working! But for...

<n2:IssueDateTime>
	<n3:DateTimeString format="102">20201117</n3:DateTimeString>
</n2:IssueDateTime>

I get

<ram:IssueDateTime>
	<udt:DateTimeString>20201117</udt:DateTimeString>
</ram:IssueDateTime>

So for some reason, the format attribute is being lost.

Read only

5,797

@* must be added for attributes:

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

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

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

matt
Active Contributor
5,797

Brilliant!

I finally constructed this:

<?sap.transform xslt?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:n0="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
                xmlns:n1="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
                xmlns:n2="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
                xmlns:n3="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
                xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
                xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
                xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
                xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
                version="2.0">
	<xsl:output encoding="UTF-8"
	            indent="yes"
	            method="xml"/>
	<xsl:strip-space elements="*"/>
	<xsl:template match="/">
		<xsl:apply-templates select="*|text()|@*"/>
	</xsl:template>
	<xsl:template match="@*">
		<xsl:attribute name="{local-name()}">
			<xsl:value-of select="."/>
		</xsl:attribute>
	</xsl:template>
	<!-- change the namespace declaration for n0 to rsm -->
	<xsl:template match="n0:*">
		<xsl:element name="rsm:{local-name()}">
			<xsl:apply-templates select="*|text()|@*"/>
		</xsl:element>
	</xsl:template>
	<!-- change the namespace declaration for n1 to qdt -->
	<xsl:template match="n1:*">
		<xsl:element name="qdt:{local-name()}">
			<xsl:apply-templates select="*|text()|@*"/>
		</xsl:element>
	</xsl:template>
	<!-- change the namespace declaration for n2 to ram -->
	<xsl:template match="n2:*">
		<xsl:element name="ram:{local-name()}">
			<xsl:apply-templates select="*|text()|@*"/>
		</xsl:element>
	</xsl:template>
	<!-- change the namespace declaration for n3 to udt -->
	<xsl:template match="n3:*">
		<xsl:element name="udt:{local-name()}">
			<xsl:apply-templates select="*|text()|@*"/>
		</xsl:element>
	</xsl:template>
	<xsl:template match="text()">
		<xsl:value-of select="."/>
	</xsl:template>
</xsl:stylesheet>

I've checked in both ZUGFeRD community and gefeg/invoice.fans and it works. It's all valid.