How to find and change inner text of xml elements? C# - c#

I have a following xml file. I need to change the inner text of ANY tag, which contains the value «Museum», or just a tag for a start:
<src>
<riga>
<com>¾</com>
<riquadro797>Direction to move</riquadro797>
</riga>
<riga>
<llt>
<com>Museum</com>
<elemento797>Direction not to move</elemento797>
</llt>
</riga>
<operation>
<com> </com>
<riquadro797>Museum</riquadro797>
</operation>
<riga>
<gt>
<elemento797>Direction not to move</elemento797>
</gt>
</riga>
</src>
I've parsed this file to XElement. What I've tried and it dos not work:
var tt = xmlCluster.Elements(First(x => x.Value == "Museum");
This code is not proper, as I cannot predict which element will contain "Museum":
var el = rootElRecDocXml.SelectSingleNode("src/riga/gt/elemento797[text()='"+mFilePath+"']");
How to do it? Any help will be greatly appreciated!

just grab all elements with Museum values:
var doc = XDocument.Parse(xml);
var elements = doc.Descendants().Where(e => e.Value == "Museum");
foreach (var ele in elements)
ele.Value = "Test";
//doc is updated with new values
as Selman22 noted, doc will just be a working copy of your xml. You'll need to call doc.Save to apply anything back to the disk, or wherever you need

Elements() only looks at a single level in the heirarchy. I think you want Descendants() instead...

If you want an older-school XPath option, you need to do a global search on the tree - you can use the // XPath expression for this:
var els = rootElRecDocXml.SelectNodes("//[text()='"+mFilePath+"']");

Related

how to clean duplicate (parent) nodes?

My input payload would be something like the following:
<ns0:SourceFacilityCode FieldTypeToTranslate="Store">
<ns0:SourceFacilityCode>CRA</ns0:SourceFacilityCode>
</ns0:SourceFacilityCode>
<ns0:Alex FieldTypeToTranslate="Facility">
<ns0:Alex>CRA</ns0:Alex>
</ns0:Alex>
<ns0:Shoes>Red</Shoes>
As you can see SourceFacilityCode and Alex are both unnecessary. In order to deserialize this to a concrete C# object, we would need to transform the input to something like this:
<ns0:SourceFacilityCode>CRA</ns0:SourceFacilityCode>
<ns0:Alex>CRA</ns0:Alex>
<ns0:Shoes>Red</nso0:Shoes>
How do I transform this payload to look like that?
What I've tried:
1. simple `string.Replace(a,b)`- but this is too messy and ungeneric
2. trying to load this into an Xml concrete object, but this was too difficult to do with the nested nodes having the same name
3. attempting to transform to json and then to concrete object
Here is a solution using LINQ to XML:
First, wrap your example XML into a Root element to make it valid XML which can be parsed by XDocument.Parse:
var xml = #"<Root xmlns:ns0=""http://example.org/ns0"">
<ns0:SourceFacilityCode FieldTypeToTranslate=""Store"">
<ns0:SourceFacilityCode>CRA</ns0:SourceFacilityCode>
</ns0:SourceFacilityCode>
<ns0:Alex FieldTypeToTranslate=""Facility"">
<ns0:Alex>CRA</ns0:Alex>
</ns0:Alex>
<ns0:Shoes>Red</ns0:Shoes>
</Root>";
var doc = XDocument.Parse(xml);
Then we determine all elements with a single child element that has the same name as the element and that has no child elements:
var elementsWithSingleChildHavingSameName = doc.Root.Descendants()
.Where(e => e.Elements().Count() == 1
&& e.Elements().First().Name == e.Name
&& !e.Elements().First().HasElements)
.ToArray();
Last, loop through the found elements removing the child element while transferring the value:
foreach (var element in elementsWithSingleChildHavingSameName)
{
var child = element.Elements().First();
child.Remove();
element.Value = child.Value;
}
To transform back to a string and remove the Root wrapper:
var cleanedUpXml = doc.ToString();
var output = Regex.Replace(cleanedUpXml, #"</?Root.*?>", "");

Sequence contains no elements when trying to grab xml node value from xml string

Please read the code bellow. There I am trying to grab all elements under the <GetSellerListResponse> node, then my goal is to grab TotalNumberOfPages value (currently it's 9 as you can see in the XML).
But my problem is I am getting an error:
System.InvalidOperationException: 'Sequence contains no elements'
Error screenshot is attached for better understanding. Can you tell me what's wrong the way I am trying to grab all elements? Also if possible can you tell how I can grab that 9 from TotalNumberOfPage?
Thanks in advance
C#:
var parsedXML = XElement.Parse(xml);
var AllElements = parsedXML.Descendants("GetSellerListResponse")
.Where(x => x.Attribute("xmlns").Value.Equals("urn:ebay:apis:eBLBaseComponents"))
.First();
XML:
<?xml version="1.0" encoding="UTF-8"?>
<GetSellerListResponse xmlns="urn:ebay:apis:eBLBaseComponents">
<Timestamp>2018-06-20T17:26:29.518Z</Timestamp>
<Ack>Success</Ack>
<Version>1059</Version>
<Build>E1059_CORE_APISELLING_18694654_R1</Build>
<PaginationResult>
<TotalNumberOfPages>9</TotalNumberOfPages>
</PaginationResult>
</GetSellerListResponse>
EDIT: your mistake is the usage of XElement: it is searching for matching elements in the children of <GetSellerListResponse>; that's why you are not getting any result. Change XElement.Parse(xml); to XDocument.Parse(xml);, then the following snippets will work.
You could simply check for the local name:
var AllElements = parsedXML.Descendants().First(x => x.Name.LocalName == "GetSellerListResponse");
I would suggest to use XDocument instead of XElement for parsedXML, because you could shorten the above query to var AllElements = parsedXML.Root;
Another thing you could try is prepending the namespace:
XNamespace ns = "urn:ebay:apis:eBLBaseComponents";
var AllElements = parsedXML.Descendants(ns + "GetSellerListResponse").First();
To answer the question "how to get the number of pages":
var pages = AllElements.Element(ns + "PaginationResult").Element(ns + "TotalNumberOfPages").Value;
I would suggest using the XmlDocument class from System.Xml.
Try the code below:
XmlDocument doc = new XmlDocument();
doc.LoadXml("<GetSellerListResponse xmlns=\"urn:ebay:apis:eBLBaseComponents\"><Timestamp>2018-06-20T17: 26:29.518Z</Timestamp><Ack>Success</Ack><Version>1059</Version><Build>E1059_CORE_APISELLING_18694654_R1</Build><PaginationResult><TotalNumberOfPages>9</TotalNumberOfPages></PaginationResult></GetSellerListResponse>");
XmlNodeList nodeList = doc.GetElementsByTagName("TotalNumberOfPages");
In this case, your nodeList will have just the one element for TotalNumberOfPages and you can access the value by checking
nodeList.FirstOrDefault().InnerText

Find and delete all occurrences of a string that starts with x

I'm parsing an XML file, to compare it to another XML file. XML Diff works nicely, but we have found there are a lot of junk tags that exist in one file, not in the other, that have no bearing on our results, but clutter up the report. I have loaded the XML file into memory to do some other things to it, and I'm wondering if there is an easy way at the same time to go through that file, and remove all tags that start with, as an example color=. The value of color is all over the map, so not easy to grab them all remove them.
Doesn't seem to be any way in XML Diff to specify, "ignore these tags".
I could roll through the file, find each instance, find the end of it, delete it out, but I'm hoping there will be something simpler. If not, oh well.
Edit: Here's a piece of the XML:
<numericValue color="-103" hidden="no" image="stuff.jpg" key="More stuff." needsQuestionFormatting="false" system="yes" systemEquivKey="Stuff." systemImage="yes">
<numDef increment="1" maximum="180" minimum="30">
<unit deprecated="no" key="BPM" system="yes" />
</numDef>
</numericValue>
If you are using Linq to XML, you can load your XML into an XDocument via:
var doc = XDocument.Parse(xml); // Load the XML from a string
Or
var doc = XDocument.Load(fileName); // Load the XML from a file.
Then search for all elements with matching names and use System.Xml.Linq.Extensions.Remove() to remove them all at once:
string prefix = "L"; // Or whatever.
// Use doc.Root.Descendants() instead of doc.Descendants() to avoid accidentally removing the root element.
var elements = doc.Root.Descendants().Where(e => e.Name.LocalName.StartsWith(prefix, StringComparison.Ordinal));
elements.Remove();
Update
In your XML, the color="-103" substring is an attribute of an element, rather than an element itself. To remove all such attributes, use the following method:
public static void RemovedNamedAttributes(XElement root, string attributeLocalNamePrefix)
{
if (root == null)
throw new ArgumentNullException();
foreach (var node in root.DescendantsAndSelf())
node.Attributes().Where(a => a.Name.LocalName == attributeLocalNamePrefix).Remove();
}
Then call it like:
var doc = XDocument.Parse(xml); // Load the XML
RemovedNamedAttributes(doc.Root, "color");

Selecting XML nodes?

I have a large XML file with a lot of these file nodes:
<File>
<Component>Main</Component>
<Path>C:\Logs\Main</Path>
<FileName>logfile1.log</FileName>
</File>
In my C# program I want to select a node with a certain file name, eg in the above example I would like to select the File node where the FileName is logfile1.log. Is there a way I can do this in my C#, or maybe I need to make the FileName as an attribute for each File node, e.g.:
<File name="logfile1.log">...</File>
Could anybody advise me on the best practise here? Thanks for any help!
Using query syntax;
var xml = XDocument.Load("input.xml");
var node = (from file in xml.Descendants("File")
where (string)file.Element("FileName") == "logfile1.log"
select file).Single();
Obviously the call to force the query (Single() in this case) should be swapped out to suit your own app.
XPath query would be a good choice for that. You can use xpath to search for either an element name or an attribute name.
something like:
var doc = new XPathDocument(path);
var xpath = doc.CreateNavigator();
//with element
var node = xpath.SelectSingleNode("//File[FileName='logfile1.log']");
//or with attribute
var node = xpath.SelectSingleNode("//File[#name='logfile1.log']");
Or, if there could be more than one you can use Select to find multiple matches and then iterate them.
var node = xpath.Select("//File...");
var doc = new XmlDocument();
doc.LoadXml(xml); // or Load(path)
var node = doc.SelectSingleNode("//File/FileName[.='logfile1.log']");
(see XPath selection by innertext)
or
var doc = XDocument.Load(path);
var node = doc.Elements("Path").FirstOrDefault(e => (string)e.Element("FileName") == "logfile1.log");

Direct access and edit to an xml node, using properties

El Padrino showed a solution:
How to change XML Attribute
where an xml element can be loaded directly (no for each..), edited and saved!
My xml is:
<?xml version="1.0" encoding="ISO-8859-8"?>
<g>
<page no="1" href="page1.xml" title="נושא 1">
<row>
<pic pos="1" src="D:\RuthSiteFiles\webSiteGalleryClone\ruthCompPics\C_WebBigPictures\100CANON\IMG_0418.jpg" width="150" height="120">1</pic>
</row>
</page>
</g>
and I need to select a node by two attributes(1. "no" in the page tag and "pos" in the pic tag)
I've found :
How to access a xml node with attributes and namespace using selectsinglenode()
where direct access is possible but beside the fact that I dont understand the solution, I think it uses the xpath object which can't be modified and save changes.
What's the best way to
access directly an xml node (I'm responsible that the node will be unique)
edit that node
save changes to the xml
Thanks
Asaf
You can use the same pattern as the first answer you linked to, but you will need to include the conditions on the attributes in the XPath. Your basic XPath would be g/page/row/pic. Since you want the no attribute of page to be 1, you add [#no='1'] as a predicate on page. So, the full XPath query is something like g/page[#no='1']/row/pic[#pos='1']. SelectSingleNode will return a mutable XmlNode object, so you can modify that object and save the original document to save changes.
Putting the XPath together with El Padrino's answer:
//Here is the variable with which you assign a new value to the attribute
string newValue = string.Empty;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlFile);
XmlNode node = xmlDoc.SelectSingleNode("g/page[#no='1']/row/pic[#pos='1']");
node.Attributes["src"].Value = newValue;
xmlDoc.Save(xmlFile);
//xmlFile is the path of your file to be modified
Use the new, well-designed XDocument/XElement instead of the old XmlDocument API.
In your example,
XDocument doc = XDocument.Load(filename);
var pages = doc.Root.Elements("page").Where(page => (int?) page.Attribute("no") == 1);
var rows = pages.SelectMany(page => page.Elements("row"));
var pics = rows.SelectMany(row => row.Elements("pic").Where(pic => (int?) pic.Attribute("pos") == 1));
foreach (var pic in pics)
{
// outputs <pic pos="1" src="D:\RuthSiteFiles\webSiteGalleryClone\ruthCompPics\C_WebBigPictures\100CANON\IMG_0418.jpg" width="150" height="120">1</pic>
Console.WriteLine(pic);
// outputs 1
Console.WriteLine(pic.Value);
// Changes the value
pic.Value = 2;
}
doc.Save(filename);

Categories

Resources