I'm trying to make my own Move to Resource refactoring (like the one found in ReSharper). In my CodeAction.GetChangedDocumentAsync method I'm doing these 3 steps:
Add my resource to the Resources.resx file with XmlDocument
Use DTE to run the custom tool to update the Resources.Designer.cs file
Replace the literal string by the qualified identifier of the new resource with SyntaxNode.ReplaceNode
Steps 1 & 2 are OK, but 3 does not work. If I remove the step 2, 3 is working.
I don't know if it is because I'm mixing Roslyn & DTE or if it's because step 2 generate new code in the solution and my cached context becomes invalid.
// Add the resource to the Resources.resx file
var xmlDoc = new XmlDocument();
xmlDoc.Load(resxPath);
XmlNode node = xmlDoc.SelectSingleNode($"//data[#name='{resourceIndentifierName}']");
if (node != null) return;
XmlElement dataElement = xmlDoc.CreateElement("data");
XmlAttribute nameAtt = xmlDoc.CreateAttribute("name");
nameAtt.Value = resourceIndentifierName;
dataElement.Attributes.Append(nameAtt);
XmlAttribute spaceAtt = xmlDoc.CreateAttribute("space", "xml");
spaceAtt.Value = "preserve";
dataElement.Attributes.Append(spaceAtt);
XmlElement valueElement = xmlDoc.CreateElement("value");
valueElement.InnerText = value;
dataElement.AppendChild(valueElement);
XmlNode rootNode = xmlDoc.SelectSingleNode("//root");
Debug.Assert(rootNode != null, "rootNode != null");
rootNode.AppendChild(dataElement);
xmlDoc.Save(resxPath);
// Update the Resources.Designer.cs file
var dte = (DTE2)Package.GetGlobalService(typeof(SDTE));
ProjectItem item = dte.Solution.FindProjectItem(resxPath);
((VSProjectItem) item.Object)?.RunCustomTool();
// Replace the node
SyntaxNode oldRoot = await context.Document.GetSyntaxRootAsync(cancellationToken)
.ConfigureAwait(false);
SyntaxNode newRoot = oldRoot.ReplaceNode(oldNode, newNode);
return context.Document.WithSyntaxRoot(newRoot);
it's because step 2 generate new code in the solution and my cached context becomes invalid
This is what is happening. When you call RunCustomTool the visual studio file watcher api tells roslyn that the files have been updated and roslyn generates a new set of solution snapshots. When you attempt to apply your codefix, roslyn looks at the solution snapshot that your codefix came from, sees that it does not match the current snapshot, and fails to apply it.
I'm trying to update an existing XML file, but always when I update it adding new tags the xmlns="" attribute mysteriously appears in all tags and I didn't find a way to remove it.
private static void EditarXML(string path, List<SiteUrl> listaUrls, bool indice, string loc)
{
XmlDocument documentoXML = new XmlDocument();
documentoXML.Load(path);
XmlNode sitemap = documentoXML.CreateElement("sitemap");
XmlNode xloc = documentoXML.CreateElement("loc");
xloc.InnerText = loc;
sitemap.AppendChild(xloc);
XmlNode lastmod = documentoXML.CreateElement("lastmod");
lastmod.InnerText = DateTime.Now.ToShortDateString();
sitemap.AppendChild(lastmod);
documentoXML.DocumentElement.AppendChild(sitemap);
}
Any help or ideas would be appreciated.
This will happen with the parent node you are appending to has a namespace, but you don't specify it in the CreateElement() call.
To handle this, you can get the namespace from the DocumentElement, like this (my sample just creates the document in memory, but the principle is the same), and pass it to CreateElement().
if (x.DocumentElement != null) {
var xmlns = (x.DocumentElement.NamespaceURI);
var sitemap = x.CreateElement("sitemap", xmlns);
var xloc = x.CreateElement("loc", xmlns);
xloc.InnerText = "Hello";
sitemap.AppendChild(xloc);
var lastmod = x.CreateElement("lastmod", xmlns);
lastmod.InnerText = DateTime.Now.ToShortDateString();
sitemap.AppendChild(lastmod);
x.DocumentElement.AppendChild(sitemap);
}
Console.WriteLine(x.InnerXml);
Output
<test xmlns="jdphenix"><sitemap><loc>Hello</loc><lastmod>4/20/2015</lastmod></sitemap></test>
Note that if I did not pass the parent namespace to each CreateElement() call, the children of that call would have the blank xmlns.
// incorrect - appends xmlns=""
if (x.DocumentElement != null) {
var sitemap = x.CreateElement("sitemap");
var xloc = x.CreateElement("loc");
xloc.InnerText = "Hello";
sitemap.AppendChild(xloc);
var lastmod = x.CreateElement("lastmod");
lastmod.InnerText = DateTime.Now.ToShortDateString();
sitemap.AppendChild(lastmod);
x.DocumentElement.AppendChild(sitemap);
}
Console.WriteLine(x.InnerXml);
Output
<test xmlns="jdphenix"><sitemap xmlns=""><loc>Hello</loc><lastmod>4/20/2015</lastmod></sitemap></test>
Related reading: Why does .NET XML append an xlmns attribute to XmlElements I add to a document? Can I stop it?
How to prevent blank xmlns attributes in output from .NET's XmlDocument?
I'm having a little problem formatting my xml file using C# code in a Windows Form app. Here is the code i'm using for this project:
private void btnSend_Click(object sender, EventArgs e)
{
string _name = tbName.ToString();
string _st = tbSt.ToString();
string _dx = tbDx.ToString();
string _iq = tbIq.ToString();
string _filename = #"c:\Add.xml";
if (File.Exists(_filename))
{
XDocument xDoc = XDocument.Load(_filename);
xDoc.Root.Add(new XElement("character",
new XElement("name", _name),
new XElement("st", _st),
new XElement("dx", _dx),
new XElement("iq", _iq)
));
xDoc.Save(_filename);
}
else if (!File.Exists(_filename))
{
XmlDocument doc = new XmlDocument();
XmlNode docNode = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
doc.AppendChild(docNode);
XmlNode productsNode = doc.CreateElement("characters");
doc.AppendChild(productsNode);
XmlNode productNode = doc.CreateElement("character");
productsNode.AppendChild(productNode);
XmlNode nmNode = doc.CreateElement("name");
nmNode.AppendChild(doc.CreateTextNode(_name));
productNode.AppendChild(nmNode);
XmlNode stNode = doc.CreateElement("st");
stNode.AppendChild(doc.CreateTextNode(_st));
productNode.AppendChild(stNode);
XmlNode dxNode = doc.CreateElement("dx");
dxNode.AppendChild(doc.CreateTextNode(_dx));
productNode.AppendChild(dxNode);
XmlNode iqNode = doc.CreateElement("iq");
iqNode.AppendChild(doc.CreateTextNode(_iq));
productNode.AppendChild(iqNode);
doc.Save(#"c:\Add.xml");//must have to save
}
}
The problem is that my .xml file comes out with the whole TextBox class prefix attached such as this:
...
- <character>
<name>System.Windows.Forms.TextBox, Text: bob</name>
<st>System.Windows.Forms.TextBox, Text: 10</st>
<dx>System.Windows.Forms.TextBox, Text: 12</dx>
<iq>System.Windows.Forms.TextBox, Text: 08</iq>
</character>
I'd like to have it look like this:
- <character>
<name>bob</name>
<st>10</st>
<dx>12</dx>
<iq>08</iq>
</character>
If any of you fine knowledgeable folks could lend a hand (or point me a link) I'd appreciate it. I did comb through the Google but nothing turned up with this specific odd problem. Many thanks for any help you can offer.
The obvious I did not see. Thanks to those who may respond. Now that I post it, it is obvious.
Changing
string _name = tbName.ToString();
simply to
string _name = tbName.Text;
of course fixed the problem. Hope this may help someone else.
WCFService.cs
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class WCFService : IWCFService{
public Boolean insertUser(String name, String password)
{
Boolean successInsert = false;
XDocument xDoc = XDocument.Load("`http://localhost:57833/DataProvider/XML/User.xml`");
Boolean userExist = (from user in xDoc.Descendants("user")
where (String)user.Attribute("name") == name
select user).Any();
if (!userExist)
{
XElement root = xDoc.Root;
int lastUserId = Convert.ToInt16(root.Elements("user").Last().Attribute("id").Value);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("`http://localhost:57833/DataProvider/XML/User.xml`");
XmlNode xmlElementUser = xmlDoc.CreateNode(XmlNodeType.Element, "user", "");
XmlAttribute xmlAttributeUserID = xmlDoc.CreateAttribute("id");
XmlAttribute xmlAttributeName = xmlDoc.CreateAttribute("name");
XmlAttribute xmlAttributePassword = xmlDoc.CreateAttribute("password");
XmlAttribute xmlAttributeUserType = xmlDoc.CreateAttribute("userType");
xmlAttributeUserID.Value = (lastUserId + 1).ToString();
xmlAttributeName.Value = name;
xmlAttributePassword.Value = password;
xmlAttributeUserType.Value = "borrower";
xmlElementUser.Attributes.Append(xmlAttributeUserID);
xmlElementUser.Attributes.Append(xmlAttributeName);
xmlElementUser.Attributes.Append(xmlAttributePassword);
xmlElementUser.Attributes.Append(xmlAttributeUserType);
xmlDoc.DocumentElement.AppendChild(xmlElementUser);
xmlDoc.Save("`http://localhost:57833/DataProvider/XML/User.xml`");
successInsert = true;
}
return successInsert;
}
}
I'm doing a windows phone 7 app, and i wish to retrieve from and append to XML file by using WCF. And i encounter an error of "URI formats are not supported." when i wish to save the XML which is this line "xmlDoc.Save("http://localhost:57833/DataProvider/XML/User.xml");". It seem like WCF cannot append the XML file in server.
You cannot append to a file on a remote server but only on the local disk.
So if the code above is executed on the machine where you want to save the xml use:
xmlDoc.Save("c:\\User.xml")
If you are not on that server (and it is not accessible via unc) then you need to upload the file to another wcf service on that machine so it will save it locally.
I am using XDocument.Validate (it seems to function the same as XmlDocument.Validate) to validate an XML document against an XSD - this works well and I am informed of validation errors.
However, only some information seems to be exposed [reliably] in the ValidationEventHandler (and XmlSchemaException), e.g.:
the error message (i.e. "The 'X' attribute is invalid - The value 'Y' is invalid according to its datatype 'Z' - The Pattern constraint failed"),
the severity
What I would like is to get the "failing XPath" for the validation failure (where it makes sense): that is, I would like to get the failure in relation to the XML document (as opposed to the XML text).
Is there a way to obtain the "failing XPath" information from XDocument.Validate? If not, can the "failing XPath" be obtained through another XML validation method such as an XmlValidatingReader1?
Background:
The XML will be sent as data to my Web Service with an automatic conversion (via JSON.NET) from JSON to XML. Because of this I begin processing the XDocument data1 and not text, which has no guaranteed order due to the original JSON data. The REST client is, for reasons I care not to get into, basically a wrapper for HTML form fields over an XML document and validation on the server occurs in two parts - XML schema validation and Business Rule validation.
In the Business Rule validation it's easy to return the "XPath" for the fields which fail conformance that can be used to indicate the failing field(s) on the client. I would like to extend this to the XSD schema validation which takes care of the basic structure validation and, more importantly, the basic "data type" and "existence" of attributes. However, due to the desired automatic process (i.e. highlight the appropriate failing field) and source conversions, the raw text message and the source line/column numbers are not very useful by themselves.
Here is a snippet of the validation code:
// Start with an XDocument object - created from JSON.NET conversion
XDocument doc = GetDocumentFromWebServiceRequest();
// Load XSD
var reader = new StringReader(EmbeddedResourceAccess.ReadResource(xsdName));
var xsd = XmlReader.Create(reader, new XmlReaderSettings());
var schemas = new XmlSchemaSet();
schemas.Add("", xsd);
// Validate
doc.Validate(schemas, (sender, args) => {
// Process validation (not parsing!) error,
// but how to get the "failing XPath"?
});
Update: I found Capture Schema Information when validating XDocument which links to "Accessing XML Schema Information During Document Validation" (cached) from which I determined two things:
XmlSchemaException can be specialized into XmlSchemaValidationException which has a SourceObject property - however, this always returns null during validation: "When an XmlSchemaValidationException is thrown during validation by a validating XmlReader object, the value of the SourceObject property is null".
I can read through the document (via XmlReader.Read) and "remember" the path prior to the validation callback. While this "seems like it works" in initial tests (without a ValidationCallback), it feels quite inelegant to me - but I've been able to find little else.
Sender of validation event is a source of event. So, you can search over the network for code which gets XPath for node (e.g. Generating an XPath expression) and generate XPath for source of event:
doc.Validate(schemas, (sender, args) => {
if (sender is XObject)
{
xpath = ((XObject)sender).GetXPath();
}
});
Take it :-)
var xpath = new Stack<string>();
var settings = new XmlReaderSettings
{
ValidationType = ValidationType.Schema,
ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings,
};
MyXmlValidationError error = null;
settings.ValidationEventHandler += (sender, args) => error = ValidationCallback(sender, args);
foreach (var schema in schemas)
{
settings.Schemas.Add(schema);
}
using (var reader = XmlReader.Create(xmlDocumentStream, settings))
{
// validation
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
xpath.Push(reader.Name);
}
if (error != null)
{
// set "failing XPath"
error.XPath = xpath.Reverse().Aggregate(string.Empty, (x, y) => x + "/" + y);
// your error with XPath now
error = null;
}
if (reader.NodeType == XmlNodeType.EndElement ||
(reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement))
{
xpath.Pop();
}
}
}
I don't know the API but my guess is no, you can't get the xpath because validation may be implemented as a finite state machine. A state may not translate to an xpath or in the case when it is valid for more than one element to follow and the element found is not in the expected set, the xpath doesn't exist.
Finally successed in this way!
When I use XmlReader.Create(xmlStream, settings) and xmlRdr.Read() to Validate a XML, I captured the sender of the ValidationEventHandler and find it is an object of {System.Xml.XsdValidatingReader},so I transfer sender to a xmlreader object, there are some functions in XMLReader class to help you find the parent node of the error attributes.
There is one thing to watch out that when i use XMLReader.MoveToElement(), the Validation function will be stuck into a loop in the error attribute,so i used MoveToAtrribute(AttributeName) and then MoveToNextAttribute to avoid stuck into the loop, maybe there is more elegant way to handle this.
Without further ado,below is my code.
public string XMLValidation(string XMLString, string SchemaPath)
{
string error = string.Empty;
MemoryStream xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(XMLString));
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add(null, SchemaPath);
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.Schemas.Add(schemas);
settings.ValidationEventHandler += new ValidationEventHandler(delegate(object sender, ValidationEventArgs e)
{
switch (e.Severity)
{
case XmlSeverityType.Error:
XmlReader senRder = (XmlReader)sender;
if (senRder.NodeType == XmlNodeType.Attribute)
{//when error occurs in an attribute,get its parent element name
string attrName = senRder.Name;
senRder.MoveToElement();
error += string.Format("ERROR:ElementName'{0}':{1}{2}", senRder.Name, e.Message, Environment.NewLine);
senRder.MoveToAttribute(attrName);
}
else
{
error += string.Format("ERROR:ElementName'{0}':{1}{2}", senRder.Name, e.Message, Environment.NewLine);
}
break;
case XmlSeverityType.Warning:
break;
}
});
XmlReader xmlRdr = XmlReader.Create(xmlStream, settings);
while (xmlRdr.Read()) ;
return error;
}
Alternatively you could use the code at How to find an XML node from a line and column number in C#? to get the failing node by using the args.Exception.LineNumber and args.Exception.LinePosition and then navigate the XML document as required to provide more information about what data caused the validation to fail.
If, like me, you are using the "XmlDocument.Validate(ValidationEventHandler validationEventHandler)" method to validate your XML:
// Errors and alerts collection
private ICollection<string> errors = new List<String>();
// Open xml and validate
...
{
// Create XMLFile for validation
XmlDocument XMLFile = new XmlDocument();
// Validate the XML file
XMLFile.Validate(ValidationCallBack);
}
// Manipulator of errors occurred during validation
private void ValidationCallBack(object sender, ValidationEventArgs args)
{
if (args.Severity == XmlSeverityType.Warning)
{
errors.Add("Alert: " + args.Message + " (Path: " + GetPath(args) + ")");
}
else if (args.Severity == XmlSeverityType.Error)
{
errors.Add("Error: " + args.Message + " (Path: " + GetPath(args) + ")");
}
}
The secret is to get the "Exception" property data of the "args" parameter. Do like this:
// Return this parent node
private string GetPath(ValidationEventArgs args)
{
var tagProblem =((XmlElement)((XmlSchemaValidationException)args.Exception).SourceObject);
return iterateParentNode(tagProblem.ParentNode) + "/" +tagProblem.Name;
}
private string iterateParentNode(XmlNode args)
{
var node = args.ParentNode;
if (args.ParentNode.NodeType == XmlNodeType.Element)
{
return interateParentNode(node) + #"/" + args.Name;
}
return "";
}