C# XML - XPath Query Builder - Dynamically Create a Query - c#

I am trying to build a XPath Query Builder in order to have a generic code as portable as possible.
So far, this is what I came up with :
private static string XpathQueryBuilder (string NodeName,string AttributeName = null, string AttributeValue = null)
{
string XpathAttr = "";
if (AttributeName != null)
if (AttributeValue != null)
XpathAttr = "[#" + AttributeName + "='" + AttributeValue + "']";
else
XpathAttr = "[#" + AttributeName + "='*']";
return "//" + NodeName + XpathAttr;
}
The problem I see with this method though is that if I have more than one attribute or node that I would like to look for, this function won't work. Is there a way to create an XPath Query dynamically that could theorically accept any number of attributes and/or Nodes.
My priority is on having a function that accepts more than one attribute and attribute value as this is the more likely case than more than one node.
Thank you for your time!

You can use Dictionary<string,string> to make the function capable of receiving multiple attributes parameter :
private static string XpathQueryBuilder(string nodeName, Dictionary<string,string> attributes = null)
{
string xpathAttr = "";
if (attributes != null)
{
xpathAttr =
"[" +
String.Join(" and ",
attributes.Select(o =>
{
var attrVal = o.Value ?? "*";
return "#" + o.Key + "='" + attrVal + "'";
})
) + "]";
}
return "//" + nodeName + xpathAttr;
}
example usage :
var node = "Root";
var attrs = new Dictionary<string, string>
{
{"foo", "bar"},
{"baz", null},
};
var result = XpathQueryBuilder(node, attrs);
Console.WriteLine(result);
dotnetfiddle demo
output :
//Root[#foo='bar' and #baz='*']

You can use LINQ to XML.
It will allow you select any data that you want.
Also, if you need more generic solution, you can try to implement your own LINQ Provider for that.
The second way is more complicated than the first one, but as a result you will have more generic solution that will provide access to your xml file by LINQ chains and expressions (lambda etc).
A few links with examples for help:
http://weblogs.asp.net/mehfuzh/writing-custom-linq-provider
http://fairwaytech.com/2013/03/writing-a-custom-linq-provider-with-re-linq/
http://jacopretorius.net/2010/01/implementing-a-custom-linq-provider.html
https://aashishkoirala.wordpress.com/2014/03/10/linq-provider-1/

Have you tried using LINQ to XML?
using System.Linq;
using System.Xml;
using System.Xml.Linq;
String GUID = "something";
XElement profilesXel = XElement.Load("your xml file path");
XElement currProfile = (from el in profilesXel
where (String)el.Element("GUID") == GUID
select el).First();
....

Related

Find element in json file

I have JSON file like below, and I want to find a specific string or list of strings in this file.
I need to know if the values are in this file:
{
"naglowek": {
"dataGenerowaniaDanych": "20200107",
"liczbaTransformacji": "5000",
"schemat": "RRRRMMDDNNNNNNNNNNBBBBBBBBBBBBBBBBBBBBBBBBBB, gdzie R to cyfra roku, M – miesiąca, D - dnia daty generowania pliku, N to cyfra NIPu, a B to cyfra rachunku bankowego"
},
"skrotyPodatnikowCzynnych": [
"0000023d01e60fa522c2535d37c93051bdb9bc74e31b824460cd743a66a5282abb45567e43f09cfb26cd454b75ee6d6b0dfa83ef26db33581510afa421c3d430",
"0000025fe2175d2639990a7918baf727c41bbf554b1a88b679e32f3dc460c4dc44454b6a98417c31c4f2ee9e1c705ff951a1d7601553b327ec380213f2186a0f",
"00000cd37d8ded5c477552f61b647bcf9e6a967036823b7515b1e01e7fe3fe1854c470fb30f56beef1bc80d83d7350a53fe8677cb932f4f251837a767e0f8d63",
"00000d939549219dd4cd795c9b9680a3e5147791b1ddc4148f3463d6b3aa22849bcc30729cc60fc1282977d52d635c70d353f450c2abaef22f7d22439ac7b6e6",
"00001df757bb678d654308b1137c7dc8381d0457043009f4fc63edab93b32f60e1f460375e7da6965dbfd58f447d173c4c6c42add0d3dac181816782cf297cd8",
"0000248aef22c8ebddebd272cdc03e023f1dca221a5c7a731ade2989f1996b00b440c7410d52b89ef6f7927608bed66ad42a230f8e2cf86b97037597640d1da0",
"00002c000cd48dc44e63fa56d314962ff16b08c2e135a4c5352261a8e1c6b6fed9fefa01f01494d554e3158039450811a727c32576656d80963ed7b81a3732e3",
"00003666894d6872169f1f5212ba30a7441580f90d115823ca2d9cb6c5aca6e58ce277943bb284dd52cd669e8f05adba8d406ea8fb81c3e26bfce46b1cf8f120",
"00003bb55a8f67914ff5553a42f2bf2c8456b4f5d1a140ffdb1069442122114c61ad7bcbc715b35862c9e4566a8ddfbe9d9ca25457daa4cda51cdd796252b770",
"000041debf38337bb23391ccc9624483370a4e2d63dc4634c4f7c8d9071e5337d65464e59feedebe082bb7cbf6bb0a132b92194be457c92b1111132a51c81dcf",
"00004c88c01bc05ed4aa0df33cdbbe41aa77d49f94a6c9ee35efe6a59eca5cdea735acff28f05fb3d960973227b27ec81444b9afe14323fd2fc53a991b42c6ce"]
}
I need to find in this file specific string (one of the hashes)
I tried this:
public bool FindInFile(string sha512, string filePath, string date)
{
JObject o1 = JObject.Parse(File.ReadAllText(filePath + date + ".json"));
// read JSON directly from a file
using (StreamReader file = File.OpenText(filePath + date + ".json"))
using (JsonTextReader reader = new JsonTextReader(file))
{
JObject o2 = (JObject)JToken.ReadFrom(reader);
IEnumerable<JToken> pricyProducts = o2.SelectTokens("[?($.skrotyPodatnikowCzynnych == " + sha512 + ")]");
}
return true;
}
This should do the trick
public bool FindInFile(string sha512, string filePath, string date)
{
var obj = JObject.Parse(File.ReadAllText(filePath + date + ".json"));
return obj["skrotyPodatnikowCzynnych"].Children().Values<string>().Contains(sha512);
}
if you are sure the file is a valid json and do not contain other fields, why bother parsing it ? just look for the strings in it...
Otherwise just use linq to see if the query you wrote yields Any result...
pricyProducts.Any()
If it does not return any results ever, just fix the query (which i think is wrong)...
https://www.newtonsoft.com/json/help/html/SelectToken.htm
o2.SelectTokens("[?($.skrotyPodatnikowCzynnych == " + sha512 + ")]")
probably should be
o2.SelectTokens("$.skrotyPodatnikowCzynnych[?(# == '" + sha512 + "')]"

FileDialog filter - LINQ concatenation

While creating service to display OpenFileDialog/SaveFileDialog, I was thinking about creating LINQ query/clear C# code to Concatinate()/Join() filter expression.
Do the filter based on this call:
string res = "";
if(new Service().ShowOpenFileDialog(out res,
new string[]{ "JPG", "TXT", "FBX"},
new string[]{ "Images", "TextFile", "FilmBox"}))
Console.WriteLine(res); //DisplayResult
Example definition:
public bool ShowOpenFileDialog(out string result, string[] ext, string[] desc)
{
if(ext.Length != desc.Length) return false;
OpenFileDialog diag = new OpenFileDialog();
// problematic part
// diag.Filter = "Text File (*.txt)|*.txt";
// diag.Filter = desc[0] + " (*." + ext[0] + ")|*." + ext[0];
// diag.Filter += "|"+desc[1] + " (*." + ext[1] + ")|*." + ext[1];
// I tried something like:
// diag.Filter = String.Join("|", desc.Concat(" (*." + ext[0] + ")|*." + ext[0]));
// but not sure how to pass indexes across LINQ queries
diag.Filter = /* LINQ? */
if(diag.ShowDialog() == true)
{
result = diag.FileName;
return true;
}
return false;
}
Question: Is it possible to create LINQ to concatenate/join 2 arrays in such format? Is it needed to do it by code? If so, what is the cleanest/least expensive solution?
Note: As a filter (result) example:
"Images (*.JPG)|*.JPG |TextFile (*.TXT)|*.TXT |FilmBox (*.FBX)|*.FBX"
EDIT: Also please consider there might be n items in the arrays.
You should use Select to iterate over your collection. Also, string interpolation comes in handy here.
string filter = string.Join
( "|"
, ext.Zip
( desc
, (e, d) => new
{ Ext = e
, Desc = d
}
)
.Select(item => $"{item.Desc} (*.{item.Ext})|*.{item.Ext}")
);

Build XML file from XPathExpressions

I have a bunch of XPathExpressions that I used to read an XML file. I now need go the other way. (Generate an XML file based on the values I have.)
Here is an example to illustrate. Say I have a bunch of code like this:
XPathExpression hl7Expr1 = navigator.Compile("/ORM_O01/MSH/MSH.6/HD.1");
var hl7Expr2 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/PID/PID.18/CX.1");
var hl7Expr3 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/ORM_O01.PATIENT_VISIT/PV1/PV1.19/CX.1");
var hl7Expr4 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/PID/PID.3[1]/CX.1");
var hl7Expr5 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/PID/PID.5[1]/XPN.1/FN.1");
var hl7Expr6 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/PID/PID.5[1]/XPN.2");
string hl7Value1 = "SomeValue1";
string hl7Value2 = "SomeValue2";
string hl7Value3 = "SomeValue3";
string hl7Value4 = "SomeValue4";
string hl7Value5 = "SomeValue5";
string hl7Value6 = "SomeValue6";
Is there a way to take the hl7Expr XPathExpressions and generate an XML file with the corresponding hl7Value string in it?
Or maybe just use the actual path string to do the generation (instead of using the XPathExpression object)?
Note: I saw this question: Create XML Nodes based on XPath? but the answer does not allow for [1] references like I have on hl7Expr4.
I found this answer: https://stackoverflow.com/a/3465832/16241
And I was able to modify the main method to convert the [1] to attributes (like this):
public static XmlNode CreateXPath(XmlDocument doc, string xpath)
{
XmlNode node = doc;
foreach (string part in xpath.Substring(1).Split('/'))
{
XmlNodeList nodes = node.SelectNodes(part);
if (nodes.Count > 1) throw new ApplicationException("Xpath '" + xpath + "' was not found multiple times!");
else if (nodes.Count == 1) { node = nodes[0]; continue; }
if (part.StartsWith("#"))
{
var anode = doc.CreateAttribute(part.Substring(1));
node.Attributes.Append(anode);
node = anode;
}
else
{
string elName, attrib = null;
if (part.Contains("["))
{
part.SplitOnce("[", out elName, out attrib);
if (!attrib.EndsWith("]")) throw new ApplicationException("Unsupported XPath (missing ]): " + part);
attrib = attrib.Substring(0, attrib.Length - 1);
}
else elName = part;
XmlNode next = doc.CreateElement(elName);
node.AppendChild(next);
node = next;
if (attrib != null)
{
if (!attrib.StartsWith("#"))
{
attrib = " Id='" + attrib + "'";
}
string name, value;
attrib.Substring(1).SplitOnce("='", out name, out value);
if (string.IsNullOrEmpty(value) || !value.EndsWith("'")) throw new ApplicationException("Unsupported XPath attrib: " + part);
value = value.Substring(0, value.Length - 1);
var anode = doc.CreateAttribute(name);
anode.Value = value;
node.Attributes.Append(anode);
}
}
}
return node;
}

LINQ to XML without knowing the nodes

I have this LINQ query:
XNamespace ns = NAMESPACE;
var items = (from c in doc.Descendants(ns +"Item")
select new Item
{
Title = c.Element(ns + "ItemAttributes").Element(ns + "Title").Value,
MFR = c.Element(ns + "ItemAttributes").Element(ns + "Manufacturer").Value,
Offer = c.Element(ns + "Offers").Element(ns + "TotalOffers").Value,
Amazon = c.Element(ns + "Offer").Element(ns + "Merchant").Elements(ns + "MerchantId"),
LowPrice = Convert.ToDouble(c.Element(ns + "FormattedPrice").Value),
SalesRank = Convert.ToInt32(c.Element(ns +"SalesRank").Value),
ASIN = c.Element(ns + "ASIN").Value
}).ToList<Item>();
It works great expect for when a node is not present. For example it my not have a MFR or a sales rank. How can I make it so if it does not have the node in question, it gives me a default value or at the very doesn't make me try catch my whole query for one item.
As far as I'm aware LINQ to XML doesn't support this. However I ran into this same mess in a project I was working on and created this extension for XElement to allow it. Maybe it could work for you:
public static XElement ElementOrDummy(this XElement parentElement,
XName name,
bool ignoreCase)
{
XElement existingElement = null;
if (ignoreCase)
{
string sName = name.LocalName.ToLower();
foreach (var child in parentElement.Elements())
{
if (child.Name.LocalName.ToLower() == sName)
{
existingElement = child;
break;
}
}
}
else
existingElement = parentElement.Element(name);
if (existingElement == null)
existingElement = new XElement(name, string.Empty);
return existingElement;
}
Basically it just checks to see if the element exists and if it doesn't it returns one with the same name and an empty value.
You can use XElement Explicit Conversion, e.g.:
(int?)c.Element(ns +"SalesRank")
Reference: http://msdn.microsoft.com/en-us/library/bb340386.aspx
if the problem that the XElement exists, but the value is blank? i.e.
<Item>
<ItemAttributes>
<Manufacturer></Manufacturer>
</ItemAttributes>
</Item>
then you can use the string.IsNullOrEmpty function
XNamespace ns = NAMESPACE;
var items = (from c in doc.Descendants(ns +"Item")
select new Item
{
MFR = if (string.IsNullOrEmpty(c.Element(ns + "ItemAttributes").Element(ns + "Manufacturer").Value)) ? "default value here" : c.Element(ns + "ItemAttributes").Element(ns + "Manufacturer").Value,
// omitted for brevity
}).ToList<Item>();

get the ancestor nodes of a selected xml node using c# into a multiline text box

Im using c#.net. I have an xml file that contains many nodes. I have got the xml file into a tree view. Now when I select a particular node in the treeview, I should be able to display all its ancestors in a multiline text box. Please suggest me to do this job.
I´m not really sure what you want but this might be some thing to start with.
The extension method will get the xpath to an XElement node with attributes to specify the exact element more precisely.
public static string ToXPath(this XElement element)
{
var current = element.Parent;
string result = "";
while (current != null)
{
string currentDef = current.Name.ToString();
string attribsDef = "";
foreach (var attrib in current.Attributes())
{
attribsDef += " and #" + attrib.Name + "='" + attrib.Value + "'";
}
if (attribsDef.Length > 0)
{
currentDef += "[" + attribsDef.Substring(5) + "]";
}
result = "/" + currentDef + result;
current = current.Parent;
}
return result.Substring(1);
}

Categories