Search code examples
c#xmlxmlreaderxmlwriter

Formulate a well-formed xml from Xml fragments using c#


I am formulating xmlnodes from a loop. so what it does is

   var settings = new XmlWriterSettings();
   settings.OmitXmlDeclaration = true;
   settings.Indent = true;
   var ns = new XmlSerializerNamespaces();
   ns.Add("", "");
   foreach (Person human in bar)
    {
        var serializer = new XmlSerializer(typeof(Person));
        using (var stream = new FileStream(filepath, FileMode.Append))
        using (var writer = XmlWriter.Create(stream, settings))
        {
            serializer.Serialize(writer, human, ns);
        }
    }

It formulates xml fragments when the loop is done the output XML is like this

    <Person>
  <Name>mar8a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar1a</Name>
  <Age>1</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar2a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar3a</Name>
  <Age>1</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person><Person>
  <Name>mar4a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar5a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar6a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar7a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>
<Person>
  <Name>mar8a</Name>
  <Age>11</Age>
  <Sex>MALE</Sex>
  <Address>TOP 92 BOTTOM</Address>
  <SingleYn>false</SingleYn>
</Person>

the problem i am running thru is how to modify it and make it a a well formed xml with root node and declaration at the end of the loop.

ive tried the following concept but no luck on it for it restricts me to write no root elements on the writer.

        StringBuilder output = new StringBuilder();
        XmlReaderSettings ws = new XmlReaderSettings();
        ws.ConformanceLevel = ConformanceLevel.Fragment;
        String xmlString =
                @"<Item>test with a child element stuff</Item>
                <Item>test with a child element stuff</Item>";
        // Create an XmlReader
        using (XmlReader reader = XmlReader.Create(new StringReader(xmlString), ws))
        {
            XmlWriterSettings ws2 = new XmlWriterSettings();
            ws2.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(output, ws2))
            {
                writer.WriteStartDocument();
                // Parse the file and display each of the nodes.
                while (reader.Read())
                {
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            writer.WriteStartElement(reader.Name);
                            break;
                        case XmlNodeType.Text:
                            writer.WriteString(reader.Value);
                            break;
                        case XmlNodeType.XmlDeclaration:
                        case XmlNodeType.ProcessingInstruction:
                            writer.WriteProcessingInstruction(reader.Name, reader.Value);
                            break;
                        case XmlNodeType.Comment:
                            writer.WriteComment(reader.Value);
                            break;
                        case XmlNodeType.EndElement:
                            writer.WriteFullEndElement();
                            break;
                    }
                }
                writer.WriteEndDocument();

            }
        }

Update!!

here is the code that serialize a list my implementation of serializer

        public static async Task WriteXMLAsync<T>(this List<T> listRows, T entity, VMEXPORT[] arrVmExport, string filePath)
        where T : class
    {
        XmlWriterSettings Xmlsettings = new XmlWriterSettings();
        Xmlsettings.Indent = true;
        Xmlsettings.OmitXmlDeclaration = false;
        Xmlsettings.NewLineOnAttributes = true;
        Xmlsettings.Async = true;
        Xmlsettings.Encoding = Encoding.UTF8;
        Xmlsettings.CheckCharacters = false;

        XmlAttributeOverrides Xmloverrides = new XmlAttributeOverrides();
        XmlAttributes Xmlattribs = new XmlAttributes();
        Xmlattribs.XmlIgnore = true;
        Xmlattribs.XmlElements.Add(new XmlElementAttribute("SfiObjectState"));
        Xmloverrides.Add(typeof(T), "SfiObjectState", Xmlattribs);


        if (!File.Exists(filePath))
        {
            using (var fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 4096, true))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<T>), Xmloverrides);
                using (XmlWriter xmlWriter = XmlWriter.Create(fileStream, Xmlsettings))
                {
                    serializer.Serialize(xmlWriter, listRows);
                    await xmlWriter.FlushAsync();
                }
            }
        }
        else
        {

            using (var fileStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.None, 4096, true))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<T>), Xmloverrides);
                using (XmlWriter xmlWriter = XmlWriter.Create(fileStream, Xmlsettings))
                {
                    serializer.Serialize(xmlWriter, listRows);
                    await xmlWriter.FlushAsync();
                }
            }


        }

    }

And heres what iterates on the method above implementing take and skip

 public async Task WriteXmlDataAsync<TEntity>(IQueryable<TEntity> listToWrite, [DataSourceRequest]DataSourceRequest dataRequest,
                                          int countno, VMEXPORT[] vmExportarr, CancellationToken token,
                                          TEntity entity, string csvFileNametx, string XmlFilePathtx)
      where TEntity : class
    {
        dataRequest.GroupingToSorting();
        int datapageno = (countno / GeneralConst.L_MAX_EXPORT_REC) + 1;
        for (int ctrno = 1; ctrno <= datapageno; )
        {
            if (token.IsCancellationRequested)
            {
                RemoveTask(csvFileNametx);
                token.ThrowIfCancellationRequested();
            }
            dataRequest.Page = ctrno;
            dataRequest.PageSize = GeneralConst.L_MAX_EXPORT_REC;
            var dataSourceResult = listToWrite.ToDataSourceResult(dataRequest);
            await dataSourceResult.Data.Cast<TEntity>().ToList().WriteXMLAsync(entity, vmExportarr, XmlFilePathtx);
            ctrno = ctrno + 1;
            int percentageno = (ctrno * 100) / datapageno;
            if (percentageno > 100) percentageno = 100;
            UpdateTask(csvFileNametx, percentageno);
        }

    }

Solution

  • Option 1

    You could do something quick and dirty like this after the XML file is written:

        public static void AddOuterElement(string fileName, string elementName)
        {
            var startElement = string.Format(@"<{0}>", elementName);
            var endElement = string.Format(@"</{0}>", elementName);
    
            var tmpName = Path.GetTempFileName();
            try
            {
                using (var writer = new StreamWriter(tmpName, false, Encoding.UTF8))
                {
                    writer.WriteLine(startElement);
                    foreach (var line in File.ReadLines(fileName))  // Reads lines incrementally rather than all at once.
                        writer.WriteLine(line);
                    writer.WriteLine(endElement);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
                try
                {
                    System.IO.File.Delete(tmpName);
                }
                catch (Exception)
                {
                }
                throw;
            }
            System.IO.File.Delete(fileName);
            System.IO.File.Move(tmpName, fileName);
        }
    

    This requires writing the XML file twice.

    Option 2

    Assume you have some method that can return lists of your Person class in chunks, say with the following signature:

        IEnumerable<IEnumerable<Person>> GetPeopleInChunks()
        {
            // Query the database in chunks of 200 and yield return each list.
        }
    

    Then you could use the following classes adapted from this answer to serialize all the people in the database sequentially without ever loading them all into memory at once:

    // Proxy class for any enumerable with the requisite `Add` methods.
    public class EnumerableProxy<T> : IEnumerable<T>
    {
        [XmlIgnore]
        public IEnumerable<T> BaseEnumerable { get; set; }
    
        public void Add(T obj)
        {
            throw new NotImplementedException();
        }
    
        #region IEnumerable<T> Members
    
        public IEnumerator<T> GetEnumerator()
        {
            if (BaseEnumerable == null)
                return Enumerable.Empty<T>().GetEnumerator();
            return BaseEnumerable.GetEnumerator();
        }
    
        #endregion
    
        #region IEnumerable Members
    
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    
        #endregion
    }
    
    [XmlRoot("People")]
    public class People
    {
        [XmlIgnore]
        public IEnumerable<Person> Results { get; set; }
    
        [XmlElement("Person")]
        public EnumerableProxy<Person> ResultsProxy
        {
            get
            {
                return new EnumerableProxy<Person> { BaseEnumerable = Results };
            }
            set
            {
                throw new NotImplementedException();
            }
        }
    }
    

    And then:

        public void WriteXml(string fileName)
        {
            var people = new People { Results = GetPeopleInChunks().SelectMany(chunk => chunk) };
            using (var writer = XmlWriter.Create(fileName))
            {
                new XmlSerializer(typeof(People)).Serialize(writer, people);
            }
        }