How to Change Xml Attribute

How to find and replace an attribute value in a XML

In order to modify some element or attribute values in the XML file, while still being respectful of XML structure, you will need to use a XML parser. It's a bit more involved than just String$replace()...

Given an example XML like:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using -->
<property name="beanTwo" ref="anotherBean"/>
<property name="integerProperty" value="!Here:Integer:Foo"/>
</bean>
<bean id="anotherBean" class="examples.AnotherBean">
<property name="stringProperty" value="!Here:String:Bar"/>
</bean>
</beans>

In order to change the 2 markers !Here, you need

  1. to load the file into a dom Document,
  2. select with xpath the wanted nodes. Here I search for all nodes in the document with an attribute value that contains the string !Here. The xpath expression is //*[contains(@value, '!Here')].
  3. do the transformation you want on each selected nodes. Here I just change !Here by What?.

  4. save the modified dom Document into a new file.


static String inputFile = "./beans.xml";
static String outputFile = "./beans_new.xml";

// 1- Build the doc from the XML file
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(new InputSource(inputFile));

// 2- Locate the node(s) with xpath
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList nodes = (NodeList)xpath.evaluate("//*[contains(@value, '!Here')]",
doc, XPathConstants.NODESET);

// 3- Make the change on the selected nodes
for (int idx = 0; idx < nodes.getLength(); idx++) {
Node value = nodes.item(idx).getAttributes().getNamedItem("value");
String val = value.getNodeValue();
value.setNodeValue(val.replaceAll("!Here", "What?"));
}

// 4- Save the result to a new XML doc
Transformer xformer = TransformerFactory.newInstance().newTransformer();
xformer.transform(new DOMSource(doc), new StreamResult(new File(outputFile)));

The resulting XML file is:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans>
<bean class="examples.ExampleBean" id="exampleBean">
<!-- setter injection using -->
<property name="beanTwo" ref="anotherBean"/>
<property name="integerProperty" value="What?:Integer:Foo"/>
</bean>
<bean class="examples.AnotherBean" id="anotherBean">
<property name="stringProperty" value="What?:String:Bar"/>
</bean>
</beans>

How to change the value of an attribute in an XML document?

There are several ways of doing this, including:

XmlAttribute formId = (XmlAttribute)xmlDoc.SelectSingleNode("//FormData/@FormId");
if (formId != null)
{
formId.Value = "newValue"; // Set to new value.
}

Or this:

XmlElement formData = (XmlElement)xmlDoc.SelectSingleNode("//FormData");
if (formData != null)
{
formData.SetAttribute("FormId", "newValue"); // Set to new value.
}

The SelectSingleNode method uses XPath to find the node; there is a good tutorial about XPath here. Using SetAttribute means the FormId attribute will be created if it does not already exist, or updated if it does already exist.

In this case, FormData happens to be the document's root element, so you can also do this:

xmlDoc.DocumentElement.SetAttribute("FormId", "newValue"); // Set to new value.

This last example will only work where the node you are changing happens to be the root element in the document.

To match a specific FormId guid (it is not clear if this is what you wanted):

XmlElement formData = (XmlElement)xmlDoc.SelectSingleNode("//FormData[@FormId='d617a5e8-b49b-4640-9734-bc7a2bf05691']");
if (formData != null)
{
formData.SetAttribute("FormId", "newValue"); // Set to new value.
}

Note that the select in this last example returns the FormData element and not the FormId attribute; the expression in [] brackets enables us to search for a node with a particular matching attribute.

How to set attribute value for existing XML element


        XmlNodeList dataNodes = doc.GetElementsByTagName("librarymaterial");

for (int i = 0; i < dataNodes.Count; i++)
{
XmlAttribute attr = dataNodes[i].Attributes["librarymaterial_id"];
attr.Value = "USA";
}

Try this code.

How to change the value of an attribute in a new XML document?

The problem is that you are searching for nodes with an attribute named type with value session -- then replacing that value if the current value is system. That's not going to work, since the value can't be both.

You must want either:

    foreach (XmlNode node in root.SelectNodes("//node()[@type='session']"))
node.Attributes["type"].Value = "system";

or

    foreach (XmlNode node in root.SelectNodes("//node()[@type='system']"))
node.Attributes["type"].Value = "session";

Update

If you have two XmlDocuments that have identical element hierarchies but different sets of attributes for each element, and wish to propagate some attribute information from the first to the second, you need to walk the element hierarchies and create temporary mapping tables between them. The following does that, assuming the elements are corresponded by name, and then by order if duplicate names exist (e.g. in a list):

    static void WalkMatchingElements(XmlElement root1, XmlElement root2, Action<XmlElement, XmlElement> action)
{
WalkMatchingElements(root1, root2, (element) => (element.Name), action);
}

static void WalkMatchingElements<TKey>(XmlElement root1, XmlElement root2, Func<XmlElement, TKey> getKey, Action<XmlElement, XmlElement> action)
{
if (EqualityComparer<TKey>.Default.Equals(getKey(root1), getKey(root2)))
action(root1, root2);
var children1GroupedByName = root1.ChildNodes.OfType<XmlElement>().GroupBy(getKey);
var children2LookupByName = root2.ChildNodes.OfType<XmlElement>().ToLookup(getKey);
foreach (var child1group in children1GroupedByName)
{
var child2group = children2LookupByName[child1group.Key];
foreach (var pair in child1group.Zip(child2group, (el1, el2) => new KeyValuePair<XmlElement, XmlElement>(el1, el2)))
WalkMatchingElements(pair.Key, pair.Value, getKey, action);
}
}

And then call it like:

        var oldDoc = new XmlDocument();
oldDoc.LoadXml(oldXml);

var newDoc = new XmlDocument();
newDoc.LoadXml(newXml);

WalkMatchingElements(oldDoc.DocumentElement, newDoc.DocumentElement, (elOld, elNew) =>
{
var attrOld = elOld.Attributes["type"];
if (attrOld != null && attrOld.Value == "session")
{
elNew.SetAttribute("type", "system");
}
});

Update2 If you don't want the entire old XmlDocument in memory at once (though I don't see why not), you can built a lookup table of elements with a type attribute, indexed by path, then use that later:

    const string AttributeName = "type";

var lookup = oldDoc.DocumentElement.DescendantsAndSelf().OfType<XmlElement>().Where(el => el.HasAttribute(AttributeName)).ToLookup(el => el.Path(), el => el.Attributes[AttributeName].Value);

// And then later

WalkMatchingElements(new XmlElement[] { newDoc.DocumentElement }, lookup, (el, oldValue) =>
{
if (oldValue != null && oldValue == "session")
el.SetAttribute(AttributeName, "session");
});

private static void WalkMatchingElements<TValue>(IEnumerable<XmlElement> elements, ILookup<string, TValue> pathLookup, Action<XmlElement, TValue> action)
{
var elementsByPath = elements.GroupBy(el => el.Path());
foreach (var elementsGroup in elementsByPath)
{
foreach (var pair in elementsGroup.Zip(pathLookup[elementsGroup.Key], (el, value) => new KeyValuePair<XmlElement, TValue>(el, value)))
action(pair.Key, pair.Value);
foreach (var element in elementsGroup)
WalkMatchingElements(element.ChildNodes.OfType<XmlElement>(), pathLookup, action);
}
}

You'll need the following extension methods:

public static class XmlNodeExtensions
{
public static string Path(this XmlElement element)
{
if (element == null)
throw new ArgumentNullException();
return element.AncestorsAndSelf().OfType<XmlElement>().Reverse().Aggregate(new StringBuilder(), (sb, el) => sb.Append("/").Append(el.Name)).ToString();
}

public static IEnumerable<XmlNode> AncestorsAndSelf(this XmlNode node)
{
for (; node != null; node = node.ParentNode)
yield return node;
}

public static IEnumerable<XmlNode> DescendantsAndSelf(this XmlNode root)
{
if (root == null)
yield break;
yield return root;
foreach (var child in root.ChildNodes.Cast<XmlNode>())
foreach (var subChild in child.DescendantsAndSelf())
yield return subChild;
}
}

public static class EnumerableExtensions
{
// Back ported from .Net 4.0
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (resultSelector == null) throw new ArgumentNullException("resultSelector");
return ZipIterator(first, second, resultSelector);
}

static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>(IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext())
yield return resultSelector(e1.Current, e2.Current);
}
}

(I had forgotten that Zip is not in .Net 3.5.)

How to change the value of XML Element attribute using PowerShell?

Try the following:

$nodes = $xml.SelectNodes("/office/staff");
foreach($node in $nodes) {
$node.SetAttribute("branch", "New York");
}

This will iterate through all nodes returned by SelectNodes() and modify each one.



Related Topics



Leave a reply



Submit