Home > Software engineering >  How to insert node/element in XML using Attribute
How to insert node/element in XML using Attribute

Time:07-02

I am trying to insert these nodes price, qty, amount under the descendants Apple > Store only using the attribute to find the exact location of insert.

<?xml version="1.0" encoding="utf-8"?>
<Fruits>
    <node text="Apple" tag="a" imageindex="0">
        <node text="Store" tag="b" imageindex="1" />
        <node text="City" tag="c" imageindex="2" />
    </node>
    <node text="Orange" tag="a" imageindex="0">
        <node text="Store" tag="b" imageindex="1" />
        <node text="City" tag="c" imageindex="2" />
    </node>
</Fruits>

Expected Result:

<?xml version="1.0" encoding="utf-8"?>
<Fruits>
    <node text="Apple" tag="a" imageindex="0">
        <node text="Store" tag="b" imageindex="1" />
            <node text="price" tag="b" imageindex="1" />
            <node text="qty" tag="b" imageindex="1" />
            <node text="amount" tag="b" imageindex="1" />
        <node text="City" tag="c" imageindex="2" />
    </node>
    <node text="Orange" tag="a" imageindex="0">
        <node text="Store" tag="b" imageindex="1" />
        <node text="City" tag="c" imageindex="2" />
    </node>
</Fruits>

Here's what I have done so far. I am having difficulty defining the detail path using the attribute. I know Linq to XML has a straight forward approach but couldn't figure out. Though I tried foreach but no avail, it gets more complicated at least to me.

XElement xFruit = XElement.Load(@"D:\Xml\Fruit.xml");

XElement detail = xFruit...

detail.Add(
    new XElement("price", "$10"),
    new XElement("qty", "10"),
    new XElement("amount", "$100")
);

xFruit.Save(@"D:\Xml\Fruit.xml");

Any help would be greatly appreciated!

CodePudding user response:

You're using an attribute named "text" to define a path to the location for the insert. To make it straightforward to iterate to that location, you could try writing a simple FindPath extension that knows that the "text" attribute is the one to look for, and then call the extension like this:

var detail = xFruit.FindPath(@"Apple\Store");
detail?.Add(
        new XElement("price", "$10"),
        new XElement("qty", "10"),
        new XElement("amount", "$100")
);

So in a top-level static class...

static class Extensions
{

First make an extension to get the value of an attribute if it exists without throwing an exception if it doesn't (the FindPath extension will be using this to examine the "text" attribute).

    public static bool TryGetAttributeValue(
        this XElement xel,
        string name,
        out string value)
    {
        var attr = xel.Attributes()
            .FirstOrDefault(@try => string.Equals(name, @try.Name.LocalName));
        if (attr == null)
        {
            value = string.Empty;
            return false;
        }
        else
        {
            value = attr.Value;
            return true;
        }
    }

That makes it simple to write the second extension that iterates to find the node at the path if it exists, returning null if it doesn't. (This method takes a more direct route than the foreach iteration that you mentioned.)

    public static XElement FindPath(
        this XElement xRoot,
        string path)
    {
        XElement xTraverse = xRoot;
        string[] split = path.Split('\\');
        for (int i = 0; i < split.Length; i  )
        {
            xTraverse =
                xTraverse.Elements()
                .FirstOrDefault(match =>
                    match.TryGetAttributeValue("text", out string value) &&
                    (value == split[i]));
            if (xTraverse == null) break;
        }
        return xTraverse;
    }
}

TESTBENCH

static void Main(string[] args)
{
    var xFruit = XElement.Parse(source);
    var detail = xFruit.FindPath(@"Apple\Store");
    detail?.Add(
            new XElement("price", "$10"),
            new XElement("qty", "10"),
            new XElement("amount", "$100")
    );
    Console.WriteLine(xFruit.ToString());
}
const string source =
@"<Fruits>
    <node text=""Apple"" tag=""a"" imageindex=""0"">
        <node text = ""Store"" tag=""b"" imageindex=""1"" />
        <node text = ""City"" tag=""c"" imageindex=""2"" />
    </node>
    <node text = ""Orange"" tag=""a"" imageindex=""0"">
        <node text = ""Store"" tag=""b"" imageindex=""1"" />
        <node text = ""City"" tag=""c"" imageindex=""2"" />
    </node>
</Fruits>";

This should match the output you're looking for.

console output

  • Related