This may be a duplicate, but I haven't been able to find a solution.
I have a ComboBox that is populated from an xml file. When I select an item in the ComboBox I want to populate a listBox with the "Special" elements from that same xml file.
xml:
<ClassID Barbarian="Barbarian">
<Name>Barbarian</Name>
<Alignment>NG CG NN CN NE CE</Alignment>
<Special>Fast Movement</Special>
<Special>Rage Power</Special>
</ClassID>
I can get the listBox to populate with ALL the "Special" elements, but I only want the ones for the specified class name (i.e. Barbarian attribute).
Code to populate listBox with ALL the "Special" elements, regardless of what ComboBox item is selected:
//Gets Specials from xml
public static List<string> GetSpecialsFromXml(string filename, string tagname)
{
List<string> Specials = new List<string>();
XmlDocument doc = new XmlDocument();
doc.Load(filename);
XmlNodeList specials = doc.GetElementsByTagName(tagname);
foreach(XmlNode special in specials)
{
Specials.Add(special.InnerText);
}
return Specials;
}
//Loads feats into feat list
public void LoadFeats()
{
List<string> Special = GetSpecialsFromXml(Gamepath, "Special");
FeatBox.Items.AddRange(Special.ToArray());
}
LoadFeats is called with the ComboBox SelectedItemChanged event.
I am still a noob when working with xml files.
Edit: TL;DR solution:
Changed public static List<string> GetSpecialsFromXml(string filename, string tagname) to public List<string> GetSpecialsFromXml(string filename, string tagname).
Added string Combo = ComboBox.Text; and string strXPath = $"ClassID[{Combo}='{Combo}']/Special"
Replaced XmlNodeList specials = doc.GetElementsByTagName(tagname);
with XmlNodeList specials = doc.SelectNodes( strXPath );
Instead of looking for "tagname", with doc.GetElemnetByTagName, use XPath in a SelectNodes() function call. XPath is to XML what SQL is to a database, and is very powerful.
Use an XPath statement like in a "SelectNodes()" call
string strXPath= "//ClassID[#Barbarian='Barbarian']/Special"
Replace your XmlNodeList specials = doc.GetElementsByTagName(tagname); with this
XmlNodeList specials = doc.SelectNodes( strXPath );
Then iterate away like you're already doing.
Good Luck with your project
Related
I'm delving into the world of XmlDocument building and thought I'd try to re-build (at least, in part) the Desktop tree given by Microsoft's program UISpy.
So far I am able to grab a child of the desktop and write that to a XML document, and then grab each child of that and write those to an XML document.
So far the code looks like this...
using System.Windows.Automation;
using System.Xml;
namespace MyTestApplication
{
internal class TestXmlStuff
{
public static void Main(string[] args)
{
XmlDocument xDocument = new XmlDocument();
AutomationElement rootElement = AutomationElement.RootElement;
TreeWalker treeWalker = TreeWalker.ContentViewWalker;
XmlNode rootXmlElement = xDocument.AppendChild(xDocument.CreateElement("Desktop"));
AutomationElement autoElement = rootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "GitHub"));
string name = autoElement.Current.Name;
while (autoElement != null)
{
string lct = autoElement.Current.LocalizedControlType.Replace(" ", "");
lct = (lct.Equals("") ? "Cusotm" : lct);
XmlElement temp = (XmlElement)rootXmlElement.AppendChild(xDocument.CreateElement(lct));
//temp.InnerText = lct;
string outerXML = temp.OuterXml;
rootXmlElement = temp;
autoElement = treeWalker.GetNextSibling(autoElement);
}
}
}
}
...and the resulting XML file...
Now, when I add a line to change the InnerText Property of each XML element, like temp.InnerText = lct I get an oddly formated XML file.
What I expected from this was that each InnerText would be on the same line as the start and end tags of the XML element, but instead all but the last element's InnerText is located on a new line.
So my question is, why is that? Is there something else I could be doing with my XML elements to have their InnerText appear on the same line?
As I said in a comment, XML isn't a display format, so it gets formatted however IE chooses to do so.
To get closer to what you were expecting, you might want to consider using an attribute rather than innertext:
XmlElement temp = (XmlElement)rootXmlElement.AppendChild(xDocument.CreateElement(lct));
var attr = xDocument.CreateAttribute("type");
attr.Value = lct;
temp.Attributes.Append(attr);
IE displays the attributes within the opening element, which may be good enough for your purposes.
From the XML perspective, what you're currently creating is called Mixed Content - you have an element that contains both text and other elements. From a hierarchical perspective, those text nodes and other elements occupy the same position within the hierarchy - so I'd assume that this is why IE is displaying them as "equals" - both nested under their parent element and at the same indentation level.
I have a XML world map which basically takes two inputs (its a XML file with corresponding CSS files and stuff): the country input and country address. So when I manually enter the data into the XML file (Country name and country address) the country on the map changes its color and on hovering over that country I can see what I've entered.
I have a list of all countries in my DB. So I was thinking of there is any way for me to write in all those countries from my DB into the XML file. I was thinking of something like this:
for(int i=0; i<list.count;i++)
{
list[i].CounryName = //write it into the XML file;
list[i].CountryUserAddress = //Write it into the XML file;
}
So the idea is to when the for loop goes on and on, every country is written in into the XML file. I don't have any significant experience working with XML files in ASP.NET and I'm stuck on dry land here. All this should be done via code behind. Can someone help me out with this, or at least point me into the right direction?
Thanks heaps!
P.S. I've forgot to mention that I should be actually overwriting the already existing XML file, not creating a new one...
Here's an example of how you could do this with the data you provided:
public string EditDoc()
{
string filename = "Path/MyFileName.xml";
List<string> list = new List<string>();
if (File.Exists(filename)) //we have the file, so update it
{
XmlDocument myDoc = new XmlDocument(); //create a document object
myDoc.Load(filename); //load existing info
XmlNode root = myDoc.DocumentElement; //the root node ("Country")
XmlNode nodeToUpdate = root.SelectSingleNode("CountryName"); //select the node to update
nodeToUpdate.Value = "new info"; //give it a new value
myDoc.Save(filename); //save the document
}
else //didn't find the file
{
XmlDocument myDoc = new XmlDocument(); //create a new document
XmlNode countryList = myDoc.CreateElement("CountryList");
myDoc.AppendChild(countryList);
for (int i = 0; i < list.Count; i++)
{
XmlNode country = myDoc.CreateElement("Country"); //create the parent "Country" element
myDoc.AppendChild(countryList); //append to the list
XmlNode countryName = myDoc.CreateElement("CountryName"); //create element for "CountryName"
countryName.AppendChild(myDoc.CreateTextNode(list[i].CountryName)); //add data from list index
country.AppendChild(countryName); //append this as a child to "Country"
XmlNode countryUserAddress = myDoc.CreateElement("CountryUserAddress"); //create element for "CountryUserAddress"
countryUserAddress.AppendChild(myDoc.CreateTextNode(list[i].CountryUserAddress)); //add data from list index
country.AppendChild(countryUserAddress); //append as a child to "Country"
}
myDoc.Save(filename); //save the document
}
}
The general idea is to traverse the document's tree and select that value to update. There may be a better way to do this, but this is the way I'm familiar with. Similarly, you can create an xml document in the same manner.
The subject matter is different, but this helped me tremendously in understanding reading/writing XML data: this link
EDIT: I updated the code slightly to make the parent element "CountryList" and append each "Country" in the DB to that. The document will end up coming out something like:
<CountryList>
<Country>
<CountryName>Blah</CountryName>
<CountryUserAddress>Blah</CountryUserAddress>
</Country>
<Country>
<CountryName>Blah</CountryName>
<CountryUserAddress>Blah</CountryUserAddress>
</Country>
</CountryList>
I am trying to write a test function in C# that read data from an XML file and parse into Selenium testing methods , the XML code is like:
<home>
<ask_frame>
<button>
<id>Object ID<id>
<xpath>Object XPath<xpath>
<textbox>
<id>Object ID<id>
<xpath>Object XPath<xpath>
</ask_frame>
<search_frame>
<id>Object ID<id>
<xpath>Object XPath<xpath>
</search_frame>
<home>
I am trying to create a loop that read the id and xpath value from these nodes and parse them into an method for searching a webpage element by id and xpath. My initial attempt was:
Code updated
public void CheckIdTest()
{
driver.Navigate().GoToUrl(baseURL + "FlightSearch");
XmlDocument xd = new XmlDocument();
xd.Load(#"C:\XMLFile1.xml");
XmlNodeList mainlist = xd.SelectNodes("//home/*");
XmlNode mainroot = mainlist[0];
foreach (XmlNode xnode in mainroot)
{
string objID = xnode.SelectSingleNode("id").InnerText;
string objXPath = xnode.SelectSingleNode("XPath").InnerText;
objID = objID.Trim();
objXPath = objXPath.Trim();
String checkValue = "ObjID value is: " + objID + Environment.NewLine+ "ObjXPath value is: " + objXPath;
System.IO.File.WriteAllText(#"C:\checkvalue.txt", checkValue);
objectCheck(objXPath, objID);
}
}
I have put a String and checked that correct values for ObjID and ObjXPath have been achieved, but this loop also went only twice (checked 2 nodes in first branch). How could I make it runs through every node in my XML?
Any suggestions and explanations to the code will be highly appreciated.
Basically these two lines are using incorrect XPath :
XmlNodeList idlist = xd.SelectNodes("id");
XmlNodeList xpathlist = xd.SelectNodes("XPath");
<id> and <xpath> nodes aren't located directly at the root level, so you can't access it just like above. Besides, xpath is case-sensitive so you should've used "xpath" instead of "XPath". Try to fix it like this :
XmlNodeList idlist = xd.SelectNodes("//id");
XmlNodeList xpathlist = xd.SelectNodes("//xpath");
or more verbose :
XmlNodeList idlist = xd.SelectNodes("home/*/id");
XmlNodeList xpathlist = xd.SelectNodes("home/*/xpath");
UPDATE :
Responding to your comment about looping problem, I think you want to change it like this :
foreach (XmlNode xnode in mainroot.ChildNodes)
{
string objID = xnode.SelectSingleNode("id").InnerText;
string objXPath = pathroot.SelectSingleNode("xpath").InnerText;
objectCheck(objID, objXPath);
}
You are getting this error because you are trying to use an object that is null i.e not instantiated.
Put in a breakpoint at the line
XmlDocument xd = new XmlDocument();
and step through line by line till you find where the nothing.null reference is.
It should not take long to find out what the problem is.
I have a DataGridView control where some values are popluted.
And also I have an xml file. The user can change the value in the Warning Column of DataGridView.And that needs to be saved in the xml file.
The below program just does the job
XDocument xdoc = XDocument.Load(filePath);
//match the record
foreach (var rule in xdoc.Descendants("Rule"))
{
foreach (var row in dgRulesMaster.Rows.Cast<DataGridViewRow>())
{
if (rule.Attribute("id").Value == row.Cells[0].Value.ToString())
{
rule.Attribute("action").Value = row.Cells[3].Value.ToString();
}
}
}
//save the record
xdoc.Save(filePath);
Matching the grid values with the XML document and for the matched values, updating the needed XML attribute.
Is there a better way to code this?
Thanks
You could do something like this:
var rules = dgRulesMaster.Rows.Cast<DataGridViewRow>()
.Select(x => new {
RuleId = x.Cells[0].Value.ToString(),
IsWarning = x.Cells[3].Value.ToString() });
var tuples = from n in xdoc.Descendants("Rule")
from r in rules
where n.Attribute("id").Value == r.RuleId
select new { Node = n, Rule = r };
foreach(var tuple in tuples)
tuple.Node.Attribute("action").Value = tuple.Rule.IsWarning;
This is basically the same, just a bit more LINQ-y. Whether or not this is "better" is debatable. One thing I removed is the conversion of IsWarning first to string, then to int and finally back to string. It now is converted to string once and left that way.
XPath allows you to target nodes in the xml with alot of power. Microsoft's example of using the XPathNavigator to modify an XML file is as follows:
XmlDocument document = new XmlDocument();
document.Load("contosoBooks.xml");
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
manager.AddNamespace("bk", "http://www.contoso.com/books");
foreach (XPathNavigator nav in navigator.Select("//bk:price", manager))
{
if (nav.Value == "11.99")
{
nav.SetValue("12.99");
}
}
Console.WriteLine(navigator.OuterXml);
Source: http://msdn.microsoft.com/en-us/library/zx28tfx1(v=vs.80).aspx
I was working on a bunch of XMLs that all share an attribute that contains the string "name" in them. The following code selects the attribute with string "name" in it and assign a new value to it.
public void updateXmlFile(string strFileName)
{
try
{
//Load the Document
XmlDocument doc = new XmlDocument();
doc.Load(strFileName);
//Set the changed Value
string newValue = GetUniqueKey();
//Select all nodes in the XML then choose from them
//the first node that contain string "name" in it
XmlNodeList list = doc.SelectNodes("//#*");
XmlNode filteredNode = list.Cast<XmlNode>()
.First(item => item.Name.ToLower().Contains("name"));
//Assign the newValue to the value of the node
filteredNode.Value = newValue;
doc.Save(strFileName);
}
catch (XmlException xex) { Console.WriteLine(xex); }
}
Now a new XMLs were added that dosen't have the string "name" in them, so instead of modifying the attribute with string "name" in it I decided to simply modify the last attribute no matter what it was (not the first)
Can anybody tell me how to do that?
EDIT
Here is an example of my XML
<?xml version="1.0" encoding="utf-8"?>
<CO_CallSignLists Version="24" ModDttm="2010-09-13T06:45:38.873" ModUser="EUADEV\SARE100" ModuleOwner="EUADEVS06\SS2008" CreateDttm="2009-11-05T10:19:31.583" CreateUser="EUADEV\A003893">
<CoCallSignLists DataclassId="E3FC5E2D-FE84-492D-AD94-3ACCED870714" EntityId="E3FC5E2D-FE84-492D-AD94-3ACCED870714" MissionID="4CF71AB2-0D92-DE11-B5D1-000C46F3773D" BroadcastType="S" DeputyInSpecialList="1" SunSpots="1537634cb70c6d80">
<CoCallSigns EntityId="DEBF1DDB-3C92-DE11-A280-000C46F377C4" CmdID="C45F3EF1-1292-DE11-B5D1-000C46F3773D" ModuleID="6CB497F3-AD63-43F1-ACAE-2C5C3B1D7F61" ListType="HS" Name="Reda Sabassi" Broadcast="INTO" PhysicalAddress="37" IsGS="1" HCId="0" CommonGeoPos="1" GeoLat="0.0000000" GeoLong="0.0000000">
<CoRadios EntityId="E1BF1DDB-3C92-DE11-A280-000C46F377C4" RadioType="HF" />
</CoCallSigns>
</CoCallSignLists>
</CO_CallSignLists>
#Alex: You notice that the "SunSpots" attribute (last attribute in the first child element) is successfully changed. But now when I wanna load the XML back into the DB it gives me an error
Here is the modified code
public void updateXmlFile(string strFileName)
{
try
{
XDocument doc = XDocument.Load(strFileName);
XAttribute l_attr_1 = (doc.Elements().First().Elements().First().Attributes().Last());
l_attr_1.Value = GetUniqueKey();
Console.WriteLine("Name: {0} Value:{1}", l_attr_1.Name, l_attr_1.Value);
doc.Save(strFileName);
}
catch (XmlException xex) { Console.WriteLine(xex); }
}
I was thinking of making an if statment which checks if the XML has an attribute that contains string "name" in it (since most of my XMLs has an attribute that contains name in them) if it does then change the attribute's value if not look for the last attribute and change it.. not the best solution but just throwing it out there
Then definitely use Linq to XML.
Example:
using System.Xml.Linq;
string xml = #"<?xml version=""1.0"" encoding=""utf-8""?>
<Commands Version=""439"" CreateUser=""Reda"">
<CmCommands DataclassId=""57067ca8-ef96-4d2e-a085-6bd7e8b24126"" OrderName = ""Tea"" Remark=""Black"">
<CmExecutions EntityId=""A9A5B0F2-6AB4-4619-9106-B0F85F86EE01"" Lock=""n"" />
</CmCommands>
</Commands>";
XDocument x = XDocument.Parse(xml);
Debug.Print(x.Elements().First().Elements().First().Attributes().Last().Value);
// Commands ^ CmCommands ^ Remark ^
That is, word for word, the last attribute of the first child of the first element.
You can also query for element/attribute names, like:
Debug.Print(x.Descendants(XName.Get("CmCommands", "")).First().Attribute(XName.Get("Remark", "")).Value);
And of course you can use all of the Linq goodness like Where, Select, Any, All etc.
Note: replace XDocument.Parse with XDocument.Load if appropriate etc.
I've not tested this but you should be able to do all of this in the XPath expression. Something like this:
//#*[contains(node-name(.), 'name')][last()]
This will return only the last attribute with the string name anywhere in its name.
If you only want the last attribute, irrespective of it's name, use this:
//#*[last()]
Look at class XmlAttributeCollection. You can get this collection by reading property Attributes of XmlNode. Just get the last by index.
Instead of .First(), use an extension method like this:
public static T LastOrDefault<T>(this IEnumerable<T> list)
{
T val = null;
foreach(T item in list)
{
val = item;
}
return val;
}