How to create an XML file with several roots from a StringBuilder C#

101 Views Asked by At

I'm a mechanician who has to develop tools yo manage xml files.

I have an issue and I need your help.

One of my xml file that I have to manage has several roots. Don't ask why, my provider will not change...

Below, the file i have loaded in a StringBuilder and I want to save it in a file.xml

<MLlibXMLCommand message_level='warning' command='merge'>
  <Inputs>
    <File iref='0000ancv' name='source\APS4322_TileCalibrations.bml'/>
  </Inputs>
  <Outputs>
    <File name='target\merged.bml' type='bml' />
  </Outputs>
</MLlibXMLCommand>

<MLlibXMLCommand message_level='warning' command='convert' output_model='special_transformation'>
  <Inputs>
    <File name='target\merged.bml' type='bml'/>
  </Inputs>
  <Outputs>
    <File name='target\XPlanar_TileCalibrations.bml' type='bml'/>
  </Outputs>
</MLlibXMLCommand>

I can't use the XmlDocument.Load(myStringBuilder) to save it in a new file (because of several roots).

I tried XmlWriter with fragment exception setting but I didn't succeed to write.MyStringBuilder() with the correct format. Break lines are not respected and things are added in my String. The file is useless. What i get is just below :

&lt;MLlibXMLCommand message_level="warning" command="merge"&gt;&lt;Inputs&gt;&lt;File iref="0000ancv" name="source\APS4322_TileCalibrations.bml" / /&gt;&lt;File iref="0000and1" name="source\APS4322_TileCalibrations.bml" /&gt;&lt;File iref="0000anct" name="source\APS4322_TileCalibrations.bml" /&gt;&lt;File iref="0000and1" name="source\APS4322_TileCalibrations.bml" /&gt;&lt;/Inputs&gt;&lt;Outputs&gt;&lt;File name="target\merged.bml" type="bml" /&gt;&lt;/Outputs&gt;&lt;/MLlibXMLCommand&gt;&lt;MLlibXMLCommand message_level="warning" command="convert" output_model="special_transformation"&gt;&lt;Inputs&gt;&lt;File name="target\merged.bml" type="bml" /&gt;&lt;/Inputs&gt;&lt;Outputs&gt;&lt;File name="target\XPlanar_TileCalibrations.bml" type="bml" /&gt;&lt;/Outputs&gt;&lt;/MLlibXMLCommand&gt;

Second solution is to used .WriteAllText but break lines are not respected. File I get which is no correct :

<MLlibXMLCommand message_level="warning" command="merge"><Inputs><File iref="0000ancv" name="source\APS4322_TileCalibrations.bml" /><File/></Inputs><Outputs><File name="target\merged.bml" type="bml" /></Outputs></MLlibXMLCommand><MLlibXMLCommand message_level="warning" command="convert" output_model="special_transformation"><Inputs><File name="target\merged.bml" type="bml" /></Inputs><Outputs><File name="target\XPlanar_TileCalibrations.bml" type="bml" /></Outputs></MLlibXMLCommand>

I don't know if I'm not using the right tool, or if I'm missing one or if my way of making it is bad.

Thank you in advance if you can unlock this step in my program process (the step consist to save myStringBuilder in a file like test.xml) .

I tried, and nothing is working properly :

StringBuilder sb = new 
StringBuilder(FormatTileCalibrationXml.InnerXml);
StringBuilder sb1 = new 
StringBuilder(FormatTileCalibrationXml.OuterXml);
sb.Remove(0, 6); //remove first root
int length = sb.Length - 7;
sb.Remove(length, 7); // remove last root

//Solution 1 to create the xml file with the stringbuilder as source
            XmlWriterSettings settingsw = new XmlWriterSettings();
            settingsw.OmitXmlDeclaration = true;
            settingsw.ConformanceLevel = ConformanceLevel.Fragment;
            
            using (XmlWriter writer = XmlWriter.Create("Test4.xml", settingsw))
            {
                writer.WriteString(sb.ToString());
            }
//Solution 2
            File.WriteAllText("Test3.xml", sb.ToString());

//Solution 3
            string path = @"C:\temp\Test5.xml";

            using (System.IO.StreamWriter file = new System.IO.StreamWriter(path))
            {
                file.Write(sb1);
            }
            
//Solution 4
            FormatTileCalibrationXml.LoadXml(sb.ToString());
            FormatTileCalibrationXml.Save("Test6.xml");

1

There are 1 best solutions below

1
max On

If you need to work with XML nodes then it is probably better to switch to linq-to-XML (System.Xml.Linq namespace), because it is harder to work with XML fragments like that with old XmlDocument.

You can load XML fragments (as a list of nodes, since XDocument requires single root):

static List<XNode> LoadNodes(XmlReader reader)
{
    var nodes = new List<XNode>();
    if(reader.Read())
    {
        while(reader.NodeType != XmlNodeType.None)
        {
            nodes.Add(XNode.ReadFrom(reader));
        }
    }
    return nodes;
}

static List<XNode> LoadNodesFromXML(string xml)
{
    using var textReader = new StringReader(xml);
    using var reader = XmlReader.Create(textReader,
        new XmlReaderSettings
        {
            ConformanceLevel = ConformanceLevel.Fragment,
            IgnoreWhitespace = true,
        });
    return LoadNodes(reader);
}

static List<XNode> LoadNodesFromFile(string fileName)
{
    using var reader = XmlReader.Create(fileName,
        new XmlReaderSettings
        {
            ConformanceLevel = ConformanceLevel.Fragment,
            IgnoreWhitespace = true,
        });
    return LoadNodes(reader);
}

LoadNodesFromXML() can be used to load XML from string, like

var nodes1 = LoadNodesFromXML("<Child1/><Child2/>");
var nodes2 = LoadNodesFromXML(sb.ToString());

And save them:

static void SaveNodes(IEnumerable<XNode> nodes, string fileName)
{
    using var writer = XmlWriter.Create(fileName,
        new XmlWriterSettings
        {
            ConformanceLevel = ConformanceLevel.Fragment,
            Indent = true,
        });
    foreach(var node in nodes)
    {
        node.WriteTo(writer);
    }
}

If you need more control over formatting, like inserting an empty line between nodes (like in original example), you can create StreamWriter explicilty:

static void SaveNodes(IEnumerable<XNode> nodes, string fileName)
{
    using var streamWriter = new StreamWriter(fileName);
    using var writer = XmlWriter.Create(streamWriter,
        new XmlWriterSettings
        {
            ConformanceLevel = ConformanceLevel.Fragment,
            Indent = true,
        });
    foreach(var node in nodes)
    {
        node.WriteTo(writer);
        writer.Flush();
        streamWriter.WriteLine(); // empty line between nodes
    }
}

You can even take XDocument and ignore its root element, producing multi-root file:

var doc = new XDocument(
    new XElement("Root",
        new XElement("Child1"),
        new XElement("Child2")));
SaveNodes(doc.Root.Nodes(), "output.xml");