CreateAttribute methods throws System.NotSupportedException - c#

public void AddNodeToXml(string helpid, string fileName)
{
const string STR_EXPRESSION = "/Form/Controls/Control";
XPathDocument doc = null;
try
{
doc = new XPathDocument(fileName);
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
if (doc != null)
{
XPathNavigator navigator = doc.CreateNavigator();
XPathNodeIterator localIterator = navigator.Select(STR_EXPRESSION);
while (localIterator.MoveNext())
{
if (localIterator.Current != null)
{
if (localIterator.Current.Name.Equals("Control"))
{
localIterator.Current.MoveToFirstAttribute();
if (localIterator.Current.Value.Equals(helpid))
{
localIterator.Current.MoveToParent();
localIterator.Current.CreateAttribute(string.Empty, "NewAttribute", string.Empty, "value");
}
}
}
}
}
}
My xml structure is as show in STR_EXPRESSION
I want to add new attribute to the control node if currnet cotrol name attribute value is "helpid",I tried using CreateAttribute() this method but it gives an exception as System.NotSupportedException.

this would be so much easier with Linq to XML, is there any reason you aren't using it?
This is untested code I wrote off the top of my head, but it should be pretty close, it shows how you would use Linq to solve the same problem:
XElement root = XDocument.Load(fileName).Root; //get the root element of the XML document
foreach (var controlElement in root.Descendants("Control").Where(c=>c.Attributes[0] != null && c.Attributes[0].value == helpId)) //get all of the control elements with the appropriate helpid value
{
if (controlElement.Parent == null) continue; // it's always good to be defensive
controlElement.Parent.Attributes.Add("NewAttribute", string.Empty);
}

Related

Serialize object to XML WITHIN a parent element

I've got a WPF C# program and at one point I need to serialize objects to XML. In other places, I've been using this:
TextWriter writer = new StreamWriter(xmlFilePath);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(MYOBJECT_TYPE));
try
{
xmlSerializer.Serialize(writer, MYOBJECT);
}
catch (Exception ex)
{
MessageBox.Show("Exception occured while writing to Xml" + ex.Message);
}
finally
{
writer.Close();
}
This is fantastic, but this means I have to have a different XML file for every object I want to serialize. How do I use this method (with the least amount of modifications) to serialize the object to the XML WITHIN a parent element? That way, when I want to deserialize the object later, I can just find the element that I want, and deserialize everything within that element.
As requested, here is CreateDefaultXml();:
static void CreateDefaultXml()
{
XmlDocument doc = new XmlDocument();
doc.LoadXml("<StoredObjects></StoredObjects>");
XmlNode root = doc.DocumentElement;
try
{
doc.Save(xmlFilePath);
}
catch (Exception ex)
{
Console.WriteLine("Exception occured while creating Xml" + ex.InnerException);
}
}
EDIT:
Currently, this is what I've got (but it throws an exception There was an error generating the XML document.)
if (!File.Exists(xmlFilePath))
CreateDefaultXml();
XDocument doc = XDocument.Load(xmlFilePath);
var element = doc.Descendants("Object").Where(x => x.Attribute("Name").Value.Equals("objectName")).SingleOrDefault();
if (element == null)
{
element = new XElement("Object", new XAttribute("Name", objectName));
doc.Element("StoredObjects").Add(element);
}
XmlWriter writer = element.CreateWriter();
XmlSerializer xmlSerializer = new XmlSerializer(typeof(MYOBJECT_TYPE));
try
{
xmlSerializer.Serialize(writer, MYOBJECT);
}
catch (Exception ex)
{
MessageBox.Show("Exception occured while writing to Xml: " + ex.Message);
}
finally
{
writer.Close();
doc.Save(xmlFilePath);
}
You are trying to serialize directly to some nested XElement inside an XDocument using XmlSerializer. Unfortunately, it seems that, when serializing directly to a LINQ-to-XML XElement using XmlSerializer together with XContainer.CreateWriter(), it is actually necessary to serialize to an empty XDocument thereby creating its root element. (I am not sure why this restriction exists, but it does.) Since this doesn't meet your needs, it is easy to serialize to a temporary XDocument, remove its root node, then add that node to your overall element hierarchy.
Also, when adding an object to your database using an objectName that already has XML data stored, you need to remove the old data.
I refactored your code into extension methods to accomplish this:
public static class XmlExtensions
{
public static T Deserialize<T>(this XContainer element, XmlSerializer serializer = null)
{
using (var reader = element.CreateReader())
{
object result = (serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
if (result is T)
return (T)result;
}
return default(T);
}
public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer = null)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj);
var element = doc.Root;
if (element != null)
element.Remove();
return element;
}
public static XName ContainerElementName { get { return (XName)"Object"; } }
public static XName ContainerAttributeName { get { return (XName)"Name"; } }
public static XElement SetItem<T>(this XDocument doc, string attributeValue, T obj)
{
return doc.SetItem(ContainerElementName, ContainerAttributeName, attributeValue, obj);
}
static XElement SetItem<T>(this XDocument doc, XName containerElementName, XName containerAttributeName, string attributeValue, T obj)
{
if (doc == null || containerElementName == null || containerAttributeName == null || attributeValue == null)
throw new ArgumentNullException();
if (doc.Root == null)
throw new ArgumentException("doc.Root == null");
var container = doc.Root.Elements(containerElementName).Where(e => (string)e.Attribute(containerAttributeName) == attributeValue).SingleOrDefault();
if (container == null)
{
container = new XElement(containerElementName, new XAttribute(containerAttributeName, attributeValue));
doc.Root.Add(container);
}
else
{
// Remove old content.
container.RemoveNodes();
}
var element = obj.SerializeToXElement();
container.Add(element);
return element;
}
public static T GetItem<T>(this XDocument doc, string attributeValue)
{
return doc.GetItem<T>(ContainerElementName, ContainerAttributeName, attributeValue);
}
static T GetItem<T>(this XDocument doc, XName containerElementName, XName containerAttributeName, string attributeValue)
{
if (doc == null || containerElementName == null || containerAttributeName == null || attributeValue == null)
throw new ArgumentNullException();
if (doc.Root == null)
throw new ArgumentException("doc.Root == null");
var container = doc.Root.Elements(containerElementName).Where(e => (string)e.Attribute(containerAttributeName) == attributeValue).SingleOrDefault();
if (container == null)
return default(T);
var element = container.Elements().SingleOrDefault();
if (element == null)
return default(T);
return element.Deserialize<T>();
}
public static XDocument CreateDefaultXDocument()
{
var xml = #"<StoredObjects></StoredObjects>";
return XDocument.Parse(xml);
}
}
Now you can do
doc.AddItem(MYOBJECT, objectName);
And later
var MYOBJECT2 = doc.GetItem<MYOBJECT_TYPE>(objectName);
Example fiddle.

How to compare two FlowDocuments?

I want to compare a FlowDocument to a document of Rich Text Box. Here is the code
if (rtbEditor.Document != (XamlReader.Parse(currentNote.content) as FlowDocument))
{
MessageBox.Show("Overwrite existing Note?", "Save", MessageBoxButton.OKCancel);
}
At the beginning I set rtbEditor's document as
rtbEditor.Document = XamlReader.Parse(currentNote.content) as FlowDocument;
Thus, unless the content of rtbEditor is changed, I thought that the if statement should not execute,but it does. Probably this is not the way to compare FlowDocuments. If this is not the correct way then how can we compare two documents?
If it is necessary, the currentNote.content is a string containing xml content of FlowDocument.
Assuming you have no images in your FlowDocument instances, you can just serialize to XAML and compare the XAML. First, create extension methods to generate the XAML strings:
public static class FrameworkContentElementExtensions
{
public static string ToXaml(this FrameworkContentElement element) // For instance, a FlowDocument
{
if (element == null)
return null;
var sb = new StringBuilder();
using (var xmlWriter = XmlWriter.Create(sb))
{
XamlWriter.Save(element, xmlWriter);
}
return sb.ToString();
}
public static string ToFormattedXamlString(this FrameworkContentElement element)
{
if (element == null)
return null;
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " };
var sb = new StringBuilder();
using (var xmlWriter = XmlWriter.Create(sb, settings))
{
XamlWriter.Save(element, xmlWriter);
}
return sb.ToString();
}
}
Then you can do
if (rtbEditor.Document.ToXaml() != currentNote.content)
{
MessageBox.Show("Overwrite existing Note?", "Save", MessageBoxButton.OKCancel);
}
Note that if the XAML differs only because of cosmetic formatting (XML indentation), since XAML documents are valid XML, you can parse your XAML to an XElement and use XNode.DeepEquals(). You can also serialize a FrameworkContentElement directly to an XElement without the intervening string representation for improved performance:
public static class FrameworkContentElementExtensions
{
public static XElement ToXamlXElement(this FrameworkContentElement element) // For instance, a FlowDocument
{
if (element == null)
return null;
var doc = new XDocument();
using (var xmlWriter = doc.CreateWriter())
{
XamlWriter.Save(element, xmlWriter);
}
var xElement = doc.Root;
if (xElement != null)
xElement.Remove();
return xElement;
}
}
And then
var docXaml = rtbEditor.Document.ToXamlXElement();
var currentNoteXaml = XElement.Parse(currentNote.content);
if (!XNode.DeepEquals(docXaml, currentNoteXaml))
{
MessageBox.Show("Overwrite existing Note?", "Save", MessageBoxButton.OKCancel);
}
If you are concerned there might be embedded messages and want to generate a warning message in this case, see Finding all Images in a FlowDocument.

How I can filter a Xml File with a Attribute?

hi I want to control a xml file... for this i use linq to xml.
private string GetGroup(string xml, string id)
{
XDocument document;
XElement element;
try
{
document = XDocument.Load(xml);
//element = document.Root.Elements("Permissiongroup").FirstOrDefault(e => e.Element("id").Value == id);
element = document.Elements("Permissiongroup").FirstOrDefault(e => e.Element("id").Value == id);
if (element != null)
{
return element.Element("display").Value;
}
else
{
return string.Empty;
}
}
catch (Exception)
{
return null;
}
finally
{
document = null;
element = null;
}
}
here is my xml:
<?xml version="1.0" encoding="iso-8859-1"?>
<Permissiongroup>
<Permission id="Hessen" display="KV-IT" />
<Permission id="Berlin" display="DBG_Update" />
</Permissiongroup>
For example i want if the method is ..
string group = GetGroup(xmlpath, "Hessen");
group is "KV-IT"
there are a few things wrong with what you currently have - you're missing Permission from the query and looking for an element instead of an attribute. The following works, albeit I would split it down to check for the existence of elements (e.g. make sure there is a Permission element, etc.) rather than relying on error handling.
// string group = GetGroup(xmlpath, "Hessen"); // returns KV-IT
// string group2 = GetGroup(xmlpath, "Berlin"); //DBG_Update
private string GetGroup(string xml, string id)
{
XDocument document;
XElement element;
try
{
document = XDocument.Load(xml);
element = document.Elements("Permissiongroup").Elements(("Permission")).FirstOrDefault(t => t.Attribute("id").Value == id);
if (element != null)
{
return element.Attribute("display").Value;
}
else
{
return string.Empty;
}
}
catch (Exception ex)
{
return null;
}
finally
{
document = null;
element = null;
}
}
Use method Attribute() instead of using Element() to access attributes
private string GetGroup(string xml, string id)
{
XDocument document;
XElement element;
try
{
document = XDocument.Load(xml);
//element = document.Root.Elements("Permissiongroup").FirstOrDefault(e => e.Attribute("id").Value == id);
element = document.Elements("Permissiongroup").FirstOrDefault(e => e.Attribute("id").Value == id);
if (element != null)
{
return element.Attribute("display").Value;
}
else
{
return string.Empty;
}
}
catch (Exception)
{
return null;
}
finally
{
document = null;
element = null;
}
}
You can write your xml structure and then can convert xml in to xsd using
http://www.freeformatter.com/xsd-generator.html#ad-output.
Once you have xsd file , you can download jaxb ,which will convert xsd file in to POJO file
and then in your program you can access attributes of an xml like this
JAXBContext jc2 = JAXBContext.newInstance(someclassname.class);
File xml2 = new File(xml_File);
Unmarshaller unmarshaller2 = jc2.createUnmarshaller();
someclassnameObject= (someclassname) unmarshaller2.unmarshal(xml2);
and can use object to use its attributes e.g someclassnameObject.attribute

How can C# look return an XML node with a specific element value?

A commercial application uses XML to hold a list of variables it uses. I do not have control over the format of the XML. I can use any version of .Net.
Trying to write simpler code to assign a UserVar node to an object I've created. Right now I locate the node of the section of UserVars which contains all of the individual UserVars, iterate through each UserVar looking for the element "Name" and then see if it matches my desired variable name.
For example I want the variable "Changed" I will get an AcmeVar object (my creation) with the properties Name and Width set to "Changed" and 1. But I have to manually iterate through the code.
Seems like I'm doing this the hard way. Ideally I'd love to use Linq to return a UserVar node that has the matching element Name. The similar questions on Stackoverflow don't follow a similar pattern or at least not from what I can see. Not all variables use all of the element types.
Sample: XML
<?xml version="1.0" encoding="UTF-8"?>
<Application>
<Vars>
<UserVars>
<UserVar>
<Name>"Quantity"</Name>
<Width>4</Width>
<VarValue>"1"</VarValue>
</UserVar>
<UserVar>
<Name>"Printers"</Name>
<Width>255</Width>
</UserVar>
<UserVar>
<Name>"Changed"</Name>
<Width>1</Width>
</UserVar>
<UserVar>
<Name>"Weight"</Name>
<VarValue>"450.1"</VarValue>
</UserVar>
</UserVars>
</Vars>
</Application>
Current Code:
public static bool GetVariable(string xmlDocNm, string varName, out AcmeVariable acmeVar)
{
// Returns true if found without error
bool result = false;
acmeVar = new AcmeVariable ();
try {
XPathDocument doc = new XPathDocument(xmlDocNm);
XPathNavigator nav = doc.CreateNavigator();
// Compile a standard XPath expression
XPathExpression expr;
expr = nav.Compile(AcmeConst.XPathInternalVariable);
XPathNodeIterator iterator = nav.Select(expr);
// Iterate on the node set
try {
bool variableFound;
bool skipNode;
char[] CharsToTrim = { '\"' }; //
while (iterator.MoveNext()) {
variableFound = false;
skipNode = false;
XPathNavigator nav2 = iterator.Current.Clone();
if (nav2.MoveToFirstChild()) {
// nav2 points to the first element in an UserVar Node
acmeVar = new AcmeVariable (); //Start with a fresh Acme Variable
if (nav2.LocalName == AcmeConst.AttrName) {
variableFound = true;
skipNode = nav2.Value.Trim(CharsToTrim) != varName;
}
if (!skipNode) {
AssignXMLNavNodetoAcmeVar(nav2, acmeVar);
while (nav2.MoveToNext() && !skipNode) {
if (nav2.LocalName == AcmeConst.AttrName) {
variableFound = true;
skipNode = nav2.Value.Trim(CharsToTrim) != varName;
}
AssignXMLNavNodetoAcmeVar(nav2, acmeVar);
}
}
}
if (variableFound && !skipNode) {
result = true;
break; //We have found the variable and collected all elements
}
else {
acmeVar = null;
}
}
}
catch (Exception ex) {
MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
acmeVar = null;
result = false;
}
}
catch (Exception ex) {
MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
acmeVar = null;
result = false;
}
return result;
}
Try this:
var queryValue = "Quantity";
var xDoc = XDocument.Load(#"UserVars.xml");//This is your xml path value
var userVar = xDoc.Descendents("UserVar").Where(x => x.Element("Name").Value == queryValue )
.FirstOrDefault();
var name = userVar.Element("Name").Value ?? string.Empty;
var width = userVar.Element("Width").Value ?? string.Empty;
var varValue = userVar.Element("VarValue").Value ?? string.Empty;
I just want to make comment with your XML, especially in the part where <Name>"Quantity"</Name> element value were enclosed with ""
But if you have no bound with the xml, you just need to escape those ". eg. var queryValue = #""Quantity"";
Assuming that your key is Name, and all nodes will contain that, then this should work:
string valImLookingFor = "\"Changed\"";
XDocument doc = XDocument.Load("file"); // or XDocument doc = XDocument.Parse(xmlString);
var node = doc.Descendants("UserVar").Where(x => x.Element("Name").Value == valImLookingFor).First();
That should get you your node, then you can pull out the subnodes values you need.

Foreach loop XmlNodeList

Currently I have the following code:
XmlDocument xDoc = new XmlDocument();
xDoc.Load("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=twitter");
XmlNodeList tweets = xDoc.GetElementsByTagName("text");
foreach (int i in tweets)
{
if (tweets[i].InnerText.Length > 0)
{
MessageBox.Show(tweets[i].InnerText);
}
}
Which doesn't work, it gives me System.InvalidCastException on the foreach line.
The following code works perfectly (no foreach, the i is replaced with a zero):
XmlDocument xDoc = new XmlDocument();
xDoc.Load("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=twitter");
XmlNodeList tweets = xDoc.GetElementsByTagName("text");
if (tweets[0].InnerText.Length > 0)
{
MessageBox.Show(tweets[0].InnerText);
}
I know that there is already a marked answer, but you can do it like you did in your first try, you just need to replace the int with XmlNode
XmlDocument xDoc = new XmlDocument();
xDoc.Load("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=twitter");
XmlNodeList tweets = xDoc.GetElementsByTagName("text");
foreach (XmlNode i in tweets)
{
if (i.InnerText.Length > 0)
{
MessageBox.Show(i.InnerText);
}
}
tweets is a node list. I think that what you're trying to do is this:
XmlDocument xDoc = new XmlDocument();
xDoc.Load("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=twitter");
XmlNodeList tweets = xDoc.GetElementsByTagName("text");
for (int i = 0; i < tweets.Count; i++)
{
if (tweets[i].InnerText.Length > 0)
{
MessageBox.Show(tweets[i].InnerText);
}
}
It is not of Int type, That is the reason you are getting a casting exception. You can either replace int with the appropriate type or simply make use of type inference (implicitly typed variables) to handle this. Here i am using typeinference.by saying type as var, The compiler will understand it is of type of the iterator variable in tweets collection
foreach (var i in tweets)
{
if (i!=null)
{
string tweet= (((System.Xml.XmlElement)(i))).InnerText;
MessageBox.Show(tweet);
}
}
EDIT : With the Wonderful LINQtoXML, Your code can be rewritten like this.
string url = "http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=twitter";
XElement elm = XElement.Load(url);
if (elm != null)
{
foreach (var status in elm.Elements("status"))
{
string tweet = status.Element("text").Value;
MessageBox.Show(ss);
}
}
All the answers seem to be a bit outdated Imperative examples so I will add a declarative one. This is not doing what the OP wanted but I'm sure you'll get the point.
public static List<System.Xml.XmlNode> toList(System.Xml.XmlNodeList nodelist){
List<System.Xml.XmlNode> nodes = new List<System.Xml.XmlNode>();
foreach (System.Xml.XmlNode node in nodelist)
{
nodes.Add(node);
}
return nodes;
}
public static ReadMeObject setXml(ReadMeObject readmeObject){
readmeObject.xmlDocument = new System.Xml.XmlDocument();
readmeObject.xmlDocument.LoadXml("<body>"+readmeObject.htmlStringContent+"</body>");
System.Xml.XmlNodeList images = readmeObject.xmlDocument.SelectNodes("//img");
Array.ForEach(
Functions.toList( images )
.Where((image) => image.Attributes != null)
.Where((image) => image.Attributes["src"] != null)
.Where((image) => image.Attributes["src"].Value != "")
.ToArray()
, (image) => {
Console.WriteLine(image.Attributes["src"].Value);
}
);
return readmeObject;
}
foreach (XmlNode node in tweets)
{
if (tweets[i].InnerText.Length > 0)
{
MessageBox.Show(tweets[node].InnerText);
}
}
I've changed the 'I', which you cannot use, to XmlNode, which selects a single line of your list.
You can loop through the Collection with .GetEnumerator()
this code is taken Microsoft Documentation :
XmlNodeList elemList = root.GetElementsByTagName("title");
IEnumerator ienum = elemList.GetEnumerator();
while (ienum.MoveNext()) {
XmlNode title = (XmlNode) ienum.Current;
Console.WriteLine(title.InnerText);
}
Use this simple extension method to iterate through XmlNodeList:
public static void ForEachXml<TXmlNode>(this XmlNodeList nodeList, Action<TXmlNode> action)
{
foreach (TXmlNode node in nodeList) action(node);
}
Method Call:
xDoc.GetElementsByTagName("text").ForEachXML<XmlNode>(tweet =>
{
if (tweet.InnerText.Length > 0)
MessageBox.Show(tweet.InnerText);
});

Categories