I have a program, which parses three different CrystalReport XML files (similar structure, but different descendants levels) and then populates values into a class.
Here is example of first one:
public static List<VyplatnePasky> DeserialzieRozuctovanieMzdy(ref List<VyplatnePasky> _pasky, string sPath)
{
XDocument document = XDocument.Load(sPath);
var formattedAreaPairReport = from d in document.Descendants("FormattedReport".AddNamespace())
.Descendants("FormattedAreaPair".AddNamespace())
.Descendants("FormattedAreaPair".AddNamespace())
select d.Element("FormattedAreaPair".AddNamespace());
if (formattedAreaPairReport.Count() == 0)
{
//empty;
InsertErrorMessage("<formattedAreaPairReport> contains no data! No Data to parse from.", "DeserialzieRozuctovanieMzdy");
return _pasky;
}
//check if any sequence contains any matching elements
var GotElements = formattedAreaPairReport.Elements("FormattedAreaPair".AddNamespace()).Where(n=>n.Attribute("Level").Value == "3" && n.Attribute("Type").Value == "Group");
if (GotElements == null)
{
InsertErrorMessage("There are no matching elements under <formattedAreaPairReport>.", "DeserialzieRozuctovanieMzdy");
return _pasky;
}
foreach (XElement xElement in GotElements)
{
RozuctovanieMzda_Values(xElement, ref _pasky);
}
return _pasky;
}
and here is the second one (second XML document parser):
public static List<VyplatnePasky> DeserializeVyplatnePasky(string sPath)
{
List<VyplatnePasky> _pasky = new List<VyplatnePasky>();
XDocument document = XDocument.Load(sPath);
var formattedAreaPairReport = from d in document.Descendants("FormattedReport".AddNamespace())
select d.Element("FormattedAreaPair".AddNamespace());
if (formattedAreaPairReport.Count() == 0)
{
//empty;
InsertErrorMessage("<formattedAreaPairReport> contains no data! No Data to parse from.", "DeserializeVyplatnePasky");
}
else
{
//sequence contains data
foreach (XElement xElement in formattedAreaPairReport.Elements("FormattedAreaPair".AddNamespace()))
{
VyplatnePasky _paska = new VyplatnePasky();
VyplatnePasky_Items(xElement, ref _paska);
_pasky.Add(_paska);
}
}
return _pasky;
}
As you can see from the code above, all three XML parsing methods look almost the same; the main difference is how deep I go in the descendants under e.g. formattedAreaPairReport
What I would like to do, is to make this code more professional and reusable by creating Generic void that can be used by all three methods.
I was thinking of creating multiple Delegates, where I would pass my lambda commands such as:
var formattedAreaPairReport = ProcessFirstLevel(from d in document.Descendants("FormattedReport".AddNamespace())
select d.Element("FormattedAreaPair".AddNamespace()));
however it would become just one big messy void.
The question is - is this actually doable/worth doing? Could you help, please?
You could to DRY the code up a bit by using some clean coding techniques. Try doing something like this:
public static List<VyplatnePasky> DeserialzieRozuctovanieMzdy(ref List<VyplatnePasky> _pasky, string sPath)
{
var formattedAreaPairReport =
tryToGetItemsFromDocument
(
sPath,
document=>from d in document.Descendants("FormattedReport".AddNamespace())
.Descendants("FormattedAreaPair".AddNamespace())
.Descendants("FormattedAreaPair".AddNamespace())
select d.Element("FormattedAreaPair".AddNamespace()),
"DeserialzieRozuctovanieMzdy"
);
addItemsToVyplatnePasky(formattedAreaPairReport, ref _pasky);
return _pasky;
}
public static List<VyplatnePasky> DeserializeVyplatnePasky(string sPath)
{
List<VyplatnePasky> _pasky = new List<VyplatnePasky>();
var formattedAreaPairReport =
tryToGetItemsFromDocument
(
sPath,
document=>from d in document.Descendants("FormattedReport".AddNamespace())
select d.Element("FormattedAreaPair".AddNamespace()),
"DeserializeVyplatnePasky"
);
addItemsToVyplatnePasky2(formattedAreaPairReport, _pasky);
return _pasky;
}
private static void addItemsToVyplatnePasky(IEnumerable<XElement> formattedAreaPairReport, ref List<VyplatnePasky> _pasky)
{
if (formattedAreaPairReport.Count() > 0)
{
//check if any sequence contains any matching elements
var GotElements = formattedAreaPairReport.Elements("FormattedAreaPair".AddNamespace()).Where(n=>n.Attribute("Level").Value == "3" && n.Attribute("Type").Value == "Group");
if (GotElements == null)
{
InsertErrorMessage("There are no matching elements under <formattedAreaPairReport>.", "DeserialzieRozuctovanieMzdy");
return;
}
foreach (XElement xElement in GotElements)
{
RozuctovanieMzda_Values(xElement, ref _pasky);
}
}
}
private static void addItemsToVyplatnePasky2(IEnumerable<XElement> formattedAreaPairReport, List<VyplatnePasky> _pasky)
{
foreach (XElement xElement in formattedAreaPairReport.Elements("FormattedAreaPair".AddNamespace()))
{
VyplatnePasky _paska = new VyplatnePasky();
VyplatnePasky_Items(xElement, ref _paska);
_pasky.Add(_paska);
}
}
private static IEnumerable<XElement> tryToGetItemsFromDocument(string sPath, Func<XDocument, IEnumerable<XElement>> query, string name)
{
XDocument document = XDocument.Load(sPath);
var report = query(document);
if (report.Count() == 0)
{
//empty;
InsertErrorMessage("<formattedAreaPairReport> contains no data! No Data to parse from.", name);
}
return report;
}
Essentially, you can keep breaking down the code and then look for duplicate idioms to clean up. Generics may or may not come into play as part of that cleanup. Also, you may want to give the methods better names than I have, since I'm not familiar with the native tongue being used in this code. :-)
Related
I have problem with an editting existing xml file. I was looking for a solution but I can not find solution which I need. Can someone help me please?
Here is my code:
private void referenceToXML(string path)
{
var filePath = path;
var xmlDoc = XDocument.Load(filePath);
var parentElement = new XElement("Items");
var firstNameElement = new XElement("Item");
firstNameElement.SetAttributeValue("name", question.text);
var lastNameElement = new XElement("Cathegory", SM.text);
parentElement.Add(firstNameElement);
firstNameElement.Add(lastNameElement);
var rootElement = xmlDoc.Element("ItemCollection");
rootElement.Add(parentElement);
xmlDoc.Save(path);
}
And here is result of the code:
https://pastebin.com/LKGJER38
But I need this:
https://pastebin.com/RRC75pR8
I will appreciate every help.
Add to the Items Element instead :
private void referenceToXML(string path)
{
var xmlDoc = XDocument.Load(path);
xmlDoc.Element("ItemCollection").Element("Items").Add(
new XElement("Item", new XAttribute("name", question.text), SM.text));
xmlDoc.Save(path);
}
Your problem is that you are unconditionally adding an <Items> element every time you add an <Item> element. Instead, you need to check whether such an element exists, and if so, use it. The following extension method makes that easy:
public static partial class XNodeExtensions
{
public static XElement GetOrAddElement(this XContainer container, XName name)
{
if (container == null || name == null)
throw new ArgumentNullException();
var element = container.Element(name);
if (element == null)
container.Add(element = new XElement(name));
return element;
}
}
Now you can modify your referenceToXML method to use it as follows:
private static void referenceToXML(string path, string questionText, string smText)
{
AddItem(XDocument.Load(path), questionText, smText).Save(path);
}
static XDocument AddItem(XDocument doc, string questionText, string smText)
{
var firstNameElement = new XElement("Item");
firstNameElement.SetAttributeValue("name", questionText);
var lastNameElement = new XElement("Cathegory", smText);
firstNameElement.Add(lastNameElement);
// Get or create the root element ItemCollection
var root = doc.GetOrAddElement("ItemCollection");
// Get or create the Items list
var items = root.GetOrAddElement("Items");
// Add the item
items.Add(firstNameElement);
return doc;
}
Demo fiddle here.
Note I modified your code to make question.text and SM.text arguments instead of class members for clarity and testing purposes.
I want to make a funtion to input value (funName) and check XML file attribute (FunName) then output XML file attribute (isEnable) boolean true or false
How can I modify this code?
My XML file
<itema>
<itemb FunName="ABC" isEnable="true"></itemb>
<itemb FunName="DEF" isEnable="false"></itemb>
</itema>
My Code
public bool FunEnable(string funName , string isEnable)
{
bool result = true;
XmlDocument xDL = new XmlDocument();
xDL.Load("C://XMLFile2.xml"); //Load XML file
XmlNode xSingleNode = xDL.SelectSingleNode("//itemb");
XmlAttributeCollection xAT = xSingleNode.Attributes; //read all Node attribute
for (int i = 0; i < xAT.Count; i++)
{
if (xAT.Item(i).Name == "isEnable")
{
Console.WriteLine(xAT.Item(i).Value); //read we want attribute content
}
}
return result;
}
Thanks a lot
Well you can try this :
public static bool FunEnable(string funNam)
{
bool result = true;
XmlDocument xDL = new XmlDocument();
xDL.Load(#"C:/XMLFile2.xml"); //Load XML file
XmlNodeList nodeList = xDL.SelectNodes("//itemb");
foreach (XmlNode node in nodeList)
{
if (node.Attributes["FunName"].Value.Equals(funNam))
{
result = Convert.ToBoolean(node.Attributes["isEnable"].Value);
break;
}
}
Console.WriteLine("with funName = "+ funNam +" isEnable equal to : " + result);
return result;
}
Output
with funName = ABC isEnable equal to : True
This is fairly trivial using LINQ to XML. You can load the document using XDocument.Load and then get your isEnable value like so:
var result = doc.Descendants("itemb")
.Where(e => (string)e.Attribute("FunName") == "ABC")
.Select(e => (bool)e.Attribute("isEnable"))
.Single();
You can see a working demo here: https://dotnetfiddle.net/MYTOl6
var xDoc = XDocument.Load(path);
bool result = (from itemb in xDoc.Descendants("itemb")
where itemb.Attribute("FunName").Value == funcName
select itemb.Attribute("isEnable").Value == "true")
.FirstOrDefault();
Well, I prefer Linq to XML..
Maybe that one works:
public bool FunEnable(string funName, string isEnable)
{
bool result = true;
XDocument xDL = XDocument.Load("C://XMLFile2.xml");
var xSingleNode = from node in xDL.Descendants("itemb")
where node.Attribute("FunName").Value == funName
select node;
if(xSingleNode.Count() > 0)
{
result = xSingleNode.ElementAt(0).Attribute("isEnable").Value == "true";
//If there is at least one node with the given name, result is set to the first nodes "isEnable"-value
}
return result;
}
The XML bills usually have one Node that is returned and parsed. We have come across an issue where an XML bill had multiple Nodes. Since the code is not set up to handle that, the customer ended up with an incorrect bill.
This is the code I have that goes through the bill list. If it comes back with a node then it parses the information from the xml.
var response = new List<CustomerBill>();
try
{
foreach (GetBillForCAResponse eBillResponse in eBillResponseList)
{
var statementDetailsResponse = GetStatementDetails(
new GetStatementDetailsRequest
{
BatchId = eBillResponse.BatchId,
CustomerAccountId = eBillResponse.CA.ToString("000000000"),
StatementId = eBillResponse.CAS_NUM.ToString("0000")
});
string xmlBill = statementDetailsResponse.StatementAsXML.ToString();
var document = new XmlDocument();
document.LoadXml(xmlBill);
var saDetailedPageNode = XmlBillParser.GetDetailPageSectionBySa(requestSa, xmlBill);
if (saDetailedPageNode == null) continue;
var customerBill = new CustomerBill();
customerBill.IsSurepay = XmlBillParser.GetSurepayFlagFromBill(document);
customerBill.ServiceAddress = XmlBillParser.GetServiceAddress(requestSa, document);
customerBill.monthName = XmlBillParser.GetnillStatementDate(requestSa, xmlBill);
customerBill.EqCurlPlanBal = XmlBillParser.getEqualizerCurrentPlanBalance(document);
customerBill.EqPymntDue = XmlBillParser.getEqualizerPaymentDue(document);
customerBill.Service = GetServiceAccountUsageAndBillingDetail(requestSa, xmlBill, saDetailedPageNode);
response.Add(customerBill);
}
}
catch (Exception ex)
{
trace.Write(new InvalidOperationException(requestSa, ex));
}
return response;
}
Here is the method that checks if there is a node in the XML. I have changed the code so that is returns all the nodes. I had to change the type xmlNode to xmlNodeList because now its returning a collection of nodes. ****This is whats causing all the problems in my code in other places.***
public static xmlNode GetDetailPageSectionBySa(string sa, string statementXml)
{
XmlDocument document = new XmlDocument();
document.LoadXml(statementXml);
string requestSa = sa.PadLeft(9, '0');
string xpath = String.Format("//Para[IRBILGP_SA_SAA_ID_PRINT.SERVICE.ACCOUNT.STATMENT='{0}S{1}']/..", requestSa.Substring(0, 4), requestSa.Substring(4));
return document.SelectNodes(xpath);
//var nodes = document.SelectNodes(xpath);
// if(nodes.Count > 0) return nodes[nodes.Count - 1];
//if(!SaExistInBill(requestSa, statementXml)) return null;
//var node = GetDetailPageSectionByBillPrisminfoIndex(sa, statementXml);
//if (node != null) return node;
//return null;
}
So returning back to where it is called.. im getting an invalid arguement here
customerBill.Service = GetServiceAccountUsageAndBillingDetail(requestSA, xmlBill, saDetailedPageNode); because the parameter saDetailedPageNode is xmlNodeList now when it is expecting of type xmlNode. if i go to the method private static ServiceAddressBillDetail GetServiceAccountUsageAndBillingDetail(string requestSA, string xmlBill, XmlNode detailPageNode) which i added at the end of this code so you could see. If I change the parameter XmlNode detailPageNode to XmlNodeList detailPageNode which I have to do to fix the invalid arguement above, I get that detailPageNode.SelectNodes becomes invalid because xmlNodeList does not have SelectNodes as an extension method. I use this extention method a lot through this method. So I am getting a lot of errors.
var saDetailedPageNode = XmlBillParser.GetDetailPageSectionBySa(requestSa, xmlBill);
if (saDetailedPageNode == null) continue;
var customerBill = new CustomerBill();
customerBill.IsSurepay = XmlBillParser.GetSurepayFlagFromBill(document);
customerBill.ServiceAddress = XmlBillParser.GetServiceAddress(requestSa, document);
customerBill.monthName = XmlBillParser.GetnillStatementDate(requestSa, xmlBill);
customerBill.EqCurlPlanBal = XmlBillParser.getEqualizerCurrentPlanBalance(document);
customerBill.EqPymntDue = XmlBillParser.getEqualizerPaymentDue(document);
customerBill.Service = GetServiceAccountUsageAndBillingDetail(requestSa, xmlBill, saDetailedPageNode);
response.Add(customerBill);
}
}
catch (Exception ex)
{
trace.Write(new InvalidOperationException(requestSa, ex));
}
return response;
}
private static ServiceAddressBillDetail GetServiceAccountUsageAndBillingDetail(string requestSA, string xmlBill, XmlNode detailPageNode)
{
var saBillDetail = new ServiceAddressBillDetail();
saBillDetail.UsageServiceName = requestSA;
var meterReadEndXMLNodes = detailPageNode.SelectNodes("Usage_kWh_b");
if (meterReadEndXMLNodes.Count == 0)
{
meterReadEndXMLNodes = detailPageNode.SelectNodes("Usage_kWh_a");
}
if (meterReadEndXMLNodes.Count == 0)
{
meterReadEndXMLNodes = detailPageNode.SelectNodes("APSElec_kWh_b");
}
if (meterReadEndXMLNodes.Count == 0)
{
meterReadEndXMLNodes = detailPageNode.SelectNodes("APSElec_kWh_a");
}
var demandXMLNodes = detailPageNode.SelectNodes("Usage_kW_Total_Bold");
How can i fix my invalid arguments so that I can use xmlNodeList instead of xmlNode? is there a way to convert or cast? or is there another xml object I can use?
Since I am now returning multiple nodes. I know that I will need to loop through the customerBill portion. How can I do that without creating a new bill for every node? All the nodes in one xml need to be included in one bill.
From the code you've provided, the only part that needs to account for your refactor to XmlNodeList is GetServiceAccountUsageAndBillingDetail(), so you have two options:
Update GetServiceAccountUsageAndBillingDetail() to take in an XmlNodeList parameter instead and aggregate values inside that method to come up with your final ServiceAddressBillDetail object.
Loop through your XmlNode objects in the XmlNodeList and reconcile the different ServiceAddressBillDetail objects yourself.
Without details on what ServiceAddressBillDetail, I can only guess as to which is better, but I'd suggest using the first option.
private static ServiceAddressBillDetail GetServiceAccountUsageAndBillingDetail(
string requestSA,
string xmlBill,
XmlNodeList detailPageNodes)
{
var saBillDetail = new ServiceAddressBillDetail();
saBillDetail.UsageServiceName = requestSA;
foreach(XmlNode detailPageNode in detailPageNodes)
{
var meterReadEndXMLNodes = detailPageNode.SelectNodes("Usage_kWh_b");
if (meterReadEndXMLNodes.Count == 0)
{
meterReadEndXMLNodes = detailPageNode.SelectNodes("Usage_kWh_a");
}
if (meterReadEndXMLNodes.Count == 0)
{
meterReadEndXMLNodes = detailPageNode.SelectNodes("APSElec_kWh_b");
}
if (meterReadEndXMLNodes.Count == 0)
{
meterReadEndXMLNodes = detailPageNode.SelectNodes("APSElec_kWh_a");
}
var demandXMLNodes = detailPageNode.SelectNodes("Usage_kW_Total_Bold");
//Whatever comes next
}
}
Assuming ServiceAddressBillDetail has properties for "usage" you could simply add the appropriate values from each detailPageNode to the saBillDetail object.
It's hard to see what is going on but this may be the problem;
string xpath = String.Format("//Para..
The "//" will search in the entire document, but you probably want to search for descendent elements.
Here's some code to deal with a similar problem;
XmlNodeList staffNodes = resultXML.SelectNodes("//staff");
List<TempStaff> tempStaffs = staffNodes
.Cast<XmlNode>()
.Select(
i =>
new TempStaff()
{
StaffId = i.SelectSingleNode("id").InnerText,
Forename = i.SelectSingleNode("forename").InnerText,
Surname = i.SelectSingleNode("surname").InnerText,
}
).ToList();
My XML looks like
<staff><forename>asdf</forename><surname>ddsf</surname><id>123</id>... </staff>
<staff><forename>asdfas</forename><surname>asffdf</surname><id>456</id>...</staff>
The title says all, but I'll explain more in detail. Here is the thing.
I'm currently developing extra code in a Word VSTO add-in which uses mergefields in a template. The mergefields in the template are filled using data from an external file. What I need is to read the value from a Merge Field but I have totally no clue how to accomplish this. I've been searching for a few days now but none of the articles I read worked for me...
So the question:
How can I get the value from a specific merge field in Word using VSTO?
Mailmerge is quite simple in VSTO, Here is the two magic lines that will do
//Pass in the path of external file
document.MailMerge.OpenDataSource(Name: vm.FilePath.FullName);
document.MailMerge.Destination = WdMailMergeDestination.wdSendToNewDocument;
I found another full example here
This codeblock retrieves all fields in the document
public static List<string> GetFieldsUsedInDocument(Document document)
{
var fields = new List<string>();
foreach (MailMergeField fld in document.MailMerge.Fields)
{
if (fld.Code != null)
{
fields.Add(fld.Code.Text.ToUpper());
}
}
return fields;
}
To get the MergeField names from the list of fields returned above GetFieldsUsedInDocument
public static List<string> GetMergeFields(List<string> allFields)
{
var merges = new List<string>();
foreach (var field in allFields)
{
var isNestedField = false;
foreach (var fieldChar in field)
{
int charCode = fieldChar;
if (charCode == 19 || charCode == 21)
{
isNestedField = true;
break;
}
}
if (!isNestedField)
{
var fieldCode = field;
if (fieldCode.Contains("MERGEFIELD"))
{
var fieldName = fieldCode.Replace("MERGEFIELD", string.Empty).Replace('"', ' ').Trim();
var charsToGet = fieldName.IndexOf(" ");
if (charsToGet < 0)
charsToGet = fieldName.IndexOf(#"\");
charsToGet = charsToGet > 0 ? charsToGet : fieldName.Length;
fieldName = fieldName.Substring(0, charsToGet);
if (!merges.Contains(fieldName))
{
merges.Add(fieldName);
}
}
}
}
return merges;
}
I want to only compare the nodes of two xml files but not the value of the nodes using c#.
If the node format in two files are not same then it should pop-up a message..
I will use Linq to XML :
XDocument doc = XDocument.Parse(data);
var list = doc.DescendantNodes().Where(i => i is XElement);
and then use this to compare :
foreach (var item in list)
{
if (((XElement)item).Name.LocalName == propert.Name)
}
but your final implementation should check number of nodes and other issues
XDocument file1 = XDocument.Load("somefile1.xml");
XDocument file2 = XDocument.Load("somefile2.xml");
if (file1.Nodes().Intersect(file2.Nodes()).Count() > 0)
MessageBox.Show("hey i popped up");
Hope this helps...
Assuming that by "node format" you mean elements & their names, this will walk the element tree and compare the names:
void Main()
{
XElement thing = new XElement("test", new XElement("child") );
XElement otherThing = new XElement("test", new XElement("child") );
var comparer = new XElementComparer();
var areSame = comparer.Equals(thing, otherThing);
Console.WriteLine(areSame);
}
class XElementComparer : IEqualityComparer<XElement>
{
public bool Equals(XElement first, XElement second)
{
if (first.Name != second.Name)
return false;
else if (!first.Elements().SequenceEqual(second.Elements(), this))
return false;
else
return true;
}
public int GetHashCode(XElement element) { return element.GetHashCode(); }
}