I am trying to update the content of multiple nodes that match a specific xpath. For example, in the below example, I want to update any attribute1 node that currently is '2' with 'Hello'. What is the best way to do this?
(DotNetFiddle Here)
using System;
using System.IO;
using System.Xml;
using System.Linq;
public class Program
{
public static void Main()
{
XmlDocument job = new XmlDocument();
job.LoadXml(#"<parent>" +
"<report>" +
"<main>" +
"<attribute1>2</attribute1>"+
"<attribute1>2</attribute1>"+
"<attribute1>3</attribute1>"+
"</main>"+
"</report>" +
"</parent>");
string newAttribute1Value = "Hello";
//How do I update both attribute1's where value=2?
}
This may could help you :
using System;
using System.IO;
using System.Xml;
using System.Linq;
public class Program
{
public static void Main()
{
XmlDocument job = new XmlDocument();
job.LoadXml(#"<parent>" +
"<report>" +
"<main>" +
"<attribute1>2</attribute1>"+
"<attribute1>2</attribute1>"+
"<attribute1>3</attribute1>"+
"</main>"+
"</report>" +
"</parent>");
string newAttribute1Value = "Hello";
//How do I update both attribute1's where value=2?
// getting the list of nodes with XPath query :
XmlNodeList nodes = job.SelectNodes("//attribute1[text()=2]");
foreach (XmlNode child in nodes)
{
// replacing the old value with the new value
child.InnerText = newAttribute1Value ;
}
}
I have tried to setup a Dialogue tree within unity using XML (I have not used XML much before so am unsure if the way i am going is correct at all)
So I am trying to get the first text element from this dialogue tree but when i call the XML file and say where it is i am getting the everything stored in that branch.
Am i using the correct .XML to be able to do this also as i seen people say use .XML.LINQ or .XML.Serialization not just .XML is this correct for my case ??
Code:
using UnityEngine;
using System.Collections;
using System.IO;
using System.Xml;
using UnityEngine.UI;
using System.Collections.Generic;
public class DialogTree
{
public string text;
public List<string> dialogText;
public List<DialogTree> nodes;
public void parseXML(string xmlData)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(new StringReader(xmlData));
XmlNode node = xmlDoc.SelectSingleNode("dialoguetree/dialoguebranch");
text = node.InnerXml;
XmlNodeList myNodeList = xmlDoc.SelectNodes("dialoguebranch/dialoguebranch");
foreach (XmlNode node1 in myNodeList)
{
if (node1.InnerXml.Length > 0)
{
DialogTree dialogtreenode = new DialogTree();
dialogtreenode.parseXML(node1.InnerXml);
nodes.Add(dialogtreenode);
}
}
}
}
And here is a picture of the XML.
So i am trying to grab the first element of text then late on there response it will go to branch 1 or 2
<?xml version='1.0'?>
<dialoguetree>
<dialoguebranch>
<text>Testing if the test prints</text>
<dialoguebranch>
<text>Branch 1</text>
<dialoguebranch>
<text>Branch 1a</text>
</dialoguebranch>
<dialoguebranch>
<text>Branch 1b</text>
</dialoguebranch>
</dialoguebranch>
<dialoguebranch>
<text>Branch 2</text>
</dialoguebranch>
</dialoguebranch>
</dialoguetree>
You're getting everything in that branch because XmlNode.InnerXML returns everything in that node. See the documentation for more information on that.
You should use the branch as the base for only looking at its children, instead of starting at xmlDoc every time. Also, you need an entry point to get inside of the first dialoguetree element and then ignore that. Finally, I would only create one XmlDocument and just pass around nodes in your recursion.
Altogether, this might look like this:
public class DialogTree
{
public string text;
public List<DialogTree> nodes = new List<DialogTree>();
public static DialogTree ParseXMLStart(string xmlData)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(new Stringreader(xmlData));
XmlNode rootNode = xmlDoc.SelectSingleNode("dialoguetree/dialoguebranch");
DialogTree dialogTree = new DialogTree();
dialogTree.ParseXML(rootNode);
return dialogTree;
}
public void ParseXML(XmlNode parentNode)
{
XmlNode textNode = parentNode.SelectSingleNode("text");
text = textNode.InnerText;
XmlNodeList myNodeList = parentNode.SelectNodes("dialoguebranch");
foreach (XmlNode curNode in myNodeList)
{
if (curNode.InnerXml.Length > 0)
{
DialogTree dialogTree = new DialogTree();
dialogTree.ParseXML(curNode);
nodes.Add(dialogTree);
}
}
}
}
And you could use it like so:
string xmlStringFromFile;
DialogTree dialogue = DialogTree.ParseXMLStart(xmlStringFromFile);
All of this code is untested but I hope the general idea is clear. Let me know if you find any errors in the comments below and I will try to fix them.
The XML file is like this,There are about 20 Nodes(modules) like this.
<list>
<module code="ECSE502">
<code>ECSE502</code>
<name>Algorithms and Data structures</name>
<semester>1</semester>
<prerequisites>none</prerequisites>
<lslot>0</lslot>
<tslot>1</tslot>
<description>all about algorythms and data structers with totorials and inclass tests</description>
</module>
</list>
I did the following code. But when I debugged it it even didn't went inside to foreach function.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
namespace ModuleEnrolmentCW
{
class XMLRead
{
public string[] writeToXML(string s)
{
string text = s;
string[] arr = new string[6];
XmlDocument xml = new XmlDocument();
xml.Load("modules.xml");
XmlNodeList xnList = xml.SelectNodes("list/module[#code='" + text + "']");
foreach (XmlNode xn in xnList)
{
arr[0] = xn.SelectSingleNode("code").InnerText;
arr[1] = xn.SelectSingleNode("name").InnerText;
arr[2] = xn.SelectSingleNode("semester").InnerText;
arr[3] = xn.SelectSingleNode("prerequisites").InnerText;
arr[4] = xn.SelectSingleNode("lslot").InnerText;
arr[5] = xn.SelectSingleNode("tslot").InnerText;
}
return arr;
}
}
}
Please tell me where is the wrong??
Here is the rest of the code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ModuleEnrolmentCW
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
string selected;
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
XMLRead x = new XMLRead();
selected = (string)listBox1.SelectedItem;
string[] arr2 = x.writeToXML(selected);
label11.Text = arr2[0];
}
}
}
Make sure you are specifying correct path for your xml file.
It is working for me.
This line:
XmlNodeList xnList = xml.SelectNodes("list/module[#code='" + text + "']");
should read:
XmlNodeList xnList = xml.SelectNodes("list/module"); //Does not answer full scope of the question
Edit following a reread of the question:
The OP's code works fine in my tests. Either the file path is not correct, or the the string s passed into text matches the case of the Code value by which you are reading the nodes.
The SelectNodes XPath as you have it is case sensitive.
You appear to be working with XPath V1.0 which doesn't appear to support out of the box case insensitivity if that's a issue. See this link for a way to perform case insensitive XPath searches: http://blogs.msdn.com/b/shjin/archive/2005/07/22/442025.aspx
See also this link: case-insensitive matching in xpath?
Your code is correct, if the input is really the one you shown, and s point to an actual present code. Since you are pointing the file by a relative path, ensure you are loading the file you really expect.
Found the error. I was passing a wrong value to writeToXML method. Insted of passing code, I have passed name
Is there a way to sign an XML file with RSA and to have the namespace prefix "ds:Signature" instead of "Signature"? I spent many hourstrying to solve this and from what I can see there is no solution.
It seems that it is hard-coded in the class System.Security.Cryptography.Xml.Signature.
XmlElement element = document.CreateElement("Signature", "http://www.w3.org/2000/09/xmldsig#");
If anyone knows a solution, I need to sign it like that cause the software importing it verifies it with "ds:signature", so with "ds" prefix the software verifies it like this:
public static bool VerifySignature(XmlDocument doc, RSA key, string prefix)
{
SignedXml xml = new SignedXml(doc);
string str = "Signature";
if (!string.IsNullOrEmpty(prefix))
{
str = string.Format("{0}:{1}", prefix, str);
}
XmlNodeList elementsByTagName = doc.GetElementsByTagName(str);
xml.LoadXml((XmlElement)elementsByTagName[0]);
return xml.CheckSignature(key);
}
VerifySignature(xmlDoc, rsa, "ds");
normally it signs like this:
<kk>blabla<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue>rVL2nKjPTBhL9IDHYpu69OiE8gI=</DigestValue></Reference></SignedInfo><SignatureValue>CfXW9D/ErmHjzxIjy0/54/V3nst6j/XXcu7keR17LApfOZEpxjEvAlG3VnBZIi3jxQzU6t9RkmfDyngcRZccJByuuA6YDwFTQxZNRgu2GRoZxMKWnkm+MtQ0jH0Fo78GivCxV+iIewZvsrUQLzG01cXuZSH/k2eeMUaEooJaLQiYpO2aNVn5xbosTPtGlsACzFWz34E69/ZeeLZbXLc3jpDO+opxdYJ5e+Tnk/UM2Klt+N+m7Gh/sUNTPgkDiwP3q3y3O9tvCT0G2XmQaWBP4rw9TIoYHQtucm2b8R2JeggbeRKOetbRYV218RT8CK2Yuy0FIUlQXdabKyp9F96Yc55g8eNe10FGtgietH2iqquIVFLCA8fu3SZNLDPMoyHnVNKdBvI35+S8hrAaybEkMvo7iYnUSY5KrlGSfGGtfQXdaISutAzcnGPDFXgZXPNzNy7eL0u+Lt3yWWkj7wh6Zeh4fH2+nXDWYCWbLpegAEX4ZWSI5Ts6D1TplMJTGH1F0GyflehH4u+W4Lc3TvkB4dWjEuiKgnpl3hcvoj2CWFaeAxXMd/64tU/YMm8+1gSBjkVH6oV+QlI/m0z6M8FPVEVC2as0wLG2woVwmzVLcaQKyPi7NN4eO9ea7QNfaRHaofU4LQO/Y3FNJOP+uMfYlGJKWSr3qv29+BQjeNldNJY=</SignatureValue></Signature></kk>
and I need it to do it like this:
<kk>blabla<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><ds:Reference URI=""><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><ds:DigestValue>rVL2nKjPTBhL9IDHYpu69OiE8gI=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>CfXW9D/ErmHjzxIjy0/54/V3nst6j/XXcu7keR17LApfOZEpxjEvAlG3VnBZIi3jxQzU6t9RkmfDyngcRZccJByuuA6YDwFTQxZNRgu2GRoZxMKWnkm+MtQ0jH0Fo78GivCxV+iIewZvsrUQLzG01cXuZSH/k2eeMUaEooJaLQiYpO2aNVn5xbosTPtGlsACzFWz34E69/ZeeLZbXLc3jpDO+opxdYJ5e+Tnk/UM2Klt+N+m7Gh/sUNTPgkDiwP3q3y3O9tvCT0G2XmQaWBP4rw9TIoYHQtucm2b8R2JeggbeRKOetbRYV218RT8CK2Yuy0FIUlQXdabKyp9F96Yc55g8eNe10FGtgietH2iqquIVFLCA8fu3SZNLDPMoyHnVNKdBvI35+S8hrAaybEkMvo7iYnUSY5KrlGSfGGtfQXdaISutAzcnGPDFXgZXPNzNy7eL0u+Lt3yWWkj7wh6Zeh4fH2+nXDWYCWbLpegAEX4ZWSI5Ts6D1TplMJTGH1F0GyflehH4u+W4Lc3TvkB4dWjEuiKgnpl3hcvoj2CWFaeAxXMd/64tU/YMm8+1gSBjkVH6oV+QlI/m0z6M8FPVEVC2as0wLG2woVwmzVLcaQKyPi7NN4eO9ea7QNfaRHaofU4LQO/Y3FNJOP+uMfYlGJKWSr3qv29+BQjeNldNJY=</ds:SignatureValue></ds:Signature></kk>
if anyone know a solution, i need to sign it like that cause the software importing it verifies it with "ds:signature" , so with "ds" prefix
The prefix should be unimportant - all that should matter is what namespace the element is in. It shouldn't matter how that namespace is expressed. If it does, that shows brokenness in the verifying code, I'd say.
However, if you really want to do this, is there any reason you don't want to just replace the element with one with the same contents, but using the prefix you want? It shouldn't be hard to do that in LINQ to XML.
i found the solution here
using System;
using System.Reflection;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Text;
using System.Xml;
namespace mysign
{
public class PrefixedSignedXML : SignedXml
{
public PrefixedSignedXML(XmlDocument document)
: base(document)
{ }
public PrefixedSignedXML(XmlElement element)
: base(element)
{ }
public PrefixedSignedXML()
: base()
{ }
public void ComputeSignature(string prefix)
{
this.BuildDigestedReferences();
AsymmetricAlgorithm signingKey = this.SigningKey;
if (signingKey == null)
{
throw new CryptographicException("Cryptography_Xml_LoadKeyFailed");
}
if (this.SignedInfo.SignatureMethod == null)
{
if (!(signingKey is DSA))
{
if (!(signingKey is RSA))
{
throw new CryptographicException("Cryptography_Xml_CreatedKeyFailed");
}
if (this.SignedInfo.SignatureMethod == null)
{
this.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
}
}
else
{
this.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
}
}
SignatureDescription description = CryptoConfig.CreateFromName(this.SignedInfo.SignatureMethod) as SignatureDescription;
if (description == null)
{
throw new CryptographicException("Cryptography_Xml_SignatureDescriptionNotCreated");
}
HashAlgorithm hash = description.CreateDigest();
if (hash == null)
{
throw new CryptographicException("Cryptography_Xml_CreateHashAlgorithmFailed");
}
this.GetC14NDigest(hash, prefix);
this.m_signature.SignatureValue = description.CreateFormatter(signingKey).CreateSignature(hash);
}
public XmlElement GetXml(string prefix)
{
XmlElement e = this.GetXml();
SetPrefix(prefix, e);
return e;
}
//Invocar por reflexión al método privado SignedXml.BuildDigestedReferences
private void BuildDigestedReferences()
{
Type t = typeof(SignedXml);
MethodInfo m = t.GetMethod("BuildDigestedReferences", BindingFlags.NonPublic | BindingFlags.Instance);
m.Invoke(this, new object[] { });
}
private byte[] GetC14NDigest(HashAlgorithm hash, string prefix)
{
//string securityUrl = (this.m_containingDocument == null) ? null : this.m_containingDocument.BaseURI;
//XmlResolver xmlResolver = new XmlSecureResolver(new XmlUrlResolver(), securityUrl);
XmlDocument document = new XmlDocument();
document.PreserveWhitespace = true;
XmlElement e = this.SignedInfo.GetXml();
document.AppendChild(document.ImportNode(e, true));
//CanonicalXmlNodeList namespaces = (this.m_context == null) ? null : Utils.GetPropagatedAttributes(this.m_context);
//Utils.AddNamespaces(document.DocumentElement, namespaces);
Transform canonicalizationMethodObject = this.SignedInfo.CanonicalizationMethodObject;
//canonicalizationMethodObject.Resolver = xmlResolver;
//canonicalizationMethodObject.BaseURI = securityUrl;
SetPrefix(prefix, document.DocumentElement); //establecemos el prefijo antes de se que calcule el hash (o de lo contrario la firma no será válida)
canonicalizationMethodObject.LoadInput(document);
return canonicalizationMethodObject.GetDigestedOutput(hash);
}
private void SetPrefix(string prefix, XmlNode node)
{
foreach (XmlNode n in node.ChildNodes)
SetPrefix(prefix, n);
node.Prefix = prefix;
}
}
}
I tried these solutions, but they didn't worked out. However, by looking at .NET source code (http://referencesource.microsoft.com/) we can see that this can be acomplished easily by providing a derived XmlDocument class to SignedXml, where namespace can be added. However, having the "ds" prefix within 'SignedInfo' and descendants will cause siganture to fail. Here is the best I could do without breaking the signature:
XmlDsigDocument.cs
using System;
using System.Collections.Generic;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;
namespace CustomSecurity
{
class XmlDsigDocument : XmlDocument
{
// Constants
public const string XmlDsigNamespacePrefix = "ds";
/// <summary>
/// Override CreateElement function as it is extensively used by SignedXml
/// </summary>
/// <param name="prefix"></param>
/// <param name="localName"></param>
/// <param name="namespaceURI"></param>
/// <returns></returns>
public override XmlElement CreateElement(string prefix, string localName, string namespaceURI)
{
// CAntonio. If this is a Digital signature security element, add the prefix.
if (string.IsNullOrEmpty(prefix))
{
// !!! Note: If you comment this line, you'll get a valid signed file! (but without ds prefix)
// !!! Note: If you uncomment this line, you'll get an invalid signed file! (with ds prefix within 'Signature' object)
//prefix = GetPrefix(namespaceURI);
// The only way to get a valid signed file is to prevent 'Prefix' on 'SignedInfo' and descendants.
List<string> SignedInfoAndDescendants = new List<string>();
SignedInfoAndDescendants.Add("SignedInfo");
SignedInfoAndDescendants.Add("CanonicalizationMethod");
SignedInfoAndDescendants.Add("InclusiveNamespaces");
SignedInfoAndDescendants.Add("SignatureMethod");
SignedInfoAndDescendants.Add("Reference");
SignedInfoAndDescendants.Add("Transforms");
SignedInfoAndDescendants.Add("Transform");
SignedInfoAndDescendants.Add("InclusiveNamespaces");
SignedInfoAndDescendants.Add("DigestMethod");
SignedInfoAndDescendants.Add("DigestValue");
if (!SignedInfoAndDescendants.Contains(localName))
{
prefix = GetPrefix(namespaceURI);
}
}
return base.CreateElement(prefix, localName, namespaceURI);
}
/// <summary>
/// Select the standar prefix for the namespaceURI provided
/// </summary>
/// <param name="namespaceURI"></param>
/// <returns></returns>
public static string GetPrefix(string namespaceURI)
{
if (namespaceURI == "http://www.w3.org/2001/10/xml-exc-c14n#")
return "ec";
else if (namespaceURI == SignedXml.XmlDsigNamespaceUrl)
return "ds";
return string.Empty;
}
}
}
This is used on the SignedXml Creation:
// Create a new XML document.
XmlDsigDocument doc = new XmlDsigDocument();
// Load the passed XML file using its name.
doc.Load(new XmlTextReader(FileName));
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(doc);
You can see full source files at:
https://social.msdn.microsoft.com/Forums/en-US/cd595379-f66a-49c8-8ca2-62acdc58b252/add-prefixds-signedxml?forum=xmlandnetfx
May be correctly SetPrefix code looks like this:
private void SetPrefix(String prefix, XmlNode node) {
foreach (XmlNode n in node.ChildNodes)
{
SetPrefix(prefix, n);
n.Prefix = prefix;
}
}
I agree that Prefix should not be important, but...
XML becomes much easier in C# if you use XPath:
var s = signedXml.GetXml();
XmlNodeList nodes = s.SelectNodes("descendant-or-self::*");
foreach (XmlNode childNode in nodes)
{
childNode.Prefix = "dsig";
}
The code George Dima provide works.
I will explain how it works.
When you call the ComputeSignature method this will generate the Signature Value by digesting the value of the SignedInfo node.
The code provided by George Dima adds the Prefix to the SignedInfo node and its children BEFORE getting the digest value. This won't add the prefix to the whole xml structure
this is the method that generates the digest value of the signedinfo node
private byte[] GetC14NDigest(HashAlgorithm hash, string prefix)
{
XmlDocument document = new XmlDocument();
document.PreserveWhitespace = true;
XmlElement e = this.SignedInfo.GetXml();
document.AppendChild(document.ImportNode(e, true));
Transform canonicalizationMethodObject = this.SignedInfo.CanonicalizationMethodObject;
SetPrefix(prefix, document.DocumentElement); //HERE'S WHERE THE PREFIX IS ADDED TO GET THE DIGEST VALUE
canonicalizationMethodObject.LoadInput(document);
return canonicalizationMethodObject.GetDigestedOutput(hash);
}
So you now have the digest value of the SignedInfo node WITH the prefix, and this value will be use to get the Signature Value, but you still DON'T have the xml with the prefix yet, so if you just do this
signedXml.GetXml();
you will get the xml without the prefix and of course because the signature value was calculated considering the ds prefix you will have an invalid signature so what you have to do is call the GetXml passing it the value of the prefix, in this case "ds" like this
signedXml.GetXml("ds");
I'm trying to list down all elements and attributes of an xml into two separate List objects.
I was able to get all the elements in the xml.
But when I tried to add the functionality to get all the attributes within each element, I always encounter System.NullReferenceException: Object reference not set to an instance of an object.
Please review my code below and advise where I'm not doing it right. Or is there any better way to accomplish this? Your comments and suggestions will be highly appreciated.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Xml;
using System.IO;
namespace TestGetElementsAndAttributes
{
public partial class MainForm : Form
{
List<string> _elementsCollection = new List<string>();
List<string> _attributeCollection = new List<string>();
public MainForm()
{
InitializeComponent();
XmlDataDocument xmldoc = new XmlDataDocument();
FileStream fs = new FileStream(#"C:\Test.xml", FileMode.Open, FileAccess.Read);
xmldoc.Load(fs);
XmlNode xmlnode = xmldoc.ChildNodes[1];
AddNode(xmlnode);
}
private void AddNode(XmlNode inXmlNode)
{
try
{
if(inXmlNode.HasChildNodes)
{
foreach (XmlNode childNode in inXmlNode.ChildNodes)
{
foreach(XmlAttribute attrib in childNode.Attributes)
{
_attributeCollection.Add(attrib.Name);
}
AddNode(childNode);
}
}
else
{
_elementsCollection.Add(inXmlNode.ParentNode.Name);
}
}
catch(Exception ex)
{
MessageBox.Show(ex.GetBaseException().ToString());
}
}
}
}
Posting as well the sample XML.
<?xml version="1.0" encoding="UTF-8" ?>
<DocumentName1>
<Product>
<Material_Number>21004903</Material_Number>
<Description lang="EN">LYNX GIFT MUSIC 2012 1X3 UNITS</Description>
<Packaging_Material type="25">457</Packaging_Material>
</Product>
</DocumentName1>
You should check the existence of childNode.Attributes with something like this:
if (childNode.Attributes != null)
{
foreach(XmlAttribute attrib in childNode.Attributes)
{
...
}
}
You need to make sure childNode.Attributes has values, so add if statement prior
if (childNode.Attributes != null)
{
foreach(XmlAttribute attrib in childNode.Attributes)