I want to get an object from a Dictionary<string, object> using a string expression as key.
For example taking the following data structure:
var data = new Dictionary<string, object>()
{
{ "Products", new object[]
{
new Dictionary<string, object>()
{
{ "Name", "Tin of biscuits" }
}
}
}
}
I want to return the product name with the expression: "Products[0].Name".
This is just an example, the data could be of any depth and the string expression could be something like "BookStore.Books[3].Author.ContactDetails.Address" for eg.
So far I have been experimenting with Recursive methods and have also tried string.Split('.') with Aggregate Linq method, but I am struggling and I think this is a perfect little puzzle for SO.
Assuming you'll always have the Dictionary<string, object> and object[] in your data structure, this approach should work:
private static object? GetSelectedValueOrDefault(this Dictionary<string, object> data, string selector)
{
string[] selectorSegments = selector.Split('.');
if (selectorSegments.Length == 0)
{
return null;
}
object? currentNode = data.GetValueOrDefault(selectorSegments[0]);
if (currentNode == null)
{
return null;
}
for (int index = 1; index < selectorSegments.Length; index++)
{
string segment = selectorSegments[index];
if (currentNode is not Dictionary<string, object> dictionary)
{
return null;
}
var selectorWithIndex = GetSelectorAndElementIndexOrDefault(segment);
if (selectorWithIndex is not null &&
currentNode is Dictionary<string, object> dict)
{
currentNode = dict.GetValueOrDefault(selectorWithIndex.Value.ItemSelector);
currentNode = GetElementOrDefault(currentNode, selectorWithIndex.Value.Index);
continue;
}
currentNode = dictionary.GetValueOrDefault(segment);
if (index == selectorSegments.Length - 1)
{
return currentNode;
}
}
return null;
}
private static object? GetElementOrDefault(object? currentNode, int index)
{
if (currentNode is not object[] array)
{
return null;
}
if (index >= array.Length)
{
return null;
}
return array[index];
}
private static (string ItemSelector, int Index)? GetSelectorAndElementIndexOrDefault(string segment)
{
if (!segment.Contains('['))
{
return null;
}
string[] result = segment.Split('[', ']');
return (result[0], int.Parse(result[1]));
}
Example
var data = new Dictionary<string, object>()
{
{
"BookStore",
new Dictionary<string, object>()
{
{
"Name",
"The Book Store"
},
{
"Books",
new object[]
{
new Dictionary<string, object>(),
new Dictionary<string, object>(),
new Dictionary<string, object>(),
new Dictionary<string, object>()
{
{
"Author",
new Dictionary<string, object>()
{
{
"Name",
"Luke T O'Brien"
},
{
"ContactDetails",
new Dictionary<string, object>()
{
{
"Address",
"Some address"
}
}
}
}
}
}
}
}
}
}
};
Console.WriteLine(data.GetSelectedValueOrDefault("BookStore.Name"));
Console.WriteLine(data.GetSelectedValueOrDefault("BookStore.Books[3].Author.Name"));
Console.WriteLine(data.GetSelectedValueOrDefault("BookStore.Books[3].Author.ContactDetails.Address"));
Output
The Book Store
Luke T O'Brien
Some address
When trying to send two 19MB documents in the envelope I am getting this error code REQUEST_ENTITY_TOO_LARGE.
I developed in C# with binary transfer as suggested by DocuSign.
SendEnvelope main console code:
DocuSignLib.SendEnvelope sendEnvelope = new DocuSignLib.SendEnvelope();
// 19MB file 1
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "19MB.pdf");
sendEnvelope.AddDocument(filePath);
// 19MB file 2
filePath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "19MB_1.pdf");
sendEnvelope.AddDocument(filePath);
sendEnvelope.AddSigner(ConfigurationManager.AppSettings["SignerName"],
ConfigurationManager.AppSettings["SignerEmail"], "1", "*s1*", "*r1*", null, null, 20, 10);
var envSummary = sendEnvelope.Send(config.UserId,
config.IntegrationKey,
config.RSAPrivateKey,
config.IsDeveloperSandbox,
ConfigurationManager.AppSettings["EmailSubject"],
ConfigurationManager.AppSettings["EmailBody"],
600);
Console.WriteLine("\tEnvelope Id: " + envSummary.EnvelopeId);
Console.WriteLine("\tEnvelope Status: " + envSummary.Status);
Console.WriteLine("\tEnvelope Uri: " + envSummary.Uri);
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
SendEnvelope class:
using DocuSign.eSign.Model;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Web;
public class SendEnvelope
{
private List<Dictionary<string, dynamic>> signers = new List<Dictionary<string, dynamic>>();
private List<Dictionary<string, dynamic>> carbonCopies = new List<Dictionary<string, dynamic>>();
List<dynamic> docs = new List<dynamic>();
private List<Dictionary<string, dynamic>> witnesses = new List<Dictionary<string, dynamic>>();
private DocuSignConfig Config;
public void AddSigner(string Name,
string Email,
string RecipientId,
string SignHereAnchor,
string InitialHereAnchor,
string EmailSubject,
string EmailBody,
int AnchorXOffset,
int AnchorYOffset)
{
if (!string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Email) && !string.IsNullOrEmpty(RecipientId))
{
if (Util.IsValidEmail(Email))
{
Dictionary<string, dynamic> signer = new Dictionary<string, dynamic>()
{
{ "email", Email },
{ "name", Name },
{ "recipientId", RecipientId }
};
Dictionary<string, dynamic> signHere = null;
Dictionary<string, dynamic> initialHere = null;
if (!string.IsNullOrEmpty(SignHereAnchor))
{
signHere = new Dictionary<string, dynamic>()
{
{ "anchorString", SignHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(InitialHereAnchor))
{
initialHere = new Dictionary<string, dynamic>()
{
{ "anchorString", InitialHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(EmailSubject) && !string.IsNullOrEmpty(EmailBody))
{
Dictionary<string, dynamic> emailNotification = new Dictionary<string, dynamic>()
{
{ "emailSubject", EmailSubject },
{ "emailBody", EmailBody }
};
signer.Add("emailNotification", emailNotification);
}
if (initialHere != null || signHere != null)
{
Dictionary<string, dynamic> Tabs = new Dictionary<string, dynamic>()
{
{ "initialHereTabs", new dynamic[] { initialHere } },
{ "signHereTabs", new dynamic[] { signHere } }
};
signer.Add("tabs", Tabs);
}
signers.Add(signer);
}
else
throw new Exception("The e-mail " + Email + " is ivalid!");
}
}
public void AddCarbonCopy(string Name, string Email, string EmailSubject, string EmailBody)
{
if (!string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Email))
{
if (Util.IsValidEmail(Email))
{
Dictionary<string, dynamic> cc = new Dictionary<string, dynamic>()
{
{ "email", Email },
{ "name", Name }
};
if (!string.IsNullOrEmpty(EmailSubject) && !string.IsNullOrEmpty(EmailBody))
{
Dictionary<string, dynamic> emailNotification = new Dictionary<string, dynamic>()
{
{ "emailSubject", EmailSubject },
{ "emailBody", EmailBody }
};
cc.Add("emailNotification", emailNotification);
}
carbonCopies.Add(cc);
}
else
throw new Exception("The e-mail " + Email + " is ivalid!");
}
}
public void AddWitness(string Name,
string Email,
string RecipientId,
string WitnessFor, // Signer RecipentId
string SignHereAnchor,
string InitialHereAnchor,
string EmailSubject,
string EmailBody,
int AnchorXOffset,
int AnchorYOffset)
{
if (!string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Email) &&
!string.IsNullOrEmpty(RecipientId) && !string.IsNullOrEmpty(WitnessFor))
{
if (Util.IsValidEmail(Email))
{
Dictionary<string, dynamic> wit = new Dictionary<string, dynamic>()
{
{ "email", Email },
{ "name", Name },
{ "recipientId", RecipientId },
{ "witnessFor", WitnessFor }
};
Dictionary<string, dynamic> signHere = null;
Dictionary<string, dynamic> initialHere = null;
if (!string.IsNullOrEmpty(SignHereAnchor))
{
signHere = new Dictionary<string, dynamic>()
{
{ "anchorString", SignHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(InitialHereAnchor))
{
initialHere = new Dictionary<string, dynamic>()
{
{ "anchorString", InitialHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(EmailSubject) && !string.IsNullOrEmpty(EmailBody))
{
Dictionary<string, dynamic> emailNotification = new Dictionary<string, dynamic>()
{
{ "emailSubject", EmailSubject },
{ "emailBody", EmailBody }
};
wit.Add("emailNotification", emailNotification);
}
if (initialHere != null || signHere != null)
{
Dictionary<string, dynamic> Tabs = new Dictionary<string, dynamic>()
{
{ "initialHereTabs", new dynamic[] { initialHere } },
{ "signHereTabs", new dynamic[] { signHere } }
};
wit.Add("tabs", Tabs);
}
witnesses.Add(wit);
}
else
throw new Exception("The e-mail " + Email + " is ivalid!");
}
}
public void AddWitnessGroup(string GroupName,
string GroupId,
string RecipientId,
string WitnessFor, // Signer RecipentId
string SignHereAnchor,
string InitialHereAnchor,
string EmailSubject,
string EmailBody,
int AnchorXOffset,
int AnchorYOffset)
{
if (!string.IsNullOrEmpty(GroupName) && !string.IsNullOrEmpty(GroupId) &&
!string.IsNullOrEmpty(RecipientId) && !string.IsNullOrEmpty(WitnessFor))
{
Dictionary<string, dynamic> wit = new Dictionary<string, dynamic>()
{
{ "signingGroupId", GroupId },
{ "signingGroupName", GroupName },
{ "recipientId", RecipientId },
{ "witnessFor", WitnessFor }
};
Dictionary<string, dynamic> signHere = null;
Dictionary<string, dynamic> initialHere = null;
if (!string.IsNullOrEmpty(SignHereAnchor))
{
signHere = new Dictionary<string, dynamic>()
{
{ "anchorString", SignHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(InitialHereAnchor))
{
initialHere = new Dictionary<string, dynamic>()
{
{ "anchorString", InitialHereAnchor },
{ "anchorYOffset", AnchorYOffset.ToString() },
{ "anchorUnits", "pixels" },
{ "anchorXOffset", AnchorXOffset.ToString() }
};
}
if (!string.IsNullOrEmpty(EmailSubject) && !string.IsNullOrEmpty(EmailBody))
{
Dictionary<string, dynamic> emailNotification = new Dictionary<string, dynamic>()
{
{ "emailSubject", EmailSubject },
{ "emailBody", EmailBody }
};
wit.Add("emailNotification", emailNotification);
}
if (initialHere != null || signHere != null)
{
Dictionary<string, dynamic> Tabs = new Dictionary<string, dynamic>()
{
{ "initialHereTabs", new dynamic[] { initialHere } },
{ "signHereTabs", new dynamic[] { signHere } }
};
wit.Add("tabs", Tabs);
}
witnesses.Add(wit);
}
}
private void ValidateFileExtension(string FileExtension)
{
if (!Util.IsValidFileExtension(FileExtension))
{
throw new Exception("Invalid file extension '" + FileExtension + "'! DocuSign eSignature supports the following file types: " + Util.GetValidFileExtensions());
}
}
public void AddDocument(string FileName, byte[] FileContent)
{
ValidateFileExtension(Path.GetExtension(FileName));
docs.Add(
new
{
mime = MimeMapping.GetMimeMapping(FileName),
name = FileName,
documentId = (docs.Count + 1).ToString(),
bytes = FileContent
});
}
public void AddDocument(string FilePath)
{
ValidateFileExtension(Path.GetExtension(FilePath));
byte[] buff = File.ReadAllBytes(FilePath);
if (buff != null)
{
docs.Add(
new
{
mime = MimeMapping.GetMimeMapping(FilePath),
name = Path.GetFileNameWithoutExtension(FilePath),
documentId = (docs.Count + 1).ToString(),
bytes = buff
});
}
}
private Dictionary<string, dynamic> CreateEnvelope(string EmailSubject, string EmailBody)
{
return new Dictionary<string, dynamic>()
{
{ "emailSubject", !string.IsNullOrEmpty(EmailSubject) ? EmailSubject : null},
{ "emailBlurb", !string.IsNullOrEmpty(EmailBody) ? EmailBody : null},
{ "documents", docs.ToArray() },
{ "recipients", CreateRecipients() },
{ "status", "sent" }
};
}
private Dictionary<string, dynamic> CreateRecipients()
{
// Order the recipients
int order = 1;
for (int i = 0; i < signers.Count; i++)
{
signers[i]["routingOrder"] = order.ToString();
order++;
}
for (int i = 0; i < witnesses.Count; i++)
{
witnesses[i]["routingOrder"] = order.ToString();
order++;
}
for (int i = 0; i < carbonCopies.Count; i++)
{
carbonCopies[i]["routingOrder"] = order.ToString();
order++;
}
return new Dictionary<string, dynamic>()
{
{ "signers", signers },
{ "carbonCopies", carbonCopies },
{ "witnesses", witnesses }
};
}
public EnvelopeSummaryCopy Send(string UserId, string IntegrationKey, string RSAPrivateKey, bool IsDeveloperSandbox, string EmailSubject, string EmailBody, int ApiTimeout)
{
if (docs.Count == 0 || signers.Count == 0 || string.IsNullOrEmpty(EmailSubject))
throw new Exception(string.Format("No {0} found!",
docs.Count == 0 ? "document" : (signers.Count == 0 ? "signer" : "e-mail subject")));
else
{
EnvelopeSummaryCopy esc;
try
{
esc = SendNow(UserId, IntegrationKey, RSAPrivateKey, IsDeveloperSandbox, EmailSubject, EmailBody, ref ApiTimeout);
}
catch (Exception ex)
{
// Token expired, try again
if (ex.Message.Contains("USER_AUTHENTICATION_FAILED"))
{
JWTAuth.ClearToken();
esc = SendNow(UserId, IntegrationKey, RSAPrivateKey, IsDeveloperSandbox, EmailSubject, EmailBody, ref ApiTimeout);
}
else
throw ex;
}
return esc;
}
}
private EnvelopeSummaryCopy SendNow(string UserId, string IntegrationKey, string RSAPrivateKey, bool IsDeveloperSandbox, string EmailSubject, string EmailBody, ref int ApiTimeout)
{
ApiTimeout *= 1000;
Config = new DocuSignConfig(UserId, IntegrationKey, RSAPrivateKey, IsDeveloperSandbox);
APICallInfo apiCallInfo = JWTAuth.GetToken(Config);
if (string.IsNullOrEmpty(apiCallInfo.ErrorMessage))
{
dynamic envelope = CreateEnvelope(EmailSubject, EmailBody);
byte[] CRLF = Encoding.ASCII.GetBytes("\r\n");
byte[] boundary = Encoding.ASCII.GetBytes("multipartboundary_multipartboundary");
byte[] hyphens = Encoding.ASCII.GetBytes("--");
string uri = apiCallInfo.BaseUri
+ "/v2.1/accounts/" + apiCallInfo.AccountId + "/envelopes";
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.Method = "POST";
request.Accept = "application/json";
request.ContentType = "multipart/form-data; boundary=" + Encoding.ASCII.GetString(boundary);
request.Headers.Add("Authorization", "Bearer " + apiCallInfo.AccessToken);
request.Timeout = ApiTimeout < 100000 ? 100000 : ApiTimeout;
using (var buffer = new BinaryWriter(request.GetRequestStream(), Encoding.ASCII))
{
buffer.Write(hyphens);
buffer.Write(boundary);
buffer.Write(CRLF);
buffer.Write(Encoding.ASCII.GetBytes("Content-Type: application/json"));
buffer.Write(CRLF);
buffer.Write(Encoding.ASCII.GetBytes("Content-Disposition: form-data"));
buffer.Write(CRLF);
buffer.Write(CRLF);
var json = JsonConvert.SerializeObject(envelope, Formatting.Indented);
buffer.Write(Encoding.ASCII.GetBytes(json));
// Loop to add the documents.
// See section Multipart Form Requests on page https://developers.docusign.com/esign-rest-api/guides/requests-and-responses
foreach (var d in docs)
{
buffer.Write(CRLF);
buffer.Write(hyphens);
buffer.Write(boundary);
buffer.Write(CRLF);
buffer.Write(Encoding.ASCII.GetBytes("Content-Type:" + d.mime));
buffer.Write(CRLF);
buffer.Write(Encoding.ASCII.GetBytes("Content-Disposition: file; filename=\"" + d.name + ";documentid=" + d.documentId));
buffer.Write(CRLF);
buffer.Write(CRLF);
buffer.Write(d.bytes);
}
// Add closing boundary
buffer.Write(CRLF);
buffer.Write(hyphens);
buffer.Write(boundary);
buffer.Write(hyphens);
buffer.Write(CRLF);
buffer.Flush();
}
WebResponse response;
try
{
response = request.GetResponse();
}
catch (WebException ex)
{
throw ex;
}
var res = "";
using (var stream = response.GetResponseStream())
{
using (var reader = new StreamReader(stream))
{
res = reader.ReadToEnd();
}
}
HttpStatusCode code = ((HttpWebResponse)response).StatusCode;
dynamic obj = JsonConvert.DeserializeObject(res);
bool statusOk = code >= HttpStatusCode.OK && code < HttpStatusCode.MultipleChoices;
if (statusOk)
{
return new EnvelopeSummaryCopy()
{
EnvelopeId = obj.envelopeId,
Status = obj.status,
StatusDateTime = obj.statusDateTime,
Uri = obj.uri
};
}
else
{
throw new Exception(obj.errorCode + " : " + obj.message);
}
}
else throw new Exception(apiCallInfo.ErrorMessage);
}
}
Error from DocuSign API:
{
"errorCode": "REQUEST_ENTITY_TOO_LARGE",
"message": "The request size of 92875839 bytes exceeded the maximum size of 35651584 bytes."
}
The limit for a single API call is 25mb. As IvanD says, you're trying to upload too much in one API call.
The total document size limit for a single Envelope is larger and should accommodate your two documents. But you'll need to upload them separately.
Or you could create a template that includes the documents and then use the template in your Create Envelope call.
The limit for an envelope is 25MB max (all docs combined), you are trying to upload 2x19MB
Try with only one and see what the result will be
I've seen lots of questions about this, but I haven't seen any about doing it recursively. I've created some extension methods that do a pretty good job of pretty printing at a depth of 1. It looks like this:
public static string PrettyToString<K, V>(this Dictionary<K, V> dictionary)
{
string result = "{";
foreach (var kvp in dictionary)
{
result += $"({kvp.Key}, {kvp.Value}) ";
}
result += "}";
return result;
}
public static string PrettyToString<T>(this List<T> list)
{
string result = "{";
foreach (var element in list)
{
result += $"{element}, ";
}
result += "}";
return result;
}
public static string PrettyToString<T>(this T [] list)
{
string result = "{";
foreach (var element in list)
{
result += $"{element}, ";
}
result += "}";
return result;
}
But, what if K, V, or T is another collection like a List or a Dictionary? I want to recursively pretty print, but I'm not sure how to do that. As a result, my output looks like this:
{(foo, System.Collections.Generic.Dictionary`2[System.String,System.Boolean])...
I want it to look like this instead:
{(foo, {(bar, true)})...
I'm looking for methods that print recursively regardless of the nested types:
var x = new List<Dictionary<string, string>>();
var y = new Dictionary<Dictionary<string, string>, string>>();
var z = new Dictionary<Dictionary<string, string>, List<string>>>();
...
x.PrettyToString();
y.PrettyToString();
z.PrettyToString();
Should all recursively print out the contents. How do I achieve that?
I changed the signature of your methods and made them non-generic.
The trick is to determine the type before converting.
Look at the sample code below. I hope it helps.
Please look at the Ext class at the bottom of the source code.
Try it online
using System;
using System.Collections;
using System.Collections.Generic;
namespace ConsoleApp8
{
class Program
{
static void Main(string[] args)
{
var Dic = new Dictionary<int, string> { { 1, "Ali" }, { 2, "B" } };
Console.WriteLine(Dic.PrettyToString());
var Dic1 = new Dictionary<string, float> { { "Ali", 12.5f }, { "B", 99.9f } };
Console.WriteLine(Dic1.PrettyToString());
var Dic2 = new Dictionary<List<int>, string>
{
{ new List<int> { 1, 2, 3 }, "A" },
{ new List<int> { 4, 5, 6 }, "B" }
};
Console.WriteLine(Dic2.PrettyToString());
var Dic3 = new Dictionary<Dictionary<string, string>, string>
{
{ new Dictionary<string, string> { { "a", "A" }, { "b", "B" } }, "Capital" },
{ new Dictionary<string, string> { { "1", "1" }, { "2", "4" }, { "4", "16" } }, "Power" }
};
Console.WriteLine(Dic3.PrettyToString());
var Dic4 = new Dictionary<Dictionary<string, string>, List<string>>
{
{ new Dictionary<string, string> { { "a", "A" }, { "b", "B" } }, new List<string> { "A", "B" } },
{ new Dictionary<string, string> { { "1", "1" }, { "2", "4" }, { "4", "16" } }, new List<string> { "1", "2", "4" } }
};
Console.WriteLine(Dic4.PrettyToString());
var L = new List<List<int>>
{
new List<int> { 1, 2, 3 },
new List<int> { 4, 5, 6 }
};
Console.WriteLine(L.PrettyToString());
Console.ReadKey();
}
}
static class Ext
{
public static string PrettyToString(this IDictionary dictionary)
{
string result = "{";
foreach (var Key in dictionary.Keys)
{
result += string.Format("({0}, {1}) ", PrettyToString(Key), PrettyToString(dictionary[Key]));
}
result += "}";
return result;
}
public static string PrettyToString(this IEnumerable list)
{
string result = "{";
foreach (var element in list)
{
result += string.Format("{0}, ", PrettyToString(element));
}
result += "}";
return result;
}
private static string PrettyToString(object O)
{
var S = O as string;
if (S != null) return S;
var D = O as IDictionary;
if (D != null) return D.PrettyToString();
var L = O as IEnumerable;
if (L != null) return L.PrettyToString();
return O.ToString();
}
}
}
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>"
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;
}
}