XML - Check for existence of a specific node - c#

I don't know why I'm having so much trouble with this, but I'm hoping someone can get me pointed in the right direction.
I have these few lines of code :
var xDoc = new XmlDocument();
xDoc.LoadXml(xelementVar.ToString());
if (xDoc.ChildNodes[0].HasChildNodes)
{
for (int i = 0; i < xDoc.ChildNodes[0].ChildNodes.Count; i++)
{
var sFormatId = xDoc.ChildNodes[0].ChildNodes[i].Attributes["formatID"].Value;
// Do some stuff
}
// Do some more stuff
}
The problem is that the xDoc I'm getting doesn't always have the formatID node, so I end up getting a null reference exception, although 99% of the time it works perfectly fine.
My question :
How can I check if the formatID node exists before I try to read the Value out of it?

if a node does not exist, it returns null.
if (xDoc.ChildNodes[0].ChildNode[i].Attributes["formatID"] != null)
sFormatId = xDoc.ChildNodes[0].ChildNodes[i].Attributes["formatID"].Value;
of you can do it a shortcut way
var sFormatId = xDoc.ChildNodes[0].ChildNodes[i].Attributes["formatID"] != null ? xDoc.ChildNodes[0].ChildNodes[i].Attributes["formatID"].Value : "formatID not exist";
The format is like this.
var variable = condition ? A : B;
this is basically saying that if the condition is true, then variable = A, otherwise, variable = B.

Could you use DefaultIfEmpty()?
E.g
var sFormatId = xDoc.ChildNodes[0].ChildNodes[i].Attributes["formatID"]
.Value.DefaultIfEmpty("not found").Single();
Or as others have suggested, check that the attribute is not null:
if (xDoc.ChildNodes[0].ChildNodes[i].Attributes["formatID"] != null)

You can also do this:
if (xDoc.ChildNodes[0].HasChildNodes)
{
foreach (XmlNode item in xDoc.ChildNodes[0].ChildNodes)
{
string sFormatId;
if(item.Attributes["formatID"] != null)
sFormatId = item.Attributes["formatID"].Value;
// Do some stuff
}
}

you can check that like this
if(null != xDoc.ChildNodes[0].ChildNode[i].Attributes["formatID"])

I think a cleaner way to do this would be:
var xDoc = new XmlDocument();
xDoc.LoadXml(xelementVar.ToString());
foreach(XmlNode formatId in xDoc.SelectNodes("/*/*/#formatID"))
{
string formatIdVal = formatId.Value; // guaranteed to be non-null
// do stuff with formatIdVal
}

In most case we face issues because an XPath does not exists, It returns null and our code breaks because of the InnerText.
You can only check XPath exists or not and it returns null when does not exist.
if(XMLDoc.SelectSingleNode("XPath") <> null)
ErrorCode = XMLDoc.SelectSingleNode("XPath").InnerText

Related

XPath not working with SelectSingleNode

private const string TECHACCOUNTAMTITEM_AMT_XPATH = #"//Part[translate(#Type, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = 'instalment']/acord:Jv-Ins-Reinsurance/acord:TechAccount/acord:Subaccount/acord:TechAccountAmtItem[translate(#Type, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') != 'ipt' and translate(#AmtStatus, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') != 'informational']/acord:Amt";
var xmlNamespaceManager = this._namespaceManager.GetNamespaceManager(this.TransactionPayload.OuterXml);
var techAccountAmtItemAmt = Decimal.Parse(this.TransactionPayload.SelectSingleNode(TECHACCOUNTAMTITEM_AMT_XPATH, xmlNamespaceManager).InnerText);
The above two statement gives out the value 4500.
But i dont want to use translate and just want to use it directly to fetech the value .
It seems that != is not working in the below and not fetching the value and resulting in null .
Here is the new Xpath i m trying to achieve but not working .
private const string TECHACCOUNTAMTITEM_AMT_XPATH = #"//Part[#Type = 'Instalment']/acord:Jv-Ins-Reinsurance/acord:TechAccount/acord:Subaccount/acord:TechAccountAmtItem[#Type != 'ipt' and #AmtStatus != 'informational']/acord:Amt";
How can I acheive the same ?
The reason for this is that != requires the attribute to exist, while translate will also produce a result if the attribute is missing.
You need to use not() instead:
.../acord:‌​TechAccountAmtItem[not(#‌​Type = 'ipt') and not(#AmtStatus = 'informational')]/...
This assumes that Type could be missing as well. If not, you can use #Type != 'ipt' instead.

Roslyn to insert nodes after specified node

I'm writing a code analyzer which inverts an if statement to reduce nesting.
I'm able to generate a new if node and replace it to the document root. However I must move all content(statements) coming from this if statement to below it. Let me show what I've achieved so far:
var ifNode = #if;
var ifStatement = #if.Statement as BlockSyntax;
var returnNode = (ifNode.Parent as BlockSyntax).Statements.Last() as ReturnStatementSyntax ?? SyntaxFactory.ReturnStatement();
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var invertedIf = ifNode.WithCondition(Negate(ifNode.Condition, semanticModel, cancellationToken))
.WithStatement(returnNode)
.WithAdditionalAnnotations(Formatter.Annotation);
var root = await document.GetSyntaxRootAsync(cancellationToken);
var newRoot = root.ReplaceNode(ifNode, invertedIf);
newRoot = newRoot.InsertNodesAfter(invertedIf, ifStatement.Statements); //It seems no to be working. There's no code after specified node.
return document.WithSyntaxRoot(newRoot);
Before:
public int Foo()
{
if (true)
{
var a = 3;
return a;
}
return 0;
}
After:
public int Foo()
{
if (false)
return 0;
var a = 3;
return a;
}
Carlos, the problem is that after you ReplaceNode you generated a new node. When you go InsertNodeAfter and pass a node from the original root node, the new node can't find it.
In an analyzer you need to either do all the changes at once, or annotate or track the nodes so you can come back to them later.
But since you are replacing a node first, the new node will be exactly at the same place. So you can shortcut and FindNode, like this:
newRoot = newRoot.InsertNodesAfter(newRoot.FindNode(ifNode.Span), ifStatement.Statements);
I haven't tested this code, but it should work.

NullReferenceException was unhandled error when trying to retrieve config attributes

public string GetLogName(string config)
{
XDocument xDoc = XDocument.Load(config);
XElement[] elements = xDoc.Descendants("listeners").Descendants("add").ToArray();
foreach (var element in elements)
{
if (element.Attribute("fileName").Value != null)
{
string filename = element.Attribute("fileName").Value;
int location = filename.IndexOf("%");
Console.WriteLine("string to return: " + filename.Substring(0, location));
return filename.Substring(0, location);
}
}
}
I am trying to retrieve the "fileName" attribute from each element in the elements array, but there are some cases when the "fileName" attribute does not exist and fails with the following error: NullReferenceException was unhandled. Object reference not set to an instance of an object.
In my case there are two "add" nodes that do not have a "fileName" attribute, but the third add node has it.
How can I skip over entries that do not have a "fileName" attribute, or can you recommend a better way to retrieve this attribute?
One way is to filter out the list before you process it:
XElement[] elements = xDoc.Descendants("listeners")
.Descendants("add")
.Where (d => d.Attribute("filename") != null )
.ToArray();
--- IMHO this is how I would rewrite the method, using linq and regex ---
var elements =
XDocument.Load(config);
.Descendants("listeners")
.Descendants("add")
.Where (node => node.Attribute("filename") != null )
.ToList();
return elements.Any() ? elements.Select (node => node.Attribute("filename").Value )
.Select (attrValue => Regex.Match(attrValue, "([^%]+)").Groups[1].Value)
.First ()
: string.Empty;
You should be able to do this simply by changing this line:
if (element.Attribute("fileName").Value != null)
To:
if (element.Attribute("fileName") != null)
change your if statement to this :
if (element.Attribute("fileName") != null)

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.

XML in C# - hasAttribute, getAttribute is not there? Why?

I'm working on a project that requires me to build a game's rooms, items and NPC in a separate database. I've chosen XML, but something prevents me from properly parsing the XML in my C# code. What am I doing wrong?
My errors are these:
System.xml.xmlnode does not contain a definition for HasAttribute
(this goes for GetAttribute as well) and no extension method accepting 'HasAttribute' accepting a first argument of type System.Xml.XmlNode ?
This also goes for GetParentNode, and my very last line
string isMoveableStr = xmlRoom.GetAttribute("isMoveable");
somehow goes:
the name xmlRoom does not exist in the current context
Here's the method:
public void loadFromFile()
{
XmlDocument xmlDoc = new XmlDocument(); // create an xml document object in memory.
xmlDoc.Load("gamedata.xml"); // load the XML document from the specified file into the object in memory.
// Get rooms, NPCs, and items.
XmlNodeList xmlRooms = xmlDoc.GetElementsByTagName("room");
XmlNodeList xmlNPCs = xmlDoc.GetElementsByTagName("npc");
XmlNodeList xmlItems = xmlDoc.GetElementsByTagName("item");
foreach(XmlNode xmlRoom in xmlRooms) { // defaults for room:
string roomID = "";
string roomDescription = "this a standard room, nothing special about it.";
if( !xmlRoom.HasAttribute("ID") ) //http://msdn.microsoft.com/en-us/library/acwfyhc7.aspx
{
Console.WriteLine("A room was in the xml file without an ID attribute. Correct this to use the room");
continue; //skips remaining code in loop
} else {
roomID = xmlRoom.GetAttribute("id"); //http://msdn.microsoft.com/en-us/library/acwfyhc7.aspx
}
if( xmlRoom.hasAttribute("description") )
{
roomDescription = xmlRoom.GetAttribute("description");
}
Room myRoom = new Room(roomDescription, roomID); //creates a room
rooms.Add(myRoom); //adds to list with all rooms in game ;)
} foreach(XmlNode xmlNPC in xmlNPCs)
{ bool isMoveable = false;
if( !xmlNPC.hasAttribute("id") )
{
Console.WriteLine("A NPC was in the xml file, without an id attribute, correct this to spawn the npc");
continue; //skips remaining code in loop
}
XmlNode inRoom = xmlNPC.getParentNode();
string roomID = inRoom.GetAttribute("id");
if( xmlNPC.hasAttribute("isMoveable") )
{
string isMoveableStr = xmlRoom.GetAttribute("isMoveable");
if( isMoveableStr == "true" )
isMoveable = true;
}
}
}
System.Xml.XmlElement has the function you are looking for. You are getting XMLNode's. You will need to cast the nodes to XmlElement to get that function.
xmlElement = (System.Xml.XmlElement)xmlRoom;
This is not specifically germane to your question, but a response to #ChaosPandion's suggestion and your question in the comments, here is your code example using Linq to XML:
var xdoc = XDocument.Load("gamedata.xml");
var xRooms = xdoc.Descendants("room");
List<Room> rooms;
//If an element doesn't have a given attribute, the Attribute method will return null for that attribute
//Here we first check if any rooms are missing the ID attribute
if (xRooms.Any( xRoom => (string)xRoom.Attribute("ID") == null )) {
Console.WriteLine("A room was in the xml file without an ID attribute...");
} else {
rooms = (
from xRoom in xRooms
select new Room(
xRoom.Attribute("description") ?? "this a standard room, nothing special about it.",
(int)xRoom.Attribute("ID")
)
).ToList();
}
var xNPCs = xdoc.Descendants("npc");
if (xNPCs.Any( xNPC => (string)xNPC.Attribute("id") == null )) {
Console.WriteLine("A NPC was in the xml file, without an id attribute, correct this to spawn the npc");
} else {
var npcs = (
from xNPC in xNPCs
let inRoom = xNPC.Parent
select new {
xNPC,
inRoom,
isMoveable = (string)xNPC.Attribute("isMoveable") != null &&
(string)inRoom.Attribute("isMoveable") == true
}
).ToList();
}
Then you can use a simple foreach on the npcs collection:
foreach (var npc in npcs) {
Console.WriteLine(inRoom.Attribute("ID"));
Console.WriteLine(npc.IsMoveable);
}
OTOH since this code makes use of the Descendants method, which returns an collection of XElement (the type corresponding to an XML element) and not of XNode (the type corresponding to an XML node), the whole issue of a node object not having attributes is neatly sidestepped.
XmlNode does not have methods HasAttribute or GetAttribute. If you look at the MSDN entry for XmlNode, you can see the methods it has available.
http://msdn.microsoft.com/en-us/library/system.xml.xmlnode.aspx
If you use XmlNode.Attributes["ATTRIBUTE_NAME"] or in your case xmlRoom.Attributes["ID"], you should be able to find the attribute you're looking for. That is, if you would like to continue using XmlNodes.
The following link has an example of how to retrieve attributes by name from an XmlNode:
http://msdn.microsoft.com/en-us/library/1b823yx9.aspx

Categories