How to parse XML to Generic List in c# - c#

I'm calling WebAPI and getting an XmlResult so I want to read this XML result and convert to Generic List.
Here my xml format
<SalesList xmlns="http://schemas.microsoft.com/2003/10/Serialization/"><row Total="103700.0000" Tip="Dry"/><row Total="9341.0000" Tip="Wet"/></SalesList>
I decode my XML and delete first node of XML and I can catch my pure XML but now when I try to fill list I cannot reach Elements("row")
Why? Have any idea?
Here my code
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:8095/ ");
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/xml"));
string link = "api/Values/GeneralReport";
var response = client.GetAsync(link).Result;
string res = "";
using (HttpContent content = response.Content)
{
Task<string> result = content.ReadAsStringAsync();
res = result.Result;
}
XDocument doc = XDocument.Parse(res);
XElement firstChild = doc.Root.Elements().First();
string res1 = firstChild.ToString();
XDocument doc1 = XDocument.Parse(res1);
if (doc1.Root != null)
{
var listxml = (from r in doc1.Root.Elements("row")
select new StationInfo{
ItemType = (string)r.Element("Tip"),
Total = (decimal)r.Element("Total")}).ToList();
}

a) You don't use the xml namespace
b) Tip and Total are attributes, not elements
XNamespace ns = "http://schemas.microsoft.com/2003/10/Serialization/";
var listxml = XDocument.Parse(res)
.Descendants(ns + "row")
.Select(x => new
{
ItemType = (string)x.Attribute("Tip"),
Total = (string)x.Attribute("Total"),
})
.ToList();

Related

Reading XML Response from envelope event notification

In my MakeEnvelope method, I have added an event notification as follows:
var en = new EventNotification
{
Url = "<Listener URL>",
LoggingEnabled = "true",
RequireAcknowledgment = "true",
EnvelopeEvents = new List<EnvelopeEvent>
{
new EnvelopeEvent
{
EnvelopeEventStatusCode = "Completed",
IncludeDocuments = "true"
},
new EnvelopeEvent
{
EnvelopeEventStatusCode = "Delivered",
IncludeDocuments = "false"
},
new EnvelopeEvent
{
EnvelopeEventStatusCode = "Sent",
IncludeDocuments = "false"
}
}
};
envelopeDefinition.EventNotification = en;
I get the following XML response (I removed the Names and Emails, and changed the Guids):
<?xml version="1.0" encoding="utf-8"?><DocuSignEnvelopeInformation xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.docusign.net/API/3.0"><EnvelopeStatus><RecipientStatuses><RecipientStatus><Type>Signer</Type><Email>someEmail#email.com</Email><UserName>SomeUserName</UserName><RoutingOrder>1</RoutingOrder><Sent>2021-04-28T14:14:41.163</Sent><Delivered>2021-04-28T14:14:54.997</Delivered><Signed>2021-04-28T14:14:59.73</Signed><DeclineReason xsi:nil="true" /><Status>Completed</Status><RecipientIPAddress>IP Address</RecipientIPAddress><ClientUserId>The ClientId</ClientUserId><CustomFields /><TabStatuses><TabStatus><TabType>SignHere</TabType><Status>Signed</Status><XPosition>938</XPosition><YPosition>1169</YPosition><TabLabel>Sign Here</TabLabel><TabName>SignHere</TabName><TabValue /><DocumentID>3</DocumentID><PageNumber>1</PageNumber></TabStatus></TabStatuses><AccountStatus>Active</AccountStatus><RecipientId>f571daaf-cd2c-4fge-a72e-d32277929305</RecipientId></RecipientStatus><RecipientStatus><Type>Signer</Type><Email>anotherEmail.Email.com</Email><UserName>Another Name</UserName><RoutingOrder>1</RoutingOrder><Sent>2021-04-28T14:14:41.503</Sent><DeclineReason xsi:nil="true" /><Status>Sent</Status><RecipientIPAddress /><CustomFields /><TabStatuses><TabStatus><TabType>SignHere</TabType><Status>Active</Status><XPosition>196</XPosition><YPosition>1169</YPosition><TabLabel>Sign Here</TabLabel><TabName>SignHere</TabName><TabValue /><DocumentID>3</DocumentID><PageNumber>1</PageNumber></TabStatus></TabStatuses><AccountStatus>Active</AccountStatus><RecipientId>1eb9d346-33ae-4444-b5f1-be30f8bcf041</RecipientId></RecipientStatus></RecipientStatuses><TimeGenerated>2021-04-28T14:15:03.3011225</TimeGenerated><EnvelopeID>3c9281c7-3345-44ac-9c4f-3d39274c49f4</EnvelopeID><Subject>Please sign this document</Subject><UserName>User Name</UserName><Email>Users Email Address Here</Email><Status>Sent</Status><Created>2021-04-28T14:14:40.41</Created><Sent>2021-04-28T14:14:41.557</Sent><ACStatus>Original</ACStatus><ACStatusDate>2021-04-28T14:14:40.41</ACStatusDate><ACHolder>Account Holders name</ACHolder><ACHolderEmail>Account Holders Email</ACHolderEmail><ACHolderLocation>DocuSign</ACHolderLocation><SigningLocation>Online</SigningLocation><SenderIPAddress>Senders IP Address</SenderIPAddress><EnvelopePDFHash /><CustomFields /><AutoNavigation>true</AutoNavigation><EnvelopeIdStamping>true</EnvelopeIdStamping><AuthoritativeCopy>false</AuthoritativeCopy><DocumentStatuses><DocumentStatus><ID>3</ID><Name>PG Document</Name><TemplateName /><Sequence>1</Sequence></DocumentStatus></DocumentStatuses></EnvelopeStatus></DocuSignEnvelopeInformation>
I was having issues parsing the response, so I created a console application to parse the xml string, and using the response xml shown above. The following is the code that I used to parse.
static void Main(string[] args)
{
string strXML = GetXML();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(strXML);
string xpath = "DocuSignEnvelopeInformation/EnvelopeStatus";
var nodes = xmlDoc.SelectNodes(xpath);
foreach (XmlNode childrenNode in nodes)
{
Console.WriteLine(childrenNode.SelectSingleNode("//status").Value);
}
Console.Read();
}
nodes is always empty.
What am I missing here?
Thank you in advance.
I was able to get it to work by using the namespace it was using.
var ns = new XmlNamespaceManager(xmlDoc.NameTable);
ns.AddNamespace("docusign", "http://www.docusign.net/API/3.0");
var envelopeId = xmlDoc.SelectSingleNode("//docusign:EnvelopeID", ns);
var status = xmlDoc.SelectSingleNode("//docusign:Status", ns);
I changed the method and I am able to get the values:
Request.InputStream.Position = 0;
var xmlDoc = new XmlDocument();
xmlDoc.Load(Request.InputStream);
var ns = new XmlNamespaceManager(xmlDoc.NameTable);
ns.AddNamespace("docusign", "http://www.docusign.net/API/3.0");
var envelopeId = xmlDoc.SelectSingleNode("//docusign:EnvelopeID", ns);
var status = xmlDoc.SelectSingleNode("//docusign:EnvelopeStatus/docusign:Status", ns);
if (envelopeId == null || String.IsNullOrEmpty(envelopeId.InnerText) || status == null || string.IsNullOrEmpty(status.InnerText)) return;
if (status.InnerText.ToLower() == "completed")
{
// Update the Document Status
var extra = _uow.JobExtraRepository.GetByEnvelopeId(envelopeId.InnerText);
extra.StatusId = (int)ExtraJobStatus.Completed;
Save();
var docBytes = xmlDoc.SelectSingleNode("//docusign:docBytes", ns);
}

Parsing site using HtmlAgilityPack in C#

For example, I have link https://shikimori.one/animes/38256-magia-record-mahou-shoujo-madoka-magica-gaiden-tv/art
I wanna get from there list of div classes by name "container packery" using HtmlAgilityPack in C#. (in order to download images from all the links) But this part
var httpClient = new HttpClient();
var html = await httpClient.GetStringAsync(link);
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(link);
return me html code from this page https://shikimori.one/animes/38256-magia-record-mahou-shoujo-madoka-magica-gaiden-tv as i understood. So, I can`t parse anything from "/art". That because next part of code just returns null.
var links = htmlDocument.DocumentNode.Descendants("div")
.Where(node => node.GetAttributeValue("class", "")
.Equals("menu-slide-outer x199")).ToList();
What am I missing?
Final code:
class Program
{
static List<string> sources = new List<string>();
[STAThread]
static void Main(string[] args)
{
var link = "https://shikimori.one/animes/1577-taiho-shichau-zo/art";
var web = new HtmlWeb();
web.BrowserTimeout = TimeSpan.FromTicks(0);
var htmlDocument = web.LoadFromBrowser(link);
var divlink = htmlDocument.DocumentNode.Descendants("div")
.Where(node => node.GetAttributeValue("class", "")
.Equals("container packery")).ToList();
var alink = htmlDocument.DocumentNode.Descendants("a")
.Where(node => node.GetAttributeValue("class", "")
.Equals("b-image")).ToList();
foreach(var a in alink)
{
sources.Add(a.GetAttributeValue("href", string.Empty));
}
Console.WriteLine("done");
Console.ReadKey();
}
With HttpClient:
string html = string.Empty;
using (var httpClient = new HttpClient())
{
html = await httpClient.GetStringAsync(link);
}
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(html); // LoadHtml expects the page source, not the URL.
Or, a simple way to parse HTML code from a URL:
var web = new HtmlAgilityPack.HtmlWeb();
var htmlDocument = await web.LoadFromWebAsync(link);
Or, load dynamic (i.e. Ajax) content:
var web = new HtmlAgilityPack.HtmlWeb();
var htmlDocument = web.LoadFromBrowser(link);

Nested XML from list with needed data on multiple lines

I need to format an XML to a given hierarchy from a list (List<>). The list contains, for the banking information, data spread across multiple rows as shown in the image.
The XML output needs to be formatted like this:
<ROOT>
<DocumentElement>
<Supplier>
<Company>STV</Company>
<Code>000199</Code>
<Name>TrafTrans</Name>
<BankAccounts>
<SupplierBankAccount>
<Bban>220-012510-63</Bban>
<Name>B1</Name>
</SupplierBankAccount>
<SupplierBankAccount>
<Bban>RUIL</Bban>
<Name>RUIL</Name>
</SupplierBankAccount>
</BankAccounts>
<SupplierAddresses>
<SupplierAddress>
<Type>PostalAddress</Type>
<Name>Loc TrafTrans</Name>
<IsDefault>true</IsDefault>
<AddressParts>
<SupplierAddressPart>
<AddressPartKey>STREET_NAME</AddressPartKey>
<AddressPartText>Somewhere</AddressPartText>
</SupplierAddressPart>
<SupplierAddressPart>
<AddressPartKey>COUNTRY</AddressPartKey>
<AddressPartText>SPAIN</AddressPartText>
</SupplierAddressPart>
</AddressParts>
</SupplierAddress>
</SupplierAddresses>
</Supplier>
</DocumentElement>
</ROOT>
I already have a method that converts a list to an XML and returns a string. But the problem is that this only formats one item from the list and there could be additional info in the following items.
public static string SuppliersToXML(List<SupplierItem> supplier)
{
CultureInfo ci = new CultureInfo("en-US");
XmlDocument doc = new XmlDocument();
var root = doc.CreateElement("ROOT");
var rootNode = doc.AppendChild(root);
var docElem = doc.CreateElement("DocumentElement");
var docElemNode = rootNode.AppendChild(docElem);
foreach (var item in supplier)
{
var supplierElem = doc.CreateElement("Supplier");
var companyElem = (XmlNode)doc.CreateElement("Company");
companyElem.InnerText = item.Company.ToString();
//more code...
supplierElem.AppendChild(companyElem);
//more code...
}
return doc.OuterXml;
}
I have found the answer myself, it may not be the prettiest code ever written. But it does the job.
public static string SuppliersToXML(List<SupplierItem> supplier)
{
//A distinct select is needed because bank info can be on multiple lines. So first a list is generated with correct info except for bank information
List<SupplierItem> distinctsupplier = supplier
.GroupBy(p => p.Code)
.Select(g => g.First())
.ToList();
CultureInfo ci = new CultureInfo("en-US");
XmlDocument doc = new XmlDocument();
var root = doc.CreateElement("ROOT");
var rootNode = doc.AppendChild(root);
var docElem = doc.CreateElement("DocumentElement");
var docElemNode = rootNode.AppendChild(docElem);
foreach (var item in distinctsupplier)
{
var supplierElem = doc.CreateElement("Supplier");
var companyElem = (XmlNode)doc.CreateElement("Company");
companyElem.InnerText = item.Company.ToString();
var codeElem = (XmlNode)doc.CreateElement("Code");
codeElem.InnerText = item.Code.ToString();
var codeName = (XmlNode)doc.CreateElement("Name");
codeName.InnerText = item.Name.ToString();
//supplieridentifier part
var supplierIdsElem = doc.CreateElement("SupplierIdentifiers");
var supplierIdElem = doc.CreateElement("SupplierIdentifier");
var supplierIdValueElem = (XmlNode)doc.CreateElement("SupplierIDValue");
supplierIdValueElem.InnerText = item.SupplierIDValue;
//supplieraddress part
var supplierAddressesElem = doc.CreateElement("SupplierAddresses");
var supplierAddressElem = doc.CreateElement("SupplierAddress");
var supplierTypeValueElem = (XmlNode)doc.CreateElement("Type");
supplierTypeValueElem.InnerText = item.Type;
var supplierNameValueElem = (XmlNode)doc.CreateElement("Name");
supplierNameValueElem.InnerText = item.AddressName;
var supplierDefaultValueElem = (XmlNode)doc.CreateElement("IsDefault");
supplierDefaultValueElem.InnerText = item.AddressIsDefault;
//address part
var AddressPartElem = doc.CreateElement("AddressParts");
var supplierAddressPartsElem = doc.CreateElement("SupplierAddressPart");
//Street
var AddressPartElemStreetKeyElem = (XmlNode)doc.CreateElement("AddressPartKey");
AddressPartElemStreetKeyElem.InnerText = item.AddressStreetKey;
var AddressPartElemStreetValueElem = (XmlNode)doc.CreateElement("AddressPartText");
AddressPartElemStreetValueElem.InnerText = item.AddressStreetText;
//Country
var AddressPartElemCountryKeyElem = (XmlNode)doc.CreateElement("AddressPartKey");
AddressPartElemCountryKeyElem.InnerText = item.AddressCountryKey;
var AddressPartElemCountryValueElem = (XmlNode)doc.CreateElement("AddressPartText");
AddressPartElemCountryValueElem.InnerText = item.AddressCountryText;
//add elements to supplierelem
supplierElem.AppendChild(companyElem);
supplierElem.AppendChild(codeElem);
supplierElem.AppendChild(codeName);
//bankaccounts part
var bankAccountElem = doc.CreateElement("BankAccounts");
//select all rows that contain multiple lines, so the bank info can be extracted, for a certain supplier
var bankInformation = supplier.Where(s => s.Code == item.Code).ToList();
foreach (var bankitem in bankInformation)
{
if (item.Code == bankitem.Code)
{
var bankAccountSupplElem = doc.CreateElement("SupplierBankAccount");
var bankAccountBbanElem = doc.CreateElement("Bban");
var bankAccountBbanValueElem = (XmlNode)doc.CreateElement("Bban");
bankAccountBbanValueElem.InnerText = bankitem.Bban;
var bankAccountNameElem = doc.CreateElement("Name");
var bankAccountNameValueElem = (XmlNode)doc.CreateElement("Name");
bankAccountNameValueElem.InnerText = bankitem.BankName;
var bankAccountElemNode = supplierElem.AppendChild(bankAccountElem);
var bankAccountSupplElemNode = bankAccountElemNode.AppendChild(bankAccountSupplElem);
bankAccountSupplElemNode.AppendChild(bankAccountBbanValueElem);
bankAccountSupplElemNode.AppendChild(bankAccountNameValueElem);
}
}
//add address elements to supplierelem
var supplierAddressesElemNode = supplierElem.AppendChild(supplierAddressesElem);
var supplierAddressElemNode = supplierAddressesElemNode.AppendChild(supplierAddressElem);
supplierAddressElemNode.AppendChild(supplierTypeValueElem);
supplierAddressElemNode.AppendChild(supplierNameValueElem);
supplierAddressElemNode.AppendChild(supplierDefaultValueElem);
//add addressparts to supplieraddressesnode
//Street
var AddressPartElemNode = supplierAddressElemNode.AppendChild(AddressPartElem);
var supplierAddressPartsElemNode = AddressPartElemNode.AppendChild(supplierAddressPartsElem);
supplierAddressPartsElemNode.AppendChild(AddressPartElemStreetKeyElem);
supplierAddressPartsElemNode.AppendChild(AddressPartElemStreetValueElem);
//Country (first reinitialize supplieraddresspart)
supplierAddressPartsElem = doc.CreateElement("SupplierAddressPart");
var supplierAddressPartsCountryElemNode = AddressPartElemNode.AppendChild(supplierAddressPartsElem);
supplierAddressPartsCountryElemNode.AppendChild(AddressPartElemCountryKeyElem);
supplierAddressPartsCountryElemNode.AppendChild(AddressPartElemCountryValueElem);
//add all supplierelements to docelemnode
docElemNode.AppendChild(supplierElem);
}
return doc.OuterXml;
}

Html Agility Pack get contents from table

I need to get the location, address, and phone number from "http://anytimefitness.com/find-gym/list/AL" So far I have this...
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.OptionFixNestedTags = true;
htmlDoc.LoadHtml(stateURLs[0].ToString());
var BlankNode =
htmlDoc.DocumentNode.SelectNodes("/div[#class='segmentwhite']/table[#style='width: 100%;']//tr[#class='']");
var GrayNode =
htmlDoc.DocumentNode.SelectNodes("/div[#class='segmentwhite']/table[#style='width: 100%;']//tr[#class='gray_bk']");
I have looked around stackoverflow for a while but none of the present post regarding htmlagilitypack has really helped. I have also have been using http://www.w3schools.com/xpath/xpath_syntax.asp
Since <div> you're after is not direct child of root node, you need to use // instead of /. Then you can combine XPath for BlankNode and GrayNode using or operator, for example :
var htmlweb = new HtmlWeb();
HtmlDocument htmlDoc = htmlweb.Load("http://anytimefitness.com/find-gym/list/AL");
htmlDoc.OptionFixNestedTags = true;
var AllNode =
htmlDoc.DocumentNode.SelectNodes("//div[#class='segmentwhite']/table//tr[#class='' or #class='gray_bk']");
foreach (HtmlNode node in AllNode)
{
var location = node.SelectSingleNode("./td[2]").InnerText;
var address = node.SelectSingleNode("./td[3]").InnerText;
var phone = node.SelectSingleNode("./td[4]").InnerText;
//do something with above informations
}
Here's an example I tested in LinqPad.
string url = #"http://anytimefitness.com/find-gym/list/AL";
var client = new System.Net.WebClient();
var data = client.DownloadData(url);
var html = Encoding.UTF8.GetString(data);
var htmlDoc = new HtmlAgilityPack.HtmlDocument();
htmlDoc.OptionFixNestedTags = true;
htmlDoc.LoadHtml(html);
var gyms = htmlDoc.DocumentNode.SelectNodes("//tbody/tr[#class='' or #class='gray_bk']");
foreach (var gym in gyms) {
var city = gym.SelectSingleNode("./td[2]").InnerText;
var address = gym.SelectSingleNode("./td[3]").InnerText;
var phone = gym.SelectSingleNode("./td[4]").InnerText;
}
Since the HtmlAgilityPack also supports Linq, you could also do something like:
string [] classes = {"", "gray_bk"};
var gyms = htmlDoc
.DocumentNode
.Descendants("tr")
.Where(t => classes.Contains(t.Attributes["class"].Value))
.ToList();
gyms.ForEach(gym => {
var city = gym.SelectSingleNode("./td[2]").InnerText;
var address = gym.SelectSingleNode("./td[3]").InnerText;
var phone = gym.SelectSingleNode("./td[4]").InnerText;
});

extracting values of text from html source file

in this code var TempTxt holds An Html Body Content
as string
how can i extract element <table> or <td> inner text/ html using lambada syntax ?
public string ExtractPageValue(IWebDriver DDriver, string url="")
{
if(string.IsNullOrEmpty(url))
url = #"http://www.boi.org.il/he/Markets/ExchangeRates/Pages/Default.aspx";
var service = InternetExplorerDriverService.CreateDefaultService(directory);
service.LogFile = directory + #"\seleniumlog.txt";
service.LoggingLevel = InternetExplorerDriverLogLevel.Trace;
var options = new InternetExplorerOptions();
options.IntroduceInstabilityByIgnoringProtectedModeSettings = true;
DDriver = new InternetExplorerDriver(service, options, TimeSpan.FromSeconds(60));
DDriver.Navigate().GoToUrl(url);
var TempTxt = DDriver.PageSource;
return "";//Math.Round(Convert.ToDouble( TempTxt.Split(' ')[10]),2).ToString();
}
If you are open to try HtmlAgilityPack
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);
var table = doc.DocumentNode.SelectNodes("//table/tr")
.Select(tr => tr.Elements("td").Select(td => td.InnerText).ToList())
.ToList();

Categories