0

I have an XML file with this structure:

<inventory>
<product>
    <recordNumber>1</recordNumber>
    <name>Dustbin</name>
    <stock>190</stock>
    <price>33</price>
</product>
<product>
    <recordNumber>2</recordNumber>
    <name>Broom</name>
    <stock>200</stock>
    <price>76</price>
</product>

</inventory>

I have a program which takes in these values and creates a List of 'Item' objects called 'stockArray'. each item has an int-Id(from recordNumber) and an int-count(from stock).

The program updates the "stock" value accordingly and what I am looking to do is then update the XML file to have the new 'stock' value.

I am very new to C# and XML, so this is the path I have been going down:

XDocument doc = XDocument.Load(xml);

var list = doc.Element("inventory").Elements("product");

foreach (var node in list)
{
    foreach (Item item in stockArray)
    {
        if (node.Element("recordNumber").Value == Convert.ToString(item.Id))
            node.SetElementValue("count", Convert.ToString(item.count));
    }
}

But so far this seems way off course. I can find a lot of information about adding new nodes to an XML but iterating through the stockArray item list and updating the XML seems to be a different process.

Any advice would be greatly appreciated.

9
  • 2
    What's wrong with your current approach? Commented Aug 25, 2014 at 6:55
  • Probably that it is very slow if the file is large. YOu have to iterate over everything. Yuck. I'd look into using linq Commented Aug 25, 2014 at 7:00
  • It does not seem to be modifying the XML document at all. When I reload the program the original stock number is there. Thanks Commented Aug 25, 2014 at 7:00
  • 1
    @Daviepark Have you called doc.Save(myPathToTheXmlFile) ? Commented Aug 25, 2014 at 7:01
  • 1
    you don't have <count> element, did you mean stock : node.SetElementValue("stock", Convert.ToString(item.count)); ? Commented Aug 25, 2014 at 7:07

4 Answers 4

2

Your XML doesn't have <count> element. If you meant to update <stock> element value, the first parameter of SetElementValue() should be "stock" :

if (node.Element("recordNumber").Value == Convert.ToString(item.Id))
    node.SetElementValue("stock", Convert.ToString(item.count));

One possible way using LINQ join to create anonymous type that pair <product> element with the corresponding item from stockArray :

var list = from product in doc.Element("inventory").Elements("product")
           join item in stockArray on (int)product.Element("recordNumber") equals item.id
           select new {product = product, item = item};
foreach (var joinedProduct in list)
{
    joinedProduct.product.SetElementValue("stock", joinedProduct.item.count);
}
Sign up to request clarification or add additional context in comments.

Comments

0

Try this, but you will want to do some error handling for null values.

var doc = XDocument.Load(xml);
var list = doc.Element("inventory").Elements("product");

 foreach (var item in stockArray)
   {
   var node = list.FirstOrDefault(p => p.Element("recordNumber").Value == item.ToString());
            node.Element("stock").Value = item.Count.ToString();
   }

Edit: fixing the code!

2 Comments

p => (string)p.Element("recordNumber") == item.ToString() will handle the case of an element being null without having to check if it's null or not (or if the element is missing).
That is rad! Learn something new every day.
0

You can use XmlSerializer to do this. Only thing is you have to write your xml structure as classes. see the code sample.

class Program
{
    static void Main(string[] args)
    {
        // Create a new file stream for reading the XML file
        FileStream ReadFileStream = new FileStream(@"C:\files\data.xml", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);

        // Load the object saved above by using the Deserialize function
        Inventory LoadedObj = (Inventory)SerializerObj.Deserialize(ReadFileStream);

        // Cleanup
        ReadFileStream.Close();

        foreach (var node in LoadedObj.Products)
        {
             // node.Stock = 0;
            //Do what ever changes to stock
        }

        XmlSerializer SerializerObj = new XmlSerializer(typeof(Inventory));
        //// Create a new file stream to write the serialized object to a file
        TextWriter WriteFileStream = new StreamWriter(@"C:\files\data.xml");
        SerializerObj.Serialize(WriteFileStream, TestObj);
        //// Cleanup
        WriteFileStream.Close();


    }
}


[System.SerializableAttribute()]
[System.Xml.Serialization.XmlRoot("inventory")]
public class Inventory
{
    [System.Xml.Serialization.XmlElement("product")]
    public List<Product> Products { get; set; }

}

[System.SerializableAttribute()]

   public class Product
{
    [System.Xml.Serialization.XmlElementAttribute("recordNumber")]
    public string RecordNumber { get; set; }
    [System.Xml.Serialization.XmlElementAttribute("name")]
    public string Name { get; set; }
    [System.Xml.Serialization.XmlElementAttribute("stock")]
    public int Stock { get; set; }
    [System.Xml.Serialization.XmlElementAttribute("price")]
    public int Price { get; set; }
}

Comments

0

You are editing the count element instead of stock element.

And to improve performance (here the complexity is O(n^2)). You can create a dictionary from the stockArray.

Creating the dictionary has a complexity of O(n). And getting a value in the dictionary has a complexity of O(1).

So, the complexity of the program with the dictionary is O(n).

I would also suggest you to use the explicit cast to convert the value of the XElement to int.

XDocument doc = XDocument.Load(xml);

var list = doc.Element("inventory").Elements("product");
var stockDictionary = stockArray.ToDictionary(item => item.Id, item => item.Count);

foreach (var node in list)
{
    int newCount;
    if (if(stockDictionary.TryGetValue((int)node.Element("recordNumber"), out newCount)
        node.SetElementValue("stock", newCount);
}

2 Comments

Does the compiler automatically generate a recursive loop from LINQ Statements. For example if he grabbed the node using:list.FirstOrDefault(p => p.Element("recordNumber").Value ....
Yes it does (in a way). This is why the complexity of using list.FirstOrDefault(...) is O(n). So with this, the complexity of the program is still O(n^2). There is a way to do it in pure LINQ without dictionaries and having a O(n) complexity for the program (using the .Join(...) method) but it unecessarily complexify the code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.