Read last n elements of an XML file with Linq XDocument - c#

I am using the follow Code to read an XML file:
XDocument doc = XDocument.Load(xmlPath);
foreach (var node in doc.Descendants("LogInfo"))
{
ListViewItem item = new ListViewItem(new string[]
{
node.Element("MailBox").Value,
node.Element("LastRun").Value,
});
listViewHome.Items.Add(item);
}
How can I change this Code to receive just the last "n" number of elements from that XML file?
Thanks in advance.

You could do this:
var nodes = doc.Descendants("LogInfo");
foreach (var node in nodes.Skip(Items.Count() - n))
{
...
}
Or this:
var nodes = doc.Descendants("LogInfo");
foreach (var node in nodes.Reverse().Take(n).Reverse())
{
...
}
If you're feeling adventurous, you could also write your own extension method which should be more efficient than either of these. Here's my quick and dirty solution:
public static IEnumerable<T> TakeFromEnd<T>(this IEnumerable<T> items, int n)
{
var arry = new T[n];
int i = 0;
foreach(var x in items)
{
arry[i++ % n] = x;
}
if (i < n)
{
n = i;
i = 0;
}
for (int j = 0; j < n; j++)
{
yield return arry[(i + j) % n];
}
}
var nodes = doc.Descendants("LogInfo");
foreach (var node in nodes.TakeFromEnd(n))
{
...
}

XDocument doc = XDocument.Load(xmlPath);
var logs = doc.Descendants("LogInfo");
var logsCount = logs.Count();
foreach (var node in logs.Skip(logsCount - n).Take(n))
{
ListViewItem item = new ListViewItem(new string[]
{
node.Element("MailBox").Value,
node.Element("LastRun").Value,
});
listViewHome.Items.Add(item);
}
And here is XPath solution, which will enumerate xml once
var xpath = String.Format("//LogInfo[position()>last()-{0}]", n);
foreach (var log in doc.XPathSelectElements(xpath))
{
ListViewItem item = new ListViewItem(new string[]
{
(string)log.Element("MailBox"),
(string)log.Element("LastRun")
});
listViewHome.Items.Add(item);
}

Related

Prevent ' Process is terminated due to StackOverflowException' in C#

I have a program which builds a very large tree from input data and traverses it, both by recursion. I have tested the program on smaller inputs (and thus smaller trees) and it functions as intended. However when the input data is much larger i run into 'Process is terminated due to StackOverflowException'. I assume this is due to the stack running out of space. Is there any way to prevent this or do I have to switch to building the tree via iteration instead? Or perhaps I am missing a case of infinite recursion somewhere?
Here is the code:
class Program
{
static int[] tileColors;
static Color[] colors;
static int totalTiles;
static void Main(string[] args)
{
Stopwatch s = new Stopwatch();
s.Start();
string[] data = File.ReadAllLines("colors.txt");
totalTiles = int.Parse(data[0].Split(' ')[0]);
int totalColors = int.Parse(data[0].Split(' ')[1]);
string[] colorsRaw = data[1].Split(' ');
tileColors = new int[totalTiles];
for (int i = 0; i < totalTiles; i++)
{
tileColors[i] = int.Parse(colorsRaw[i]) - 1;
}
colors = new Color[totalColors];
for (int i = 3; i < data.Length; i++)
{
string[] raw = data[i].Split(' ');
int[] pair = new int[] { int.Parse(raw[0]) - 1, int.Parse(raw[1]) - 1 };
if (colors[pair[0]] == null)
colors[pair[0]] = new Color(pair[1]);
else
colors[pair[0]].pairs.Add(pair[1]);
if (colors[pair[1]] == null)
colors[pair[1]] = new Color(pair[0]);
else
colors[pair[1]].pairs.Add(pair[0]);
}
Tree t = new Tree();
t.root = new Node(0);
PopulateTree(t.root);
long ans = t.CountMatchingLeaves(t.root, totalTiles - 1) % 1000000007;
Console.WriteLine(ans);
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds);
}
static void PopulateTree(Node root)
{
for (int i = root.tile + 1; i < totalTiles; i++)
{
if (colors[tileColors[i]] == null) continue;
if (colors[tileColors[i]].Compatible(tileColors[root.tile]))
{
var node = new Node(i);
root.children.Add(node);
PopulateTree(node);
}
}
}
}
class Color
{
public List<int> pairs = new List<int>();
public Color(int pair)
{
pairs.Add(pair);
}
public bool Compatible(int c)
{
return pairs.Contains(c);
}
}
class Node
{
public List<Node> children = new List<Node>();
public int tile;
public Node(int tile)
{
this.tile = tile;
}
}
class Tree
{
public Node root;
public List<Node> GetMatchingLeaves(Node root, int match)
{
if (root.children.Count == 0)
{
if (root.tile == match)
{
return new List<Node>() { root };
}
return new List<Node>();
}
List<Node> list = new List<Node>();
foreach(var c in root.children)
{
list.AddRange(GetMatchingLeaves(c, match));
}
return list;
}
public long CountMatchingLeaves(Node root, int match)
{
if (root.children.Count == 0)
{
if (root.tile == match)
{
return 1;
}
return 0;
}
long count = 0;
foreach (var c in root.children)
{
count += CountMatchingLeaves(c, match);
}
return count;
}
}
You can always rewrite recursion as iteration, usually by using a stack class rather than rely on your thread's stack. For your code it would look like this:
static void PopulateTree(Node start)
{
var nodes = new Stack<Node>();
nodes.Push(start);
while(nodes.Count != 0)
{
var root = nodes.Pop();
for (int i = root.tile + 1; i < totalTiles; i++)
{
if (colors[tileColors[i]] == null) continue;
if (colors[tileColors[i]].Compatible(tileColors[root.tile]))
{
var node = new Node(i);
root.children.Add(node);
nodes.Push(node);
}
}
}
}
The while loop checking for more items is the equivalent of your terminating condition in recursion.

Difficulties retrieving List<int> within List<List<int>>

I am having trouble with the below code:
foreach (var result in results)
{
foreach (int individualresult in result)
{
//Operation
}
}
'results' is a List<List<int>> and I am trying to retrieve each integer from each list within the 'results' list of lists (sorry if that's confusing), however when I run the code no errors are received but it doesn't get any further than the first line.
I've put it in a 'try catch' and it doesn't pick up any exceptions or errors so I am flummoxed as to why it isn't working. Additionally I have tried changing var to List<int> but that didn't change anything either.
Any and all help will be appreciated. Thanks
Try this code in a Console-Application and check the Output. I assume that ether your results is empty or that each individual List in your results is empty.
Console.WriteLine("Start");
if (results == null || !results.Any())
Console.WriteLine("No result received!");
foreach (var result in results)
{
Console.WriteLine("new result-set");
if (result == null || !result.Any())
Console.WriteLine(" Result: Empty list!");
foreach (var individualresult in result)
{
Console.WriteLine(" Result: " + individualresult);
}
}
Console.WriteLine("End");
Console.Readline();
See this example it might help you
class Program
{
static void Main(string[] args)
{
List<int> list1d = new List<int>();
List<List<int>> list2d = new List<List<int>>();
for (int i = 0; i < 10; i++)
{
list1d = new List<int>();
for (int j = 0; j < 10; j++)
{
list1d.Add(i * j + i);
}
list2d.Add(list1d);
}
foreach (var result in list2d)
{
foreach (var i in result)
{
Console.WriteLine(i);
}
}
Console.ReadKey();
}
}

How to retrieve text commented or bookmarked in Microsoft Office Word, with OpenXML

I've been trying for a while to retrieve text that is commented or bookmarked by the user, in the .docx document, using OpenXML. I tried building dictionaries and arrays with the start and end tag of each comments/bookmarks, and tried to navigate through the XML tree nodes, to obtain the text but I'm not obtaining all of it (just the first child, which is the first word).
IDictionary<String, BookmarkStart> bookmarkMapS = new Dictionary<String, BookmarkStart>();
IDictionary<String, BookmarkEnd> bookmarkMapE = new Dictionary<String, BookmarkEnd>();
var _bkms = doc.MainDocumentPart.RootElement.Descendants<BookmarkStart>();
var _bkme = doc.MainDocumentPart.RootElement.Descendants<BookmarkEnd>();
foreach (BookmarkStart item in _bkms)
{
Run bookmarkText = item.NextSibling<Run>();
if (bookmarkText != null)
{
try
{
for (int i = 0; i < bookmarkText.ChildElements.Count(); i++)
{
Console.WriteLine(bookmarkText.ChildElements.ElementAt(i).InnerText);
}
}
catch (Exception)
{
}
}
}
There is probably a better way to do this, but this is what I came up with.
List<OpenXmlElement> elems =new List<OpenXmlElement>(doc.MainDocumentPart.Document.Body.Descendants());
var crs=doc.MainDocumentPart.RootElement.Descendants<CommentRangeStart>();
var cre=doc.MainDocumentPart.RootElement.Descendants<CommentRangeEnd>();
var dic_cr=new Dictionary<CommentRangeStart, CommentRangeEnd>();
for (int i = 0; i < crs.Count(); i++)
{
dic_cr.Add(crs.ElementAt(i), cre.ElementAt(i));
}
for (int i = 0; i < elems.Count; i++)
if (elems.ElementAt(i).LocalName.Equals("t"))
if (isInsideAnyComment(dic_cr, elems.ElementAt(i)))
for (int j = 0; j < dic_cr.Count; j++)
if (isInsideAComment(dic_cr.ElementAt(j), elems.ElementAt(i)))
String text = ((Text)elems.ElementAt(i)).InnerText;
public bool isInsideAnyComment(IDictionary<CommentRangeStart, CommentRangeEnd> dic_cr, OpenXmlElement item)
{
foreach (var i in dic_cr)
if (item.IsAfter(i.Key) && item.IsBefore(i.Value))
return true;
return false;
}
public bool isInsideAComment(KeyValuePair<CommentRangeStart, CommentRangeEnd> dic_cr_elem, OpenXmlElement item)
{
if (item.IsAfter(dic_cr_elem.Key) && item.IsBefore(dic_cr_elem.Value))
return true;
return false;
}

C# Nested List not adding a new list

The first List AStore iterates and creates a new list for each iteration. The second List APages iterates, but does not create a new list on each iteration. I have the same placement for each list creation and List.Add. What is wrong here?
public void Promos()
{
//get store info and id
var storeinfo = new HtmlWeb();
var storeshtm = storeinfo.Load(#"Stores.htm");
var nodes = storeshtm.DocumentNode.SelectNodes("div");
List<Store> AStore = new List<Store>();
nodes = nodes[0].ChildNodes;
int a = 0;
foreach (var node in nodes)
{
if (node.Name != "#text")
{
AStore.Add(new Store(
node.ChildNodes[1].ChildNodes[1].Attributes[7].Value,//storewebid
"Astore",//storename
node.ChildNodes[3].ChildNodes[1].ChildNodes[1].ChildNodes[3].InnerText,//storeaddress
node.ChildNodes[3].ChildNodes[1].ChildNodes[1].ChildNodes[5].InnerText,//storecity
node.ChildNodes[3].ChildNodes[1].ChildNodes[1].ChildNodes[5].InnerText,//storestate
node.ChildNodes[3].ChildNodes[1].ChildNodes[1].ChildNodes[5].InnerText,//storezip
node.ChildNodes[3].ChildNodes[1].ChildNodes[1].ChildNodes[7].InnerText,//storephone
""//storehours
));
}
}
for (int i = 0; i <= a; i++)
{
var circualr = new HtmlWeb();
var storehtm = circualr.Load(#"http://storewebsite/" + AStore[i].StoreWebID);
var cnodes = storehtm.DocumentNode.SelectNodes("//*[#id="+'"'+"Wrapper"+'"'+"]");
List<Pages> APages = new List<Pages>();
foreach (var cnode in cnodes)
if(cnode.ChildNodes[3].ChildNodes[3].ChildNodes[5].ChildNodes[3].ChildNodes[1].Name == "a")
APages.Add(new Pages(cnode.ChildNodes[3].ChildNodes[3].ChildNodes[5].ChildNodes[3].ChildNodes[1].Attributes[2].Value));//get inner page links
}
I have the same placement for each list creation
You do NOT have the same placement of creation, the AStore is created outside of the for loop, and the APages one is.

How can I write Xelement values?

I would like to create a method which collect custom childnode values from an xml file and rewrite whit datas from a form. I had an idea thet I collect the datas in an ArrayList and give it to the method. But I cant change it in a foreach, because it throws ArgumentOutOfRangeException( although the ArraList contains 8 elements and the incremental variable's value also 8). So I would ask for help.
Here is the Code:
public static void Search(ArrayList nodeIds, ArrayList values)
{
XDocument doc = XDocument.Load("Options.xml");
int i = 0;
foreach (XElement option in doc.Descendants("BasicOptions"))
{
foreach(string nodeId in nodeIds)
{
if (option.Attribute("id").Value == nodeId)
{
foreach (XElement prop in option.Nodes())
{
prop.Value = values[i].ToString();
i++;
}
}
}
}
doc.Save("Options.xml");
}
It seems to me that i will go out of range without question because it is declared externally to 3 foreach statements and used within the center foreach. You should rethink your approach.
I suggest, but without knowing your incoming values or why your calling this, to redclare your internal foreach as a for statement like the following:
public static void Search(ArrayList nodeIds, ArrayList values)
{
XDocument doc = XDocument.Load("Options.xml");
foreach (XElement option in doc.Descendants("BasicOptions"))
{
foreach (string nodeId in nodeIds)
{
if (option.Attribute("id").Value == nodeId)
{
var nodes = option.Nodes().ToList();
for (int i = 0; i < nodes.Count && i < values.Count; i++)
{
XElement node = (XElement)nodes[i];
node.Value = values[i].ToString();
}
}
}
}
doc.Save("Options.xml");
}

Categories

Resources