yaloner
yaloner

Reputation: 758

C#: How to add indentation to the root element when writing an object to XML

I need to serialize an object to an XML file, but have some formatting restrictions:

  1. No namespace data.
  2. No encoding tag in the first line.
  3. Root element must be indented.

The following code I wrote takes care of restrictions 1 and 2:

public void WriteToXml(TextWriter writer)
{
    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
    ns.Add("", "");
    using (XmlWriter xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true, IndentChars = "\t"  }))
    {
        new XmlSerializer(typeof(CalledUnit)).Serialize(xmlWriter, this, ns);
    }
}

and gets me the following result:

<CalledUnit>
    <Caller>some info</Caller>
    <CallerId>1001</CallerId>
    <Called>some more info</Called>
    <CalledId>31</CalledId>
</CalledUnit>

but I want the following result (note the indentation):

    <CalledUnit>
        <Caller>some info</Caller>
        <CallerId>1001</CallerId>
        <Called>some more info</Called>
        <CalledId>31</CalledId>
    </CalledUnit>

Any ideas how to tackle this problem?

Upvotes: 0

Views: 354

Answers (3)

dbc
dbc

Reputation: 117323

There's no obvious way to do this using the .Net XML classes. (For instance, XmlTextWriter.Indent() isn't virtual, as shown by the reference source.) You may need to stream the XML into intermediate representation then patch it. However, instead of using a regex to do the patching (which can cause problems with CDATA nodes) you could use Mark Fussell's WriteShallowNode from Combining the XmlReader and XmlWriter classes for simple streaming transformations to do the patching:

    public void WriteToXml(TextWriter writer)
    {
        var ns = new XmlSerializerNamespaces();
        ns.Add("", "");
        var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true, IndentChars = "\t" };

        string xml;
        using (var tempWriter = new StringWriter())
        {
            using (XmlWriter xmlWriter = XmlWriter.Create(tempWriter, settings))
                new XmlSerializer(this.GetType()).Serialize(xmlWriter, this, ns);
            xml = tempWriter.ToString();
        }

        using (var reader = new StringReader(xml))
        using (var xmlReader = XmlReader.Create(reader))
        {
            XmlNodeType? prevType = null;
            using (var xmlWriter = XmlWriter.Create(writer, settings))
            {
                while (xmlReader.Read())
                {
                    if ((xmlReader.NodeType == XmlNodeType.Element || xmlReader.NodeType == XmlNodeType.EndElement)
                         && (prevType == null || prevType == XmlNodeType.Whitespace))
                    {
                        xmlWriter.WriteWhitespace(settings.IndentChars); // Add one more indentation
                    }
                    xmlWriter.WriteShallowNode(xmlReader);
                    prevType = xmlReader.NodeType;
                }
            }
        }
    }

And then, adapting Mark Fussell's code to an extension method:

public static class XmlWriterExtensions
{
    public static void WriteShallowNode(this XmlWriter writer, XmlReader reader)
    {
        // adapted from http://blogs.msdn.com/b/mfussell/archive/2005/02/12/371546.aspx
        if (reader == null)
            throw new ArgumentNullException("reader");

        if (writer == null)
            throw new ArgumentNullException("writer");

        switch (reader.NodeType)
        {
            case XmlNodeType.Element:
                writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
                writer.WriteAttributes(reader, true);
                if (reader.IsEmptyElement)
                {
                    writer.WriteEndElement();
                }
                break;

            case XmlNodeType.Text:
                writer.WriteString(reader.Value);
                break;

            case XmlNodeType.Whitespace:
            case XmlNodeType.SignificantWhitespace:
                writer.WriteWhitespace(reader.Value);
                break;

            case XmlNodeType.CDATA:
                writer.WriteCData(reader.Value);
                break;

            case XmlNodeType.EntityReference:
                writer.WriteEntityRef(reader.Name);
                break;

            case XmlNodeType.XmlDeclaration:
            case XmlNodeType.ProcessingInstruction:
                writer.WriteProcessingInstruction(reader.Name, reader.Value);
                break;

            case XmlNodeType.DocumentType:
                writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);
                break;

            case XmlNodeType.Comment:
                writer.WriteComment(reader.Value);
                break;

            case XmlNodeType.EndElement:
                writer.WriteFullEndElement();
                break;

            default:
                Debug.WriteLine("unknown NodeType " + reader.NodeType);
                break;

        }
    }
}

Upvotes: 0

Dan Field
Dan Field

Reputation: 21661

First, this is not really an XML issue - XML doesn't really care about such whitespace issues.

However, you could achieve this by doing the formatting yourself. In your WriteToXml method, instead of writing directly to your TextWriter, write to a StringBuilder, then run a regex on your string replacing \r\n with \r\n + the number of spaces you want to pad it by. Alternatively, you could read your stream with something like a StringReader.ReadLine() and write the output of that (prepended by your spaces) to your TextWriter.

Upvotes: 1

Leo Y
Leo Y

Reputation: 699

If the file ain't big you can serialize into a memory stream and then read from the stream line by line and write it into a file while prefixing with a needed space indentation. Comment if you need a code sample.

Upvotes: 1

Related Questions