I have an XML document from a web service that I am trying to query. However, I am not sure how to query the XML when it has elements nested inside other elements.
Here is a section of the XML file (I haven't included all of it because it's a long file):
<response>
<display_location>
<full>London, United Kingdom</full>
<city>London</city>
<state/>
<state_name>United Kingdom</state_name>
<country>UK</country>
<country_iso3166>GB</country_iso3166>
<zip>00000</zip>
<magic>553</magic>
<wmo>03772</wmo>
<latitude>51.47999954</latitude>
<longitude>-0.44999999</longitude>
<elevation>24.00000000</elevation>
</display_location>
<observation_location>
<full>London,</full>
<city>London</city>
<state/>
<country>UK</country>
<country_iso3166>GB</country_iso3166>
<latitude>51.47750092</latitude>
<longitude>-0.46138901</longitude>
<elevation>79 ft</elevation>
</observation_location>
I can query "one section at a time" but I'm constructing an object from the LINQ. For example:
var data = from i in weatherResponse.Descendants("display_location")
select new Forecast
{
DisplayFullName = i.Element("full").Value
};
var data = from i in weatherResponse.Descendants("observation_location")
select new Forecast
{
ObservationFullName = i.Element("full").Value
};
And my "Forecast" class is basically just full of properties like this:
class Forecast
{
public string DisplayFullName { get; set; };
public string ObservationFullName { get; set; };
//Lots of other properties that will be set from the XML
}
However, I need to "combine" all of the LINQ together so that I can set all the properties of the object. I have read about nested LINQ but I do not know how to apply it to this particular case.
Question: How do I go about "nesting/combining" the LINQ so that I can read the XML and then set the appropriate properties with said XML?
One possible way :
var data = from i in weatherResponse.Descendants("response")
select new Forecast
{
DisplayFullName = (string)i.Element("display_location").Element("full"),
ObservationFullName = (string)i.Element("observation_location").Element("full")
};
Another way ... I prefer using the Linq extension methods in fluent style
var results = weatherResponse.Descendants()
.SelectMany(d => d.Elements())
.Where(e => e.Name == "display_location" || e.Name == "observation_location")
.Select(e =>
{
if(e.Name == "display_location")
{
return new ForeCast{ DisplayFullName = e.Element("full").Value };
}
else if(e.Name == "observation_location")
{
return new ForeCast{ ObservationFullName = e.Element("full").Value };
}
else
{
return null;
}
});
Related
## XML FİLE ##
<Title month="1" year="2016">
<film day="1">
<morning>
Fight Club
</morning>
<night>
Inceptıon
</night>
</film>
<film day="2">
<morning>
xyzasda
</morning>
<night>
czxsadasas
</night>
</film>
</Title>
MY CLASS
public class FilmController : Controller
{
public ActionResult DisplayXML()
{
var data = new List<Films>();
data = ReturnData();
return View(data);
}
private List<Films> ReturnData(){
string xmldata = "myxmldata.xml";
DataSet ds = new DataSet();
ds.ReadXml(xmldata);
var filmlist= new List<Films>();
filmlist= (from rows in ds.Tables[0].AsEnumerable()
select new Films
{
month= Convert.ToInt32(rows[0].ToString()),
year= rows[1].ToString(),
film= rows[2].ToString(),
morning= rows[3].ToString(),
night= rows[4].ToString(),
}).ToList();
return filmlist;
}
}
Model
public int month{ get; set; }
public string year{ get; set; }
public string day { get; set; }
public string morning { get; set; }
public string night{ get; set; }
How to read child node? I want to create a table. I will create a table using this data. I want to keep it on a list.
I edited..
Error: Additional information: Cannot find column 3.
where is the error? I want to read the xml file.
You can parse your XML with the following:
var xml = XDocument.Load(xmlFile);
var films = xml.Descendants("film").Select(d => new Films()
{
month = Convert.ToInt32(d.Parent.Attribute("month").Value),
year = d.Parent.Attribute("year").Value,
day = d.Attribute("day").Value,
morning = d.Element("morning").Value,
night = d.Element("night").Value
});
See it in action HERE.
You can retrieve Films collection using Linq-To-XML easily like this:
XDocument xdoc = XDocument.Load(xmldata);
List<Films> result = xdoc.Descendants("film")
.Select(x =>
{
var film = x;
var title = x.Parent;
return new Film
{
month = (int)title.Attribute("month"),
year = (string)title.Attribute("year"),
day = (string)film.Attribute("day"),
morning = (string)film.Element("morning"),
night = (string)film.Element("night")
};
}
).ToList();
This will return two films, and each will have month & year based on Title node.
Code Explanation:
First we are finding all the film nodes, then projecting it using Select. In the select clause we can save variable for each film (you can think of film inside select method like alias in foreach loop). Also, we are storing the parent Title node in title variable. After this all we need to do is read the elements & attributes.
If understanding Method syntax is difficult, then here is the equivalent query syntax:
List<Films> result2 = (from x in xdoc.Descendants("film")
let film = x
let title = x.Parent
select new Film
{
month = (int)title.Attribute("month"),
year = (string)title.Attribute("year"),
day = (string)film.Attribute("day"),
morning = (string)film.Element("morning"),
night = (string)film.Element("night")
}).ToList();
Working Fiddle
When i read your xml in a dataset i get 2 tables
Table 1 "Title" with one row and 3 columns
Table 2 "film" with two rows and 4 columns
you read on Tables[0] (3 columns) - and you try to read 4th column of 3..
you need to change your loop - since you need to load Data from two tables.
I'm modifying .docx documents with DocumentFormat.OpenXml library. I know element ordering is important, otherwise the document will not pass schema validation and might result a document that can't be opened in Word.
Now I need to add a DocumentProtection element to DocumentSettingsPart. And I need to insert this child element in the right place inside of a parent.
The schema looks like this:
There are quite a lot of possible ordering of child elements. At the moment I'm adding this element like this:
var documentProtection = new DocumentProtection()
{
// do the configuration
};
DocumentSettingsPart settings = doc.MainDocumentPart.DocumentSettingsPart;
var rootElement = settings.RootElement;
var prevElement =
rootElement.GetFirstChild<DoNotTrackFormatting>() ??
rootElement.GetFirstChild<DoNotTrackMoves>() ??
rootElement.GetFirstChild<TrackRevisions>() ??
rootElement.GetFirstChild<RevisionView>() ??
rootElement.GetFirstChild<DocumentType>() ??
rootElement.GetFirstChild<StylePaneSortMethods>() ??
// SNIP
rootElement.GetFirstChild<Zoom>() ??
rootElement.GetFirstChild<View>() ??
(OpenXmlLeafElement)rootElement.GetFirstChild<WriteProtection>();
rootElement.InsertAfter(documentProtection, prevElement);
I.e. I'm trying to find if any possible element that should go before mine already exists in the document. And then insert DocumentProtection after that element. And given amount of elements this list gets pretty boring.
Is there a better way to add DocumentProtection so it is schema compliant and does not involve enumeration of all possible elements?
There isn't a nice way to achieve what you want. You'll have to tinker with the collection and you're responsible for keeping the order correct.
Using ILSpy on the Settings class you'll find that the implementors used a helper method SetElement<T> on the base class that takes a position and an instance to insert.
Unfortunately that helper method is marked internal so we can't leverage it if you try to subclass Settings. Instead I re-implemented the needed functionality so you'll have a subclass of Settings that does offer a property for DocumentProtection but uses the re-implemented solution to find the correct location to insert the node:
SettingsExt
public class SettingsExt: Settings
{
// contruct based on XML
public SettingsExt(string outerXml)
:base(outerXml)
{
// empty
}
public DocumentProtection DocumentProtection
{
// get is easy
get
{
return this.GetFirstChild<DocumentProtection>();
}
// reimplemented SetElement based on
// reversed engineered Settings class
set
{
// eleTagNames is a static string[] declared later
// it holds all the names of the elements in the right order
int sequenceNumber = eleTagNames
.Select((s, i) => new { s= s, idx = i })
.Where(s => s.s == "documentProtection")
.Select((s) => s.idx)
.First();
OpenXmlElement openXmlElement = this.FirstChild;
OpenXmlElement refChild = null;
while (openXmlElement != null)
{
// a bit naive
int currentSequence = eleTagNames
.Select((s, i) => new { s = s, idx = i })
.Where(s => s.s == openXmlElement.LocalName)
.Select((s) => s.idx)
.First(); ;
if (currentSequence == sequenceNumber)
{
if (openXmlElement is DocumentProtection)
{
refChild = openXmlElement.PreviousSibling();
this.RemoveChild<OpenXmlElement>(openXmlElement);
break;
}
refChild = openXmlElement;
}
else
{
if (currentSequence > sequenceNumber)
{
break;
}
refChild = openXmlElement;
}
openXmlElement = openXmlElement.NextSibling();
}
if (value != null)
{
this.InsertAfter(value, refChild);
}
}
}
// order of elements in the sequence!
static readonly string[] eleTagNames = new string[]
{
"writeProtection",
"view",
"zoom",
"removePersonalInformation",
"removeDateAndTime",
"doNotDisplayPageBoundaries",
"displayBackgroundShape",
"printPostScriptOverText",
"printFractionalCharacterWidth",
"printFormsData",
"embedTrueTypeFonts",
"embedSystemFonts",
"saveSubsetFonts",
"saveFormsData",
"mirrorMargins",
"alignBordersAndEdges",
"bordersDoNotSurroundHeader",
"bordersDoNotSurroundFooter",
"gutterAtTop",
"hideSpellingErrors",
"hideGrammaticalErrors",
"activeWritingStyle",
"proofState",
"formsDesign",
"attachedTemplate",
"linkStyles",
"stylePaneFormatFilter",
"stylePaneSortMethod",
"documentType",
"mailMerge",
"revisionView",
"trackRevisions",
"doNotTrackMoves",
"doNotTrackFormatting",
"documentProtection",
"autoFormatOverride",
"styleLockTheme",
"styleLockQFSet",
"defaultTabStop",
"autoHyphenation",
"consecutiveHyphenLimit",
"hyphenationZone",
"doNotHyphenateCaps",
"showEnvelope",
"summaryLength",
"clickAndTypeStyle",
"defaultTableStyle",
"evenAndOddHeaders",
"bookFoldRevPrinting",
"bookFoldPrinting",
"bookFoldPrintingSheets",
"drawingGridHorizontalSpacing",
"drawingGridVerticalSpacing",
"displayHorizontalDrawingGridEvery",
"displayVerticalDrawingGridEvery",
"doNotUseMarginsForDrawingGridOrigin",
"drawingGridHorizontalOrigin",
"drawingGridVerticalOrigin",
"doNotShadeFormData",
"noPunctuationKerning",
"characterSpacingControl",
"printTwoOnOne",
"strictFirstAndLastChars",
"noLineBreaksAfter",
"noLineBreaksBefore",
"savePreviewPicture",
"doNotValidateAgainstSchema",
"saveInvalidXml",
"ignoreMixedContent",
"alwaysShowPlaceholderText",
"doNotDemarcateInvalidXml",
"saveXmlDataOnly",
"useXSLTWhenSaving",
"saveThroughXslt",
"showXMLTags",
"alwaysMergeEmptyNamespace",
"updateFields",
"hdrShapeDefaults",
"footnotePr",
"endnotePr",
"compat",
"docVars",
"rsids",
"mathPr",
"uiCompat97To2003",
"attachedSchema",
"themeFontLang",
"clrSchemeMapping",
"doNotIncludeSubdocsInStats",
"doNotAutoCompressPictures",
"forceUpgrade",
"captions",
"readModeInkLockDown",
"smartTagType",
"schemaLibrary",
"shapeDefaults",
"doNotEmbedSmartTags",
"decimalSymbol",
"listSeparator",
"docId",
"discardImageEditingData",
"defaultImageDpi",
"conflictMode"};
}
A typical usage scenario with this class is as follows:
using (var doc = WordprocessingDocument.Open(#"c:\tmp\test.docx", true))
{
var documentProtection = new DocumentProtection()
{
Formatting = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true)
};
DocumentSettingsPart settings = doc.MainDocumentPart.DocumentSettingsPart;
// instantiate our ExtendedSettings class based on the
// original Settings
var extset = new SettingsExt(settings.Settings.OuterXml);
// new or existing?
if (extset.DocumentProtection == null)
{
extset.DocumentProtection = documentProtection;
}
else
{
// replace existing values
}
// this is key to make sure our own DOMTree is saved!
// don't forget this
settings.Settings = extset;
}
We have a settings file which is basically Xml that we are extending for pluggable modules we are writing. Basically we want to use our existing Xml settings but allow extension methods to be written on them. Long story short if we had an Xml file like so:
<Settings>
<SettingsOne Key1_1="Value1"
Key1_2="Value2" />
<SettingsTwo Key2_1="Value1"
Key2_2="Value2" />
</Settings>
How could we load this as an collection of SettingsEntry where SettingsEntry looked like so:
public class SettingsEntry
{
public string Section { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
Where Section would be "SettingsOne", Key would be "Key1_1" and Value would be "Value1".
Is this even possible or am I going down a dark path?
EDIT:
OK the suggestion of Linq to Xml was a life save, I was trying to do this with XmlSerializer! Below is what I have so far, is there a way to turn this into a single select rather than two like I have below:
var root = XElement.Load(pathToXml);
var sections = from el in root.Elements()
select el.Name;
List<SettingsEntry> settings = new List<SettingsEntry>();
foreach (var item in sections)
{
var attributes = from el in root.Elements(item).Attributes()
select new SettingsEntry()
{
Section = item.LocalName,
Key = el.Name.LocalName,
Value = el.Value
};
settings.AddRange(attributes);
}
return settings;
EDIT 2:
This seems to work.
var sections = from el in root.Elements()
from a in root.Elements(el.Name).Attributes()
select new SettingsEntry()
{
Section = el.Name.LocalName,
Key = a.Name.LocalName,
Value = a.Value
};
You can do that in one LINQ query like this :
var attributes = from attribute in root.Elements().Attributes()
select new SettingsEntry()
{
Section = attribute.Parent.Name.LocalName,
Key = attribute.Name.LocalName,
Value = attribute.Value
};
return attributes.ToList();
var xml = #"
<Settings>
<SettingsOne Key1_1= ""Value1"" Key1_2= ""Value1""></SettingsOne>
<SettingsTwo Key2_1= ""Value1"" Key2_2= ""Value1""></SettingsTwo>
</Settings>"
var root = XDocument.Parse(xml);
var q = root.Elements("Settings").Descendants();
List<SettingsEntry> settings = (from el in root.Elements("Settings").Descendants()
select new SettingsEntry()
{
Section = el.Name.ToString(),
Key = el.FirstAttribute.Value,
Value = el.LastAttribute.Value
}).ToList();
You might have to play with it a little to get the exact Object you want, but this is a serious push in the right direction.
I have the following code and it is working fine. However I am new to using "IEnumerable code" and it would seem obvious that it could be done better.
Basically I want all Region nodes in the XML and then the data I want to output in my Asp:repeater is nested quite deeply in the XML, but the 4 fields are all at the same level.
var xDoc = xmlDoc.ToXDocument();
var jobs = xDoc.Descendants("Region")
.Select(x => new {
jobName = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job").Element("JobName").Value,
jobType = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job").Element("JobType").Value,
jobURL = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job").Element("URL").Value,
jobClose = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job").Element("JobCLDate").Value
}
);
if (jobs.Count() > 0)
{
careersListing.DataSource = jobs;
careersListing.DataBind();
careersListing.Visible = true;
}
I would be very grateful of any feedback with respect to making it more succinct
Thanks
Nigel
You're right; this can be inefficient.
You can simplify it like this:
var jobs = from x in xDoc.Descendants("Region")
let job = x.Element("Location").Element("Department").Element("Brand").Element("Jobs").Element("Job")
select new {
jobName = job.Element("JobName").Value,
...
};
If you prefer to use method call syntax, you can pass a statement lambda expression that declares a temporary variable.
If there is just one Job element for each region (which seems to be the case), why not just query for it directly?
var jobs = xDoc.Descendants("Job")
.Select(x => new {
jobName = x.Element("JobName").Value,
jobType = x.Element("JobType").Value,
jobURL = x.Element("URL").Value,
jobClose = x.Element("JobCLDate").Value
}
);
Another minor optimization: Use Any() instead of Count():
if (jobs.Any())
{
careersListing.DataSource = jobs;
careersListing.DataBind();
careersListing.Visible = true;
}
if (jobs.Count() > 0) can be rewritten as if (jobs.Any()).
I'm currently working on a Silverlight app and need to convert XML data into appropriate objects to data bind to. The basic class definition for this discussion is:
public class TabularEntry
{
public string Tag { get; set; }
public string Description { get; set; }
public string Code { get; set; }
public string UseNote { get; set; }
public List<string> Excludes { get; set; }
public List<string> Includes { get; set; }
public List<string> Synonyms { get; set; }
public string Flags { get; set; }
public List<TabularEntry> SubEntries { get; set; }
}
An example of the XML that might come in to feed this object follows:
<I4 Ref="1">222.2
<DX>Prostate</DX>
<EX>
<I>adenomatous hyperplasia of prostate (600.20-600.21)</I>
<I>prostatic:
<I>adenoma (600.20-600.21)</I>
<I>enlargement (600.00-600.01)</I>
<I>hypertrophy (600.00-600.01)</I>
</I>
</EX>
<FL>M</FL>
</I4>
So, various nodes map to specific properties. The key ones for this question are the <EX> and <I> nodes. The <EX> nodes will contain a collection of one or more <I> nodes and in this example matches up to the 'Excludes' property in the above class definition.
Here comes the challenge (for me). I don't have control over the web service that emits this XML, so changing it isn't an option. You'll notice that in this example one <I> node also contains another collection of one or more <I> nodes. I'm hoping that I could use a LINQ to XML query that will allow me to consolidate both levels into a single collection and will use a character that will delimit the lower level items, so in this example, when the LINQ query returned a TablularEntry object, it would contain a collection of Exclude items that would appear as follows:
adenomatous hyperplasia of prostate
(600.20-600.21)
prostatic:
*adenoma (600.20-600.21)
*enlargement (600.00-600.01)
*hypertrophy (600.00-600.01)
So, in the XML the last 3 entries are actually child objects of the second entry, but in the object's Excludes property, they are all part of the same collection, with the former child objects containing an identifier character/string.
I have the beginnings of the LINQ query I'm using below, I can't quite figure out the bit that will consolidate the child objects for me. The code as it exists right now is:
List<TabularEntry> GetTabularEntries(XElement source)
{
List<TabularEntry> result;
result = (from tabularentry in source.Elements()
select new TabularEntry()
{
Tag = tabularentry.Name.ToString(),
Description = tabularentry.Element("DX").ToString(),
Code = tabularentry.FirstNode.ToString(),
UseNote = tabularentry.Element("UN") == null ? null : tabularentry.Element("UN").Value,
Excludes = (from i in tabularentry.Element("EX").Elements("I")
select i.Value).ToList()
}).ToList();
return result;
}
I'm thinking that I need to nest a FROM statement inside the
Excludes = (from i...)
statement to gather up the child nodes, but can't quite work it through. Of course, that may be because I'm off in the weeds a bit on my logic.
If you need more info to answer, feel free to ask.
Thanks in advance,
Steve
Try this:
List<TabularEntry> GetTabularEntries(XElement source)
{
List<TabularEntry> result;
result = (from tabularentry in source.Elements()
select new TabularEntry()
{
Tag = tabularentry.Name.ToString(),
Description = tabularentry.Element("DX").ToString(),
Code = tabularentry.FirstNode.ToString(),
UseNote = tabularentry.Element("UN") == null ? null : tabularentry.Element("UN").Value,
Excludes = (from i in tabularentry.Element("EX").Descendants("I")
select (i.Parent.Name == "I" ? "*" + i.Value : i.Value)).ToList()
}).ToList();
return result;
}
(edit)
If you need the current nested level of "I" you could do something like:
List<TabularEntry> GetTabularEntries(XElement source)
{
List<TabularEntry> result;
result = (from tabularentry in source.Elements()
select new TabularEntry()
{
Tag = tabularentry.Name.ToString(),
Description = tabularentry.Element("DX").ToString(),
Code = tabularentry.FirstNode.ToString(),
UseNote = tabularentry.Element("UN") == null ? null : tabularentry.Element("UN").Value,
Excludes = (from i in tabularentry.Element("EX").Descendants("I")
select (ElementWithPrefix(i, '*'))).ToList()
}).ToList();
return result;
}
string ElementWithPrefix(XElement element, char c)
{
string prefix = "";
for (XElement e = element.Parent; e.Name == "I"; e = e.Parent)
{
prefix += c;
}
return prefix + ExtractTextValue(element);
}
string ExtractTextValue(XElement element)
{
if (element.HasElements)
{
return element.Value.Split(new[] { '\n' })[0].Trim();
}
else
return element.Value.Trim();
}
Input:
<EX>
<I>adenomatous hyperplasia of prostate (600.20-600.21)</I>
<I>prostatic:
<I>adenoma (600.20-600.21)</I>
<I>enlargement (600.00-600.01)</I>
<I>hypertrophy (600.00-600.01)
<I>Bla1</I>
<I>Bla2
<I>BlaBla1</I>
</I>
<I>Bla3</I>
</I>
</I>
</EX>
Result:
* adenomatous hyperplasia of prostate (600.20-600.21)
* prostatic:
* *adenoma (600.20-600.21)
* *enlargement (600.00-600.01)
* *hypertrophy (600.00-600.01)
* **Bla1
* **Bla2
* ***BlaBla1
* **Bla3
Descendants will get you all of the I children. The FirstNode will help seperate the value of prostatic: from the values of its children. The there's a return character in the value of prostatic:, which I removed with Trim.
XElement x = XElement.Parse(#"
<EX>
<I>adenomatous hyperplasia of prostate (600.20-600.21)</I>
<I>prostatic:
<I>adenoma (600.20-600.21)</I>
<I>enlargement (600.00-600.01)</I>
<I>hypertrophy (600.00-600.01)</I>
</I>
</EX>");
//
List<string> result = x
.Descendants(#"I")
.Select(i => i.FirstNode.ToString().Trim())
.ToList();
Here's a hacky way to get those asterisks in. I don't have time to improve it.
List<string> result2 = x
.Descendants(#"I")
.Select(i =>
new string(Enumerable.Repeat('*', i.Ancestors(#"I").Count()).ToArray())
+ i.FirstNode.ToString().Trim())
.ToList();