XML Attribute Values C# - c#

I am trying to retrieve the value of two XML attributes and having a hard time using Xelement.
Basically I am getting an API response that has uses the same attribute naming convention for two different values.
This is the response I am getting back after making the call.
-<ResponseData>
<Type value="7" id="Level"/>
<Type value="67.80" id="Score"/>
</ResponseData>
I want to set the two values to two different variables. level & score. I have the fulling code however I keep getting back a null reference error.
var xmlResponse = XElement.Parse(response);
var level = "";
var score = "";
if(xmlResponse.Attribute("id").Value == "Level")
{
level = (string) xmlResponse.Attribute("value");
}
if(xmlResponse.Attribute("id").Value == "Score")
{
score = (string) xmlResponse.Attribute("value");
}
I want my two variables to be set to the values I get back from the API call.
Any help is appreciated

Side note, if this is a SOAP service with a WSDL, have you tried letting Visual Studio scaffold it?
So you have to think about it this way.. your now parsed XElement is a ResponseData object, which has a collection of Type objects in it now, and each Type object has a value and id attribute, which means you need to find the right Type entry for each value you're looking for. EG:
string data = #"<ResponseData>
<Type value=""7"" id=""Level""/>
<Type value=""67.80"" id=""Score""/>
</ResponseData>";
var xmlResponse = XElement.Parse(data);
var levelElement = xmlResponse.Elements("Type")?
.FirstOrDefault(x => x.Attribute("id")?.Value == "Level");
var scoreElement = xmlResponse.Elements("Type")?
.FirstOrDefault(x => x.Attribute("id")?.Value == "Score");
int level;
double score;
level = int.Parse(levelElement?.Attribute("value")?.Value ?? "-1");
score = double.Parse(scoreElement.Attribute("value")?.Value ?? "-1");
Console.WriteLine($"Score {score}; Level {level}");

Why not use?
-<ResponseData>
<Type Level="7" Score="67.80"/>
</ResponseData>

I would recommend using XML that powershell knows how to work with.
[xml]$xml = Get-Content C:\temp\xml.txt
$TypeNode = $xml.SelectSingleNode("//Type[#id='Score']")
$TypeNode.Attributes["value"].Value

Related

Store new XML into existing Project Array with Constructor

I'll try to keep it short and thank you in advance.
I have created a Quiz. The Questions and answers, as well as the integer for the correct answer are done via get and set , into a constructor and then created in another class by just creating an object and giving it the parameters. it looks as follows:
allQuestions = new Question[3];
allQuestions[0] = new Question("Question1", "answer1", "answer2",
"answer3", "answer4", 2);
where 2 is the integer that says answer 2 is the correct one.
i do use this array in almost every function in my code.
Now i decided to get the questions from a XML Document instead of creating them here. I'm a C# beginner so i played around and could not get it working.
my self made xml looks as follows :
<questions>
<question>
<ID>1</ID>
<questiontext>Peter</questiontext>
<answer1>der</answer1>
<answer2>da</answer2>
<answer3>21</answer3>
<answer4>lol</answer4>
</question>
<question>
<ID>2</ID>
<questiontext>Paul</questiontext>
<antwort1>dasistid2</antwort1>
<antwort2>27</antwort2>
<antwort3>37</antwort3>
<antwort4>47</antwort4>
</question>
</questions>
so 2 basic nodes (?)
can you explain me how to read that one and store it into my array so i can still use my e.g. "allQuestions.Question1" ? watched a youtube tutorial, quite a lot, could still not get it working in this project.
using visual studio 2017 , WPF , C#
There are a lot of ways to do what you are trying to do. I'll give you a dirty example of a manual solution, as well as a more automatic one that should work. Note that the automatic version won't use your constructor so unless you have an empty constructor defined it may not work for you.
Manual processing using XML Linq:
public IList<Question> ParseXml(string xmlString)
{
var result = new List<Question>();
var xml = XElement.Parse(xmlString);
var questionNodes = xml.Elements("question");
//If there were no question nodes, return an empty collection
if (questionNodes == null)
return result;
foreach (var questionNode in questionNodes)
{
var idNode = questionNode.Element("ID");
var textNode = questionNode.Element("questiontext");
var ant1Node = questionNode.Element("antwort1");
var ant2Node = questionNode.Element("antwort2");
var ant3Node = questionNode.Element("antwort3");
var ant4Node = questionNode.Element("antwort4");
var question = new Question();
question.Id = Convert.ToInt32(idNode?.Value);
// note the '?.' syntax. This is a dirty way of avoiding NULL
// checks. If textNode is null, it returns null, otherwise it
// returns the textNode.Value property
question.Text = textNode?.Value;
question.AnswerOne = ant1Node?.Value;
question.AnswerTwo = ant2Node?.Value;
question.AnswerThree = ant3Node?.Value;
question.AnswerFour = ant4Node?.Value;
result.Add(question);
}
return result;
}
Next we have the XmlSerializer approach. This is not ideal in all situations, but provides an easy way to serialize objects into XML and to deserialize XML into objects.
public QuestionCollection autoparsexml(string xml)
{
//create the serializer
XmlSerializer serializer = new XmlSerializer(typeof(QuestionCollection));
//convert the string into a memory stream
MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
//deserialize the stream into a c# object
QuestionCollection resultingMessage = (QuestionCollection)serializer.Deserialize(memStream);
}
public class QuestionCollection
{
public List<Question> Questions { get; set; }
}

ExpandoObject not being populated with all values

I have inherited a project and cannot talk to/ do not know the original developer to ask this question to. This is a .NET MVC project.
This is the first time I have worked with ExpandoObject so I am trying to learn. I am following the style that was originally used where information was added to an xml file and then parsed and stored in the Model. When I debug the issue and inspect Model in the view, I only see one value of the two I have in the XML.
Here is the XML that I appened to the end of the file:
<Home_Index> <NewsletterSignup>
<SignUpMessage>
<Copy>
<![CDATA[
Want up to $20 in savings?
]]>
</Copy>
</SignUpMessage>
<SignUpMessage>
<Copy>
<![CDATA[
Sign up, stay connected & save up to $20.
]]>
</Copy>
</SignUpMessage>
</NewsletterSignup>
</Home_Index>
So when I inspect the Model, all I see is the second <SignUpMessage> but not the first.
Here is the code that creates the ExpandoObject:
public class XmlToDynamic
{
public static dynamic Parse(String file, XElement node = null)
{
if (String.IsNullOrWhiteSpace(file) && node == null) return null;
node = !String.IsNullOrWhiteSpace(file) ? XDocument.Load(System.Web.Hosting.HostingEnvironment.MapPath(file)).Root : node;
IDictionary<String, dynamic> result = new ExpandoObject();
var pluralizationService = PluralizationService.CreateService(CultureInfo.CreateSpecificCulture("en-us"));
node.Elements().ForEach(gn =>
{
var isCollection = gn.HasElements &&
(
gn.Elements().Count() > 1 &&
gn.Elements().All(e => e.Name.LocalName.ToLower() == gn.Elements().First().Name.LocalName) ||
gn.Name.LocalName.ToLower() == pluralizationService.Pluralize(gn.Elements().First().Name.LocalName).ToLower()
);
var items = isCollection ? gn.Elements().ToList() : new List<XElement> { gn };
var values = new List<dynamic>();
items.ForEach(i => values.Add((i.HasElements) ? Parse(null, i) : i.Value.Trim()));
result[gn.Name.LocalName] = isCollection ? values : values.FirstOrDefault();
});
return result;
}
}
What I want to do is to choose the two <SignUpMessage> and randomly chose one to display.
Also worth noting is that the xml file has content that is correctly parsed before where I added <NewsletterSignup>.
I believe you need a parent element to hold your two SignUpMessages:
<Home_Index>
<NewsletterSignup>
<SignUpMessages>
<SignUpMessage>
<Copy>
`<![CDATA[`
Want up to $20 in savings?
`]]>`
</Copy>
</SignUpMessage>
<SignUpMessage>
<Copy>
`<![CDATA[`
Sign up, stay connected & save up to $20.
`]]>`
</Copy>
</SignUpMessage>
</SignUpMessages>
</Home_Index>
</NewsletterSignup>
The unclosed tags for Home_Index and NewsletterSignup are also cause for concern, so I've closed them off.

How to parse nested elements using LINQ to XML

I have an XML file with multiple checkItem elements. I need to save each checkItem element into a database. I'm having a difficult time getting exactly what I need using the query below.
<checkItem>
<checkItemType>check</checkItemType>
<checkAmount>195000</checkAmount>
<nonMICRCheckData>
<legalAmount>195000</legalAmount>
<issueDate>2010-04-30</issueDate>
<other>PAY VAL 20 CHARACTER</other>
</nonMICRCheckData>
<postingInfo>
<date>2013-05-01</date>
<RT>10108929</RT>
<accountNumber>111111111</accountNumber>
<seqNum>11111111</seqNum>
<trancode>111111</trancode>
<amount>195000</amount>
<serialNumber>1111111</serialNumber>
</postingInfo>
<totalImageViewsDelivered>2</totalImageViewsDelivered>
<imageView>
<imageIndicator>Actual Item Image Present</imageIndicator>
<imageViewInfo>
<Format>
<Baseline>TIF</Baseline>
</Format>
<Compression>
<Baseline>CCITT</Baseline>
</Compression>
<ViewSide>Front</ViewSide>
<imageViewLocator>
<imageRefKey>201305010090085000316000085703_Front.TIF</imageRefKey>
<imageFileLocator>IFTDISB20130625132900M041.zip</imageFileLocator>
</imageViewLocator>
</imageViewInfo>
<imageViewInfo>
<Format>
<Baseline>TIF</Baseline>
</Format>
<Compression>
<Baseline>CCITT</Baseline>
</Compression>
<ViewSide>Rear</ViewSide>
<imageViewLocator>
<imageRefKey>201305010090085000316000085703_Rear.TIF</imageRefKey>
<imageFileLocator>IFTDISB20130625132900M041.zip</imageFileLocator>
</imageViewLocator>
</imageViewInfo>
</imageView>
</checkItem>
Here is the query I've been working with. I've tried several different ways with no luck. Without the use of .Concat, I cannot get the other elements; however, using .Concat does not allow me to get all values in a manageable format. I need to separate the Front and Rear imageViews based on the ViewSide value, and only need the imageRefKey and imageFileLocator values from the imageView element. Can anyone point me in the right direction?
var query = doc.Descendants("checkItem")
//.Concat(doc.Descendants("postingInfo"))
//.Concat(doc.Descendants("imageViewLocator"))//.Where(x => (string)x.Element("ViewSide") == "Front"))
//.Concat(doc.Descendants("imageViewInfo").Where(x => (string)x.Element("ViewSide") == "Rear"))
.Select(x => new {
CheckAmount = (string) x.Element("checkAmount"),
ImageRefKey = (string) x.Element("imageRefKey"),
PostingDate = (string) x.Element("dare"),
//FrontViewSide = (string) x.Element("ViewSide"),
//RearViewSide = (string) x.Element("BViewSide")
});
You can easily get nested elements of any XElement by just calling the Elements() method of that instance, then calling Select() on that collection, to created a nested collection of an anonymous type in your main anonymous type.
var query = doc.Elements("checkItem")
.Select( x =>
new
{
CheckAmount = (string) x.Element("checkAmount"),
ImageRefKey = (string) x.Element("imageRefKey"),
PostingDate = (string) x.Element("dare"),
ImageViews = x.Element("ImageView").Elements("ImageViewInfo")
.Select(iv=>
new
{
Format = iv.Element("Format").Element("Baseline").Value
// more parsing here
}
}

wrong value in result query so Null Reference Exception in Linq query

I have two xml files which I am comparing with each other. The Linq query result1 is throwing Null Reference Exception after executing correctly for one rule. And when I debugged I found the section is displaying wrong values. I am unable to figure out the cause.
Rules.xml file:
<rule id="1" numberofsections="2">
<section id="1" attributeid="1686" ruleoperator="==" condition="and">
<name>Processor type</name>
<value>Core i3</value>
</section>
<section id="2" attributeid="1438" ruleoperator="<" condition="and" >
<name>Weight</name>
<value>3.8 LBS</value>
</section>
<type>ultrabook</type>
</rule>
And the code snippet:
XDocument rulesXml = XDocument.Load("/RulesEnginescope/RulesEnginescope/rulesSubType.xml");
XDocument productXml = XDocument.Load("c:/RuleEngine/RuleEngine/product.xml");
var getSelectedLeafCategoryRules = from rules2 in rulesXml.Descendants("QueryTransformation").Descendants("leafcategory")
where ((long)System.Convert.ToDouble(rules2.FirstAttribute.Value) == 4590)
select rules2;
var rules = getSelectedLeafCategoryRules.Descendants("rule");
var productAttribute = productXml.Descendants("AttrList").Descendants("Attr");
foreach (var x in rules)
{
var section = x.Elements("section");
/*Wrong value in section.count()*/
Console.WriteLine(section.Count());
var result1 = from p in section
from pa in productAttribute
where (p.Attribute("attributeid").Value == pa.Attribute("id").Value
&& p.Element("name").Value == pa.Element("Name").Value)
select new
{
ruleAttribute = new
{
ruleId = p.Attribute("attributeid").Value,
ruleOperator = p.Attribute("ruleoperator").Value,
name = p.Element("name").Value,
value = p.Element("value").Value,
condition = p.Attribute("condition").Value
},
prodAttribute = new
{
productId = pa.Attribute("id").Value,
name = pa.Element("Name").Value,
value = pa.Element("ValueList").Element("Value").Value
/*Error*/ }
};
if (result1.Count() != 0 && result1.Count() == System.Convert.ToInt64(x.Attribute("numberofsections").Value))
{
//checking each section
foreach (var r in result1)
{
...
}
}
The idiomatic way to get the value of elements and attributes in LINQ-to-XML is to cast the element or attribute to the type you want, rather than accessing the Value attribute.
prodAttribute = new
{
productId = (string)pa.Attribute("id"),
name = (string)pa.Element("Name"),
// ...
}
Using this pattern avoids null ref exceptions caused when calls to Attribute() and Element() don't find a matching node. It also reduces verbosity:
((long)System.Convert.ToDouble(rules2.FirstAttribute.Value)
// should be
(long)rules2.FirstAttribute
You'll still need to add null checks when you're accessing children of children. This can get verbose; one way to keep it succinct is to use IEnumerable-oriented methods so that you're operating on a (possibly empty) collection, rather than a (possibly null) instance.
pa.Element("ValueList").Element("Value").Value
// could be
(string)pa.Elements("ValueList").Elements("Value").FirstOrDefault ()
Finally, note that capitalization matters in LINQ-to-XML. In your code you seem to be switching capitalization patterns ("id" vs. "Name") often; it's likely that your source XML is more consistent.

How to write XPath expression to select node name from its value

I'm trying to write an XPath expression to select the name of a node from its value in "qualities" and then select in "qualityNames" the value inside node whose name has previously captured.
E.g. In "qualities" - got value "4", take name "rarity3" then in "qualityNames" I got node named "rarity3" and take value "amazingrarity"
<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>
I'm doing this in C# (It's a MVC App) and I'd prefer to use XPath because I'm indexing the XML and I haven't found a fastest way to query in-memory technique (this XML file has ~3MB and I'm using IndexingXPathNavigator).
Use the local-name() and text() functions + predicates. For value "4" it will be
//qualityNames/*[local-name()=local-name(//qualities/*[text() = '4'])]
Tested with http://www.xpathtester.com
Sounds like you want to create a dictionary of key/value pairs (assuming the node names are only needed to find matches and aren't important to your code).
If so, you can use the following:
var doc = XElement.Parse(#"<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>");
var query = from quality in doc.XPathSelectElements("qualities/*")
join qualityName in doc.XPathSelectElements("qualityNames/*")
on quality.Name equals qualityName.Name
select new { Key = quality.Value, Value = qualityName.Value };
var qualities = query.ToDictionary(a => a.Key, a => a.Value);
var quality3 = qualities["3"];
// quality3 == "Vintage"
var quality4 = qualities["4"];
// quality4 == "amazingrarity"
EDIT: example of how to cache this dictionary
// add reference to System.Web dll
public Dictionary<string, string> GetQualities()
{
// assuming this code is in a controller
var qualities = this.HttpContext.Cache["qualities"] as Dictionary<string, string>;
if (qualities == null)
{
// LoadQualitiesFromXml() is the code above
qualities = LoadQualitiesFromXml();
this.HttpContext.Cache["qualities"] = qualities;
}
return qualities;
}
I think this is what you asked
var rarity3ValueInQualities = xml.SelectSingleNode("/result/qualities/rarity3").InnerText;
var rarity3ValueInqualityNames = xml.SelectSingleNode("/result/qualityNames/rarity3").InnerText;

Categories