I'm trying to learn C# (and Linq-to-Xml) for handling of XML files, yet having some troubles.
I can get the elements and values, but my output is missing the information I need for logic decisions (id and state attributes in the transaction and target elements).
I think it has to do with the Descendants, but not sure how to grab them.
A little nudge in the right direction?
My XML File
<?xml version="1.0"?>
<xliff version="1.2">
<file source-language="en-US" datatype="plaintext" category="framework">
<body>
<transaction approved="no" id="1">
<source xml:lang="en-US">Product Family</source>
<target state="translated" xml:lang="en-US">Product Family</target>
</transaction>
</body>
</file>
</xliff>
C# Code
private void btnOpen_Click(object sender, EventArgs e)
{
// Show the dialog, using defaults, and get result
OpenFileDialog ofdResult = new OpenFileDialog();
if (ofdResult.ShowDialog() == DialogResult.OK)
{
try
{
if (ofdResult.OpenFile() != null)
{
XDocument xmlFile = XDocument.Load(ofdResult.FileName);
// print elements recursively
PrintElement(xmlFile.Root);
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk." + ex.Message);
}
}
}
// display an element (and its children, if any) in the TextBox
private void PrintElement( XElement element )
{
// get element name without namespace
string name = element.Name.LocalName;
// display the element's name within its tag
tbxOutput.AppendText( '<' + name + ">\n" );
// check for child elements and print value if none contained
if ( element.HasElements )
{
// print all child elements at the next indentation level
foreach ( var child in element.Elements() )
// Display all attributes
PrintElement(child);
} // end if
else
{
// display the text inside this element
tbxOutput.AppendText( element.Value.Trim() + '\n' );
} // end else
// display end tag
tbxOutput.AppendText( "</" + name + ">\n" );
} // end method PrintElement
My Output...
<xliff>
<file>
<body>
<trans-unit>
<source>
Product Family
</source>
<target>
Product Family
</target>
</trans-unit>
</body>
</file>
</xliff>
If you need attributes why don't you read attributes? XElement has a boolean property HasAttributes and you can loop over all attributes in the same way like you do it for Elements(). Use Attributes() method for that. Please refer to the MSDN article 1
You can write a method like
string GetAttributesString(XElement element)
{
if(element == null || !element.HasAttributes)
return string.Empty;
string format = " {0}={1}";
StringBuider result = new StringBuilder();
foreach(var attribute in element.Attributes())
{
result.AppendFormat(format, attribute.Name, attribute.Value);
}
return result.ToString();
}
And use it in your PrintElement method
tbxOutput.AppendText("<" + name + GetAttributesString(element) + ">\n");
Related
I am making a Application on Windows Phone 8. The bit I am struggling with is getting the XML parsed.
Here the XML File:
<ArrayOfThemeParkList xmlns="http://schemas.datacontract.org/2004/07/WCFServiceWebRole1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ThemeParkList>
<id>1</id>
<name>Alton Towers</name>
</ThemeParkList>
<ThemeParkList>
<id>2</id>
<name>Thorpe Park</name>
</ThemeParkList>
<ThemeParkList>
<id>3</id>
<name>Chessington World Of Adventures</name>
</ThemeParkList>
<ThemeParkList>
<id>4</id>
<name>Blackpool Pleasure beach</name>
</ThemeParkList>
</ArrayOfThemeParkList>
And the c# code that tries to parse it is:
void ThemeParksNames_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
//Now need to get that data and display it on the page
//check for errors
if (e.Error == null)
{
//No errors have been passed now need to take this file and parse it
//Its in XML format
XDocument xdox = XDocument.Parse(e.Result);
//need a list for them to be put in to
List<ThemeParksClass> ParkList = new List<ThemeParksClass>();
//Now need to get every element and add it to the list
foreach (XElement item in xdox.Root.Elements("ThemeParkList"))
{
ThemeParksClass content = new ThemeParksClass();
content.ID = Convert.ToInt32(item.Element("id").Value);
content.ThemeParkName = item.Element("name").Value.ToString();
ParkList.Add(content);
}
parkList.ItemsSource = ParkList.ToList();
}
else
{
//There an Error
}
}
Now when using Break points it get to the for each loop but does not run at all just moves on. I am guessing i have the for each loop set wrong.
Many Thanks.
Your ThemeParkList elements are in a namespace http://schemas.datacontract.org/2004/07/WCFServiceWebRole1 - you'll need to adjust accordingly:
XNamespace ns = "http://schemas.datacontract.org/2004/07/WCFServiceWebRole1";
foreach (XElement item in xdox.Descendants(ns + "ThemeParkList"))
You'll need to handle the other elements in the same way.
I have a 6GB XML file and I'm using XmlReader to loop through the file. The file's huge but there's nothing I can do about that. I use LINQ, but the size doesn't let me use XDocument as I get an OutOfMemory error.
I'm using XmlReader to loop through the whole file and extract what I need. I'm including a sample XML file.
Essentially, this is what I do:
Find tag Container. If found, then retrieve attribute "ID".
If "ID" begins with LOCAL, then this is what I'll be reading.
Reader loop until I find tag Family with value CELL_FD
When found, loop the reader.read() until I find tag IMPORTANT_VALUE.
Once found, read value of IMPORTANT_VALUE.
I'm done with this container, so continue looping until I find the next Container (that's where the break comes in).
This is the simplified version of how I've been reading the file and finding the relevant values.
while (myReader.Read())
{
if ((myReader.Name == "CONTAINER"))
{
if (myReader.HasAttributes)
{
string Attribute = myReader.GetAttribute("id");
if (Attribute.IndexOf("LOCAL_") >= 0)
{
while (myReader.Read())
{
if (myReader.Name == "FAMILY")
{
myReader.Read();//read value
string Family = myReader.Value;
if (Family == "CELL_FDD")
{
while (myReader.Read())
{
if ((myReader.Name == "IMPORTANT_VALUE"))
{
myReader.Read();
string Counter = myReader.Value;
Console.WriteLine(Attribute + " (found: " + Counter + ")");
break;
}
}
}
}
}
}
}
}
}
And this is the XML:
<es:esFD xmlns:es="File.xsd">
<vs:vsFD xmlns:vs="OTHER_FILE.xsd">
<CONTAINER id="LOCAL_CONTAINER1">
<ATTRIBUTES>
<FAMILY>CELL_FDD</FAMILY>
<CELL_FDD>
<VAL1>1.1.2.3</VAL1>
<VAL2>JSMITH</VAL2>
<VAL3>320</VAL3>
<IMPORTANT_VALUE>VERY</IMPORTANT_VALUE>
<VAL4>320</VAL4>
</CELL_FDD>
<FAMILY>BLAH</FAMILY>
<BLAH>
<VAL1>1.4.43.3</VAL1>
<VAL2>NA</VAL2>
<VAL3>349</VAL3>
<IMPORTANT_VALUE>NA</IMPORTANT_VALUE>
<VAL4>43</VAL4>
<VAL5>00</VAL5>
<VAL6>12</VAL6>
</BLAH>
</ATTRIBUTES>
</CONTAINER>
<CONTAINER id="FOREIGN_ELEMENT1">
<ATTRIBUTES>
<FAMILY>CELL_FDD</FAMILY>
<CELL_FDD>
<VAL1>1.1.2.3</VAL1>
<VAL2>JSMITH</VAL2>
<VAL3>320</VAL3>
<IMPORTANT_VALUE>VERY</IMPORTANT_VALUE>
<VAL4>320</VAL4>
</CELL_FDD>
<FAMILY>BLAH</FAMILY>
<BLAH>
<VAL1>1.4.43.3</VAL1>
<VAL2>NA</VAL2>
<VAL3>349</VAL3>
<IMPORTANT_VALUE>NA</IMPORTANT_VALUE>
<VAL4>43</VAL4>
<VAL5>00</VAL5>
<VAL6>12</VAL6>
</BLAH>
</ATTRIBUTES>
</CONTAINER>
</vs:vsFD>
</es:esFD>
How can I break from the most inner loop so that I can reach the top-most loop?
Using separate methods should make it easier to control your loops:
while (myReader.Read())
{
if ((myReader.Name == "CONTAINER"))
{
ProcessContainerElement(myReader);
}
}
In the ProcessContainerElement method, you can return when you determine that you need to start looking for the next CONTAINER element.
private void ProcessContainerElement(XmlReader myReader)
{
while (whatever)
{
if ((myReader.Name == "IMPORTANT_VALUE"))
{
myReader.Read();
string Counter = myReader.Value;
Console.WriteLine(Attribute + " (found: " + Counter + ")");
return;
}
}
}
You can read with XmlReader and each node put to XmlDocument.
Something like this, not tested:
bool notFound = false;
notFound |= !reader.ReadToDescendant("root");
notFound |= !reader.ReadToDescendant("CONTAINER");
if (notFound)
Throw new Exception("[Не удаётся найти \"/root/CONTAINER\"]");
do
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(reader.ReadOuterXml());
XmlNode container = doc.DocumentElement;
// do your work with container
}
while (reader.ReadToNextSibling("CONTAINER"));
reader.Close();
Using svick's comment, I ended up combining LINQ to XML. Once I reached the correct element and checked that the attribute had the correct ID, I dumped it to XElement.Load.
I am populating textboxes with information from a particular node based on it's ConfirmNum. Then when all info is updated, I am saving information (by a submit button) back to the particular node.
However, upon saving, every node in the XML file that was empty drops down to another line.
Example XML Before Save:
<OnlineBanking>
<Transactions>
<Txn>
<Login></Login>
<UserName>userName</UserName>
<CustomerName>CustomerName</CustomerName>
<ConfirmNum>1234</ConfirmNum>
</Txn>
</Transactions>
</OnlineBanking>
My code (below) will save the information for that node, based on the Page.aspx?CID=1234 number. However, every node in the entire XML file that was blank, will now have a line break in it. Not just the Txn we just edited, but all.
Here is my code:
protected void btnSubmit_Click(object sender, EventArgs e)
{
XmlDocument item = new XmlDocument();
item.Load(xmlFileName);
if (CID != "")
{
XmlNode xlist = item.SelectSingleNode("OnlineBanking/Transactions/Txn[ConfirmNum=" + CID + "]");
if (xlist != null)
{
xlist.ChildNodes.Item(0).InnerText = tbLogin.Text;
xlist.ChildNodes.Item(1).InnerText = tbUserName.Text;
xlist.ChildNodes.Item(2).InnerText = tbCustomerName.Text;
item.Save(xmlFileName);
}
}
}
Example XML After Save:
<OnlineBanking>
<Transactions>
<Txn>
<Login>
</Login>
<UserName>userName</UserName>
<CustomerName>CustomerName</CustomerName>
<ConfirmNum>1234</ConfirmNum>
</Txn>
</Transactions>
</OnlineBanking>
Note how the <login> is on another line than </login>. This is what I am talking about. Hope someone can see clearly what I am not doing.
Try setting the PreserveWhitespace property to True and see if it will stop inserting line breaks upon calling Save:
XmlDocument item = new XmlDocument();
item.PreserveWhitespace = true;
item.Load(xmlFileName);
One compromise is:
if(string.IsNullOrWhiteSpace(tbLogin.Text))
xlist.ChildNodes.Item(0).IsEmpty = true;
else
xlist.ChildNodes.Item(0).InnerText = tbLogin.Text;
This will give you:
<Login />
The first half of my function doesn't use htmlagilitypack and I know it functions as I want. however the function finishes without doing anything with the second half and doesnt return an errors. Please help
void classListHtml()
{
HtmlElementCollection elements = browser.Document.GetElementsByTagName("tr");
html = "<table>";
int i = 0;
foreach (HtmlElement element in elements)
{
if (element.InnerHtml.Contains("Marking Period 2") && i != 0)//will be changed to current assignment reports later
{
html += "" + element.OuterHtml;
}
else if (i == 0)
{
i++;
continue;
}
else
continue;
}
html += "" + "</table>";
myDocumentText(html);
//---------THIS IS WHERE IT STOPS DOING WHAT I WANT-----------
//removing color and other attributes
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.Load(html);
HtmlNodeCollection nodeCollection = doc.DocumentNode.SelectNodes("//tr");//xpath expression for all row nodes
string[] blackListAttributes={"width", "valign","bgcolor","align","class"};
foreach(HtmlNode node in nodeCollection)//for each row node
{
HtmlAttributeCollection rows = node.Attributes;// the attributes of each row node
foreach (HtmlAttribute attribute in rows)//for each attribute
{
if (blackListAttributes.Contains(attribute.Name))//if its attribute name is in the blacklist, remove it.
attribute.Remove();
}
}
html = doc.ToString();
myDocumentText(html);//updating browser with new html
}
HtmlDocument.ToString() does not send back the text, unless you changed the original code, maybe you're looking for HtmlDocument.DocumentNode.OuterXml or Document.Save( ... text ...)?
myDocumentText(html);
What does this method do?
My assumption is that you have an exception being thrown somewhere within this method, and it's either being swallowed, or your debug environment is set to not break on user thrown exceptions.
Can you post the code within this method?
I've written a small utility that allows me to change a simple AppSetting for another application's App.config file, and then save the changes:
//save a backup copy first.
var cfg = ConfigurationManager.OpenExeConfiguration(pathToExeFile);
cfg.SaveAs(cfg.FilePath + "." + DateTime.Now.ToFileTime() + ".bak");
//reopen the original config again and update it.
cfg = ConfigurationManager.OpenExeConfiguration(pathToExeFile);
var setting = cfg.AppSettings.Settings[keyName];
setting.Value = newValue;
//save the changed configuration.
cfg.Save(ConfigurationSaveMode.Full);
This works well, except for one side effect. The newly saved .config file loses all the original XML comments, but only within the AppSettings area. Is it possible to to retain XML comments from the original configuration file AppSettings area?
Here's a pastebin of the full source if you'd like to quickly compile and run it.
I jumped into Reflector.Net and looked at the decompiled source for this class. The short answer is no, it will not retain the comments. The way Microsoft wrote the class is to generate an XML document from the properties on the configuration class. Since the comments don't show up in the configuration class, they don't make it back into the XML.
And what makes this worse is that Microsoft sealed all of these classes so you can't derive a new class and insert your own implementation. Your only option is to move the comments outside of the AppSettings section or use XmlDocument or XDocument classes to parse the config files instead.
Sorry. This is an edge case that Microsoft just didn't plan for.
Here is a sample function that you could use to save the comments. It allows you to edit one key/value pair at a time. I've also added some stuff to format the file nicely based on the way I commonly use the files (You could easily remove that if you want). I hope this might help someone else in the future.
public static bool setConfigValue(Configuration config, string key, string val, out string errorMsg) {
try {
errorMsg = null;
string filename = config.FilePath;
//Load the config file as an XDocument
XDocument document = XDocument.Load(filename, LoadOptions.PreserveWhitespace);
if(document.Root == null) {
errorMsg = "Document was null for XDocument load.";
return false;
}
XElement appSettings = document.Root.Element("appSettings");
if(appSettings == null) {
appSettings = new XElement("appSettings");
document.Root.Add(appSettings);
}
XElement appSetting = appSettings.Elements("add").FirstOrDefault(x => x.Attribute("key").Value == key);
if (appSetting == null) {
//Create the new appSetting
appSettings.Add(new XElement("add", new XAttribute("key", key), new XAttribute("value", val)));
}
else {
//Update the current appSetting
appSetting.Attribute("value").Value = val;
}
//Format the appSetting section
XNode lastElement = null;
foreach(var elm in appSettings.DescendantNodes()) {
if(elm.NodeType == System.Xml.XmlNodeType.Text) {
if(lastElement?.NodeType == System.Xml.XmlNodeType.Element && elm.NextNode?.NodeType == System.Xml.XmlNodeType.Comment) {
//Any time the last node was an element and the next is a comment add two new lines.
((XText)elm).Value = "\n\n\t\t";
}
else {
((XText)elm).Value = "\n\t\t";
}
}
lastElement = elm;
}
//Make sure the end tag for appSettings is on a new line.
var lastNode = appSettings.DescendantNodes().Last();
if (lastNode.NodeType == System.Xml.XmlNodeType.Text) {
((XText)lastNode).Value = "\n\t";
}
else {
appSettings.Add(new XText("\n\t"));
}
//Save the changes to the config file.
document.Save(filename, SaveOptions.DisableFormatting);
return true;
}
catch (Exception ex) {
errorMsg = "There was an exception while trying to update the config value for '" + key + "' with value '" + val + "' : " + ex.ToString();
return false;
}
}
If comments are critical, it might just be that your only option is to read & save the file manually (via XmlDocument or the new Linq-related API). If however those comments are not critical, I would either let them go or maybe consider embedding them as (albeit redundant) data elements.