简体   繁体   中英

Removing extra space in empty elements in XDocument.ToString()

I'm sending XML messages to a system that can't deal with spaces in the markup. Spaces in values are OK. I've been using linq/XDocument/XElements to manipulate/generate messages.

The issue is when an element is empty. For example:

XDocument xdoc = XDocument.Parse("<root><value/></root>");
Console.WriteLine(xdoc.ToString(SaveOptions.DisableFormatting));

This outputs a string with space in the markup after the element title, even though DisableFormatting is on.

<root><value /></root>

So I just stuck a replace on it:

Console.WriteLine(xdoc.ToString(SaveOptions.DisableFormatting).Replace(" />","/>"));

Is there anything bad I could run into there? Is there an obvious/more standard way to do this? It seems jank.

If your target system treats empty xml elements in the same way as self-closing xml elements - as in general, both are considered equal (but do consider the remarks at this post ) - you can implement a custom XmlWriter that outputs self-closing xml tags as empty xml tags.
Empty xml tags will not include any whitespace within their tag, eg. <value></value> .

The custom XmlTextWriter in the example below produces the following xml.
Note the <value></value> tags and that the whitespace value for the valueWithWhitespace has been preserved.

<root><value></value><valueWithWhitespace>   </valueWithWhitespace></root>

var xml = XElement.Parse(
    "<root><value /><valueWithWhitespace>   </valueWithWhitespace></root>",
    LoadOptions.PreserveWhitespace
    );

var stringWriter = new StringWriter();
using (var xmlWriter = new CustomXmlTextWriter(stringWriter))
{
    xml.WriteTo(xmlWriter);
    xmlWriter.Flush();
    Console.WriteLine(stringWriter);
}

public class CustomXmlTextWriter : XmlTextWriter
{
    public CustomXmlTextWriter(TextWriter writer)
        : base(writer)
    {}

    public CustomXmlTextWriter(Stream stream, Encoding encoding)
        : base(stream, encoding)
    {}

    public CustomXmlTextWriter(string filename, Encoding encoding)
        : base(filename, encoding)
    {}

    public override void WriteEndElement()
    {
        this.WriteFullEndElement();
    } 
}

Can also be done in a different way, if you are fine with extra buffering (using String.Replace, it seems you are):

class CustomXmlTextWriter : XmlTextWriter {
    public CustomXmlTextWriter(MemoryStream stream) : base(stream, new UTF8Encoding(false)) { }

    public override void WriteEndElement() {
        base.WriteEndElement();
        base.Flush();
        var stream = (MemoryStream)BaseStream;
        var buffer = stream.GetBuffer();
        var pos = stream.Position;
        if (pos > 3 && buffer[pos - 1] == '>' && buffer[pos - 2] == '/' && buffer[pos - 3] == ' ') {
            stream.Seek(-3, SeekOrigin.Current);
            stream.WriteByte((byte)'/');
            stream.WriteByte((byte)'>');
        }
    }
}

Then you can get it out of memory stream. The difference with string replace is that this approach won't break stuff like CDATA sections. The difference with using full closing tags is that it will probably more closely reproduce the stuff you have parsed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM