Writing XML attributes and namespace declarations in a specific order
Asked Answered
S

2

5

I am trying to make an XML file with a root element:

<urn:Command complete="true" xmlns:urn="namespaceURI">

So I have an element Command a namespace namespaceURI a prefix urn and finally an attribute string with name complete a value true and no namespace.

The code I have made to do this returns:

<urn:Command xmlns:urn="namespaceURI" complete="true">

So the problem is I would like the attribute string to be before the namespace definition in the XML file and I can't find a similar problem on this website.

I have tried writing a StartElement with a prefix and namespace then writing an AttributeString with no namespace, this returns the root element with the defined namespace first followed by the attribute string. I have also tried only defining a start element and then two attribute strings but then I can't find a way to write the prefix to the start element.

This is my original code that returns the root element with a namespace definition first the the attribute definition:

`Dim Writer as System.Xml.XmlWriter;
dim writerSettings  as System.Xml.XmlWriterSettings;
dim basePath as string;
dim source as string;
dim destination as string;

writerSettings = new System.Xml.XmlWriterSettings();
'writerSettings.ConformanceLevel= false;
'writerSettings.Encoding = new System.Text.UTF8Encoding(false);
writerSettings.OmitXmlDeclaration = false;

basePath = System.IO.Path.Combine("\\wnlcuieb502\WEI\Outbound","RolexSet");
source  = System.IO.Path.Combine(basePath,"\\wnlcuieb502\WEI\Outbound","TEST.XML");

Writer = System.Xml.XmlWriter.Create(source,writerSettings);
Writer.WriteStartDocument();

Writer.WriteStartElement("urn","SetPackagingOrder","urn:laetus.com:ws:tts:mes");
Writer.WriteAttributeString("complete",null,"true");
Writer.WriteEndElement();
Writer.WriteEndDocument();
Writer.dispose();


try
    destination = System.IO.Path.Combine(basePath,"TEST.XML");
    while not System.IO.File.Exists(destination)        
        System.IO.File.Move(source,destination);
    endwhile;
catch
    LogError(Me.HierarchicalName + ": Could not move XML file: "+ "TEST.XML" +" from " + source + " to " + destination + ", Error: " + error.Message);
endtry;`
Subconscious answered 2/9, 2019 at 11:16 Comment(3)
Can you provide code that reproduces the issue? With your code, the complete attribute is output before the namespace declaration (tried it), which is what you want if I understood correctly.Are
@Are I have editted my post to include simplified code i used. The code is in a script builder on a program called System platform by wonderware so is quite simplified to begin with. using this code i still get the output <urn:Command xmlns:urn="namespaceURI" complete="true">Subconscious
I also found out that the program uses a .Net library so this could be why we have diferencesSubconscious
G
6

XML attribute and namespace declaration order should never matter

Attribute order is insignificant per the XML Recommendation:

Note that the order of attribute specifications in a start-tag or empty-element tag is not significant.

Namespace declaration are like attributes (W3C Namespaces in XML Recommendation, section 3 Declaring Namespaces),

[Definition: A namespace (or more precisely, a namespace binding) is declared using a family of reserved attributes. Such an attribute's name must either be xmlns or begin xmlns:. These attributes, like any other XML attributes, may be provided directly or by default. ]

with similarly insignificant ordering.

So, no conformant XML tool or library will care about the order of XML attributes and XML namespace declarations, and neither should you.

This is why XML libraries generally do not provide a way to constrain attribute ordering, and trying to do so is almost always a sign that you're doing something wrong.


...except for rarely needed normalization applications

The XML recommendations will all consider attribute ordering and namespace declaration ordering to be insignificant, but see the section on attribute processing in the XML Normalization Recommendation or the Canonical XML Recommendation if your application has an unavoidable need for attribute ordering. The need to establish lexical equality/inequality for digital signatures (XML Signature Syntax and Processing Version 1.1) is one such exception.

See also (but only if you absolutely must order XML attributes and namespaces declarations):

Grassplot answered 2/9, 2019 at 12:51 Comment(2)
"XML attribute and namespace declaration order should never matter" - which is great, until you're trying to diff a problem in an attribute an it shows every single tag as being changed because they're out of order.Damask
@MauryMarkowitz: At the text level, diff is just doing its (lexical) job, unaware of XML semantics. If you're forced to use such a blunt instrument for diff'ing XML, see the answer's next section, ...except for rarely needed normalization applications.Grassplot
N
0

XML attribute order can indeed matter, for example when diffing documents or when generating hashes (or when using them in conjunction with other cryptographic operations).

There are at least two ways of controlling the attribute order using C# and .NET:

  • manually sorting all attributes of all elements
  • using canonical XML

1. Manually sorting all attributes of all elements

Assuming you have a XmlDocument object to work with (you can get one e.g. when serializing your .NET object), you can sort all attributes of all elements in any way you want by removing all attributes and adding them again in the order you want them to be in:

public static void SortAllAttributes(XmlDocument xmlDocument)
{
    foreach (XmlElement element in xmlDocument.SelectNodes("//*"))
    {
        var attributes = element.Attributes
            .Cast<XmlAttribute>()
            .ToArray();
        
        element.Attributes.RemoveAll();

        foreach (var attribute in attributes
                        .OrderBy(a => a.LocalName) // <-- custom sort order
                        .ThenBy(a => a.Name))
        {
            element.Attributes.Append(attribute);
        }
    }
}

2. Using Canonical XML

Assuming you have a XmlDocument object to work with (you can get one e.g. when serializing your .NET object), you can generate a canonical XML representation of that document. As part of that representation, all attributes are ordered in a specific way (first by namespace URI, then by attribute name without namespace prefix):

public static XmlDocument GetCanonicalXml(XmlDocument xmlDocument)
{
    var xmlTransform = new XmlDsigC14NTransform();
    xmlTransform.LoadInput(xmlDocument);

    using var memoryStream = (MemoryStream)xmlTransform.GetOutput(typeof(Stream));
    var canonicalXmlDocument = new XmlDocument();
    canonicalXmlDocument.Load(memoryStream);

    return canonicalXmlDocument;
}

Canonical XML has a couple of properties, e.g. forcing start/end tag pairs even for empty elements, that you might want to remedy. You can alter the XmlDocument to your liking after it has been transformed to canonical XML (here, we use the empty element syntax (<foo /> instead of <foo></foo>) again for empty elements):

public static void EnsureEmptyElementSyntaxUsage(XmlDocument xmlDocument)
{
    foreach (XmlElement element in xmlDocument.SelectNodes("//*"))
    {
        if (!element.IsEmpty &&
            string.IsNullOrWhiteSpace(element.InnerText) &&
            string.IsNullOrWhiteSpace(element.InnerXml))
        {
            element.IsEmpty = true;
        }
    }
}

You might also want to output the document with line breaks and indentations enabled (the default .NET implementation of C14N (canonical XML) does not output any line breaks, nor does is indent anything):

public static void WriteIndentedXmlDocument(string filePath, XmlDocument xmlDocument)
{
    using var xmlTextWriter = new XmlTextWriter(filePath, Encoding.Default);
    xmlTextWriter.Formatting = Formatting.Indented;
    xmlDocument.WriteTo(xmlTextWriter);
}
Notorious answered 30/11, 2023 at 15:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.