Colin Burnett
Colin Burnett

Reputation: 11518

XML indenting when injecting an XML string into an XmlWriter

I have an XmlTextWriter writing to a file and an XmlWriter using that text writer. This text writer is set to output tab-indented XML:

XmlTextWriter xtw = new XmlTextWriter("foo.xml", Encoding.UTF8);
xtw.Formatting = Formatting.Indented;
xtw.IndentChar = '\t';
xtw.Indentation = 1;

XmlWriter xw = XmlWriter.Create(xtw);

Changed per Jeff's MSDN link:

XmlWriterSettings set = new XmlWriterSettings();
set.Indent = true;
set.IndentChars = "\t";
set.Encoding = Encoding.UTF8;

xw = XmlWriter.Create(f, set);

This does not change the end result.


Now I'm an arbitrary depth in my XmlWriter and I'm getting a string of XML from elsewhere (that I cannot control) that is a single-line, non-indented XML. If I call xw.WriteRaw() then that string is injected verbatim and does not follow my indentation I want.

...
string xml = ExternalMethod();
xw.WriteRaw(xml);
...

Essentially, I want a WriteRaw that will parse the XML string and go through all the WriteStartElement, etc. so that it gets reformatted per the XmlTextWriter's settings.

My preference is a way to do this with the setup I already have and to do this without having to reload the final XML just to reformat it. I'd also prefer not to parse the XML string with the likes of XmlReader and then mimic what it finds into my XmlWriter (very very manual process).

At the end of this I'd rather have a simple solution than one that follows my preferences. (Best solution, naturally, would be simple and follows my preferences.)

Upvotes: 18

Views: 15157

Answers (6)

landete85
landete85

Reputation: 11

I was looking for an answer to this issue but in VB.net.

Thanks to Colin Burnett, I solved it. I made two corrections: first, the XmlReader has to ignore white spaces (settings.IgnoreWhiteSpaces); second, the reader has to be back into the element after it reads attributes. Below you can see how the code looks like.

Also I tried the solution of GreyCloud, but in the generated XML there were some annoying empties attributes (xlmns).

Private Sub PipeXMLIntoWriter(xw As XmlWriter, xml As String)
    Dim dat As Byte() = New System.Text.UTF8Encoding().GetBytes(xml)
    Dim m As New MemoryStream()
    m.Write(dat, 0, dat.Length)
    m.Seek(0, SeekOrigin.Begin)
    Dim settings As New XmlReaderSettings
    settings.IgnoreWhitespace = True
    settings.IgnoreComments = True
    Dim r As XmlReader = XmlReader.Create(m, settings)

    While r.Read()
          Select Case r.NodeType
                Case XmlNodeType.Element
                    xw.WriteStartElement(r.Name)

                    If r.HasAttributes Then
                        For i As Integer = 0 To r.AttributeCount - 1
                            r.MoveToAttribute(i)
                            xw.WriteAttributeString(r.Name, r.Value)
                        Next
                        r.MoveToElement()
                    End If

                    If r.IsEmptyElement Then
                        xw.WriteEndElement()
                    End If
                    Exit Select
                Case XmlNodeType.EndElement
                    xw.WriteEndElement()
                    Exit Select
                Case XmlNodeType.Text
                    xw.WriteString(r.Value)
                    Exit Select
                Case Else

                    Throw New Exception("Unrecognized node type: " + r.NodeType)
            End Select
      End While
End Sub

Upvotes: 1

GreyCloud
GreyCloud

Reputation: 3100

composing the answers above I have found this works:

private static string FormatXML(string unformattedXml) {
    // first read the xml ignoring whitespace
    XmlReaderSettings readeroptions= new XmlReaderSettings {IgnoreWhitespace = true};
    XmlReader reader = XmlReader.Create(new StringReader(unformattedXml),readeroptions);

    // then write it out with indentation
    StringBuilder sb = new StringBuilder();
    XmlWriterSettings xmlSettingsWithIndentation = new XmlWriterSettings { Indent = true};                       
    using (XmlWriter writer = XmlWriter.Create(sb, xmlSettingsWithIndentation)) {
        writer.WriteNode(reader, true);
    }

    return sb.ToString();
}

Upvotes: 2

zam6ak
zam6ak

Reputation: 7259

How about:

string xml = ExternalMethod();
var xd = XDocument.Parse(xml);
xd.WriteTo(xw);

Upvotes: 1

Jeff Yates
Jeff Yates

Reputation: 62367

You shouldn't use XmlTextWriter, as indicated in MSDN where it states:

In the .NET Framework version 2.0 release, the recommended practice is to create XmlWriter instances using the XmlWriter.Create method and the XmlWriterSettings class. This allows you to take full advantage of all the new features introduced in this release. For more information, see Creating XML Writers.

Instead, you should use XmlWriter.Create to get your writer. You can then use the XmlWriterSettings class to specify things like indentation.

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = "\t";

Update

I think you can just use WriteNode. You take your xml string and load it into an XDocument or XmlReader and then use the node from that to write it into your XmlWriter.

Upvotes: 3

aaronb
aaronb

Reputation: 2229

How about using a XmlReader to read the xml as xml nodes?

string xml = ExternalMethod();
XmlReader reader =  XmlReader.Create(new StringReader(xml));
xw.WriteNode(reader, true);

Upvotes: 30

Colin Burnett
Colin Burnett

Reputation: 11518

This is the best I've got so far. A very manual process that only supports what is written. My string XML is nothing more than tags, attributes, and text data. If it supported namespaces, CDATA, etc. then this would have to grow accordingly.

Very manual, very messy and very likely prone to bugs but it does accomplish my preferences.

private static void PipeXMLIntoWriter(XmlWriter xw, string xml)
{
    byte[] dat = new System.Text.UTF8Encoding().GetBytes(xml);
    MemoryStream m = new MemoryStream();
    m.Write(dat, 0, dat.Length);
    m.Seek(0, SeekOrigin.Begin);
    XmlReader r = XmlReader.Create(m);

    while (r.Read())
    {
        switch (r.NodeType)
        {
            case XmlNodeType.Element:
                xw.WriteStartElement(r.Name);

                if (r.HasAttributes)
                {
                    for (int i = 0; i < r.AttributeCount; i++)
                    {
                        r.MoveToAttribute(i);
                        xw.WriteAttributeString(r.Name, r.Value);
                    }
                }

                if (r.IsEmptyElement)
                {
                    xw.WriteEndElement();
                }
                break;
            case XmlNodeType.EndElement:
                xw.WriteEndElement();
                break;
            case XmlNodeType.Text:
                xw.WriteString(r.Value);
                break;

            default:
                throw new Exception("Unrecognized node type: " + r.NodeType);
        }
    }
}

Upvotes: 2

Related Questions