Can't select nodes in XML document - c#

My XML document is the Corpus.xml file in the TEI XML Brown Corpus
I want to select every s node in the document, so I can iterate over them and extract data.
The problem is that no matter which method I try, I cannot select the s nodes! I've tried:
xml.Root.Descendants("s")
xml.Root.XPathSelectElements("s")
XPathDocument
But nothing works. I get no errors. The result returned is an empty set!

Your XML documents has namespace declared:
<?xml version="1.0"?>
<teiCorpus xmlns="http://www.tei-c.org/ns/1.0"
xmlns:xi="http://www.w3.org/2001/XInclude">
<!-- content -->
</teiCorpus>
So you should use XNamespace to select elements:
var xdoc = XDocument.Load(path_to_xml);
XNamespace ns = "http://www.tei-c.org/ns/1.0";
var elements = xdoc.Descendants(ns + "s");
Take a look on Working with XML Namespaces for further information.

Related

Removing header in xml file required?

I have the xml file below and I want to retrieve the sym attribute from the Instrmt element using LINQ to XML. In order to do that, do I have to remove the first line: FIXML element?
If I do that then the code below works:
IEnumerable<string> symbols = from c in xml.Descendants("Instrmt")
select (string)c.Attribute("Sym");
Here is the xml.
<FIXML r="20030618" s="20040109" v="4.4" xr="FIA" xv="1" xmlns="http://www.fixprotocol.org/FIXML-4-4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.fixprotocol.org/FIXML-4-4 https://optionsclearing.com/components/docs/membership/dds_ref/fia_1_1/fixml-main-4-4-FIA-1-1.xsd">
<Batch>
<SecList ListTyp="109" ListID="20175" BizDt="2017-12-07">
<SecL Ccy="USD">
<Instrmt Desc="iShares S&P 100 ETF" SecTyp="OPT" SubTyp="ETO" Sym="OEF" Mult="100.0">
<AID AltID="00013" AltIDSrc="RBHP"/>
</Instrmt>
<InstrmtExt>
<Attrb Typ="101" Val="1.0000"/>
<Attrb Typ="108" Val="1.0000"/>
</InstrmtExt>
<Undly Desc="iShares S&P 100 ETF" Px="117.110000" Ccy="USD" Sym="OEF" ID="464287101" Src="1"/>
<Stip Typ="RBHMIN" Val="2.500"/>
<Stip Typ="CPMMIN" Val="3.750"/>
</SecL>
</SecList>
</Batch>
</FIXML>
Ok, your problem is not about the header, it's about the namespace (defined by the xmlns attribute). As your xml define one, all your xml children elements will have it as their default. Try the following code:
var xml = XDocument.Load(pathToFile);
XNamespace ns = xml.Root.GetDefaultNamespace();
IEnumerable<string> symbols = from c in xml.Descendants(ns + "Instrmt")
select (string)c.Attribute("Sym");

I am having an xml inside xml and want to test a condition is met inside the inner xml. C# solution required

<?xml version="1.0"?>
<TextType IsKey="false" Name="XMLReport"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Providers
xmlns="Reporting"/>
<Sales
xmlns="Reporting"/>
<Value
xmlns="Reporting">
<?xml version="1.0" encoding="utf-8"?>
<TestReport>
<StudyUid>
<![CDATA[123]]>
</StudyUid>
<Modality>
<![CDATA[XYZ]]>
</Modality>
<StudyDate format="DICOM">123456</StudyDate>
<StudyTime format="DICOM">6789</StudyTime>
<AccessionNumber>
<![CDATA[123]]>
</AccessionNumber>
<StudyDescription>
<![CDATA[abc def]]>
</StudyDescription>
<OperatorName format="xyz">
<![CDATA[abc]]>
</OperatorName>
<PhysicianReadingStudy format="xyz">
<![CDATA[^^^^]]>
</PhysicianReadingStudy>
<InstitutionName>
<![CDATA[xyz]]>
</InstitutionName>
<HospitalName>
<![CDATA[Hospital Name]]>
</HospitalName>
<ReportSet>
<MyReport ID="1">
<ReportStatus>
<![CDATA[Done]]>
</ReportStatus>
</MyReport>
<MyReport ID="2">
<ReportStatus>
<![CDATA[Done]]>
</ReportStatus>
</MyReport>
<MyReport ID="3">
<ReportStatus>
<![CDATA[Initial]]>
</ReportStatus>
</MyReport>
</ReportSet>
<ReportImageSet />
<FetusSet />
</TestReport>
</Value>
<WhoSetMe xmlns="Reporting">NotSpecified
</WhoSetMe>
</TextType>
I want to parse the xml above in C# and check whether "ReportStatus" is "Done" for all the ReportStatus under MyReport/ReportSet. One more twist here is the xml contains one more xml starts at "Value" tag as in above example.It may contatin many ReportStatus tag under ReportSet tag. Can someone please help me?
// Can you try this? I tried to do it with LINQ to XML.
// I assume you have multiple <TestReport /> elements in <Value /> tag
// and var xelement is your xml variable
// First we get all TestReport elemnts
IEnumerable<XElement> allReports =
from el in xelement.Elements("TextType/Value/TestReport")
select el;
// From allReports we get all MyReport elemnts
IEnumerable<XElement> allMyReports =
from el in allReports.Elements("ReportSet/MyReport")
select el;
// From allReports we also get all MyReport elemnts with element ReportStatus value equals "Done"
IEnumerable<XElement> allDoneMyReports =
from el in allMyReports
where (string)el.Element("ReportStatus") == "Done"
select el;
// Now we compare allMyReport with allDoneMyReports
if (allMyReports.Count() == allDoneMyReports.Count())
{
//DO Somehing
}
Your XML document is invalid. You need to fix it before trying to parse it. The issue is that a document can only have one top-level element; you have 2 <TextType> and <Providers>.
Most of your elements are the namespace Reporting. You need to use it when referencing the element.
XNamespace ns = "Reporting";
var value = doc.Element("Value" + ns);
Update
Just use the namespace for each element
XNamespace ns = "Reporting";
var value = xelement.Elements("Value" + ns);
Another Update
The XML document is considered invalid because it has multiple XML declarations; there is no way to disable this. I suggest you pre-process the document to remove the extra declarations. Here's an example (https://dotnetfiddle.net/UnuAF6)
var xml = "<?xml version='1.0'?><a> <?xml version='1.0'?><b id='b' /></a>";
var doc = XDocument.Parse(xml.Replace(" <?xml version='1.0'?", " "));
var bs = doc.Descendants("b");
Console.WriteLine("{0} 'b' elements", bs.Count());

System.InvalidOperationException when modifiying value in an xml file in C#

So i... Have this snippet of code what writes to an existing xml file... the code to me is VERY simple...
XElement element;
XDocument xdoc = XDocument.Load(FileLoc);
element = xdoc.Elements(XName.Get("gold", "http://schemas.datacontract.org/2004/07/DumaLegend")).Single();
element.Value = Gold.Text;
Good Right? good! but why does it give out that error which means that it can't find the thing? it's a very valid thing....
here is the xml file:
<?xml version="1.0" encoding="utf-8"?>
<Save xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/DumaLegend">
<saveInfo>
<energyPieces>0</energyPieces>
<fullEnergyCells>4</fullEnergyCells>
<fullHearts>4</fullHearts>
<globalSwitches xmlns:d3p1="a">
<d3p1:switchList xmlns:d4p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</globalSwitches>
<gold>0</gold>
<hasBigFireball>false</hasBigFireball>
<hasCombo>false</hasCombo>
<hasCrossbow>false</hasCrossbow>
<hasDash>false</hasDash>
<hasDashUpgrade>false</hasDashUpgrade>
<hasDoubleJump>false</hasDoubleJump>
<hasFireball>false</hasFireball>
<hasHookshot>false</hasHookshot>
<hasInvisPot>false</hasInvisPot>
<hasSecondCombo>false</hasSecondCombo>
<hasShieldUpgrade>false</hasShieldUpgrade>
<hasSmallFireball>false</hasSmallFireball>
<heartPieces>0</heartPieces>
<heroPosOnMap>0</heroPosOnMap>
<heroTokens>0</heroTokens>
<itemSlot1 xmlns:d3p1="http://schemas.datacontract.org/2004/07/DumaLegend.Objects.Consumables" i:nil="true" />
<itemSlot2 xmlns:d3p1="http://schemas.datacontract.org/2004/07/DumaLegend.Objects.Consumables" i:nil="true" />
<lives>3</lives>
<worldsUnlocked>0</worldsUnlocked>
<worldsUnlockedOnMap>0</worldsUnlockedOnMap>
</saveInfo>
<saveSlot>0</saveSlot>
</Save>
Use xdoc.Descendants(XName.Get("gold", "http://schemas.datacontract.org/2004/07/DumaLegend")).
From the docs for Elements
Returns a filtered collection of the child elements of this element or document, in document order. Only elements that have a matching XName are included in the collection.
There is only one child elements of your document, and that is the Save element.
What you are looking for is at the path Save/saveInfo/gold. So you can either use Elements like this:
XNamespace ns = "http://schemas.datacontract.org/2004/07/DumaLegend";
var gold = doc.Elements(ns + "Save")
.Elements(ns + "saveInfo")
.Elements(ns + "gold")
.Single();
Or you can use Descendants, which will search all child elements recursively.
XNamespace ns = "http://schemas.datacontract.org/2004/07/DumaLegend";
var gold = doc.Descendants(ns + "gold").Single();

XDocument get element that defines it's own namespace

As question states. I have a xml document (below) and I need to get X_ScalarWebApi_DeviceInfo that defines namespace urn:schemas-sony-com:av. Unfortunately it results in an error: {System.NullReferenceException: Object reference not set to an instance of an object. I'm mainly interested in ServiceList element, but it doesn't work as well. Platform - Windows 10 mobile.
Any ideas?
<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<device>
<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
<friendlyName>ILCE-6000</friendlyName>
<manufacturer>Sony Corporation</manufacturer>
<manufacturerURL>http://www.sony.net/</manufacturerURL>
<modelDescription>SonyDigitalMediaServer</modelDescription>
<modelName>SonyImagingDevice</modelName>
<UDN>uuid:000000001000-1010-8000-62F1894EE7BE</UDN>
<serviceList>
<service>
<serviceType>urn:schemas-sony-com:service:ScalarWebAPI:1</serviceType>
<serviceId>urn:schemas-sony-com:serviceId:ScalarWebAPI</serviceId>
<SCPDURL/>
<controlURL/>
<eventSubURL/>
</service>
</serviceList>
<av:X_ScalarWebAPI_DeviceInfo xmlns:av="urn:schemas-sony-com:av">
<av:X_ScalarWebAPI_Version>1.0</av:X_ScalarWebAPI_Version>
<av:X_ScalarWebAPI_ServiceList>
<av:X_ScalarWebAPI_Service>
<av:X_ScalarWebAPI_ServiceType>guide</av:X_ScalarWebAPI_ServiceType>
<av:X_ScalarWebAPI_ActionList_URL>http://192.168.122.1:8080/sony</av:X_ScalarWebAPI_ActionList_URL>
<av:X_ScalarWebAPI_AccessType/>
</av:X_ScalarWebAPI_Service>
<av:X_ScalarWebAPI_Service>
<av:X_ScalarWebAPI_ServiceType>accessControl</av:X_ScalarWebAPI_ServiceType>
<av:X_ScalarWebAPI_ActionList_URL>http://192.168.122.1:8080/sony</av:X_ScalarWebAPI_ActionList_URL>
<av:X_ScalarWebAPI_AccessType/>
</av:X_ScalarWebAPI_Service>
<av:X_ScalarWebAPI_Service>
<av:X_ScalarWebAPI_ServiceType>camera</av:X_ScalarWebAPI_ServiceType>
<av:X_ScalarWebAPI_ActionList_URL>http://192.168.122.1:8080/sony</av:X_ScalarWebAPI_ActionList_URL>
<av:X_ScalarWebAPI_AccessType/>
</av:X_ScalarWebAPI_Service>
</av:X_ScalarWebAPI_ServiceList>
</av:X_ScalarWebAPI_DeviceInfo>
</device>
</root>
Ah, the code:
XDocument xDoc = XDocument.Parse(xml_text);
//var av = xDoc.Root.GetDefaultNamespace();//.Attribute("xmlns");//
XNamespace av = "urn:schemas-sony-com:av";
System.Diagnostics.Debug.WriteLine(av);
<XElement> api_list = (List<XElement>)xDoc.Element(av + "X_ScalarWebAPI_DeviceInfo").Elements();
==EDIT==
Well, both solutions were ok, so I'm upvoting one and marking as answer the other :P
It was mentioned that the solution using only a 'local name' might cause false positive search results, so to be safe I'm using the first one. Thanks for help!
Element only returns elements directly beneath the current node. You need the to specify the entire path (note there are multiple namespaces):
XNamespace ns = "urn:schemas-upnp-org:device-1-0";
XNamespace av = "urn:schemas-sony-com:av";
var api_list = xDoc.Root.Element(ns + "device")
.Element(av + "X_ScalarWebAPI_DeviceInfo").Elements();
Using Xml Linq
XDocument doc = XDocument.Load(FILENAME);
XElement x_ScalarWebAPI_DeviceInfo = doc.Descendants().Where(x => x.Name.LocalName == "X_ScalarWebAPI_DeviceInfo").FirstOrDefault();
XNamespace ns = x_ScalarWebAPI_DeviceInfo.Name.Namespace;
The issue is indeed the Element or Elements methods only search the direct child elements. You can use Decendants() but you will get a collection, so you have to do First(expression) to het a single one.
But in the expression you can use .Name.LocalName to skip the whole namespace thing and look for just the name of the element.
For this question:
XDocument xDoc = XDocument.Parse(xml_text);
XElement x_ScalarWebAPI_DeviceInfo = doc.Descendants().First(x.Name.LocalName == "X_ScalarWebAPI_DeviceInfo");

How to load xml code block into existing xml file at a specific node using linq

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.

Categories