Remove all the JTokens except for a specific path - c#

Problem
Let's say I have:
var json = JObject.Parse("imagine some large JSON here");
Now let's do this:
var subtree = json.SelectNode("root.child.grandson");
I care only about:
The grandson
All its descendants
Ancestors in a straight line to the root (parents, their parents, etc...)
Some solution
Great. So I came up with this method:
private static void KillMySiblings(JToken token)
{
if (token.Root == token)
return;
var siblingsAndSelf = token.Parent.Children().ToArray();
foreach (var sibling in siblingsAndSelf)
{
if (ReferenceEquals(sibling, token))
continue;
// this removes the token from its parent
sibling.Remove();
}
}
which with this code:
var ancestors = subtree.AncestorsAndSelf();
foreach (var ancestor in ancestors)
KillMySiblings(ancestor);
does the trick.
So why u postin'?
Because I don't like my solution and am seeking something simpler in concept and more efficient. I don't like the idea of going through potentially O(n) nodes and removing them. I feel I should extract those parents and build a new tree from them - but I don't know how to do this properly.
Any help would be greatly appreciated! :D

Related

Loop to iterate up parent nodes until it finds specific tag

What I'm doing is finding a specific value within an XML document and then I want to iterate upwards through each parent until it finds the parent with a specific tag name.
List<XElement> fieldReferences = new List<XElement>();
fieldReferences.AddRange(element.XPathSelectElements(string.Format("descendant::nameValue[#idref='{0}']", fieldName)));
fieldReferences.AddRange(element.XPathSelectElements(string.Format("descendant::nameValue[#value='{0}']", fieldName)));
string parentIterator = ".Parent";
string attributeValue = ".Attribute('id').Value";
string parentElementName = ".Name";
foreach (var value in fieldReferences)
{
var parentField = string.Format("{0}{1}", parentIterator, parentElementName);
while (value + parentField != "private" || value + parentField != "public")
{
// keep appending .Parent until it finds what it needs
}
//var parentField = value.Parent.Parent.Parent.Parent.Attribute("id").Value;
outputFields.Add(parentField, name.FirstOrDefault());
}
The issue that I'm having is that parentField will always be evaluated as a string so it'll never actually check the .Parent.Name property of value.
I don't work often with C# so I'm sure there's a much easier way to do this so my question is: How can I get my parentField string to evaluate the way I want OR how can I do this in a different way to achieve the same end result?
EDIT: Here's what my xml looks like. The XPAthSelectElement gets the nameValue element and I want to iterate through each parent element until I find the private tag
<private id="I need to iterate upwards through each parent element until I find this one">
<value>
<request>
<nameValues>
<nameValue idref="I found this" />
<nameValue value=""/>
</nameValues>
</request>
</value>
</private>
So you don't actually need to do this many string operations to then go crazy with XPath. Once you found your child target element, you can just use the Parent property on the XElement iteratively until you find the XElement with a private/public tag. So that gives us this code:
foreach (XElement element in fieldReferences)
{
XElement currentElement = element;
while (currentElement.Parent != null)
{
currentElement = currentElement.Parent;
if (currentElement.Name == "private" || currentElement.Name == "public") {
outputFields.Add(currentElement, /* not sure what you want here */);
break;
}
}
}
So currentElement would start out as the element with the nameValue tag from your example. In the while loop, each iteration currentElement changes to its parent node until there is no more parent or currentElement has become a private or a public tag. If the latter is the case, it gets appended to your result.
You can use the XElement.Ancestors function to get a list of all the elements that contain the nodes you found, then just select the ones you want using LINQ. No need for any loops.
var results = fieldReferences.Select(element => element.Ancestors()
.Where(ancestor => ancestor.Name == "public" ||
ancestor.Name == "private")
.FirstOrDefault());
Note that this will go all the way up the tree, and may have issues if there are multiple matching ancestors (or no matching ancestor). Let me know if that is a problem for you, and what result you want in that case, and I can make adjustments.

Find an element with Selenium Webdriver which fulfils multiple By-criteria

I am using v2.52 of Selenium and the WebDriver with C#. What I am trying to achieve should be fairly simple, but I am unable to find the solution for: I'd like to find an element by multiple By-criteria.
Let's say, I have something like this:
Click me!
Click me!
<p class="foo">Click me!</p>
Ignore the fact, that I could use By.CssSelector, By.CssSelector, By.XPath and so on. I was assuming that it should be possible to do something like this:
driver.FindElement(By.TagName("a").ClassName("foo"))
// ...or something like this...
driver.FindElement(By.TagName("a"), By.ClassName("foo"))
OpenQA.Selenium.Support.PageObjects.ByChained does not do the trick, since it is searching hierarchical.
Is there a way to find element(s) which match multiple By-criteria?
Best regards,
Carsten
I imagine something like this may work for your case:
public IWebElement FindElementByMultipleCriteria(List<By> Criteria, IReadOnlyCollection<IWebElement> toFilter = null)
{
// If we've reached the end of the criteria list, return the first element:
if (Criteria.Count == 0 && toFilter != null) return toFilter.ElementAt(0);
// Take the head of the criteria list
By currentCriteria = Criteria[0];
Criteria.RemoveAt(0);
// If no list of elements found exists, we get all elements from the current criteria:
if (toFilter == null)
{
toFilter = Driver.FindElements(currentCriteria);
}
// If a list does exist, we must filter out the ones that aren't found by the current criteria:
else
{
List<IWebElement> newFilter = new List<IWebElement>();
foreach(IWebElement e in Driver.FindElements(currentCriteria))
{
if (toFilter.Contains(e)) newFilter.Add(e);
}
toFilter = newFilter.AsReadOnly();
}
// Pass in the refined criteria and list of elements found.
return FindElementByMultipleCriteria(Criteria, toFilter);
}
IWebElement example = FindElementByMultipleCriteria(new List<By>(){ By.TagName("a"), ClassName("foo") });
Essentially, you're taking the list of elements found by the first By passed in. You are then going through the remaining By criteria and removing elements from that initial list that are never found again.
This is horribly inefficient and I don't really understand why you would want to do this, but it exists.
Oh also, you'll need to add using System.Linq; in order to interact with the IReadOnlyCollection.
Well I think you can try something like this, and let me know if it works:
public IWebElement FindElementUsingNestedBy(By firstCriteria, By secondCriteria)
{
var allElements = Driver.FindElements(firstCriteria);
foreach (var webElement in allElements)
{
try
{
var desiredObject = webElement.FindElement(secondCriteria);
return desiredObject;
}
catch (NotFoundException ex)
{
}
}
return null;
}

Get all elements in IWebElement using XPATH

I've tried using webelement(and IWebElement) and string lists, but i keep getting errors. How I do get a list or strings of all the elements text by XPath? I have all Selenium references. Do i need some java.util dll? Should I implement a foreach loop?
I don't know what exactly you want to do but you can get element text using following code.
public List<String> policy1Details = new List<String>();
public void PolicySummary1(int i)
{
//var driver = new FirefoxDriver();
policy1Details.Clear();
var psummary = driver.FindElements(By.XPath("//text()"));//give your xPath.
//var psummary = driver.FindElement(By.XPath("//div[#id='PolicyDetails_" + i + "']/div/table"));
foreach (IWebElement d in psummary)
{
//resultText.Add(d.Text);
policy1Details.Add(d.Text);
}
}
if any issue the let me know.
Here is the topic with a similar question! - XPath to get all child nodes (elements, comments, and text) without parent
And here is the quote from there!
child::* selects all element children of the context node
So you can do the:
var childs = parent.findElements(By.xpath("./child::*"));
Also here is the documentation of XPath, you can read more here!

Finding an element with Linq and lambda expressions: but doesn't work

Have a look to the following example:
The first solution, using the foreach, works pretty well and easily. But I was trying to write it using Linq and I could not achieve this result. I made some attempts but no one succeeded.
I expect to find just one element.
The problem is not at runtime: I don't know very well the Linq sintax and so I don't know how to get the element called PlacedSelection (the foreach structure clarifies where I'm looking for it). Instead in my attempt I could get the PlacedCategory elements.. but I don't need this..
PlacedSelection ActualSelection = null;
foreach (var placedCategory in Model.Coupon.Categories)
{
foreach (PlacedSelection placedSelection in placedCategory.Value.Selections)
{
var pp = placedSelection.EventId;
if (pp == Model.EventId)
{
ActualSelection = placedSelection;
break;
}
}
}
//IEnumerable<KeyValuePair<string, PlacedCategory>> p = Model.Coupon.Categories(c => c.Value.Selections.Any(s=> s.EventId == Model.EventId));
It looks like you want:
PlacedSelection actualSelection = Model.Coupon.Categories
.SelectMany(cat => cat.Value.Selections)
.FirstOrDefault(selection => selection.EventId == Model.EventId);
Any would be used if you were trying to find the category, but you're trying to find the selection, by the looks of it.

Representing a chain of nodes in a human readable way

I'm trying to represent a subway map type thing that has to be drawn progressively (like its growing).
My code all works perfectly but its unreadable. Basically it's a tree structure with recursive nodes and subnodes and my test code looks like this:
Children.Add(new TrackLine(800));
Children[0].Children.Add(new TrackSpot());
Children[0].Children[0].Children.Add(new TrackSplitter());
Children[0].Children[0].Children[0].Children.Add(new TrackRotate(-45));
Children[0].Children[0].Children[0].Children[0].Children.Add(new TrackColorChange(Color.Red));
Children[0].Children[0].Children[0].Children[0].Children[0].Children.Add(new TrackLine(100));
Children[0].Children[0].Children[0].Children[0].Children[0].Children[0].Children.Add(new TrackRotate(45));
Children[0].Children[0].Children[0].Children[0].Children[0].Children[0].Children[0].Children.Add(new TrackLine(200));
Does anyone have any suggestions of how to fix that mess?
You're looking for a way to add it to the deepest child that has no children of it's own?
class Node
{
List<Node> children ;
public void addNode( Node newNode )
{
if( children.Count > 0 )
children[0].addNode( newNode ) ; // recursive call
// to ask first child to add newNode to it
else
children.Add( newNode ) ; // just add it to the children list of THIS node
}
}
To make that code more readable I'd use variables with appropriate names (since I don't know your subject domain, mine are probably not appropriate):
Children.Add(new TrackLine(800));
var lineA = Children[0];
lineA.Children.Add(new TrackSpot());
var spotA = lineA.Children[0];
spotA.Children.Add(new TrackSplitter());
var splitterA = spotA.Children[0];
splitterA.Children.Add(new TrackRotate(-45));
var rotateNeg45 = splitterA.Children[0];
rotateNeg45.Children.Add(new TrackColorChange(Color.Red));
var colorRed = rotateNeg45.Children[0];
colorRed.Add(new TrackLine(100));
var lineB = colorRed.Children[0];
lineB.Children.Add(new TrackRotate(45));
var rotatePos45 = lineB.Children[0];
rotatePos45.Children.Add(new TrackLine(200));
var lineC = rotatePos45.Children[0];
Another approach is to use strings as indexes for Children like this:
Children.Add(new TrackLine(800));
Children["lineA"].Children.Add(new TrackSpot());
Children["lineA"].Children["spotA"].Children.Add(new TrackSplitter());
However I probably would not do it because managing the string constants would be a mess on its own and it would probably require changing Children implementation.

Categories