Roslyn Find Same Node in Changed Document - c#

As we all know Roslyn Syntax Trees are Immutable, so after making changes you need to get a new node.
I'm trying to update a document using the document editor, but I keep getting an error that the node is not found in the syntax tree.
public static T FindEquivalentNode<T>(this Document newDocument, T node)
where T : CSharpSyntaxNode
{
var root = newDocument.GetSyntaxRootAsync().Result;
return root.DescendantNodes().OfType<T>()
.FirstOrDefault(newNode => SyntaxFactory.AreEquivalent(newNode, node));
}
When I try to Call this again the document editor:
var newFieldDeclaration = documentEditor.GetChangedDocument().FindEquivalentNode(syntaxNode);
documentEditor.ReplaceNode(newFieldDeclaration, propertyDeclaration);
I get an error:
The node is not part of the tree
The newField Declaration is not null it find an equivalent field yet I still get this error, How Can I Replace this node?

You get the node because in your FindEquivalentNode method, you are doing that:
SyntaxFactory.AreEquivalent(newNode, node)
SyntaxFactory.AreEquivalent is not return true for "real" same nodes, but for nodes\tokens that are seems equal in their structure (with consideration of topLevel parameter).
Back to your question, if you want to call ReplaceNode you must have the "real" old node, so you wouldn't get an
The node is not part of the tree
To achive that, you have a few options, one of them is what #Tamas wrote in comments, use SyntaxAnnotations.
Example:
//Add annotation to node
var annotation = new SyntaxAnnotation("your_annotation", "some_data");
node = node .WithAdditionalAnnotations(annotation);
// Now you can change the tree as much you want
// And when you want to get the node from the changed tree
var annotatedNode = someNode.DescendantNodesAndSelf().
FirstOrDefault(n => n.GetAnnotations("your_annotation").Any())

Related

Get symbol info from new node

I have a CSharpSyntaxRewriter that overrides VisitMemberAccessExpression, inside that method, I am calling MemberAccessExpressionSyntax.WithName(), but the node it returns has a different SyntaxTree compared to the original node, this is a problem since it means an error is thrown when calling SemanticModel.GetSymbolInfo(node).
Is there a way to change the Name of a MemberAccessExpressionSyntax but still have a SyntaxTree that works with SemanticModel.GetSymbolInfo(node)?
My code is:
public sealed override SyntaxNode VisitNode(SyntaxNode node)
{
var nodeSyntax = (MemberAccessExpressionSyntax) node;
if (nodeSyntax.Name.ToString() == OldModifier && !HasSymbol(nodeSyntax, out _))
{
if (ModifierType == ModifierType.Damage)
nodeSyntax = nodeSyntax.WithName(IdentifierName($"GetDamage({NewModifier})"));
else
nodeSyntax = nodeSyntax.WithName(IdentifierName($"GetCritChance({NewModifier})"));
nodeSyntax = nodeSyntax.WithLeadingTrivia(node.GetLeadingTrivia()).WithTrailingTrivia(node.GetTrailingTrivia());
}
return nodeSyntax;
}
(VisitNode is called from VisitMemberAccessExpression)
And here are images showing the difference in the SyntaxTree:
original:
After calling WithName:
There's a few ways to approach something like this:
Change Your Visit Method
Can you restructure your code to call VisitNode before you've done any changes to VisitMemberAccessExpression? For example can you go from:
var memberAccess = memberAccess.With...();
memberAccess = VisitNode(memberAccess);
return memberAccess;
to
var memberAccess = VisitNode(memberAccess);
memberAccess = memberAccess.With...();
return memberAccess;
Then it might mean that you are then visiting with the original node before you've done rewriting. Note this can still be tricky though if you are doing recursive stuff.
Use ReplaceNodes
There's a helper method you can use instead of a rewriter. This will call the function you pass to do rewriting at each step, but that function is handed both the original node in the original tree and the node after child nodes have been rewritten; you can use the original one to ask binding questions (since that's from the original tree) and consume the one that's been rewritten for recursive rewriting.
Break Your Problem into Two Steps
Do two walks: first get a list of all the places will need to update, add those SyntaxNodes to a HashSet or like, and then do the rewrite second where you are updating those places.

In AngleSharp, how can I create DOM elements using string?

Is there a way to create DOM elements using string in AngleSharp? For example:
var result = document.Create...("<div id='div1'>hi<p>world</p></div>");
This is possible using a document fragment.
There are multiple possibilities how to use a document fragment, one way would be to use fragment parsing for generating a node list in the right context:
var context = BrowsingContext.New(Configuration.Default);
var document = await context.OpenAsync(r => r.Content("<div id=app><div>Some already available content...</div></div>"));
var app = document.QuerySelector("#app");
var parser = context.GetService<IHtmlParser>();
var nodes = parser.ParseFragment("<div id='div1'>hi<p>world</p></div>", app);
app.Append(nodes.ToArray());
The example shows how nodes can be created in the context of a certain element (#app in this case) and that the behavior is different than, e.g., using InnerHtml, which would remove existing nodes.
Hope this helps!

How to read same child element types from an XML tree recursively?

I have a sample xml file in following format:-
Also, I have a Control class shown below:
class Control
{
private string id;
public string Id
{
get { return id; }
set { id = value; }
}
private string controlType;
public string ControlType
{
get { return controlType; }
set { controlType = value; }
}
private string searchProperties;
public string SearchProperties
{
get { return searchProperties; }
set { searchProperties = value; }
}
public List<Control> ChildrenControl = new List<Control>();
}
I need to read the XML file mentioned above and populate the code. I am not sure how to recursively do that. I was thinking to use Linq to XML, but not sure how to use it recursively in this case in which parent and child elements are of the same type. Can someone please help me with this problem?
Thanks,
Harit
Update:
Try the following. It uses Linq to XML and a recursive function to parse the controls from out of the XML document. It assumes the existence of your XML data in a file called "Controls.xml", and obviously your Control class. Its not the greatest of code, but it should get you started.
private void ParseControlsData()
{
var doc = XDocument.Load("Controls.xml");
var controls = from control in doc.Element("controls").Elements("control")
select CreateFromXElement(control);
var controlsList = controls.ToList();
Console.ReadLine();
}
private Control CreateFromXElement(XElement element)
{
var control = new Control()
{
Id = (string)element.Attribute("id"),
ControlType = (string)element.Attribute("controlType"),
SearchProperties = (string)element.Attribute("searchProperties")
};
var childrenElements = element.Element("childControls");
if (childrenElements != null)
{
var children = from child in childrenElements.Elements("control")
select CreateFromXElement(child);
control.ChildrenControl = children.ToList();
}
return control;
}
Notes:
In the ParseControlsData function, it uses query expression syntax to select the first element in the document called "controls" (your root), and then selects all sub-elements named "control". A very similar expression occurs inside the CreateFromXElement function, except it needs to find an element called "childControls".
There's no real error checking. You'll definitely need some.
Update:
Don't do this, it doesn't work for your example because you have values stored in attributes, and by default the DataContractSerializer + DataContractAttributes combination does not support that (without a whole bunch of extra work). Other options are Linq to XML (as you suggested) and using the XmlSerializer (which is similar to the DataContractSerializer, but uses its own set of attributes). I'll look into it further.
Previous Answer:
One way to turn an XML document into an Object Graph is to mark the classes you want to create from the XML with DataContract attributes, and to use the DataContractSerializer. All you need to do is make sure that the DataContract(Name = "X") and DataMember(Name = "Y") match the names of the elements inside your XML.
Have a look at my answer on XML Element Selection, which is performing the operation you want (taking existing XML and turning it into an Object Graph). You probably wont need to worry about the CDATA stuff that that user ran into, so your solution will probably be a bit simpler.
Also, have a look at my answer on How to catch/send XML doc with various sub arrays?, which is performing the reverse operation (that user wanted to create XML from an Object Graph).
If you can't tell, I'm a fan of the DataContractSerializer :)
You can use recursion using Func<> delegate, but you have to declare it before specifying the actual delegate logic:
var xDoc = XDocument.Load("Input.xml");
Func<XElement, List<Control>> childControlsQuery = null;
childControlsQuery =
x => (from c in x.Elements("control")
select new Control
{
Id = (string)c.Attribute("id"),
ControlType = (string)c.Attribute("controltype"),
SearchProperties = (string)c.Attribute("searchproperties"),
ChildrenControl = childControlsQuery(c.Element("childControls") ?? new XElement("childControls"))
}).ToList();
var controls = childControlsQuery(xDoc.Root);
You can remove ?? new XElement("childControls") if you're sure there is always childControls element, even when given control does not have any childs.
And if you're sure there is always only one main control, you can get it as:
var mainControl = controls.First();

Populate XML elements in existing document

I have this string of XML elements in a document
<dmc><avee><modelic></modelic><sdc></sdc><chapnum></chapnum><section></section>
<subsect></subsect><subject></subject><discode></discode><discodev></discodev>
<incode></incode><incodev></incodev><itemloc></itemloc></avee></dmc>
What I need to do is now populate those elements with user inputted variables using Linq. I currently have:
XDocument doc = XDocument.Load(sgmlReader);
doc.Element("modelic").Add(MI);
doc.Element("sdc").Add(sd);
doc.Element("chapnum").Add(sys);
doc.Element("section").Add(subsys);
doc.Element("subsect").Add(subsubsys);
doc.Element("subject").Add(unit);
doc.Element("discode").Add(dc);
doc.Element("discodev").Add(dcv);
doc.Element("incode").Add(infcode);
doc.Element("incodev").Add(infCV);
doc.Element("itemloc").Add(loc);
(yes I'm using sgmlReader but this works fine in my program in other areas) I'm clearly missing something fundamental as it's giving me a NullReferenceException was unhandled - Object reference not set to an instance of an object.
Any ideas/suggestions please?
The Element() method only matches the immediate children of the container.
You can either chain Descendants() into First():
doc.Descendants("modelic").First().Add(MI);
Or navigate to the immediate parents of the elements you want to modify:
doc.Root.Element("avee").Element("modelic").Add(MI);
This should work:
var avee = dmc.Root.Element("avee");
avee.Element("modelic").Value = MI;
avee.Element("sdc").Value = sd;
Just repeat the last line for each of your remaining elements (chapnum, section...).
The problem was that first you have to retrieve root element (dmc), then avee, and then you can set values for child elements of avee.

Retrieve SiteMapNode matching custom attribute using LINQ

I am new to LINQ. I have an ordinary siteMap XML document with custom attributes. One of these attributes is: id
I would like to use LINQ to retrieve a single node matching the value of the custom attribute (id).
etc.
My attempt at the LINQ looks like this:
private SiteMapNode FindNodeById(SiteMapNodeCollection nodes, int siteMapNodeId)
{
var pageNode = from SiteMapNode node in nodes.Cast<SiteMapNode>()
where node["id"] == Convert.ToString(siteMapNodeId)
select node;
return (SiteMapNode)pageNode;
}
During debugging, pageNode becomes assigned with:
{System.Linq.Enumerable.WhereEnumerableIterator<System.Web.SiteMapNode>}
And on the return statement an InvalidCastException is thrown:
Unable to cast object of type 'WhereEnumerableIterator`1[System.Web.SiteMapNode]' to type 'System.Web.SiteMapNode'.
Any help is appreciated! :)
EDIT: I've re-posted this question in a clearer manner here: Re-worded Question
Thanks to Stefan for putting me on the right track!
You try to cast a IEnumerable<SiteMapNode> to a SiteMapNode. Use First to filter and return one node:
return nodes
.Cast<SiteMapNode>()
.First(node => node["id"] == Convert.ToString(siteMapNodeId));
pageNode is a sequence of nodes.
You want to call First() to get the first item in the sequence.

Categories

Resources