Sanitizing unknown number of descendants with agility pack doesn't work - c#

The purpose of this code below is to be able to accept strings from cliƫnts that might contain HTML and remove styling, scripting, certain tags and replace H tags by B tags.
private IDictionary<string, string[]> Whitelist;
public vacatures PostPutVacancy(vacancy vacancy)
{
//List of allowed tags
Whitelist = new Dictionary<string, string[]> {
{ "p", null },
{ "ul", null },
{ "li", null },
{ "br", null },
{ "b", null },
{ "table", null },
{ "tr", null },
{ "th", null },
{ "td", null },
{ "strong", null }
};
foreach (var item in vacancy.GetType().GetProperties())
{
if (vacancy.GetType().GetProperty(item.Name).PropertyType.FullName.Contains("String"))
{
var value = item.GetValue(vacancy, null);
if (value != null)
{
item.SetValue(vacancy, CallSanitizers(item.GetValue(vacancy, null)));
var test1 = item.GetValue(vacancy);
}
}
}
return vacancy;
}
private List<string> hList = new List<string>
{
{ "h1"},
{ "h2"},
{ "h3"},
{ "h4"},
{ "h5"},
{ "h6"}
};
private string CallSanitizers(object obj)//==Sanitize()
{
string str = obj.ToString();
if (str != HttpUtility.HtmlEncode(str))
{
doc.LoadHtml(str);
SanitizeNode(doc.DocumentNode);
string test = doc.DocumentNode.WriteTo().Trim();
return doc.DocumentNode.WriteTo().Trim();
}
else
{
return str;
}
}
private void SanitizeChildren(HtmlNode parentNode)
{
for (int i = parentNode.ChildNodes.Count - 1; i >= 0; i--)
{
SanitizeNode(parentNode.ChildNodes[i]);
}
}
private void SanitizeNode(HtmlNode node)
{
if (node.NodeType == HtmlNodeType.Element)
{
if (!Whitelist.ContainsKey(node.Name))
{
if (hList.Contains(node.Name))
{
HtmlNode b = doc.CreateElement("b");
b.InnerHtml = node.InnerHtml;
node.ParentNode.ReplaceChild(b, node);
}
else
{
node.ParentNode.RemoveChild(node, true);
}
}
if (node.HasAttributes)
{
for (int i = node.Attributes.Count - 1; i >= 0; i--)
{
HtmlAttribute currentAttribute = node.Attributes[i];
node.Attributes.Remove(currentAttribute);
}
}
}
if (node.HasChildNodes)
{
SanitizeChildren(node);
}
}
It works but there is one problem, child nodes of child nodes don't get sanitized, see example.
Input:
"Lorem ipsum<h1 style='font-size:38px;'><p style='font-size:38px;'>dolor sit</p></h1> amet <h1 style='font-size:38px;'><strong style='font-size:38px;'>consectetur adipiscing</strong></h1>"
Result:
"Lorem ipsum<b><p style='font-size:38px;'>dolor sit</p></b> amet <b style='font-size:38px;'><strong style='font-size:38px;'>consectetur adipiscing</strong></b>"
The problem must be due to not being able to place a child back into a changed parent since the parent not recognized anymore because of the change of tag type.
Does anybody know how to fix this?
Please post a comment if the question is unclear or not well formulated.
Thanks in advance

This fixes it
private string CallSanitizers(string str)
{
if (str != HttpUtility.HtmlEncode(str))
{
doc.LoadHtml(str);
str = Sanitizers();
return doc.DocumentNode.WriteTo().Trim();
}
else
{
return str;
}
}
private string Sanitizers()
{
doc.DocumentNode.Descendants().Where(l => l.Name == "script" || l.Name == "style").ToList().ForEach(l => l.Remove());
doc.DocumentNode.Descendants().Where(l => hList.Contains(l.Name)).ToList().ForEach(l => l.Name = "b");
doc.DocumentNode.Descendants().Where(l => l.Attributes != null).ToList().ForEach(l => l.Attributes.ToList().ForEach(a => a.Remove()));
doc.DocumentNode.Descendants().Where(l => !Whitelist.Contains(l.Name) && l.NodeType == HtmlNodeType.Element).ToList().ForEach(l => l.ParentNode.RemoveChild(l, true));
return doc.DocumentNode.OuterHtml;
}
//lijst van tags die worden vervangen door <b></b>
private List<string> hList = new List<string>
{
{ "h1"},
{ "h2"},
{ "h3"},
{ "h4"},
{ "h5"},
{ "h6"}
};
List<string> Whitelist = new List<string>
{
{ "p"},
{ "ul"},
{ "li"},
{ "br"},
{ "b"},
{ "table"},
{ "tr"},
{ "th"},
{ "td"},
{ "strong"}
};
The input is
"<head><script>alert('Hello!');</script></head><div><div><h1>Lorem ipsum </h1></div></div> <h1 style='font-size:38px;'><p style='font-size:38px;'>dolor </p></h1> sit <h1 style='font-size:38px;'><strong style='font-size:38px;'>amet</strong></h1>"
And the output is
"<b>Lorem ipsum</b> <b><p>dolor</p></b> sit <b><strong>amet</strong></b>"

Related

Make a method generic

I have a method that extract days names from a particular object,
like this:
private string ExtractWeekDayNames(FiscalReceipt fiscalReceipt)
{
string retVal = "";
Dictionary<string, bool> WeekDays =
new Dictionary<string, bool>() {
{ "Sun", fiscalReceipt.Sunday },
{ "Mon", fiscalReceipt.Monday },
{ "Tue", fiscalReceipt.Tuesday },
{ "Wed", fiscalReceipt.Wednesday },
{ "Thu", fiscalReceipt.Thursday },
{ "Fri", fiscalReceipt.Friday },
{ "Sat", fiscalReceipt.Saturday }
};
//Find week days
foreach (var item in WeekDays)
{
if (item.Value == true)
retVal += item.Key + ",";
}
if (!string.IsNullOrEmpty(retVal))
retVal = retVal.Substring(0, retVal.Length - 1);
return retVal;
}
I also have a similar method that performs the same operations
but having a different type of parameter, like:
private string ExtractWeekDayNames(NonFiscalReceipt nonFiscalReceipt)
{
...
}
Also the NonFiscalReceipt has the properties Sunday, Monday, etc.
How can I substitute these 2 methods with just one?
public class FiscalReceipt : Receipt{
FiscalReceipt specific fields
}
public class NonFiscalReceipt : Receipt{
NonFiscalReceipt specific fields..
}
public class Receipt{
fields common to both classes
}
private string ExtractWeekDayNames(Receipt receipt){
}
Both types of receipt inherit from receipt, that was you can pass in either and will still have all the fields :)
You need to create a common interface for your classes to implement, so that the method could accept anything that implements this interface.
interface IReceipt
{
bool Sunday {get; }
bool Monday {get; }
...
bool Saturday {get; }
}
Then your method should look like this:
private string ExtractWeekDayNames<T>(T receipt) where T : IReceipt
{
string retVal = "";
Dictionary<string, bool> WeekDays =
new Dictionary<string, bool>() {
{ "Sun", receipt.Sunday },
{ "Mon", fiscalReceipt.Monday },
{ "Tue", receipt.Tuesday },
{ "Wed", receipt.Wednesday },
{ "Thu", receipt.Thursday },
{ "Fri", receipt.Friday },
{ "Sat", receipt.Saturday }
};
//Find week days
foreach (var item in WeekDays)
{
if (item.Value == true)
retVal += item.Key + ",";
}
if (!string.IsNullOrEmpty(retVal))
retVal = retVal.Substring(0, retVal.Length - 1);
return retVal;
}
However, as juharr and Remy Grandin rightfully wrote in the comments - There is no need for generics in this case - you can simply pass the interface to the method:
private string ExtractWeekDayNames(IReceipt receipt)
{
string retVal = "";
Dictionary<string, bool> WeekDays =
new Dictionary<string, bool>() {
{ "Sun", receipt.Sunday },
{ "Mon", receipt.Monday },
{ "Tue", receipt.Tuesday },
{ "Wed", receipt.Wednesday },
{ "Thu", receipt.Thursday },
{ "Fri", receipt.Friday },
{ "Sat", receipt.Saturday }
};
//Find week days
foreach (var item in WeekDays)
{
if (item.Value == true)
retVal += item.Key + ",";
}
if (!string.IsNullOrEmpty(retVal))
retVal = retVal.Substring(0, retVal.Length - 1);
return retVal;
}
Create base interface for NonFiscalReceipt and FiscalReceipt that contains the common properties:
public interface IReceipt
{
bool Sunday{get;}
......
}
Replace the method signature with this: private string ExtractWeekDayNames(IReceipt fiscalReceipt)
Try to use DayOfWeek enum if it possible.

How to save more values in XML

I have a textfield where user can fill in strings under each other. But how to save now the different strings. Because now it is one long string. And not seperated strings
This is the class for Serialize and Deserialize:
public class PreConditionSettings
{
[Display(Name = "PreConditionResidentsOnly", ResourceType = typeof(Resources.Entity.Product))]
public bool ResidentsOnly { get; set; }
[Display(Name = "PreConditionMinimumAge", ResourceType = typeof(Resources.Entity.Product))]
public int MinimumAge { get; set; }
[SfsHelpers.PreConditionRedirectValidation(ErrorMessageResourceType = typeof(Resources.Entity.Product), ErrorMessageResourceName="PreConditionRedirectUrlValidation")]
[Display(Name = "PreConditionRedirectUrl", ResourceType = typeof(Resources.Entity.Product))]
public string RedirectUrl { get; set; }
[Display(Name = "PreConditionIpAddress", ResourceType = typeof(Resources.Entity.Product))]
public string IpAddress { get; set; }
public PreConditionSettings() {
this.ResidentsOnly = false;
this.MinimumAge = 0;
this.RedirectUrl = null;
this.IpAddress = null;
}
internal string Serialize(EditProductModel model) {
if (this.ResidentsOnly == false && this.MinimumAge == 0)
return model.Product.AuthenticationSettings;
XElement settings = XElement.Parse(model.Product.AuthenticationSettings ?? "<settings/>");
if (settings == null || settings.Attribute("authenticationrequired") == null || settings.Attribute("authenticationrequired").Value != "true")
return model.Product.AuthenticationSettings;
settings.Add(
new XElement("preconditions",
new XElement("residentsonly", this.ResidentsOnly ? "1" : "0"),
new XElement("minimumage", this.MinimumAge),
new XElement("redirecturl", this.RedirectUrl),
new XElement("ipaddress", this.IpAddress)
)
);
return settings.ToString();
}
internal void Deserialize(EditProductModel model) {
Deserialize(model.Product);
}
internal void Deserialize(Product product) {
XElement settings = XElement.Parse(product.AuthenticationSettings ?? "<settings/>");
if (settings == null || settings.Attribute("authenticationrequired") == null || settings.Attribute("authenticationrequired").Value != "true")
return;
XElement conditions = settings.Element("preconditions");
if (conditions == null)
return;
XElement condition = conditions.Element("residentsonly");
if (condition!= null)
this.ResidentsOnly = (condition.Value == "1");
condition = conditions.Element("minimumage");
if (condition != null) {
int age = 0;
if (Int32.TryParse(condition.Value, out age))
this.MinimumAge = age;
}
condition = conditions.Element("redirecturl");
if (condition != null) {
this.RedirectUrl = condition.Value;
}
condition = conditions.Element("ipaddress");
if (condition != null) {
this.IpAddress = condition.Value;
}
}
And it is about the propertie: IpAddress. The output of this is:
<settings authenticationrequired="true">
<accesslevel>level10</accesslevel>
<profile>test</profile>
<preconditions>
<residentsonly>1</residentsonly>
<minimumage>55</minimumage>
<redirecturl>/page/newpage</redirecturl>
<ipaddress>88888888
999999999</ipaddress>
</preconditions>
</settings>
But you see that it is one string, and not two strings: 88888888 and 999999999.
Thank you
I try it like this:
condition = conditions.Element("ipaddress");
if (condition != null) {
string[] lines = IpAddress.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
this.IpAddress = condition.Value;
}
I try it something like this:
condition = conditions.Element("ipaddress");
if (condition != null) {
string[] lines = IpAddress.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
string lines = string.Join("\n",condition.Value);
//this.IpAddress = condition.Value;
}
If I try this:
condition = conditions.Element("ipaddress");
if (condition != null) {
string[] lines = IpAddress.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
for (int i = 0; i < lines.Length; i++) {
lines[i] = condition.Value.ToString();
//lines = string.Join("\n", condition.Value.ToArray());
}
//lines = string.Join("\n",condition.Value);
//this.IpAddress = condition.Value;
}
I get this error:
Object reference not set to an instance of an object.
on this line:
string[] lines = IpAddress.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
You should split your string IpAddress by \n or Environment.NewLine and save as array of strings.
After your edits :
condition = conditions.Element("ipaddress");
if (condition != null) {
string[] lines = condition.Value.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
this.IpAddress = new XElement("ipaddresses", lines.Select(o=> new XElement("item", o))).ToString();
}
I would recommend you to either change the property to an ICollection or create a custom type IpAddresses, implementing ICollection and custom serialization/business logic.
This would bring the following change to your xml file:
<ipaddresses>
<item>88888888</item>
<item>99999999</item>
</ipaddresses>

Parsing JSON Array in .NET 2.0

I am using JSON.NET to parse some JSON returned by my server. I feel kind of dumb, because I feel like I'm trying to do something simple. Yet, the code doesn't work. Currently, I'm getting some JSON bck from the server that looks like this:
[
{ id:'1', description:'Some kind of description' },
{ id:'2', description:'Another description' },
{ id:'3', description:'Here is another' }
]
Currently, I'm trying to take this JSON in C# and convert it to a Dictionary<string, string>. My (failing) attempt looks like this:
private Dictionary<string, string> ParseResults(string json)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
string descriptionPropertyName = "description";
string descriptionPropertyValue = null;
bool isDescriptionProperty = false;
string idPropertyName = "id";
string idPropertyValue = null;
bool isIdProperty = false;
JsonTextReader jsonReader = new JsonTextReader(new StringReader(json));
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName)
{
if (descriptionPropertyName.Equals(jsonReader.Value))
{
isDescriptionProperty = true;
}
else if (idPropertyName.Equals(jsonReader.Value))
{
isIdProperty = true;
}
}
else
{
if ((isDescriptionProperty == true) && (descriptionPropertyValue == null))
{
if (jsonReader.TokenType == JsonToken.String)
{
if (jsonReader.Value != null)
{
descriptionPropertyValue = jsonReader.Value.ToString();
}
}
}
else if ((isIdProperty == true) && (idPropertyValue == null))
{
if (jsonReader.TokenType == JsonToken.String)
{
if (jsonReader.Value != null)
{
idPropertyValue = jsonReader.Value.ToString();
}
}
}
}
if ((isDescriptionProperty == true) && (isIdProperty == true) && (descriptionPropertyValue != null) && (idPropertyValue != null))
{
dictionary.Add(idPropertyValue, descriptionPropertyValue);
}
}
return dictionary;
}
That's a lot of code for something that seems so simple. I feel I have to do something wrong. In fact, its not working, so something has to be wrong. I just can't determine what.
Thank you for any help you may be able to provide. Please remember, this is based on the .NET 2.0 framework.
You don't have to parse the json string manually.
You can deserialize it in a strongly typed collection using JSON.NET
class MyClass
{
public string id { get; set; }
public string description { get; set; }
}
then
string json = #"[
{ id:'1', description:'Some kind of description' },
{ id:'2', description:'Another description' },
{ id:'3', description:'Here is another' }
]";
List<MyClass> collection = JsonConvert.DeserializeObject<List<MyClass>>(json);
foreach (var item in collection)
Console.WriteLine(item.id + " - " + item.description);
Or you can convert it to a dictionary
Dictionary<string, string> dict = collection.ToDictionary(e => e.id, e => e.description);

JSON from Adjacency list?

I have an adjacency list like this:
A - A1
A - A2
A - A3
A3 - A31
A31 - A311
A31 - A312
I am trying to obtain the following output:
{
"name": "A",
"children": [{
"name": "A1"
}, {
"name": "A2"
}, {
"name": "A3",
"children": [{
"name": "A31",
"children": [{
"name": "A311"
}, {
"name": "A312"
}]
}]
}]
};
I have a modestly large graph containing 100K links. What is a good way of doing this? I am thinking there is a very elegant recursive way of doing this but am not sure about how to create the JSON string directly.
Something like should work:
static void Main(string[] args)
{
var adjList = new List<Link>
{
new Link("A","A1"),
new Link("A","A2"),
new Link("A","A3"),
new Link("A3","A31"),
new Link("A31","A311"),
new Link("A31","A312"),
};
var rootsAndChildren = adjList.GroupBy(x => x.From)
.ToDictionary(x => x.Key, x => x.Select(y => y.To).ToList());
var roots = rootsAndChildren.Keys
.Except(rootsAndChildren.SelectMany(x => x.Value));
using (var wr = new StreamWriter("C:\\myjson.json"))
{
wr.WriteLine("{");
foreach (var root in roots)
AppendSubNodes(wr, root, rootsAndChildren, 1);
wr.WriteLine("};");
}
}
static void AppendSubNodes(TextWriter wr, string root,
Dictionary<string, List<string>> rootsAndChildren, int level)
{
string indent = string.Concat(Enumerable.Repeat(" ", level * 4));
wr.Write(indent + "\"name\" : \"" + root + "\"");
List<string> children;
if (rootsAndChildren.TryGetValue(root, out children))
{
wr.WriteLine(",");
wr.WriteLine(indent + "\"children\" : [{");
for (int i = 0; i < children.Count; i++)
{
if (i > 0)
wr.WriteLine(indent + "}, {");
AppendSubNodes(wr, children[i], rootsAndChildren, level + 1);
}
wr.WriteLine(indent + "}]");
}
else
{
wr.WriteLine();
}
}
With Link being the following class:
class Link
{
public string From { get; private set; }
public string To { get; private set; }
public Link(string from, string to)
{
this.From = from;
this.To = to;
}
}
Result of the previous code:
{
"name" : "A",
"children" : [{
"name" : "A1"
}, {
"name" : "A2"
}, {
"name" : "A3",
"children" : [{
"name" : "A31",
"children" : [{
"name" : "A311"
}, {
"name" : "A312"
}]
}]
}]
};
EDIT :
If you want to check the existence of graph cycles you can do the following (just after the creation of rootsAndChildren dictionary)
var allNodes = rootsAndChildren.Keys.Concat(rootsAndChildren.SelectMany(x => x.Value)).Distinct();
Func<string, IEnumerable<string>> getSuccessors =
(x) => rootsAndChildren.ContainsKey(x) ? rootsAndChildren[x] : Enumerable.Empty<string>();
var hasCycles = new Tarjan<string>().HasCycle(allNodes, getSuccessors);
With Tarjan being the following class:
// Please note that Tarjan does not detect a cycle due to a node
// pointing to itself. It's pretty trivial to account for that though...
public class Tarjan<T>
{
private class Node
{
public T Value { get; private set; }
public int Index { get; set; }
public int LowLink { get; set; }
public Node(T value)
{
this.Value = value;
this.Index = -1;
this.LowLink = -1;
}
}
private Func<T, IEnumerable<T>> getSuccessors;
private Dictionary<T, Node> nodeMaps;
private int index = 0;
private Stack<Node> stack;
private List<List<Node>> SCC;
public bool HasCycle(IEnumerable<T> nodes, Func<T, IEnumerable<T>> getSuccessors)
{
return ExecuteTarjan(nodes, getSuccessors).Any(x => x.Count > 1);
}
private List<List<Node>> ExecuteTarjan(IEnumerable<T> nodes, Func<T, IEnumerable<T>> getSuccessors)
{
this.nodeMaps = nodes.ToDictionary(x => x, x => new Node(x));
this.getSuccessors = getSuccessors;
SCC = new List<List<Node>>();
stack = new Stack<Node>();
index = 0;
foreach (var node in this.nodeMaps.Values)
{
if (node.Index == -1)
TarjanImpl(node);
}
return SCC;
}
private IEnumerable<Node> GetSuccessors(Node v)
{
return this.getSuccessors(v.Value).Select(x => this.nodeMaps[x]);
}
private List<List<Node>> TarjanImpl(Node v)
{
v.Index = index;
v.LowLink = index;
index++;
stack.Push(v);
foreach (var n in GetSuccessors(v))
{
if (n.Index == -1)
{
TarjanImpl(n);
v.LowLink = Math.Min(v.LowLink, n.LowLink);
}
else if (stack.Contains(n))
{
v.LowLink = Math.Min(v.LowLink, n.Index);
}
}
if (v.LowLink == v.Index)
{
Node n;
List<Node> component = new List<Node>();
do
{
n = stack.Pop();
component.Add(n);
} while (n != v);
SCC.Add(component);
}
return SCC;
}
}

Anti XSS Sanitization of IFrames with specific src attribute values in .NET

I'm looking to accomplish the following: sanitize WYSIWIG user-input using either AntiXSS or AntiSamy libraries, however, allow iframe tags which have "src" attribute from particular domains. Is there a way to accomplish this?
I was thinking about parsing it up with Regex somehow and swapping it out "< iframe" for something like {{iframe-begin}} tag and later on swapping it out in the controller logic with "
Thank you.
I also wanted to do some HTML sanitization for one of my WYSISWIG editors.
One aproach is to use the Microsoft Anti-Cross Site Scripting Library. http://msdn.microsoft.com/en-us/library/aa973813.aspx
Another is to create a whitelist parser for HTML.
Here is what I used along with HTML Agility Pack, you can configure the whitelist with tags and allowed atributes:
public static class HtmlSanitizer
{
private static readonly IDictionary Whitelist;
private static List DeletableNodesXpath = new List();
static HtmlSanitizer()
{
Whitelist = new Dictionary<string, string[]> {
{ "a", new[] { "href" } },
{ "strong", null },
{ "em", null },
{ "blockquote", null },
{ "b", null},
{ "p", null},
{ "ul", null},
{ "ol", null},
{ "li", null},
{ "div", new[] { "align" } },
{ "strike", null},
{ "u", null},
{ "sub", null},
{ "sup", null},
{ "table", null },
{ "tr", null },
{ "td", null },
{ "th", null }
};
}
public static string Sanitize(string input)
{
if (input.Trim().Length < 1)
return string.Empty;
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(input);
SanitizeNode(htmlDocument.DocumentNode);
string xPath = HtmlSanitizer.CreateXPath();
return StripHtml(htmlDocument.DocumentNode.WriteTo().Trim(), xPath);
}
private static void SanitizeChildren(HtmlNode parentNode)
{
for (int i = parentNode.ChildNodes.Count - 1; i >= 0; i--)
{
SanitizeNode(parentNode.ChildNodes[i]);
}
}
private static void SanitizeNode(HtmlNode node)
{
if (node.NodeType == HtmlNodeType.Element)
{
if (!Whitelist.ContainsKey(node.Name))
{
if (!DeletableNodesXpath.Contains(node.Name))
{
//DeletableNodesXpath.Add(node.Name.Replace("?",""));
node.Name = "removeableNode";
DeletableNodesXpath.Add(node.Name);
}
if (node.HasChildNodes)
{
SanitizeChildren(node);
}
return;
}
if (node.HasAttributes)
{
for (int i = node.Attributes.Count - 1; i >= 0; i--)
{
HtmlAttribute currentAttribute = node.Attributes[i];
string[] allowedAttributes = Whitelist[node.Name];
if (allowedAttributes != null)
{
if (!allowedAttributes.Contains(currentAttribute.Name))
{
node.Attributes.Remove(currentAttribute);
}
}
else
{
node.Attributes.Remove(currentAttribute);
}
}
}
}
if (node.HasChildNodes)
{
SanitizeChildren(node);
}
}
private static string StripHtml(string html, string xPath)
{
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
if (xPath.Length > 0)
{
HtmlNodeCollection invalidNodes = htmlDoc.DocumentNode.SelectNodes(#xPath);
foreach (HtmlNode node in invalidNodes)
{
node.ParentNode.RemoveChild(node, true);
}
}
return htmlDoc.DocumentNode.WriteContentTo(); ;
}
private static string CreateXPath()
{
string _xPath = string.Empty;
for (int i = 0; i < DeletableNodesXpath.Count; i++)
{
if (i != DeletableNodesXpath.Count - 1)
{
_xPath += string.Format("//{0}|", DeletableNodesXpath[i].ToString());
}
else _xPath += string.Format("//{0}", DeletableNodesXpath[i].ToString());
}
return _xPath;
}
}

Categories

Resources