Well, here is the final answer. I have used great Jimmy idea (which unfortunately is not complete itself) and complete recursion function to work properly.
Based on interface:
string RemoveAllNamespaces(string xmlDocument);
I represent here final clean and universal C# solution for removing XML namespaces:
//Implemented based on interface, not part of algorithm
public static string RemoveAllNamespaces(string xmlDocument)
{
XElement xmlDocumentWithoutNs = RemoveAllNamespaces(XElement.Parse(xmlDocument));
return xmlDocumentWithoutNs.ToString();
}
//Core recursion function
private static XElement RemoveAllNamespaces(XElement xmlDocument)
{
if (!xmlDocument.HasElements)
{
XElement xElement = new XElement(xmlDocument.Name.LocalName);
xElement.Value = xmlDocument.Value;
foreach (XAttribute attribute in xmlDocument.Attributes())
xElement.Add(attribute);
return xElement;
}
return new XElement(xmlDocument.Name.LocalName, xmlDocument.Elements().Select(el => RemoveAllNamespaces(el)));
}
It's working 100%, but I have not tested it much so it may not cover some special cases... But it is good base to start.
Answer from Peter Stegnar on Stack OverflowHow can i remove namespace prefix without removing the xmlns tag?
Remove all namespace attributes from xml using xmlstarlet - Unix & Linux Stack Exchange
How to remove namespaces from XML using XSLT - Stack Overflow
c# - How to remove 2 parent namespace tags from XML document - Stack Overflow
Videos
Well, here is the final answer. I have used great Jimmy idea (which unfortunately is not complete itself) and complete recursion function to work properly.
Based on interface:
string RemoveAllNamespaces(string xmlDocument);
I represent here final clean and universal C# solution for removing XML namespaces:
//Implemented based on interface, not part of algorithm
public static string RemoveAllNamespaces(string xmlDocument)
{
XElement xmlDocumentWithoutNs = RemoveAllNamespaces(XElement.Parse(xmlDocument));
return xmlDocumentWithoutNs.ToString();
}
//Core recursion function
private static XElement RemoveAllNamespaces(XElement xmlDocument)
{
if (!xmlDocument.HasElements)
{
XElement xElement = new XElement(xmlDocument.Name.LocalName);
xElement.Value = xmlDocument.Value;
foreach (XAttribute attribute in xmlDocument.Attributes())
xElement.Add(attribute);
return xElement;
}
return new XElement(xmlDocument.Name.LocalName, xmlDocument.Elements().Select(el => RemoveAllNamespaces(el)));
}
It's working 100%, but I have not tested it much so it may not cover some special cases... But it is good base to start.
The tagged most useful answer has two flaws:
- It ignores attributes
- It doesn't work with "mixed mode" elements
Here is my take on this:
public static XElement RemoveAllNamespaces(XElement e)
{
return new XElement(e.Name.LocalName,
(from n in e.Nodes()
select ((n is XElement) ? RemoveAllNamespaces(n as XElement) : n)),
(e.HasAttributes) ?
(from a in e.Attributes()
where (!a.IsNamespaceDeclaration)
select new XAttribute(a.Name.LocalName, a.Value)) : null);
}
Sample code here.
I am using this xslt but it does not remove the prefix after xmlns:
Sample input:
<root> <ns1:AppHdr xmlns:ns1="http://example.com/ns1"> <ns1:sender>Sender1</ns1:sender> <ns1:receiver>Receiver1</ns1:receiver> </ns1:AppHdr> <ns2:Document xmlns:ns2="http://example.com/ns2"> <ns2:title>Document Title</ns2:title> <ns2:author>Author Name</ns2:author> </ns2:Document> </root>
Expected output:
<root> <AppHdr xmlns="http://example.com/ns1"> <sender>Sender1</sender> <receiver>Receiver1</receiver> </AppHdr> <Document xmlns="http://example.com/ns2"> <title>Document Title</title> <author>Author Name</author> </Document> </root>
Output with current xslt:
<root> <AppHdr xmlns:ns1="http://example.com/ns1"> <sender>Sender1</sender> <receiver>Receiver1</receiver> </AppHdr> <Document xmlns:ns2="http://example.com/ns2"> <title>Document Title</title> <author>Author Name</author> </Document> </root>
I have found a couple of XSLT solutions to do this, both of which can conveniently be processed with xmlstarlet:
- How to remove the namespace and its prefixes in an XML file using XSLT? - IBM
- XSLT: Remove namespace prefix from elements - StackOverflow
In my worked example below, I've used the IBM code and saved it into the file xslt_ibm:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- https://www.ibm.com/support/pages/how-remove-namespace-and-its-prefixes-xml-file-using-xslt -->
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/|comment()|processing-instruction()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
If you consider your XML file to be saved in the file file.xml then this command will rename each element and attribute out of its namespace:
xmlstarlet transform xslt_ibm file.xml
Input (file.xml)
<md:EntityDescriptor xmlns="urn:_" xmlns:md="_"></md:EntityDescriptor>
Output
<?xml version="1.0"?>
<EntityDescriptor/>
Using Andrey Kislyuk's xq, an XML-parsing wrapper around jq (installed together with yq):
xq -x 'walk(del( .["@xmlns"]?, .["@xmlns:md"]? ))' file
This walks all nodes in the XML document structure and deletes all xmlns and xmlns:md attributes wherever these are found.
Given some input document,
<?xml version="1.0"?>
<root test="val">
<md:EntityDescriptor xmlns="urn:_" xmlns:md="_"/>
</root>
... this would output
<root test="val">
<md:EntityDescriptor></md:EntityDescriptor>
</root>
You get in-place editing with the --in-place or -i option.
Your XSLT removes attributes also, because you don't have a template that would copy them. <xsl:template match="*"> matches only elements, not attributes (or text, comments or processing instructions).
Below is a stylesheet that removes all namespace definitions from the processed document but copies all other nodes and values: elements, attributes, comments, text and processing instructions. Please pay attention to 2 things
- Copying the attributes as such is not enough to remove all namespaces. Also an attribute can belong to a namespace, even when the containing element doesn't belong to a namespace. Therefore also attributes need to be created, like elements. Creating attributes is done with
<xsl:attribute>element. - A valid XML document cannot contain an element that has two or more attributes with same expanded name but elements can contain multiple attributes with same local name if the attributes have different namespaces. This means that removing the namespace prefix from an attribute name will cause dataloss if there is an element that has at leas two attributes with same local name. Other one of these attributes will be removed (or overwritten).
...and the code:
<?xml version="1.0"?>
<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"/>
<!-- Stylesheet to remove all namespaces from a document -->
<!-- NOTE: this will lead to attribute name clash, if an element contains
two attributes with same local name but different namespace prefix -->
<!-- Nodes that cannot have a namespace are copied as such -->
<!-- template to copy elements -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@* | node()"/>
</xsl:element>
</xsl:template>
<!-- template to copy attributes -->
<xsl:template match="@*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<!-- template to copy the rest of the nodes -->
<xsl:template match="comment() | text() | processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
You could also use <xsl:template match="node()"> instead of that last template but then you should use priority attribute to prevent elements matching to this template.
How can I include the attributes there?
Just append this template to the one you already have:
<xsl:template match="@*">
<xsl:copy/>
</xsl:template>
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>" +
"<mgns1:PlaceXmlMessage xmlns:mgns1=\"http://www.testing.com/\">" +
"<mgns1:xmlDocument>" +
"<HubChangeRequest version=\"1.0\">" +
"<TransactionReference>" +
"<AuthenticationID>TestSUPPLIERS</AuthenticationID>" +
"<AuthenticationKey>hidden</AuthenticationKey>" +
"<TransactionNumber>hidden</TransactionNumber>" +
"</TransactionReference>" +
"<MessageNumber>hidden</MessageNumber>" +
"<MessageCreatedDate>2016-03-01T12:31:31</MessageCreatedDate>" +
"<ReferenceNumber>ABC123456789</ReferenceNumber>" +
"<ProductDetails>" +
"<StockItem LineReference=\"123456/1\">" +
"<NewStatus>Despatched</NewStatus>" +
"<DespatchReference>3 PARCEL LINE</DespatchReference>" +
"</StockItem>" +
"<StockItem LineReference=\"123345/2\">" +
"<NewStatus>Despatched</NewStatus>" +
"<DespatchReference>3 PARCEL LINE</DespatchReference>" +
"</StockItem>" +
"</ProductDetails>" +
"</HubChangeRequest>" +
"</mgns1:xmlDocument>" +
"</mgns1:PlaceXmlMessage>";
XDocument doc = XDocument.Parse(xml);
XElement placeXmlMessage = (XElement)doc.FirstNode;
XElement secondNode = placeXmlMessage.Elements().FirstOrDefault();
XElement hubChangeRequest = secondNode.Elements().FirstOrDefault();
placeXmlMessage.ReplaceWith(hubChangeRequest);
}
}
}
You can use an XSLT transformation to get your input into the right output shape.
The following code transforms your input xml, assuming it lives in a string variable caled input, to an MemoryStream.
Transform input via XSLT to stream
// Create an XSLT Transformer
var xslt = new XslCompiledTransform();
xslt.Load("copydoc.xslt");
var settings = new XmlWriterSettings
{
Encoding = new UTF8Encoding(false) // NO BOM
};
// open all streams for reading and writing
using (var ms = new MemoryStream())
{
using (var xw = XmlWriter.Create(ms, settings))
{
// input is the string with the xml input
using (var sr = new StringReader(input))
using (var xr = XmlReader.Create(sr))
{
xslt.Transform(xr, xw);
// the memorystream now has the result
}
}
Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
}
Copydoc.xslt
The Xslt file is fairly simple and uses two templates and a strategic apply-template to get the childnode under your <mgsn1:xmlDocument> node:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:mgns1="http://www.testing.com/"
exclude-result-prefixes="msxsl mgns1 "
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<!-- select the child node of xmlDocument-->
<xsl:apply-templates select="mgns1:PlaceXmlMessage/mgns1:xmlDocument/child::*" />
</xsl:template>
<!-- match each element -->
<xsl:template match="*">
<!-- make sure to get rid of the namespaces-->
<xsl:element name="{local-name(.)}">
<!-- be explicit about copying attributes -->
<xsl:apply-templates select="@*" />
<!-- copy childs-->
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<!-- handle attributes -->
<xsl:template match="@*">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
If you add the file copydoc.xslt to your project don't forget to set the projectfile property Copy to output directory to copy always otherwise the xslt file will not be found.
I found the answer above to be helpful, but it didn't quite work for me. This ended up working better:
// Gets rid of all namespace definitions
$xml_string = preg_replace('/xmlns[^=]*="[^"]*"/i', '', $xml_string);
// Gets rid of all namespace references
$xml_string = preg_replace('/[a-zA-Z]+:([a-zA-Z]+[=>])/', '$1', $xml_string);
If you're using XPath then it's a limitation with XPath and not PHP look at this explanation on xpath and default namespaces for more info.
More specifically its the xmlns="" attribute in the root node which is causing the problem. This means that you'll need to register the namespace then use a QName thereafter to refer to elements.
$feed = simplexml_load_file('http://www.sitepoint.com/recent.rdf');
$feed->registerXPathNamespace("a", "http://www.domain.com/schema/data");
$result = $feed->xpath("a:Data/a:Something/...");
Important: The URI used in the registerXPathNamespace call must be identical to the one that is used in the actual XML file.
@Markus Freitag To override the behaviour of XmlSerializer you need XmlWriterSettings for override or remove XML declaration and XmlSerializerNamespaces for override namespace:
public static string ToXML(this T obj)
{
// Remove Declaration
var settings = new XmlWriterSettings
{
Indent = true,
OmitXmlDeclaration = true
};
// Remove Namespace
var ns = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
using (var stream = new StringWriter())
using (var writer = XmlWriter.Create(stream, settings))
{
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(writer, obj, ns);
return stream.ToString();
}
}
See full example here.
The most natural and easy way to implement it is via XSLT.
Please find below an example of it.
The XSLT is generic. It will remove (1) XML prolog declaration, and (2) any namespace(s) from any XML file.
Input XML
XSLT
Output XML
c# to launch XSLT transformation
void Main()
{
const string SOURCEXMLFILE = @"e:\Temp\UniversalShipment.xml";
const string XSLTFILE = @"e:\Temp\RemoveNamespaces.xslt";
const string OUTPUTXMLFILE = @"e:\temp\UniversalShipment_output.xml";
try
{
XsltArgumentList xslArg = new XsltArgumentList();
using (XmlReader src = XmlReader.Create(SOURCEXMLFILE))
{
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(XSLTFILE, new XsltSettings(true, true), new XmlUrlResolver());
XmlWriterSettings settings = xslt.OutputSettings.Clone();
settings.IndentChars = "\t";
// to remove BOM
settings.Encoding = new UTF8Encoding(false);
using (XmlWriter result = XmlWriter.Create(OUTPUTXMLFILE, settings))
{
xslt.Transform(src, xslArg, result, new XmlUrlResolver());
result.Close();
}
}
Console.WriteLine("File '{0}' has been generated.", OUTPUTXMLFILE);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}