Skip to content

Instantly share code, notes, and snippets.

@ChaunceyHoover
Last active August 11, 2023 19:46
Show Gist options
  • Save ChaunceyHoover/3ac18e33445afb2535c4f6afa3af8ac7 to your computer and use it in GitHub Desktop.
Save ChaunceyHoover/3ac18e33445afb2535c4f6afa3af8ac7 to your computer and use it in GitHub Desktop.
Period separated, string-based nodes to a C# parent-child / tree-like node list
public class PermissionNode : List<PermissionNode> {
/// <summary>
/// Represents each text-based node inside of a permission string
/// </summary>
public string Key { get; set; }
/// <summary>
/// The original, non-split permission string for this node
/// </summary>
public string Original { get; set; }
/// <summary>
/// The nodes below this node or "module", if any
/// </summary>
public List<PermissionNode> Children { get; set; } = new List<PermissionNode>();
/// <summary>
/// Converts a list of permission strings (ex: "App.Category.Sub Category.Action", "App.X.Y.Z.Action", etc.) into a parent-child node list
/// </summary>
/// <remarks>
/// NOTE: If all permissions start with the same node (ex: "App.%"), you can safely get the first element of the list and not iterate
/// through it. However, if it has multiple (ex: "App1.%" and "App2.%") or you aren't sure if it'll have multiple, you will have to iterate
/// through this list to get all node trees.
/// </remarks>
/// <param name="nodes">The list of permissions to convert</param>
/// <returns>A parent-child node list</returns>
public static List<PermissionNode> Create(params string[] nodes) {
// "parent" will act as a container to split up the given nodes
var parent = new PermissionNode { Key = "Root", Original = "" }; // arbitrary names - have no meaning
foreach (var node in nodes) {
Process(parent, node, node);
}
return parent.Children;
}
/// <summary>
/// Processes the given permission string recursively to break it down from a string to a series of linked nodes
/// </summary>
/// <param name="parent">The root node of the list</param>
/// <param name="permission">The permission string to be broken down and parented accordingly in the parent</param>
/// <param name="original">The original, non-split permission</param>
private static void Process(PermissionNode parent, string permission, string original) {
var split = permission.Split('.');
if (split.Count() == 1) {
// End of the recursive line - add to parent with no children
parent.Children.Add(new PermissionNode { Key = permission, Original = original, Children = null });
} else {
// Check root of this node has already been processed or not
var node = parent.Children.FirstOrDefault(x => x.Key == split[0]);
// If it hasn't been, add it to list with empty list of children
if (node == null) {
node = new PermissionNode { Key = split[0] };
parent.Children.Add(node);
}
// Populate children
Process(node, String.Join(".", split.Skip(1).Take(split.Count() - 1)), original);
}
}
/// <summary>
/// Converts the given parent-child PermissionNode into a formatted HTML list (ex: unordered or ordered) based off the given `listType`
/// </summary>
/// <remarks>NOTE: This is to be used inside an existing list (ex: <ul> or <ol>) - it will NOT generate the opening tags</remarks>
/// <param name="pm">The parent node, populated with the children</param>
/// <param name="listType">The type of HTML list to use, ex: "ul" or "ol"</param>
/// <param name="tabCount">Used to specify starting amount of tabs for the list for formatting (defaults to 0)</param>
public static string ToHtmlList(PermissionNode pm, string listType, int tabCount = 0) {
// Write the initial <li> element with checkbox and proper tabs. If this is the final child, make the ID of the input the original string value (ex: 'App.X.Y.Z.Action',
// but make the label 'Action')
string listContent = $"{new string('\t', tabCount)}<li><input{(pm.Children == null ? $" id=\"{pm.Original}\"" : "")} type=\"checkbox\">{pm.Key}";
if (pm.Children != null) {
listContent += $"{Environment.NewLine}{new string('\t', tabCount + 1)}<{listType}>{Environment.NewLine}";
foreach (var node in pm.Children)
listContent += ToHtmlList(node, listType, tabCount + 1);
listContent += $"{new string('\t', tabCount + 1)}</{listType}>{Environment.NewLine}{new string('\t', tabCount)}</li>{Environment.NewLine}";
} else {
listContent += $"</li>{Environment.NewLine}";
}
return listContent;
}
/// <summary>
/// Prints out the parent-child node showing the relationship for the given PermissionNode
/// </summary>
/// <param name="pm">The PermissionNode to explore</param>
/// <param name="depth">The amount of '+' symbols to be used for this iteration of the recursive list</param>
public static void Print(PermissionNode pm, int depth = 0) {
// Print out the parent node
Console.WriteLine($"{new string('+', depth)}{pm.Key}");
// If it has children, increase the depth by 1 and print out each child under the parent
if (pm.Children != null)
foreach (var node in pm.Children)
Print(node, depth + 1);
}
}
//////////////
// EXAMPLES //
//////////////
// Convert the following list:
// ["App1.Category.Sub Category.Action 1", "App1.Category.Sub Category.Action 2", "App2.Category.Sub Category.Action 1", "App2.Category.Sub Category.Action 2"]
// into an unordered HTML list:
var list = PermissionNode.Create("App1.Category.Sub Category.Action 1",
"App1.Category.Sub Category.Action 2",
"App2.Category.Sub Category.Action 1",
"App2.Category.Sub Category.Action 2");
foreach (var rootNode in list)
Console.WriteLine(PermissionNode.ToHtmlList(rootNode, "ul"));
/**
* Outputs the following:
<li><input type="checkbox">App1
<ul>
<li><input type="checkbox">Category
<ul>
<li><input type="checkbox">Sub Category
<ul>
<li><input id="App1.Category.Sub Category.Action 1" type="checkbox">Action 1</li>
<li><input id="App1.Category.Sub Category.Action 2" type="checkbox">Action 2</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><input type="checkbox">App2
<ul>
<li><input type="checkbox">Category
<ul>
<li><input type="checkbox">Sub Category
<ul>
<li><input id="App2.Category.Sub Category.Action 1" type="checkbox">Action 1</li>
<li><input id="App2.Category.Sub Category.Action 2" type="checkbox">Action 2</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
*/
////////////////////////////////////////
// Debug the same list above in the terminal
var list = PermissionNode.Create("App1.Category.Sub Category.Action 1",
"App1.Category.Sub Category.Action 2",
"App2.Category.Sub Category.Action 1",
"App2.Category.Sub Category.Action 2");
foreach (var rootNode in list)
PermissionNode.Print(rootNode);
/**
* Outputs:
App1
+Category
++Sub Category
+++Action 1
+++Action 2
App2
+Category
++Sub Category
+++Action 1
+++Action 2
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment