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.
Related
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
Whilst trying to minimise the memory footprint of an XML parsing program, specifically avoiding the loading hundreds of megabytes using XElement.Load(), I came across articles suggesting using the older XmlReader e.g. here.
I need to internally reconstruct each major element as an XElement to avoid major refactoring. However, I discovered that if my source elements are directly adjoining, this approach skips every 2nd element.
I've torn down the problem to this unit-test (MSTest2 with FluentAssertions):
[DataTestMethod]
[DataRow("<data><entry>1</entry><entry>2</entry><entry>3</entry><entry>4</entry></data>")]
[DataRow("<data><entry>1</entry> <entry>2</entry> <entry>3</entry> <entry>4</entry></data>")]
public void XmlReaderCount(string input)
{
var sr = new StringReader(input);
var xml = XmlReader.Create(sr);
xml.MoveToContent();
var data = new List<string>();
while (xml.Read())
{
if (xml.LocalName == "entry" && xml.NodeType == XmlNodeType.Element)
{
var element = (XElement)System.Xml.Linq.XNode.ReadFrom(xml);
data.Add(element.Value);
}
}
data.Should()
.HaveCount(4);
}
The first (data-driven) test fails with:
Expected collection to contain 4 item(s), but found 2.
As it puts 1 and 3 into the data collection. It does loop 4 times, but every other element has an xml.NodeType of Text, not Element. The second test (with spaces between the </entry> and <entry> passes by processing all 4.
In my real world example, I can't easily change the source. I do already have a solution, inspired by another StackOverflow question so I can do the following, but it seems strange - is something wrong?
[DataTestMethod]
[DataRow("<data><entry>1</entry><entry>2</entry><entry>3</entry><entry>4</entry></data>")]
[DataRow("<data><entry>1</entry> <entry>2</entry> <entry>3</entry> <entry>4</entry></data>")]
public void XmlReaderCountSubtree(string input)
{
var data = new List<string>();
var sr = new StringReader(input);
var xml = XmlReader.Create(sr);
xml.MoveToContent();
while (xml.Read())
{
if (xml.LocalName == "entry" && xml.NodeType == XmlNodeType.Element)
{
using (var subtree = xml.ReadSubtree())
{
subtree.MoveToContent();
var content = subtree.ReadOuterXml();
var element = XElement.Parse(content);
data.Add(element.Value);
}
}
}
data.Should()
.HaveCount(4);
}
When you call ReadFrom(xml) , the state of xml is changed. Its cursor is moved forward to the next element. Your code then moves on to while (xml.Read()) and so ignores that new element completely.
With the second data set, the ignored (and uninspected) elements are the whitespace nodes so you get away with it. But basically, you reading algorithm is wrong.
A fix for your first approach, not pretty but it works:
xml.Read();
while (! xml.EOF)
{
if (xml.LocalName == "entry" && xml.NodeType == XmlNodeType.Element)
{
//using (var subtree = xml.ReadSubtree())
{
var element = (XElement)XNode.ReadFrom(xml);
data.Add(element.Value);
}
}
else
{
xml.Read();
}
}
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.
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;
I am working with LINQ to XML in C#. I have the following code, the last line keeps throwing a System.Exception: Value cannot be null. I can't figure out what the problem is. I've tried everything.
AuthorizedToSign is a List. I was able to perform the same action using a bulky nested foreach loop.
I know for sure there are no errors as far as the XML file itself.
If anyone can help I would greatly appreciate it.
BusinessAccounts = (from a in accountRoot.Elements()
where (bool)a.Element("Business") == true
select new BusinessAccount()
{
OpenDate = (DateTime)a.Element("DateOpened"),
Password = a.Element("Password").Value,
Balance = (double)a.Element("Balance"),
AccountStatus = (Status)Enum.Parse(typeof(Status), a.Element("Status").Value),
//Element AuthToSign has a collection of sub-elements called "authName"
//couldn't get the code below to work
AuthorizedToSign = (from el in a.Element("AuthorizedToSign").Elements()
select el.Element("AuthName").Value).ToList()
}).ToList();
changing select el.Element("AuthName").Value)
to select (string)el.Element("AuthName"))
doesn't help.
The XML file has a lot of entries that look like this:
<?xml version="1.0" encoding="utf-8"?>
<Accounts>
<BusinessAccount>
<Business>true</Business>
<AccountNumber>34534456</AccountNumber>
<AccountBranchID>100</AccountBranchID>
<AccountName>Elgris Tech</AccountName>
<CompanyName>Elgris Tech</CompanyName>
<CompanyID>235</CompanyID>
<CreditLimit>50000</CreditLimit>
<DateOpened>2014-12-13T00:00:00</DateOpened>
<Balance>1200</Balance>
<Password>1234</Password>
<Status>Active</Status>
<AuthorizedToSign>
<AuthName>Yechiel</AuthName>
<AuthName>Lev</AuthName>
<AuthName>Roman</AuthName>
</AuthorizedToSign>
</BusinessAccount>
<PrivateAccount>
<Business>false</Business>
<AccountNumber>34534458</AccountNumber>
<AccountBranchID>100</AccountBranchID>
<AccountName>Yechiel L.</AccountName>
<CustomerName>Yechiel L.</CustomerName>
<CustomerAddress>2sadfasosa, CA</CustomerAddress>
<CustomerPhone>8-4268</CustomerPhone>
<CardNumber>304456</CardNumber>
<CreditLimit>10000</CreditLimit>
<DateOpened>1994-06-23T00:00:00</DateOpened>
<Balance>555000</Balance>
<Password>pass</Password>
<Status>Active</Status>
</PrivateAccount>
</Accounts>
UPDATE
According to your xml, PrivateAccount element does not have AuthorizedToSign node, thus referencing to AuthorizedToSign node throws an exception. So, in your case solution will be simple:
from a in accountRoot.Elements()
let authorized = a.Element("AuthorizedToSign")
where (bool)a.Element("Business")
select new BusinessAccount()
{
OpenDate = (DateTime)a.Element("DateOpened"),
Password = (string)a.Element("Password"),
Balance = (double)a.Element("Balance"),
AccountStatus = (Status)Enum.Parse(typeof(Status), (string)a.Element("Status")),
AuthorizedToSign = authorized == null ? null : // or new List<string>()
authorized.Elements()
.Select(auth => (string)auth)
.ToList()
};
Getting auth names with query syntax:
AuthorizedToSign = authorized == null ? null : // or new List<string>()
(from auth in authorized.Elements()
select (string)auth).ToList()