What is the easiest way of uncommenting the body of some node in XML? The elements have unique name, the structure of the documents look as follows:
somefile.xml
<?xml version="1.0"?>
<name1>
<irrelevant1>
<irrelevant2>
<!--
<irrelevant3 />
-->
</irrelevant2>
</irrelevant1>
<name2>
<name3>
<!--
<name4 field="The" />
<name4 field="Owls" />
<name4 field="Are />
<name4 field="Not" />
<name4 field="What" />
<name4 field="They" />
<name4 field="Seem />
-->
</name3>
</name2>
</name1>
The goal should look like this, with comments removed:
uncommented.xml
<?xml version="1.0"?>
<name1>
<irrelevant1>
<irrelevant2>
<!--
<irrelevant3 />
-->
</irrelevant2>
</irrelevant1>
<name2>
<name3>
<name4 field="The" />
<name4 field="Owls" />
<name4 field="Are />
<name4 field="Not" />
<name4 field="What" />
<name4 field="They" />
<name4 field="Seem />
</name3>
</name2>
</name1>
My approach to parsing:
XmlDocument xdoc = new XmlDocument();
xdoc.Load(#"C:\somefile.xml");
XmlNodeList nl = xdoc.GetElementsByTagName("name2");
XmlNode xn = nl[0];
string xn_content = xn.InnerXml;
xn_content = Regex.Replace(xn_content, "<!--|-->", String.Empty);
XmlDocument doc = new XmlDocument();
doc.LoadXml(xn_content);
XmlNode newNode = doc.DocumentElement;
// this import doesn't really help
xdoc.ImportNode(newNode, true);
xn.RemoveAll();
xn.AppendChild(newNode);
xdoc.Save(#"C:\uncommented.xml");
Results with the ArgumentException:
{"The node to be inserted is from a different document context."}
Your immediate problem is that you call XmlDocument.ImportNode() but do not use the returned node. You need to do newNode = xDoc.ImportNode(newNode, true);.
However, a cleaner way to do this would be to avoid the Regex parsing entirely. Instead, descend the XmlNode hierarchy, pick out the XmlComment nodes you wish to uncomment, load their InnerText into an XmlDocumentFragment, then add its newly created children to the parent node of the comment:
public static class XmlNodeExtensions
{
public static XmlDocument Document(this XmlNode node)
{
for (; node != null; node = node.ParentNode)
{
var doc = node as XmlDocument;
if (doc != null)
return doc;
}
return null;
}
public static IEnumerable<XmlNode> AncestorsAndSelf(this XmlNode node)
{
for (; node != null; node = node.ParentNode)
yield return node;
}
public static IEnumerable<XmlNode> DescendantsAndSelf(this XmlNode root)
{
if (root == null)
yield break;
yield return root;
foreach (var child in root.ChildNodes.Cast<XmlNode>())
foreach (var subChild in child.DescendantsAndSelf())
yield return subChild;
}
public static void UncommentXmlNodes(IEnumerable<XmlComment> comments)
{
foreach (var comment in comments.ToList())
UncommentXmlNode(comment);
}
public static void UncommentXmlNode(XmlComment comment)
{
if (comment == null)
throw new NullReferenceException();
var doc = comment.Document();
if (doc == null)
throw new InvalidOperationException();
var parent = comment.ParentNode;
var innerText = comment.InnerText;
XmlDocumentFragment docFrag = doc.CreateDocumentFragment();
//Set the contents of the document fragment.
docFrag.InnerXml = innerText;
XmlNode insertAfter = comment;
foreach (var child in docFrag.ChildNodes.OfType<XmlElement>().ToList())
{
insertAfter = parent.InsertAfter(child, insertAfter);
}
parent.RemoveChild(comment);
}
}
Then call it like:
string xml = #"<?xml version=""1.0""?>
<name1>
<irrelevant1>
<irrelevant2>
<!--
<irrelevant3 />
-->
</irrelevant2>
</irrelevant1>
<name2>
<name3>
<!--
<name4 field=""The"" />
<name4 field=""Owls"" />
<name4 field=""Are"" />
<name4 field=""Not"" />
<name4 field=""What"" />
<name4 field=""They"" />
<name4 field=""Seem"" />
-->
</name3>
</name2>
</name1>
";
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xml);
Debug.WriteLine(xmlDoc.ToXml());
XmlNodeExtensions.UncommentXmlNodes(xmlDoc.DocumentElement.DescendantsAndSelf().OfType<XmlComment>().Where(c => c.ParentNode.Name == "name3"));
Debug.WriteLine(xmlDoc.ToXml());
Note that your commented XML is invalid. <name4 field="Are /> should be <name4 field="Are"/> and <name4 field="Seem /> should be <name4 field="Seem"/>. I fixed that for you in the test case since I assume it's a typo.
Related
I have this XML file:
<tree>
<grand name ="tom" id="1" sex="m" status="d" child="2" father="" />
<grand name="adam" id="11" sex="m" status="d" child="1" father="1" />
<grand name="john" id="111" sex="m" status="d" child="1" father="11" />
<grand name="pierre" id="1111" sex="m" status="d" child="3" father="111" />
<grand name="jan" id="11111" sex="f" status="d" child="" father="1111" />
<grand name="marc" id="11112" sex="m" status="d" child="" father="1111" />
</tree>
I try this code to display attribute of the first node only on load the form:
private void Form1_Load(object sender, EventArgs e)
{
XmlDocument XDoc = new XmlDocument();
XDoc.Load("F:\\tree.xml");
XmlNode att = XDoc.SelectSingleNode("//grand/#name");
string nam = att.ToString();
label1.Text = att;
}
But I get nothing.
Thank you very much.
You can do it using linq and XDocument:
XDocument doc = XDocument.Load("F:\\tree.xml");
var result= xdoc.Descendants("grand").First().Attribute("name").Value;
label1.Text = result;
or filter on id attribute this way.
you have to include System.Xml.Linq namespace for it.
I am trying to automate standalone.xml of JBoss-AS-7.1.1 from another module of my project.
Following is the part of XML im trying to update
</subsystem>
<subsystem xmlns="urn:jboss:domain:configadmin:1.0" />
<subsystem xmlns="urn:jboss:domain:datasources:1.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
</driver>
<driver name="mysql" module="com.mysql.jdbc">
<xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
</driver>
</drivers>
</datasources>
</subsystem>
<subsystem xmlns="urn:jboss:domain:deployment-scanner:1.1">
After updating correctly it would look like
<subsystem xmlns="urn:jboss:domain:configadmin:1.0" />
<subsystem xmlns="urn:jboss:domain:datasources:1.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
</driver>
<driver name="mysql" module="com.mysql.jdbc">
<xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
</driver>
</drivers>
<datasource jndi-name="java:/session-tracking-dataSource-orcl" pool-name="session-tracking-dataSource-orcl" enabled="true" use-java-context="true">
<connection-url>jdbc:mysql://127.0.0.1:3306//fusion?useUnicode=true&characterEncoding=UTF-8&</connection-url>
<connection-property name="charSet">UTF-8</connection-property>
<driver>mysql</driver>
<pool>
<max-pool-size>10</max-pool-size>
<min-pool-size>1</min-pool-size>
</pool>
<security>
<user-name>root</user-name>
<password>root</password>
</security>
<timeout>
<idle-timeout-minutes>1</idle-timeout-minutes>
<query-timeout>5</query-timeout>
</timeout>
</datasource>
</datasources>
</subsystem>
<subsystem xmlns="urn:jboss:domain:deployment-scanner:1.1">
So here goes my piece of code.
XMLObj.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
namespace XMLOpsHelper
{
class XMLObj
{
private string name;
private List<Object> value;
private Dictionary<string, string> attr;
private XMLContext context;
public XMLObj(string name, XMLContext context)
{
this.name = name;
this.context = context;
value = new List<Object>();
attr = new Dictionary<string, string>();
}
public void addAttribute(string key, string value)
{
this.attr.Add(key, value);
}
public void assignValue(Object val)
{
if ((val.GetType() == typeof(String)) || (val.GetType() == typeof(string)) || (val.GetType() == typeof(XMLObj)))
{
this.value.Add(val);
}
}
// for removing empty xmlns
public Object _toString()
{
XmlNode newNode = context.Doc.CreateNode(XmlNodeType.Element, this.name, context.XmlNS);
foreach (KeyValuePair<string, string> entry in this.attr)
{
XmlAttribute attr = context.Doc.CreateAttribute(entry.Key);
attr.Value=entry.Value;
newNode.Attributes.Append(attr);
}
foreach (Object item in this.value)
{
if (item is string)
{
newNode.InnerText=(string)item;
}
if (item is XMLObj)
{
Object nextItem = ((XMLObj)item)._toString();
if (nextItem is XmlNode)
{
newNode.AppendChild((XmlNode)nextItem);
}
}
}
return newNode;
}
}
}
Little background on the XMLObj class: For every tag new instance this class will be created. If the appended innerXML/innerText will go inside value list. The _toString (temporary name) method to be called later on the root object to translate it to tags.
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
namespace XMLOpsHelper
{
class XMLUpdater
{
XmlDocument doc = new XmlDocument();
XmlNode root;
public XMLUpdater(string xmlStr) {
doc.LoadXml(xmlStr);
}
public XMLUpdater(XmlNode xmlNode)
{
doc.LoadXml(xmlNode.OuterXml);
}
public XMLUpdater(XmlNode xmlNode, XmlNode root)
{
doc.LoadXml(xmlNode.OuterXml);
this.root = root;
}
public void appendChild(XmlNode parentNode){
XmlNode importNode = root.OwnerDocument.ImportNode(doc.DocumentElement, true);
parentNode.AppendChild(importNode);
}
public XmlDocument getDoc() {
return this.doc;
}
}
}
Once XMLObject hierarchy is created and translated to tags XMLUpdater place it in appropriate position by calling appendChild.
And the end result is
<subsystem xmlns="urn:jboss:domain:configadmin:1.0" />
<subsystem xmlns="urn:jboss:domain:datasources:1.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
</driver>
<driver name="mysql" module="com.mysql.jdbc">
<xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
</driver>
</drivers>
<datasource jndi-name="java:/session-tracking-dataSource-orcl" pool-name="session-tracking-dataSource-orcl" enabled="true" use-java-context="true" xmlns="urn:jboss:domain:datasources:1.0">
<connection-url>jdbc:mysql://127.0.0.1:3306//fusion?useUnicode=true&characterEncoding=UTF-8&</connection-url>
<connection-property name="charSet">UTF-8</connection-property>
<driver>mysql</driver>
<pool>
<max-pool-size>10</max-pool-size>
<min-pool-size>1</min-pool-size>
</pool>
<security>
<user-name>root</user-name>
<password>root</password>
</security>
<timeout>
<idle-timeout-minutes>1</idle-timeout-minutes>
<query-timeout>5</query-timeout>
</timeout>
</datasource>
</datasources>
</subsystem>
<subsystem xmlns="urn:jboss:domain:deployment-scanner:1.1">
XMLNS is getting appended at the end of the following line which is not expected since subsystem already has it
<datasource jndi-name="java:/session-tracking-dataSource-orcl" pool-name="session-tracking-dataSource-orcl" enabled="true" use-java-context="true" xmlns="urn:jboss:domain:datasources:1.0">
Any idea what i am doing wrong?
I'm trying to write a routine that updates values in an XML file and believe I'm close. Here's an example of the XML:
<?xml version="1.0" encoding="utf-8"?>
<!-- This file is generated by the GPS_Client program. -->
<Sites>
<Site>
<Id>A</Id>
<add key="landingName" value="Somewhere" />
<add key="landingLat" value="47.423719" />
<add key="landingLon" value="-123.011364" />
<add key="landingAlt" value="36" />
</Site>
<Site>
<Id>B</Id>
<add key="landingName" value="Somewhere Else" />
<add key="landingLat" value="45.629160" />
<add key="landingLon" value="-128.882934" />
<add key="landingAlt" value="327" />
</Site>
</Sites>
The key is that I need to update a specific key without updating the rest of the keys with the same name. Here's the current code:
private void UpdateOrCreateAppSetting(string filename, string site, string key, string value)
{
string path = "\"#" + filename + "\"";
XDocument doc = XDocument.Load(path);
var list = from appNode in doc.Descendants("appSettings").Elements()
where appNode.Attribute("key").Value == key
select appNode;
var e = list.FirstOrDefault();
// If the element doesn't exist, create it
if (e == null) {
new XElement(site,
new XAttribute(key, value));
// If the element exists, just change its value
} else {
e.Element(key).Value = value;
e.Attribute("value").SetValue(value);
}
}
I assume I need to concatenate site, Id and key in some way, but don't see how it's done.
Using this XmlLib, you can have it create the node if it doesn't exist automatically.
private void UpdateOrCreateAppSetting(string filename, string site,
string key, string value)
{
string path = "\"#" + filename + "\"";
XDocument doc = XDocument.Load(path);
XPathString xpath = new XPathString("//Site[Id={0}]/add[#key={1}]", site, key);
XElement node = doc.Root.XPathElement(xpath, true); // true = create if not found
node.Set("value", value, true); // set as attribute, creates attribute if not found
doc.Save(filename);
}
I've been trying to select some XML comments like so:
XDocument doc = XDocument.Load(args[0]);
var comments = from node in doc.Elements().DescendantNodesAndSelf()
where node.NodeType == XmlNodeType.Comment
select node as XComment;
With this solution I'm getting all xml comment of the file, but I want to select only those comments and create XElement with it:
<Connections>
...
<!-- START Individual Account Authentication -->
<!--<authentication mode="None"/>
<roleManager enabled="false"/>
<profile enabled="false"/>-->
<!-- END Individual Account Authentication -->
...
</Connections>
Any solutions ? :S
Here is a sample:
XDocument doc = XDocument.Load("input.xml");
foreach (XComment start in doc.DescendantNodes().OfType<XComment>().Where(c => c.Value.StartsWith(" START")).ToList())
{
XComment end = start.NodesAfterSelf().OfType<XComment>().FirstOrDefault(c => c.Value.StartsWith(" END"));
if (end != null)
{
foreach (XComment comment in end.NodesBeforeSelf().OfType<XComment>().Intersect(start.NodesAfterSelf().OfType<XComment>()).ToList())
{
comment.ReplaceWith(XElement.Parse("<dummy>" + comment.Value + "</dummy>").Nodes());
}
// if wanted/needed
start.Remove();
end.Remove();
}
}
doc.Save("output.xml");
That gives me
<Connections>
...
<authentication mode="None" /><roleManager enabled="false" /><profile enabled="false" />
...
</Connections>
I have a .net web application that uses XmlDocument to load an XML String. The format of the XML is as follows:
<Sections name="Section Opening Page" PageContentID="201" Template="ReportTags">
<Chapter Name="Introduction" PageContentID="202" Template="ReportTags">
<Pages Name="Why this Document?" PageContentID="203" Template="ReportTags" />
<Pages Name="Target Audience" PageContentID="204" Template="ReportTags" />
</Chapter>
<Chapter Name="Detailed Results" PageContentID="205" Template="ReportTags">
<Pages Name="Question List" PageContentID="206" Template="ReportTags" />
<Pages Name="Answers" PageContentID="207" Template="ReportTags" />
<Pages Name="Comments" PageContentID="208" Template="ReportTags" />
</Chapter>
<Chapter Name="Appendix 1" PageContentID="209" Template="ReportTags">
<Pages Name="Page 1" PageContentID="210" Template="ReportTags" />
<Pages Name="Page 2" PageContentID="211" Template="ReportTags" />
<Pages Name="Page 3" PageContentID="212" Template="ReportTags" />
</Chapter>
<Chapter Name="Appendix 2" PageContentID="213" Template="ReportTags">
<Pages Name="Page 1" PageContentID="214" Template="ReportTags" />
<Pages Name="Page 2" PageContentID="215" Template="ReportTags" />
</Chapter>
</Sections>
As an example, I am trying to insert a NEW NODE under the 'Chapter' named 'Detailed Results' BETWEEN 'Page' named 'Question List' and 'Answers'. The code I am using to do this is as follows:
try
{
string sPath = "Sections/Chapter/Pages[#PageContentID='" + selectedNode.Value + "']";
XmlNode xmlNode = xmlDoc.SelectSingleNode(sPath);
XmlElement xNewChild = xmlDoc.CreateElement("Pages");
xNewChild.SetAttribute("Name", "Another New Page");
xNewChild.SetAttribute("PageContentID", Guid.NewGuid().ToString());
xNewChild.SetAttribute("Template", "ReportTags");
xmlDoc.DocumentElement.InsertAfter(xNewChild, xmlNode);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
However, it is not working, my new node is not inserted into the proper place, it is inserted at the top.
Any help would be greatly appreciated!!
Are you sure that xmlNode is returning the node you think it is? Step through it with the debugger.
If it is null, then the node you insert will get added as a child of the DocumentElement as you describe. If it is returning the Pages node, then you should get an exception 'The reference node is not a child of this node'.
This is fairly self explanatory. The code:
doc.DocumentElement.InsertAfter(xNewChild, xmlNode);
should be:
xmlNode.ParentNode.InsertAfter(xNewChild, xmlNode);
This said, if possible I'd be using XDocument, a part of XLinq. The API's a lot nicer & more powerful, and is generally higher performance. An example:
var element = x.Descendants("Pages").Single(e => e.Attribute("PageContentID").Value == "206");
var newElement = new XElement("Pages",
new XAttribute("Name", "Another New Page"),
new XAttribute("PageContentID", Guid.NewGuid().ToString()),
new XAttribute("Template", "ReportTags"));
element.AddAfterSelf(newElement);
If you don't mind using Linq-To-Xml
XDocument xmlDocument = XDocument.Load("asd.xml");
try
{
var root = xmlDocument.Root;
var pages = root.Elements("Chapter").Elements("Pages");
var mypage = pages.Where(p => p.Attribute("PageContentID").Value == "206").FirstOrDefault();
XElement xNewChild = new XElement("Pages", new XAttribute("Name", "Another New Page"), new XAttribute("PageContentID", Guid.NewGuid().ToString()), new XAttribute("Template", "ReportTags"));
mypage.AddAfterSelf(xNewChild);
xmlDocument.Save("asd.xml");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
It's const value 206 put in the code, modify to your needs
After execution
"That would probably work, but i am not using Linq for this project. LOL, maybe I should be!!!!!"
So try this, you have to call insertAfter on xmlNode's parent node
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load("asd.xml");
try
{
string sPath = "//Pages[#PageContentID=\"206\"]";
var xmlNode = xmlDocument.SelectSingleNode(sPath);
XmlElement xNewChild = xmlDocument.CreateElement("Pages");
xNewChild.SetAttribute("Name", "Another New Page");
xNewChild.SetAttribute("PageContentID", Guid.NewGuid().ToString());
xNewChild.SetAttribute("Template", "ReportTags");
xmlNode.ParentNode.InsertAfter(xNewChild, xmlNode);
xmlDocument.Save("asd.xml");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}