Im using Visual studio 2019 and I would like to load, edit and save xml file.
xml file:
<?xml version="1.0" encoding="utf-8"?>
<Firstdelivery25 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.acm.nl/Operations/2018/09">
<Firstdelivery>
<MessageId xmlns="http://www.acm.nl/DataModel/2018/09">
<Source>ABC</Source>
</MessageId>
</Firstdelivery>
</Firstdelivery25>
Code in Visual studio:
using System.Linq;
using System.Xml.Linq;
using System.Xml;
using TechTalk.SpecFlow;
XDocument xdoc = XDocument.Load(#"C:\\XML files\\25.xml");
var element = xdoc.Elements("Source").FirstOrDefault();
if (element != null)
{
element.Value = "DEF";
}
xdoc.Save(#"C:\\VB_25.xml");
When I execute this, the test is passed successfully.
However, when I open the new created VB_25.xml file, the old value is still there.
Any help/suggestions/tips would be appreciated.
You can try to modifty XML Data by using XPathNavigator.
Here is a simple demo you can refer to.
http://www.acm.nl/DataModel/2018/09 is the xmlns of MessageId.
XmlDocument document = new XmlDocument();
document.Load("test.xml");
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
manager.AddNamespace("id", "http://www.acm.nl/DataModel/2018/09");
foreach (XPathNavigator nav in navigator.Select("//id:Source", manager))
{
nav.SetValue("Tessssssst");
}
document.Save(#"new.xml");
var element = xdoc.Elements("Source").FirstOrDefault();
Given your provided XML elements will always be null and so you do nothing with your XML and that's why the result looks like the input.
Reason1: Elements() is looking for direct children only and <Source> is not a direct child in your doc.
See https://learn.microsoft.com/en-us/dotnet/api/system.xml.linq.xcontainer.elements?view=net-5.0
Reason2: <Source> tag has a different namespace than the doc itself and so the query is looking in the wrong namespace.
Solution:
XNamespace ns = "http://www.acm.nl/DataModel/2018/09";
var element = xdoc.Descendants(ns + "Source").FirstOrDefault();
See https://learn.microsoft.com/en-us/dotnet/api/system.xml.linq.xcontainer.descendants?view=net-5.0 which is searching for children including their children.
If you expect a node to exist, I'd also recommend to use First() instead of FirstOrDefault(). This way you'll get an exception that would have shown you the reason for your problem.
Related
I have a XML file like this:
<?xml version="1.0" encoding="utf-8"?>
<ApplicationConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd">
<ServerConfiguration>
<SecurityPolicies>
<ServerSecurityPolicy>
<SecurityMode>None_1</SecurityMode>
</ServerSecurityPolicy>
</SecurityPolicies>
</ServerConfiguration>
</ApplicationConfiguration>
What I want is to add more node named ServerSecurityPolicy by code.
Then I use this code:
string docaddress = "D:\\abc.xml";
XDocument doc = XDocument.Load(docaddress);
var root = doc.Root;
var these = root.Descendants().Where(p => p.Name.LocalName == "SecurityPolicies");
XElement addelement = new XElement("ServerSecurityPolicy");
addelement.Add(new XElement("SecurityMode", "None_1"));
foreach (var elem in these)
{
elem.Add(addelement);
}
doc.Save(docaddress);
It actually works, but the newly added node is something like this:
<ServerSecurityPolicy xmlns="">
<SecurityMode>None_1</SecurityMode>
</ServerSecurityPolicy>
I don't want the attribute "xmlns", then I try to delete it by something like this:
var these2 = root.Descendants().Where(p => p.Name.LocalName == "ServerSecurityPolicy");
foreach (var elem in these2)
{
label2.Text += elem.Attribute("xmlns").Name.LocalName;
elem.Attribute("xmlns").Remove();
}
The label2.Text shows "xmlnsxmlnsxmlsn..." so that I think the elem.Attribute("xmlns") has pointed to the attributes I want to delete, but the Remove() not work.
Can you suggest me some ways to delete the attribute or add node without attribute?
Thank you
The reason you're getting an empty attribute - which xmlns="" refers to - is because your document's root node belongs to the namespace xsi. When you're adding a node without a namespace - as you're doing in your example - you're not actually binding it to the xsi namespace.
To make your node part of the root namespace, replace
new XElement("ServerSecurityPolicy")
with
new XElement("ServerSecurityPolicy",
new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"))
The problem is that the element you want to create is actually in the http://opcfoundation.org/UA/SDK/Configuration.xsd namespace, which is the default for the document because of this part of the root element:
xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
You can create an element in that namespace easily:
XNamespace configNs = "http://opcfoundation.org/UA/SDK/Configuration.xsd";
var these = root.Descendants(configNs + "ServerSecurityPolicy");
When you add that to your document, you'll find it won't add the xmlns="" part, which was present because you were adding an element without a namespace.
I'd also suggest using the namespace to find elements too:
XNamespace configNs = "http://opcfoundation.org/UA/SDK/Configuration.xsd";
var these = root.Descendants(configNs + "SecurityPolicies");
That's much cleaner than just using the local name, in my view.
I am currently having a problem with reading a XML file using XPath expression. I have used the XmlDocument class. When I try reading a particular node from the XML, I get an empty list. The node which I am trying to read is the ID below ProductionRequest.
Here is the XML file which I tried to read:
<?xml version="1.0" encoding="iso-8859-1"?>
<ProductionSchedule xmlns="http://www.wbf.org/xml/b2mml-v02">
<ID>00000020000000</ID>
<Location>
<EquipmentID>8283</EquipmentID>
<EquipmentElementLevel>Site</EquipmentElementLevel>
<Location>
<EquipmentID>0</EquipmentID>
<EquipmentElementLevel>Area</EquipmentElementLevel>
</Location>
</Location>
<ProductionRequest>
<ID>0009300000000</ID>
<ProductProductionRuleID>W001</ProductProductionRuleID>
<StartTime>2017-04-20T23:57:20</StartTime>
<EndTime>2017-04-20T24:00:00</EndTime>
</ProductionRequest>
</ProductionSchedule>
This is the code which I used to read the above XML
using System;
using System.Xml.Linq;
using System.Xml;
using System.Xml.XPath;
namespace XML
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
string fullName = "F:\\Programming\\XML\\Example XML.xml";
XmlDocument xreader = new XmlDocument();
xreader.Load(fullName);
XmlNode root = xreader.DocumentElement;
XmlNodeList xnList1 =
xreader.SelectNodes("/ProductionSchedule/ProductionRequest/ID");
}
}
}
I could not find the cause for this problem. Could anyone help me in this regard. Looking forward for valuable inputs.
Your xml contains namespace http://www.wbf.org/xml/b2mml-v02 at root level node <ProductionSchedule>
And you are using the XPath expression /ProductionSchedule/ProductionRequest/ID but this XPath expression is not suitable for this xml document and that's why you can't get any desired value.
You need to use the below XPath expression to get the id's of all <ProductionRequest> node.
XmlNodeList xnList1 = xreader.SelectNodes("//*[name()='ProductionSchedule']/*[name()='ProductionRequest']/*[name()='ID']");
OR you can add namespace manually like
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xreader.NameTable);
nsmgr.AddNamespace("x", "http://www.wbf.org/xml/b2mml-v02");
XmlNodeList xnList1 = xreader.SelectNodes("//x:ProductionSchedule/x:ProductionRequest/x:ID", nsmgr);
And finally, you can read id from any of the parent nodes in variable xnList1 like
foreach (XmlNode id in xnList1)
{
Console.WriteLine(id.InnerText);
}
Output:
I am very new to C#, but it seems as though this should be pretty straight forward. I am trying to parse an XML string returned from a web feed that looks like this:
<autnresponse xmlns:autn="http://schemas.autonomy.com/aci/">
<action>QUERY</action>
<response>SUCCESS</response>
<responsedata>
<autn:numhits>6</autn:numhits>
<autn:hit>
<autn:reference>http://something.what.com/index.php?title=DPM</autn:reference>
<autn:id>548166</autn:id>
<autn:section>0</autn:section>
<autn:weight>87.44</autn:weight>
<autn:links>Castlmania,POUCH</autn:links>
<autn:database>Postgres</autn:database>
<autn:title>A Pouch and Mail - Castlmania</autn:title>
<autn:content>
<DOCUMENT>
<DRETITLE>Castlmania Pouch and Mail - Castlmania</DRETITLE>
<DRECONTENT>A paragraph of sorts that would contain content</DRECONTENT>
</DOCUMENT>
</autn:content>
</autn:hit>
<autn:hit>...</autn:hit>
<autn:hit>...</autn:hit>
<autn:hit>...</autn:hit>
<autn:hit>...</autn:hit>
</autnresponse>
with no luck.
I am using this code to start:
XmlDocument xmlString = new XmlDocument();
xmlString.LoadXml(xmlUrl);
XmlElement root = xmlString.DocumentElement;
XmlNode GeneralInformationNode =
root.SelectSingleNode("//autnresponse/responsedata/autn:hit");
foreach (XmlNode node in GeneralInformationNode)
{
Console.Write("reference: "+node["autn:reference"]+" Title:"+node["DRETITLE"]+"<br />);
}
And I would like to print the DRETITLE and autn:reference element of within each of the autn:hit elements. Is that even doable with my approach?
I have tried looking and several example on the good old web like this to no avail.
The error that comes back is:
System.Xml.XPath.XpathEception {NameSpace Manager or XsltContext
needed. ...}
Thanks in advance.
UPDATE:
In trying to use XmlNamespaceManager, one has to give it a url to the schema definition like so:
XmlNamespaceManager namespmng = new XmlNamespaceManager (xmlString.NameTable);
namespmng.AddNamespace("autn","http://someURL.com/XMLschema");
The problem seems to be that now the error is gone, but the data is not displaying. I should mention that I am working off of a machine that does not have internet connectivity. The other thing is the schema seems to be unavailable. I am guessing that XmlNamespaceManager would work once able to connect to the internet right?
Using System.Xml.Linq it could be something like this:
var doc = XElement.Load(xmlUrl);
var ns = doc.GetNamespaceOfPrefix("autn");
foreach (var hit in doc.Descendants(ns + "hit"))
{
var reference = hit.Element(ns + "reference").Value;
var dretitle = hit.Descendants("DRETITLE").Single().Value;
WriteLine($"ref: {reference} title: {dretitle}");
}
Firstly, the exception you're getting is because you haven't loaded the namespace using the XmlNamespaceManager for the xml you're parsing. Something like this:
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xmlString.NameTable);
if (root.Attributes["xmlns:autn"] != null)
{
uri = root.Attributes["xmlns:autn"].Value;
namespaceManager.AddNamespace("autn", uri);
}
Secondly, what you're trying to do is possible. I'd suggest using root.SelectNodes(<your xpath here>) which will return a collection of autn:hit nodes that you can loop through instead of SelectSingleNode which will return one node. Within that you can drill down to the content/DOCUMENT/DRETITLE and pull the text for the DRETITLE node using either XmlNode.Value if you select the text specifically or XmlNode.InnerText on the DRETITLE node.
I have an xml template (ReportTemplate.xml) with the following:
<Report xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner" xmlns:cl="http://schemas.microsoft.com/sqlserver/reporting/2010/01/componentdefinition" xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition">
<ReportSections>
<ReportSection>
<Body>
<ReportItems>
<Tablix Name="Tablix1">
<TablixBody>
<TablixColumns>
</TablixColumns>
<TablixRows>
</TablixRows>
</TablixBody>
</Tablix>
</ReportItems>
</Body>
</ReportSection>
</ReportSections>
</Report>
I have created xml snippets (tablixrow.xml) not fully qualified xml documents as such
<TablixRow>
<Height>0.26736in</Height>
</TablixRow>
In my console app I am trying load both files and insert tablixrow code block into the TablixRows node of the parent document
private static XDocument GenerateReport(List<string>fieldnames)
{
//load base template
XDocument report = XDocument.Load("Templates/ReportTemplate.xml");
XNamespace ns = report.Root.Name.Namespace;
//load row template
XDocument tRows = XDocument.Load("Templates/tablixrow.xml");
//and here is where I am stuck
return report;
}
I'm new to linq and I am trying understand how to navigate to the "TablixRows" node and then insert the tRows block of code.
Can anyone provide some points of reference or examples. What I have see thus far always navigated to and ID. I cannot use id's in this schema and need to rely on the node
-cheers
private static XDocument GenerateReport(List<string>fieldnames)
{
XDocument report = XDocument.Load("Templates/ReportTemplate.xml");
XNamespace ns = report.Root.Name.Namespace;
XElement row = XElement.Load("Templates/tablixrow.xml");
// change namespace for loaded xml elements
foreach (var element in row.DescendantsAndSelf())
element.Name = ns + element.Name.LocalName;
var rows = xdoc.Descendants(ns + "TablixRows").Single();
rows.Add(row);
return report;
}
If you will not change namespace for row template xml, then after adding to report document it will have default namespace (which you don't want):
<TablixRows>
<TablixRow xmlns="">
<Height>0.26736in</Height>
</TablixRow>
</TablixRows>
If some of your templates will have attributes, then you need to provide namespace for attributes also.
I have code to get the nodes of a root element:
xmlNodes = rootElement.SelectNodes("DefinitionName");
It's not returning nodes that exist. In the debugger, I can expand rootElement to find DefinitionName. Apparently the problem is the fact that the file has a namespace defined (see XML below). MSDN says that I have to do something like this to get nodes to return:
Note: This has nothing to do with my code. This is the example from MSDN:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ab", "http://www.lucernepublishing.com");
XmlNodeList nodelist = doc.SelectNodes("//ab:book", nsmgr);
I have two questions:
Why does the namespace matter? If I want a node, and it exists, just give it to me.
My app processes many XML files. How am I supposed to specify the namespace (nsmgr.AddNamespace())? Do I need to parse the file to get that first?
I can't help but feeling that I'm taking the long, angst-filled way of doing this.
This is the XML:
<?xml version="1.0" encoding="utf-8"?>
<SessionStateInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
z:Id="1" z:Type="Company.Apps.MoreHere.Session.SessionStateInfo"
z:Assembly="assembly info here"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
xmlns="http://schemas.datacontract.org/2004/07/MoreHere.Session">
<CoaterNumber>25</CoaterNumber>
<DefinitionName z:Id="2">Two Line</DefinitionName>
<EnableManualMode>true</EnableManualMode>
<SessionStateInfo ....
xmlns="http://schemas.datacontract.org/2004/07/MoreHere.Session">
means that this element and all its descendants are in the http://schemas.datacontract.org/2004/07/MoreHere.Session namespace. Since unprefixed names in an XPath always refer to elements in no namespace, you will need to bind this URI to a prefix and use that prefix in your XPath, even though no prefix is in use in the document.
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("mhs", "http://schemas.datacontract.org/2004/07/MoreHere.Session");
xmlNodes = rootElement.SelectNodes("mhs:DefinitionName", nsmgr);
If you know that the element(s) you are looking for will always have the same local name but may or may not have a namespace (or may have different namespaces) then you can use XPath tricks like
rootElement.SelectNodes("*[local-name() = 'DefinitionName']");
It matters because if there's a namespace attached, then "DefinitionName" is not enough. Imagine you have been given a list of people, all with the first name John:
John Smith
John Jones
John Murphy
What you're doing is the equivalent of asking for "John," instead of "John Smith" for example.
This isn't exactly the answer to the question but it may be an alternative solution using XDocument
using System;
using System.Dynamic;
using System.Xml.Linq;
using Microsoft.CSharp.RuntimeBinder;
using System.Linq;
namespace ConsoleApplication8
{
class Program
{
static void Main(string[] args)
{
XDocument document = XDocument.Load("SessionStateInfo.xml");
XNamespace nameSpace = document.Root.GetDefaultNamespace();
XElement node = document.Descendants(nameSpace + "DefinitionName").FirstOrDefault();
if (node != null)
{
Console.WriteLine("Cool! XDocument rocks! value: {0}", node.Value);
}
else
{
Console.WriteLine("Spoot! Didn't find it!");
}
}
}
}
This seems to work if the default namespace is specified or not.