I'm writing an application which will need to index and store information about files fast. I'm currently using XML to store the information using this code:
XmlTextWriter xtw;
xtw = new XmlTextWriter(FilePath, Encoding.UTF8);
xtw.WriteStartDocument();
xtw.WriteStartElement("ApplicationIndex");
xtw.WriteEndElement();
xtw.Close();
XmlDocument xd = new XmlDocument();
FileStream lfile = new FileStream(FilePath, FileMode.Open);
xd.Load(lfile);
XmlElement cl = xd.CreateElement("Application");
cl.SetAttribute("Name", ApplicationName);
XmlElement na = xd.CreateElement("Path");
XmlText natext = xd.CreateTextNode(ApplicationPath);
na.AppendChild(natext);
cl.AppendChild(na);
XmlElement na1 = xd.CreateElement("UseCount");
XmlText natext1 = xd.CreateTextNode("0");
na1.AppendChild(natext1);
cl.AppendChild(na1);
XmlElement na2 = xd.CreateElement("SearchTerm");
XmlText natext2 = xd.CreateTextNode(ApplicationName.ToLower());
na2.AppendChild(natext2);
cl.AppendChild(na2);
xd.DocumentElement.AppendChild(cl);
lfile.Close();
xd.Save(FilePath);
This works fine for creating the file and storing the data, however I'm having trouble searching through the data quickly as there are several hundred nodes in the document. I've tried using Linq to XML to achieve this using this code:
listBox1.Items.Clear();
var doc = XDocument.Load(filePath);
foreach (var child in doc.Descendants("SearchTerm"))
{
if (child.Value.Contains(textBox1.Text.ToLower()))
{
listBox1.Items.Add(child.Value);
}
}
This is very fast however I can't seem to get any information about the selected node. For example I would like to sort the returned results based upon the UseCount (The higher the count the higher up the list). Is there anyway to do this in XML or any other technique to achieve this?
This is what the XML file looks like:
<?xml version="1.0" encoding="utf-8"?>
<ApplicationIndex>
<Application Name="Google Chrome">
<Path>C:\Program Files\Google\Chrome\Chrome.exe</Path>
<UseCount>0</UseCount>
<SearchTerm>google chrome</SearchTerm>
</Application>
<Application Name="Mozilla Firefox">
<Path>C:\Program Files\Mozilla\Firefox\Firefox.exe</Path>
<UseCount>0</UseCount>
<SearchTerm>mozilla firefox</SearchTerm>
</Application>
</ApplicationIndex>
You can Sort your elements by UseCount in descending order like this:
var doc = XDocument.Load(filePath);
var elements = doc.Descendants("Application")
.OrderByDescending(x => (int)x.Element("UseCount"));
In order to search a record by given SearchTerm you can do the following:
var element = doc.Descendants("Application")
.FirstOrDefault(x => (string)x.Element("SearchTerm") == value);
if(element != null)
{
// record found
}
Related
I'm trying to append raw XML data part of a List<string> to a XML file as follows:
XmlDocument doc = new XmlDocument();
XmlNode docnode = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
doc.AppendChild(docnode);
doc.AppendChild(doc.CreateProcessingInstruction("xml-stylesheet", "type='text/xsl' href=''"));
XmlElement Ver = doc.CreateElement("Run");
Ver.SetAttribute("version", "3.0");
XmlElement elem = doc.CreateElement("List");
elem.SetAttribute("Name", ObjectName_string);
doc.AppendChild(Ver);
doc.DocumentElement.AppendChild(elem);
doc.Save(#"1.xml");
List<string> data = Event_table.Rows.OfType<DataRow>().Select(dr=>dr.Field<string>(0)).ToList();
using (var writer = new StreamWriter(#"1.xml", append:true))
{
writer.WriteLine("<cmList>");
foreach (var row in data)
{
writer.WriteLine(row);
}
writer.WriteLine("</cmList>");
}
XML FILE: This is how the final result should be
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href=''?>
<Run version="3.0" startTime="3.0" endTime="3.0">
<List Name="ABCD" />
<cmList>
**// My Raw data should come here from List<string> data**
</cmList>
</Run>
How can I append the RAW XML data List<string> data in between the element <cmList> I tried doeing a write.BaseStream.Seek but that gives me an error:
Unable seek backward to overwrite data that previously existed in a
file opened in Append mode.
I wrote a blog many moons ago that specifically addressed how to get data from a DataSet/DataTable into XML (for use in Excel specifically). It appears that this may help you. Just exclude the instructions that tell the XML file to open in Excel. It is in VB.NET, so you will have to use the analogous C# code.
DataSet to XML Blog
Maybe I don't understand something in your example but why don't you append raw data before saving xml to file?
XmlDocument doc = new XmlDocument();
XmlNode docnode = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
doc.AppendChild(docnode);
doc.AppendChild(doc.CreateProcessingInstruction("xml-stylesheet", "type='text/xsl' href=''"));
XmlElement Ver = doc.CreateElement("Run");
Ver.SetAttribute("version", "3.0");
XmlElement elem = doc.CreateElement("List");
elem.SetAttribute("Name", ObjectName_string);
XmlElement cmList = doc.CreateElement("cmList");
List<string> data = Event_table.Rows.OfType<DataRow>().Select(dr => dr.Field<string>(0)).ToList();
StringBuilder builder = new StringBuilder();
foreach (var row in data)
{
builder.AppendLine(row);
}
cmList.Value = builder.ToString();
elem.AppendChild(cmList);
Ver.AppendChild(elem);
doc.AppendChild(Ver);
doc.Save(#"1.xml");
// do something with file 1.xml
I have a XML file which contains about 850 XML nodes. Like this:
<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
</NameValueItem>
........ 849 more
And I want to add a new Childnode inside each and every Node. So I end up like this:
<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
<Description>TestDescription</Description>
</NameValueItem>
........ 849 more
I've tried the following:
XmlDocument doc = new XmlDocument();
doc.Load(xmlPath);
XmlNodeList nodes = doc.GetElementsByTagName("NameValueItem");
Which gives me all of the nodes, but from here am stuck(guess I need to iterate over all of the nodes and append to each and every) Any examples?
You need something along the lines of this example below. On each of your nodes, you need to create a new element to add to it. I assume you will be getting different values for the InnerText property, but I just used your example.
foreach (var rootNode in nodes)
{
XmlElement element = doc.CreateElement("Description");
element.InnerText = "TestDescription";
root.AppendChild(element);
}
You should just be able to use a foreach loop over your XmlNodeList and insert the node into each XmlNode:
foreach(XmlNode node in nodes)
{
node.AppendChild(new XmlNode()
{
Name = "Description",
Value = [value to insert]
});
}
This can also be done with XDocument using LINQ to XML as such:
XDocument doc = XDocument.Load(xmlDoc);
var updated = doc.Elements("NameValueItem").Select(n => n.Add(new XElement() { Name = "Description", Value = [newvalue]}));
doc.ReplaceWith(updated);
If you don't want to parse XML using proper classes (i.e. XDocument), you can use Regex to find a place to insert your tag and insert it:
string s = #"<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
</NameValueItem>";
string newTag = "<Description>TestDescription</Description>";
string result = Regex.Replace(s, #"(?<=</Code>)", Environment.NewLine + newTag);
but the best solution is Linq2XML (it's much better, than simple XmlDocument, that is deprecated at now).
string s = #"<root>
<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
</NameValueItem>
<NameValueItem>
<Text>Test2</Text>
<Code>Test2</Code>
</NameValueItem>
</root>";
var doc = XDocument.Load(new StringReader(s));
var elms = doc.Descendants("NameValueItem");
foreach (var element in elms)
{
element.Add(new XElement("Description", "TestDescription"));
}
var text = new StringWriter();
doc.Save(text);
Console.WriteLine(text);
I am having a problem renaming child nodes in xml files using c#.
This is my xml file:
<?xml version="1.0" encoding="ISO-8859-1"?>
<ZACAC01>
<IDOC BEGIN="1">
<ZACGPIAD SEGMENT="1">
<IDENTIFIER>D000</IDENTIFIER>
<CUST_DEL_NO/>
<CUST_DEL_DATE/>
<TRUCKNO/>
<DRIVERNAME/>
<DRIVERID/>
<RESPONS_OFF/>
<CONFIRM_DATE>20/01/13</CONFIRM_DATE>
<SERIAL_NO>2</SERIAL_NO>
<SERIAL_CHAR/>
<DEL_INFO1/>
<QTY>0</QTY>
<DEL_INFO2/>
<QTY>0</QTY>
<DEL_INFO3/>
<QTY>0</QTY>
<TRANS_COMPANY>0</TRANS_COMPANY>
</ZACGPIAD>
</IDOC>
</ZACAC01>
And below is my requirement:
<?xml version="1.0" encoding="ISO-8859-1"?>
<ZACAC01>
<IDOC BEGIN="1">
<ZACGPIADD SEGMENT="1">
<IDENTIFIER>D000</IDENTIFIER>
<CUST_DEL_NO/>
<CUST_DEL_DATE/>
<TRUCKNO/>
<DRIVERNAME/>
<DRIVERID/>
<RESPONS_OFF/>
<CONFIRM_DATE>20/01/13</CONFIRM_DATE>
<SERIAL_NO>2</SERIAL_NO>
<SERIAL_CHAR/>
<DEL_INFO1/>
<QTY1>0</QTY1>
<DEL_INFO2/>
<QTY2>0</QTY2>
<DEL_INFO3/>
<QTY3>0</QTY3>
<TRANS_COMPANY>0</TRANS_COMPANY>
</ZACGPIADD>
</IDOC>
</ZACAC01>
I am able to change the segment tag <ZACGPIAD> to this <ZACGPIADD> using the following code:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(srcfile);
var root = xmlDoc.GetElementsByTagName("IDOC")[0];
var oldElem = root.SelectSingleNode("ZACGPIAD");
var newElem = xmlDoc.CreateElement("ZACGPIADD");
root.ReplaceChild(newElem, oldElem);
while (oldElem.ChildNodes.Count != 0)
{
newElem.AppendChild(oldElem.ChildNodes[0]);
}
while (oldElem.Attributes.Count != 0)
{
newElem.Attributes.Append(oldElem.Attributes[0]);
}
xmlDoc.Save(desfile);
But I can't change the <QTY> tag to <QTY1>, <QTY2>, <QTY3>
How can I do this?
I think you have the answer right in your code. You can use SelectSingleNode to pull the first <QTY> element with this:
var qtyNode = root.SelectSingleNode("ZACAC01/IDOC/ZACGPIADD/QTY[1]")
then use ReplaceChild on it's parent node. Then do the same for the second and third <QTY> nodes, replacing the '1' with '2' and '3' respectively.
You can use XDocument and operate on XElements which expose setter for node name (so you can simply set new name instead of doing node replacements):
var doc = XDocument.Load(srcfile);
var zacgpidNode = doc.Descendants("ZACGPIAD").First();
zacgpidNode.Name = "ZACGPIADD";
// now rename all QTY nodes
var qtyNodes = zacgpidNode.Elements("QTY").ToArray();
for (int i = 0; i < qtyNodes.Length; i++)
{
qtyNodes[i].Name = string.Format("{0}{1}", qtyNodes[i].Name, i+1);
}
doc.Save(desfile);
Having Descendants("ZACGPIAD").First() might not be suitable if your document structure is different than what you've shown in example. You can use XPathSelectElement method to have more control over what you'll be extracting:
var node = doc.XPathSelectElement("//IDOC[#BEGIN='1']/ZACGPIAD[#SEGMENT='1']");
Considering the following XML:
<Stations>
<Station>
<Code>HT</Code>
<Type>123</Type>
<Names>
<Short>H'bosch</Short>
<Middle>Den Bosch</Middle>
<Long>'s-Hertogenbosch</Long>
</Names>
<Country>NL</Country>
</Station>
</Stations>
There are multiple nodes. I need the value of each node.
I've got the XML from a webpage (http://webservices.ns.nl/ns-api-stations-v2)
Login (--) Pass (--)
Currently i take the XML as a string and parse it to a XDocument.
var xml = XDocument.Parse(xmlString);
foreach (var e in xml.Elements("Long"))
{
var stationName = e.ToString();
}
You can retrieve "Station" nodes using XPath, then get each subsequent child node using more XPath. This example isn't using Linq, which it looks like you possibly are trying to do from your question, but here it is:
XmlDocument xml = new XmlDocument();
xml.Load(xmlStream);
XmlNodeList stations = xml.SelectNodes("//Station");
foreach (XmlNode station in stations)
{
var code = station.SelectSingleNode("Code").InnerXml;
var type = station.SelectSingleNode("Type").InnerXml;
var longName = station.SelectSingleNode("Names/Long").InnerXml;
var blah = "you should get the point by now";
}
NOTE: If your xmlStream variable is a String, rather than a Stream, use xml.LoadXml(xmlStream); for line 2, instead of xml.Load(xmlStream). If this is the case, I would also encourage you to name your variable to be more accurately descriptive of the object you're working with (aka. xmlString).
This will give you all the values of "Long" for every Station element.
var xml = XDocument.Parse(xmlStream);
var longStationNames = xml.Elements("Long").Select(e => e.Value);
Having problems getting NodeList.SelectSingleNode() to work properly.
My XML looks like this:
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<inm:Results xmlns:inm="http://www.namespace.com/1.0">
<inm:Recordset setCount="18254">
<inm:Record setEntry="0">
<!-- snip -->
<inm:Image>fileName.jpg</inm:Image>
</inm:Record>
</inm:Recordset>
</inm:Results>
The data is a long series of <inm:Record> entries.
I open the doc and get create a NodeList object based on "inm:Record". This works great.
XmlDocument xdoc = new XmlDocument();
xdoc.Load(openFileDialog1.FileName);
XmlNodeList xRecord = xdoc.GetElementsByTagName("inm:Record");
I start looping through the NodeList using a for loop. Before I process a given entry, I want to check and see if the <inm:Image> is set. I thought it would be super easy just to do
string strImage = xRecord[i].SelectSingleNode("inm:Image").InnerText;
My thinking being, "For the XRecord that I'm on, go find the <inm:Image> value ...But this doesn't work as I get the exception saying that I need a XmlNameSpaceManager. So, I tried to set that up but could never get the syntax right.
Can someone show me how to use the correct XmlNameSpaceManager syntax in this case.
I've worked around the issue for now by looping through all of the childNodes for a given xRecord, and checking the tag once I loop around to it. I would like to check that value first to see if I need to loop over that <inm:Record> entry at all.
No need to loop through all the Record elements, just use XPath to specify the subset that you want:
XmlDocument xdoc = new XmlDocument();
xdoc.Load(openFileDialog1.FileName);
XmlNamespaceManager manager = new XmlNamespaceManager(xdoc.NameTable);
manager.AddNamespace("inm", "http://www.inmagic.com/webpublisher/query");
XmlNodeList nodes = xdoc.SelectNodes("/inm:Results/inm:Recordset/inm:Record[inm:Image != '']", manager);
Using the LINQ to XML libraries, here's an example for retrieving that said node's value:
XDocument doc = XDocument.Load(openFileDialog1.FileName);
List<XElement> docElements = doc.Elements().ToList();
XElement results = docElements.Elements().Where(
ele => ele.Name.LocalName == "Results").First();
XElement firstRecord = results.Elements().Where(
ele => ele.Name.LocalName == "Record").First();
XElement recordImage = firstRecord .Elements().Where(
ele => ele.Name.LocalName == "Image").First();
string imageName = recordImage.Value;
Also, by the way, using Hungarian notation for a type-checked language is overkill. You don't need to prepend string variables with str when it will always be a string.
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
string strImage = xRecord[i].SelectSingleNode("inm:Image",nsMgr).InnerText;
Should do it.
Using this Xml library, you can get all the records that have an Image child element with this:
XElement root = XElement.Load(openFileDialog1.FileName);
XElement[] records = root.XPath("//Record[Image]").ToArray();
If you want to be sure that the Image child contains a value, it can be expressed like this:
XElement[] records = root.XPath("//Record[Image != '']").ToArray();