1

I was finally able to create a Xml file with some coding (with lots of help here from some people). I can store the file but the next process is reading it back. This gave me an error: "Unexpected Xml declaration". After searching a bit I understand that a Xml file can have only one time <?xml version=1.0 etc>. But my document has several of these statements in the document. And therefore the code

xmlDocument doc = new XmlDocument

Throws me this error. The question is how do I get rid of all these comments in the xml document.

The document is create with 2 functions:

    private void btnSave_Click(object sender, EventArgs e)
    {

        //Check if all fields are filled in 
        if (txbCompany.Text == "" || txbSiteName.Text == "" || txbIMO.Text == "")
        {
            MessageBox.Show("Please fill in all empty fields");
            
        }
        else if (NumMachTot.Value == 0)
        {
            MessageBox.Show("A Client profile needs at least one assigned machine!");
        }
        else
        {
            var appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "VibroManager");
            Directory.CreateDirectory(appData);

            //Create the Company Profile Class
            CompanyProfile companyProfile = new CompanyProfile();
            companyProfile.CompanyName = txbCompany.Text;
            companyProfile.SiteName = txbSiteName.Text;
            companyProfile.Imo = txbIMO.Text;
            companyProfile.MachineTotal = (int)NumMachTot.Value;

            //Serialization of the companyProfile and append to the document
            System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(CompanyProfile));
            using (var writer = new StreamWriter(Path.Combine(appData, $"{txbCompany.Text}_{txbSiteName.Text}.xml"), true))
            {
                x.Serialize(writer, companyProfile);
            }

            //Iterate the datasource list, NOT the DataGridView.
            foreach (MachineProfile machineProfile in dsMachineProfile)
           {
                AppendMachineData(machineProfile, fileName: Path.Combine(appData, $"{txbCompany.Text}_{txbSiteName.Text}.xml"));
           }

            //Close form and open Main form
            this.Hide();
            frmMain fMain = new frmMain();
            fMain.ShowDialog();
        }
    }

changed Code:

        private void AppendMachineData(MachineProfile machineProfile, string fileName)
    {
        //Serialization of the MachineProle and append to the document
        System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(MachineProfile));
        var settings = new XmlWriterSettings();

        using (var writer = new StreamWriter(fileName, true))
        {
           
            settings.Indent = true;
            settings.OmitXmlDeclaration = true;
            x.Serialize(writer, machineProfile);
        }          
    }   

I think in these two functions the problem is created but in fact I do not know why and how. Or maybe there is another way to solve this.

This is the code that I use to read the xml file

    private void openToolStripMenuItem_Click(object sender, EventArgs e)
    {
        var filePath = string.Empty;

        using (OpenFileDialog openFileDialog = new OpenFileDialog())
        {
            openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData );
            openFileDialog.Filter = "All files (*.*)|*.*";
            openFileDialog.FilterIndex = 2;
            openFileDialog.RestoreDirectory = true;

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                //Get the path of specified file
                filePath = openFileDialog.FileName;

                XmlDocument doc = new XmlDocument();
                doc.Load(filePath);

                XmlNode node = doc.DocumentElement.SelectSingleNode("/CompanyName");
                lblCompanyName.Text = node.InnerText;


            }
        }

    }

Here is a screenshot of the Xml file enter image description here

ElectricRay81
  • 121
  • 11
  • It will be helpful to see the sample xml you are trying to read – Chetan Jun 11 '22 at 20:36
  • Yes offcourse sorry about that – ElectricRay81 Jun 11 '22 at 20:39
  • 2
    `new StreamWriter(fileName, true)` appends the data to an existing file. So if you serialize multiple xmls into the same file, of course they will show up – derpirscher Jun 11 '22 at 20:43
  • The `AppendMachineData` should use options to [omit the xml declaration](https://stackoverflow.com/questions/1772004/how-can-i-make-the-xmlserializer-only-serialize-plain-xml) – Wiktor Zychla Jun 11 '22 at 20:44
  • 2
    @wiktorzychla even if the declaration was omitted, the resulting XML file would still be invalid, because you must not have multiple top level elements. – derpirscher Jun 11 '22 at 20:46
  • Ok thanks I have placed "var settings = new xmlWriterSettings();" and "settings.OmitXmlDeclaration = true" in the "AppendMachineData()" But I keep having all those comments. – ElectricRay81 Jun 11 '22 at 21:01
  • 1
    @derpirscher: that's another story, the OP will notice that when they correct the first problem. – Wiktor Zychla Jun 11 '22 at 21:04
  • 1
    the problem is you can't have more than one root-level element, you should create a class top-level profile class and then have 2 properties one will hold the company profile the other one is a collection property that holds is the machine profiles. have a look at this XML sample view-source:https://www.w3schools.com/xml/cd_catalog.xml – coder_b Jun 11 '22 at 21:06
  • "The question is how do I get rid of all these comments in the xml document." It seems that you have already opened the file in a text editor. Did you try using the editor's search and replace functionality, or selecting the text and deleting it? I don't understand how there is a question here. (Also, they aren't "comments".) I *assume* you mean that the *code that writes* the file isn't behaving as you expect. It would be better to write the question to communicate this more clearly. (Also, what does any of this have to do with Windows Forms Designer?) – Karl Knechtel Jun 11 '22 at 21:08
  • 1
    While we are at it, the code `doc.DocumentElement.SelectSingleNode("/CompanyName")` is also odd, with the property `doc.DocumentElement` you select the root element (also called document element) of the input document, then you use XPath on that document element but with an absolute path `/CompanyName` so you are basically trying to select a root element or document element named `CompanyName`. Your snippet (at least the first part of the XML) suggests you rather want a relative path `doc.DocumentElement.SelectSingleNode("CompanyName")`. – Martin Honnen Jun 11 '22 at 21:10
  • Ok well it is the first time I'm doing this sorry for that but I was just trying to figure out how this works. Reading all the comments I'm doing something completely wrong. First with creating the file and second with reading the file (or parts of it) – ElectricRay81 Jun 11 '22 at 21:15
  • 1
    You must pass `XmlWriter` to `AppendMachineData` method instead of filename. – Alexander Petrov Jun 11 '22 at 22:42
  • 1
    Your problem essentially boils down to `//Serialization of the MachineProle and append to the document` this is wrong. You shouldn't be appending multiple XML documents together. Instead create a single `XmlDocument` and serialize that – Charlieface Jun 12 '22 at 01:29
  • 1
    @coder_b : You are wrong. The xml specification allows for more than one element at root. When you have more than one the xml is not well formed but still meet specifications. Many people use xml for log files an simple append new data at end instead of opening the file and adding new items under the root tag. The net library will give error when reading the xml file unless you use a XmlReader and then change the settings to : settings.ConformanceLevel = ConformanceLevel.Fragment; – jdweng Jun 12 '22 at 18:11
  • 1
    @jdweng - nice one, my intention was to say well-formatted, thanks for clarifying https://stackoverflow.com/questions/134494/is-there-any-difference-between-valid-xml-and-well-formed-xml/25830482#25830482 – coder_b Jun 12 '22 at 19:31
  • Thanks everybody for all your comments O learned a lot of this. – ElectricRay81 Jun 13 '22 at 19:15

1 Answers1

1

Below shows how to use XML serialization which allows one to use classes to store one's data and then serialize these classes to save the data to an XML file. Deserialization reads the data from the XML file and populates the classes.

Try the following:

To each of the classes below, add the following using statements

  • using System.Collections.Generic;
  • using System.Xml;
  • using System.Xml.Serialization;

Create a class (name: XmlCompanyProfile.cs)

public class XmlCompanyProfile
{
    public string CompanyName { get; set; }
    public string SiteName { get; set; }
    public int Imo { get; set; }
    public int MachineTotal { get; set; }
}

If an element name isn't specified, the name of the property is used in the XML file.

Example 1:

public string CompanyName { get; set; }

By specifying an ElementName, one can make the property name different than the name used in the XML file.

Example 2:

[XmlElement(ElementName = "Name")]
public string CompanyName { get; set; }

Of course, one can also set the value of ElementName to the property name.

Example 3:

[XmlElement(ElementName = "CompanyName")]
public string CompanyName { get; set; }

Note: In Example 3, since the element name is set to the same value as the property name, it will have the same result as Example 1.

If one wishes to specify the element names, the XmlCompanyProfile class will look like the following instead:

public class XmlCompanyProfile
{
    [XmlElement(ElementName = "CompanyName")]
    public string CompanyName { get; set; }

    [XmlElement(ElementName = "SiteName")]
    public string SiteName { get; set; }

    [XmlElement(ElementName = "Imo")]
    public int Imo { get; set; }

    [XmlElement(ElementName = "MachineTotal")]
    public int MachineTotal { get; set; }

}

Create a class (name: XmlMachineProfile.cs)

public class XmlMachineProfile
{
    [XmlElement(ElementName = "MachineName")]
    public string MachineName { get; set; }

    [XmlElement(ElementName = "NominalSpeed")]
    public int NominalSpeed { get; set; }
}

Create a class (name: XmlRoot.cs)

[XmlRoot(ElementName = "Root")]
public class XmlRoot
{
    [XmlElement(ElementName = "CompanyProfile")]
    public XmlCompanyProfile CompanyProfile { get; set; } = new XmlCompanyProfile();

    [XmlElement(ElementName = "MachineProfile")]
    public List<XmlMachineProfile> MachineProfiles { get; set; } = new List<XmlMachineProfile>();
}

To serialize and deserialize the data:

Create a class (name: HelperXml.cs)

public static class HelperXml
{
    public static T DeserializeXMLFileToObject<T>(string xmlFilename)
    {
        //Usage: Class1 myClass1 = DeserializeXMLFileToObject<Class1>(xmlFilename);

        T rObject = default(T);

        if (string.IsNullOrEmpty(xmlFilename))
        {
            throw new Exception($"Error: XML filename not specified (xmlFilename: '{xmlFilename}'");
        }

        using (System.IO.StreamReader xmlStream = new System.IO.StreamReader(xmlFilename))
        {
            System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
            rObject = (T)serializer.Deserialize(xmlStream);
        }

        return rObject;
    }


    public static void SerializeObjectToXMLFile(object obj, string xmlFilename)
    {
        //Usage: Class1 myClass1 = new Class1();
        //SerializeObjectToXMLFile(myClass1, xmlFilename);

        if (string.IsNullOrEmpty(xmlFilename))
        {
            throw new Exception($"Error: XML filename not specified (xmlFilename: '{xmlFilename}'");
        }

        System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings() { Indent = true, OmitXmlDeclaration = true};

        using (System.Xml.XmlWriter xmlWriter = System.Xml.XmlWriter.Create(xmlFilename, settings))
        {
            //specify namespaces
            System.Xml.Serialization.XmlSerializerNamespaces ns = new System.Xml.Serialization.XmlSerializerNamespaces();
            ns.Add(string.Empty, "urn:none"); //eliminates "xsd" and "xsi" namespaces

            //create new instance
            System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());

            //serialize (write to XML file)
            serializer.Serialize(xmlWriter, obj, ns);
        }
    }
}

Usage (serialization):

Note: In the code below, the name of the button is btnSerialize.

private XmlRoot _root = null;

           ...

private void btnSerialize_Click(object sender, EventArgs e)
{
    //create new instance
    _root = new XmlRoot();

    //set CompanyProfile info
    _root.CompanyProfile.CompanyName = "ABB";
    _root.CompanyProfile.SiteName = "Rotterdam";
    _root.CompanyProfile.Imo = 123456;
    _root.CompanyProfile.MachineTotal = 3;

    //add machine profile
    _root.MachineProfiles.Add(new XmlMachineProfile() { MachineName = "Machine 1", NominalSpeed = 50 });
    _root.MachineProfiles.Add(new XmlMachineProfile() { MachineName = "Machine 2", NominalSpeed = 60 });
    _root.MachineProfiles.Add(new XmlMachineProfile() { MachineName = "Machine 3", NominalSpeed = 50 });

    //prompt user for XML filename
    using (SaveFileDialog sfd = new SaveFileDialog())
    {
        sfd.FileName = "Test.xml"; //default filename
        sfd.Filter = "XML File (*.xml)|*.xml";
        sfd.Title = "Save XML file";

        if (sfd.ShowDialog() == DialogResult.OK)
        {
            //serialize
            HelperXml.SerializeObjectToXMLFile(_root, sfd.FileName);
        }
    }
}

Usage (deserialization):

Note: In the code below, the name of the button is btnDeserialize.

private XmlRoot _root = null;

           ...

private void btnDeserialize_Click(object sender, EventArgs e)
{
    //prompt user for XML filename
    using (OpenFileDialog ofd = new OpenFileDialog())
    {
        ofd.Filter = "XML File (*.xml)|*.xml";
        ofd.Title = "Open XML file";

        if (ofd.ShowDialog() == DialogResult.OK)
        {
            //deserialize
            _root = HelperXml.DeserializeXMLFileToObject<XmlRoot>(ofd.FileName);

            System.Diagnostics.Debug.WriteLine($"CompanyName: '{_root.CompanyProfile.CompanyName}'");
        }
    }
}

Here's what the XML file looks like:

<Root>
  <CompanyProfile>
    <CompanyName>ABB</CompanyName>
    <SiteName>Rotterdam</SiteName>
    <Imo>123456</Imo>
    <MachineTotal>3</MachineTotal>
  </CompanyProfile>
  <MachineProfile>
    <MachineName>Machine 1</MachineName>
    <NominalSpeed>50</NominalSpeed>
  </MachineProfile>
  <MachineProfile>
    <MachineName>Machine 2</MachineName>
    <NominalSpeed>60</NominalSpeed>
  </MachineProfile>
  <MachineProfile>
    <MachineName>Machine 3</MachineName>
    <NominalSpeed>50</NominalSpeed>
  </MachineProfile>
</Root>

Resources:

Tu deschizi eu inchid
  • 4,117
  • 3
  • 13
  • 24