How to save attribute value on xml file? - c#

I'm trying to save a value in my xml file. In the code below, the line "s.Attribute("Value").Value = value; break;" executes and the file is saved but it doesn't change the value of the attribute
public void CustomSettingXML_WriteValue(string key, string value)
{
XDocument doc = XDocument.Load(xmlFile);
var elements = from x in XElement.Load(xmlFile).Elements("Item") select x;
foreach (var s in elements)
{
if (s.Attribute("Text").Value == key)
{
s.Attribute("Value").Value = value;
doc.Save(#xmlFile);
break;
}
}
}

There are in fact two things that might have to vary.
a) You are reading the Xml using XDocument.Load as well as XElement.Load. While altering, you are using Elements, and while saving you are using XDocument.
b) Since hierarchy in XML is (Items.Item), it would be better you use Descendants to parse the elements.
Full Code
public void CustomSettingXML_WriteValue(string key, string value)
{
XDocument doc = XDocument.Load(xmlFile);
var elements = from x in doc.Descendants("Item") select x;
foreach (var s in elements)
{
if (s.Attribute("Text").Value == key)
{
s.Attribute("Value").Value = value;
doc.Save(#xmlFile);
break;
}
}
}

Related

Retrieving XML value in C# from arbitary key path

I've got a project where I'm currently implementing support for reading values from an XML file via an arbitrary/user-defined path within the document's keys.
For example, if the document looks like this:
<information>
<machine>
<foo></foo>
<name>
test machine
</name>
<bar>spam</bar>
</machine>
</information>
then the user might want to retrieve the value from the name key in information/machine.
Is there a way using XDocument/XPath that I can look up the values the user wants without knowing/coding in the schema for the document?
My initial thought was working through the document with a form of recursive function utilizing XElement items, but I feel like there ought to be a simpler/cleaner solution that doesn't require me rolling my own lookup code.
I also tried something along these lines
var doc = XDocument.Load("C:\Path\to\XML\file.xml");
// Split the parent keys string
XElement elem = doc.Root.XPathSelectElement("path/to/key");
if (elem != null && elem.Attribute("wantedKeyName") != null)
replace = elem.Attribute("wantedKeyName").Value;
but elem is always null. I'm assuming there's a problem with the way I'm defining my path or utilizing XPathSelectElement, but I haven't worked it out yet.
static XmlNode SearchNode(XmlNodeList nodeList, string nodeName)
{
for (int i = 0; i < nodeList.Count; i++)
{
if (nodeList[i].Name == nodeName)
{
return nodeList[i];
}
if (nodeList[i].HasChildNodes)
{
XmlNode node = SearchNode(nodeList[i].ChildNodes, nodeName);
if (node != null)
{
return node;
}
}
}
return null;
}
static XmlNodeList SearchNodeByPath(XmlNodeList nodeList, string xPath)
{
for (int i = 0; i < nodeList.Count; i++)
{
var nodes = nodeList[i].SelectNodes(xPath);
if (nodes != null && nodes.Count > 0)
{
return nodes;
}
if (nodeList[i].HasChildNodes)
{
XmlNodeList innerNodes = SearchNodeByPath(nodeList[i].ChildNodes, xPath);
if (innerNodes != null && innerNodes.Count > 0)
{
return innerNodes;
}
}
}
return null;
}
this is using of methods :
var node = SearchNode(doc.ChildNodes, "compiler");
var node1 = SearchNodeByPath(doc.ChildNodes, "compilers/compiler");
I turns out my solution using XPathSelectElement was the correct approach, I just had to prepend the path/to/key string with //.
The following code I ended up using does that and strips off any whitespace around the outside of the value (in case the value is on a separate line than the opening tag.
// xml is a struct with the path to the parent node (path/to/key)
// and the key name to look up
// Split the parent keys string
XElement elem = doc.Root.XPathSelectElement("//" + xml.KeyPath);
if (elem != null && elem.Element(xml.Key) != null)
replace = elem.Element(xml.Key).Value.Trim();

fastest starts with search algorithm

I need to implement a search algorithm which only searches from the start of the string rather than anywhere within the string.
I am new to algorithms but from what I can see it seems as though they go through the string and find any occurrence.
I have a collection of strings (over 1 million) which need to be searched everytime the user types a keystroke.
EDIT:
This will be an incremental search. I currently have it implemented with the following code and my searches are coming back ranging between 300-700ms from over 1 million possible strings. The collection isnt ordered but there is no reason it couldnt be.
private ICollection<string> SearchCities(string searchString) {
return _cityDataSource.AsParallel().Where(x => x.ToLower().StartsWith(searchString)).ToArray();
}
I've adapted the code from this article from Visual Studio Magazine that implements a Trie.
The following program demonstrates how to use a Trie to do fast prefix searching.
In order to run this program, you will need a text file called "words.txt" with a large list of words. You can download one from Github here.
After you compile the program, copy the "words.txt" file into the same folder as the executable.
When you run the program, type a prefix (such as prefix ;)) and press return, and it will list all the words beginning with that prefix.
This should be a very fast lookup - see the Visual Studio Magazine article for more details!
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace ConsoleApp1
{
class Program
{
static void Main()
{
var trie = new Trie();
trie.InsertRange(File.ReadLines("words.txt"));
Console.WriteLine("Type a prefix and press return.");
while (true)
{
string prefix = Console.ReadLine();
if (string.IsNullOrEmpty(prefix))
continue;
var node = trie.Prefix(prefix);
if (node.Depth == prefix.Length)
{
foreach (var suffix in suffixes(node))
Console.WriteLine(prefix + suffix);
}
else
{
Console.WriteLine("Prefix not found.");
}
Console.WriteLine();
}
}
static IEnumerable<string> suffixes(Node parent)
{
var sb = new StringBuilder();
return suffixes(parent, sb).Select(suffix => suffix.TrimEnd('$'));
}
static IEnumerable<string> suffixes(Node parent, StringBuilder current)
{
if (parent.IsLeaf())
{
yield return current.ToString();
}
else
{
foreach (var child in parent.Children)
{
current.Append(child.Value);
foreach (var value in suffixes(child, current))
yield return value;
--current.Length;
}
}
}
}
public class Node
{
public char Value { get; set; }
public List<Node> Children { get; set; }
public Node Parent { get; set; }
public int Depth { get; set; }
public Node(char value, int depth, Node parent)
{
Value = value;
Children = new List<Node>();
Depth = depth;
Parent = parent;
}
public bool IsLeaf()
{
return Children.Count == 0;
}
public Node FindChildNode(char c)
{
return Children.FirstOrDefault(child => child.Value == c);
}
public void DeleteChildNode(char c)
{
for (var i = 0; i < Children.Count; i++)
if (Children[i].Value == c)
Children.RemoveAt(i);
}
}
public class Trie
{
readonly Node _root;
public Trie()
{
_root = new Node('^', 0, null);
}
public Node Prefix(string s)
{
var currentNode = _root;
var result = currentNode;
foreach (var c in s)
{
currentNode = currentNode.FindChildNode(c);
if (currentNode == null)
break;
result = currentNode;
}
return result;
}
public bool Search(string s)
{
var prefix = Prefix(s);
return prefix.Depth == s.Length && prefix.FindChildNode('$') != null;
}
public void InsertRange(IEnumerable<string> items)
{
foreach (string item in items)
Insert(item);
}
public void Insert(string s)
{
var commonPrefix = Prefix(s);
var current = commonPrefix;
for (var i = current.Depth; i < s.Length; i++)
{
var newNode = new Node(s[i], current.Depth + 1, current);
current.Children.Add(newNode);
current = newNode;
}
current.Children.Add(new Node('$', current.Depth + 1, current));
}
public void Delete(string s)
{
if (!Search(s))
return;
var node = Prefix(s).FindChildNode('$');
while (node.IsLeaf())
{
var parent = node.Parent;
parent.DeleteChildNode(node.Value);
node = parent;
}
}
}
}
A couple of thoughts:
First, your million strings need to be ordered, so that you can "seek" to the first matching string and return strings until you no longer have a match...in order (seek via C# List<string>.BinarySearch, perhaps). That's how you touch the least number of strings possible.
Second, you should probably not try to hit the string list until there's a pause in input of at least 500 ms (give or take).
Third, your queries into the vastness should be async and cancelable, because it's certainly going to be the case that one effort will be superseded by the next keystroke.
Finally, any subsequent query should first check that the new search string is an append of the most recent search string...so that you can begin your subsequent seek from the last seek (saving lots of time).
I suggest using linq.
string x = "searchterm";
List<string> y = new List<string>();
List<string> Matches = y.Where(xo => xo.StartsWith(x)).ToList();
Where x is your keystroke search text term, y is your collection of strings to search, and Matches is the matches from your collection.
I tested this with the first 1 million prime numbers, here is the code adapted from above:
Stopwatch SW = new Stopwatch();
SW.Start();
string x = "2";
List<string> y = System.IO.File.ReadAllText("primes1.txt").Split(' ').ToList();
y.RemoveAll(xo => xo == " " || xo == "" || xo == "\r\r\n");
List <string> Matches = y.Where(xo => xo.StartsWith(x)).ToList();
SW.Stop();
Console.WriteLine("matches: " + Matches.Count);
Console.WriteLine("time taken: " + SW.Elapsed.TotalSeconds);
Console.Read();
Result is:
matches: 77025
time taken: 0.4240604
Of course this is testing against numbers and I don't know whether linq converts the values before, or if numbers make any difference.

C# Sort Object[] Array within ArrayList?

I am not sure how to go about sorting the ArrayList which contains an object Array which has a DateTime object and a String object. I am ultimately trying to sort the ArrayList based on the first object (DateTime) of the array within the ArrayList.
I've tried to search around for sorting but articles didn't seem to go into the level of detail or this particular use-case the application is hitting. I'm not sure if this is the best way to deal with the data either but any help suggestion will certainly be appreciative.
The goal is to read mutliple XML files and combine all the Lap data out of the all the XML files and sort them from oldest to most recent then to create a new XML file with the combined sorted information in it.
ArrayList LapsArrayList = new ArrayList();
ListBox.SelectedObjectCollection SelectedItems = lstSelectedFiles.SelectedItems;
foreach (string Selected in SelectedItems)
{
Object[] LapArray = new Object[2];
XmlDocument xDoc = new XmlDocument();
xDoc.Load(Path + #"\" + Selected);
XmlNodeList Laps = xDoc.GetElementsByTagName("Lap");
foreach (XmlElement Lap in Laps)
{
LapArray[0] = DateTime.Parse(Lap.Attributes[0].Value);
LapArray[1] = Lap.InnerXml.ToString();
LapsArrayList.Add(LapArray);
}
}
XML Data Example
<Lap StartTime="2013-06-17T12:27:21Z"><TotalTimeSeconds>12705.21</TotalTimeSeconds><DistanceMeters>91735.562500</DistanceMeters><MaximumSpeed>10.839000</MaximumSpeed><Calories>3135</Calories><AverageHeartRateBpm><Value>151</Value>.....</Lap>
This are my recommendations:
Use a class for the items you want to sort, I suggest a Tuple<T1, T2>.
Use a List<T> because it is a typed list so you avoid casts and it is more convenient in general.
We are going to use Linq to sort the array just for easy writting.
I list the code below:
//I dunno what does this has to do, but I'll leave it here
ListBox.SelectedObjectCollection SelectedItems = lstSelectedFiles.SelectedItems;
//We are going to use a List<T> instead of ArrayList
//also we are going to use Tuple<DateTime, String> for the items
var LapsList = new List<Tuple<DateTime, String>>();
foreach (string Selected in SelectedItems)
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load(Path + #"\" + Selected);
XmlNodeList Laps = xDoc.GetElementsByTagName("Lap");
foreach (XmlElement Lap in Laps)
{
var dateTime = DateTime.Parse(Lap.Attributes[0].Value);
var str = Lap.InnerXml.ToString();
//Here we create the tuple and add it
LapsList.Add(new Tuple<DateTime, String>(dateTime, str));
}
}
//We are sorting with Linq
LapsList = LapsList.OrderBy(lap => lap.Item1).ToList();
If you can't use tuples, declare you own class for the item. For example
class Lap
{
private DateTime _dateTime;
private String _string;
public Lap (DateTime dateTimeValue, String stringValue)
{
_dateTime = dateTimeValue;
_string = stringValue;
}
public DateTime DateTimeValue
{
get
{
return _dateTime;
}
set
{
_dateTime = value;
}
}
public String StringValue
{
get
{
return _string;
}
set
{
_string = value;
}
}
}
With this class you can do a easy migration of the code as follows:
//I dunno what does this has to do, but I'll leave it here
ListBox.SelectedObjectCollection SelectedItems = lstSelectedFiles.SelectedItems;
//We are going to use a List<T> instead of ArrayList
//also we are going to use the Laps class for the items
var LapsList = new List<Lap>();
foreach (string Selected in SelectedItems)
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load(Path + #"\" + Selected);
XmlNodeList Laps = xDoc.GetElementsByTagName("Lap");
foreach (XmlElement Lap in Laps)
{
var dateTime = DateTime.Parse(Lap.Attributes[0].Value);
var str = Lap.InnerXml.ToString();
//Here we create the Lap object and add it
LapsList.Add(new Lap(dateTime, str));
}
}
//We are sorting with Linq
LapsList = LapsList.OrderBy(lap => lap.DateTimeValue).ToList();
If you can't use Linq, here is a non Linq alternative:
LapsList.Sort
(
delegate(Tuple<DateTime, String> p1, Tuple<DateTime, String> p2)
{
return p1.Item1.CompareTo(p2.Item1);
}
);
Or for the case of using the Lap class:
LapsList.Sort
(
delegate(Lap p1, Lap p2)
{
return p1.DateTimeValue.CompareTo(p2.DateTimeValue);
}
);

Grabbing Attribute from XMl document using Xdoc

Having a hard time retrieving an attribute from my XML. I need to grab this attribute, and send and store it. I can't get it to garb the attributes. :( Just need help with the attributes.
<portfolios>
<portfolio>
<id>00001</id>
<investment ticker="ASD">
<shares>20</shares>
<price>42.50</price>
</investment>
</portfolio>
<pricedata days="4">
<stock ticker="ASD">
<price value="42.50"/>
<price value="43.50"/>
<price value="39.00"/>
<price value="45.00"/>
</stock>
</pricedata>
</portfolios>
What I have so far!
public bool readXmlData(String filename)
{
XDocument document = XDocument.Load(filename);
foreach (XElement portfolio in document.Descendants("portfolio"))
{
XElement id = portfolio.Element("id");
string id2 = id != null ? id.Value : string.Empty;
portList.Add(new SmallPortfolio(id2));
XAttribute ticker = portfolio.Attribute("investment");
foreach(XElement investment in document.Descendants("investment"))
{
XElement shares = investment.Element("shares");
XElement price = investment.Element("price");
temp.Add(new Investment(
ticker != null ? ticker.Value : string.Empty,
shares != null ? int.Parse(shares.Value) : default(int),
price != null ? double.Parse(shares.Value) : default(double)
));
}
}
foreach (XElement stock in document.Descendants("pricedata"))
{
XAttribute tick = stock.Attribute("stock");
List<Double> pricetemp2 = new List<Double>();
foreach (XElement price in document.Descendants("stock"))
{
XAttribute value = price.Attribute("price");
pricetemp2.Add(value.Value);
}
groupList.Add(new PriceGroup(tick,pricetemp2));
}
return true;
}
public List<SmallPortfolio> getPortfolioList() { return null; }
public List<PriceGroup> getPriceList() { return null; }
}
<price> is an element, but you are accessing it as if it was an attribute <stock price="..."/>.
Try this:
foreach (XElement stock in document.Descendants("stock"))
{
string ticker = (string)stock.Attribute("ticker");
List<Double> pricetemp2 = new List<Double>();
foreach (XElement price in stock.Descendants("price"))
{
double value = (double)price.Attribute("value");
pricetemp2.Add(value);
}
groupList.Add(new PriceGroup(ticker, pricetemp2));
}
Casting XAttribute to double will use the proper XML rules for numbers (XmlConvert.ToDouble). Using double.Parse is incorrect as it uses culture-specific number formatting (e.g. in Germany it expects a decimal comma instead of a decimal point).

changing a node type to #text whilst keeping the innernodes with the HtmlAgilityPack

I'm using the HtmlAgilityPack to parse an XML file that I'm converting to HTML. Some of the nodes will be converted to an HTML equivalent. The others that are unnecessary I need to remove while maintaining the contents. I tried converting it to a #text node with no luck. Here's my code:
private HtmlNode ConvertElementsPerDatabase(HtmlNode parentNode, bool transformChildNodes)
{
var listTagsToReplace = XmlTagMapping.SelectAll(string.Empty); // Custom Dataobject
var node = parentNode;
if (node != null)
{
var bNodeFound = false;
if (node.Name.Equals("xref"))
{
bNodeFound = true;
node = NodeXref(node);
}
if (node.Name.Equals("graphic"))
{
bNodeFound = true;
node = NodeGraphic(node);
}
if (node.Name.Equals("ext-link"))
{
bNodeFound = true;
node = NodeExtLink(node);
}
foreach (var infoTagToReplace in listTagsToReplace)
{
if (node.Name.Equals(infoTagToReplace.XmlTag))
{
bNodeFound = true;
node.Name = infoTagToReplace.HtmlTag;
if (!string.IsNullOrEmpty(infoTagToReplace.CssClass))
node.Attributes.Add("class", infoTagToReplace.CssClass);
if (node.HasAttributes)
{
var listTagAttributeToReplace = XmlTagAttributeMapping.SelectAll_TagId(infoTagToReplace.Id); // Custom Dataobject
for (int i = 0; i < node.Attributes.Count; i++ )
{
var bDeleteAttribute = true;
foreach (var infoTagAttributeToReplace in listTagAttributeToReplace)
{
if (infoTagAttributeToReplace.XmlName.Equals(node.Attributes[i].Name))
{
node.Attributes[i].Name = infoTagAttributeToReplace.HtmlName;
bDeleteAttribute = false;
}
}
if (bDeleteAttribute)
node.Attributes.Remove(node.Attributes[i].Name);
}
}
}
}
if (transformChildNodes)
for (int i = 0; i < parentNode.ChildNodes.Count; i++)
parentNode.ChildNodes[i] = ConvertElementsPerDatabase(parentNode.ChildNodes[i], true);
if (!bNodeFound)
{
// Replace with #text
}
}
return parentNode;
}
At the end I need to do the node replacement (where you see the "Replace with #text" comment) if the node is not found. I've been ripping my hair (what's left of it) out all day and it's probably something silly. I'm unable to get the help to compile and there is no online version. Help Stackoverflow! You're my only hope. ;-)
I would think you could just do this:
return new HtmlNode(HtmlNodeType.Text, parentNode.OwnerDocument, 0);
This of course adds the node to the head of the document, but I assume you have some sort of code in place to handle where in the document the node should be added.
Regarding the documentation comment, the current (as of this writing) download of the Html Agility Pack documentation contains a CHM file which doesn't require compilation in order to view.

Categories