/// <summary> /// Inserts a new child node before the reference node passed. /// </summary> /// <param name="Node">The new child node to add. This may not be already childed to another node</param> /// <param name="After">The reference node. This must be a child of the current node</param> /// <returns>The added node</returns> public virtual BBCodeNode InsertBefore(BBCodeNode Node, BBCodeNode Before) { if (Singular) { throw new InvalidOperationException("Cannot add children to a singular node"); } if (Node == null) { throw new ArgumentNullException("Node may not be null"); } if (Before == null) { throw new ArgumentNullException("After may not be null"); } if (Node.Parent != null) { throw new ArgumentException("The Node provided is already a child of another node"); } if (Before.Parent == null || Before.Parent != this) { throw new ArgumentException("The Before node provided is not a child of this node"); } children.Insert(children.IndexOf(Before), Node); return(Node); }
/// <summary> /// Adds a new child node at the end of this node's descendants /// </summary> /// <param name="TagName">The node's tag name. Mandatory.</param> /// <param name="Attribute">The node's optional attribute. This may be an empty string or null.</param> /// <returns>The newly created child node</returns> public virtual BBCodeNode AppendChild(string TagName, string Attribute) { var node = new BBCodeNode(TagName, Attribute) { Parent = this, }; return(AppendChild(node)); }
/// <summary> /// Creates a recursive copy of the current nodes and its children /// </summary> /// <returns>A deep clone of the current node</returns> public virtual object Clone() { BBCodeNode node = new BBCodeNode(TagName, Attribute); foreach (var Child in Children) { node.AppendChild((BBCodeNode)Child.Clone()); } return(node); }
/// <summary> /// Removes a specific child node /// </summary> /// <param name="Node">The child node to remove. This must be a child of the current node.</param> /// <returns>The removed node</returns> public virtual BBCodeNode RemoveChild(BBCodeNode Node) { if (Node == null) { throw new ArgumentNullException("Node may not be null"); } if (Node.Parent != null) { throw new ArgumentException("The BBCodeNode provided is not a child of this node"); } children.Remove(Node); return(Node); }
/// <summary> /// Adds a new child node at the beginning of this node's descendants /// </summary> /// <param name="Node">The existing BBCodeNode to add. This may not already be childed to another node.</param> /// <returns>The node passed</returns> public virtual BBCodeNode PrependChild(BBCodeNode Node) { if (Singular) { throw new InvalidOperationException("Cannot add children to a singular node"); } if (Node == null) { throw new ArgumentNullException("Node may not be null"); } if (Node.Parent != null) { throw new ArgumentException("The BBCodeNode provided is already a child of another node"); } children.Insert(0, Node); return(Node); }
/// <summary> /// Replaces a specific child node with another /// </summary> /// <param name="Old">The node to remove. This must be a child of this node</param> /// <param name="New">The replacement node. This may not already be childed to another node</param> /// <returns>The removed node</returns> public virtual BBCodeNode ReplaceChild(BBCodeNode Old, BBCodeNode New) { if (Old == null || New == null) { throw new ArgumentNullException("Arguments may not be null"); } if (Old.Parent != this) { throw new ArgumentException("The Old node provided is not a child of this node"); } if (New.Parent != null) { throw new ArgumentException("The New node provided is a child of another node"); } int index = children.IndexOf(Old); children.Remove(Old); children.Insert(index, New); return(Old); }
/// <summary> /// Loads a string of BBCode as a BBCodeDocument object /// </summary> /// <param name="BBCode">A string of BBCode text</param> /// <param name="ThrowOnError">Whether to throw an exception on parse error. If false, the error is ignored</param> /// <param name="SingularTags">A list of tags which should be considered singular by the parser. Singular tags are self closing and may not have children.</param> /// <returns>The DOM representation of the text</returns> public static BBCodeDocument Load(string BBCode, bool ThrowOnError, IEnumerable<string> SingularTags) { BBCodeDocument document = new BBCodeDocument(); Stack<BBCodeNode> nodestack = new Stack<BBCodeNode>(); nodestack.Push(document); // iterate through all characters in text for (int i = 0; i < BBCode.Length; i++) { // the character is not a tag if (BBCode[i] != '[') AddPlainText(document, nodestack, BBCode[i].ToString()); // beginning of a tag else { // if we have an open text node, close it. if ((nodestack.Peek() as BBCodeTextNode) != null) nodestack.Pop(); StringBuilder TagName = new StringBuilder(); i++; // edge case where there is an [ as the last character if (i == BBCode.Length) { if (ThrowOnError) throw new BBCodeParseException("Reached end of document while reading tag", i); AddPlainText(document, nodestack, "[" + TagName.ToString()); continue; } bool IsClosing = BBCode[i] == '/'; if (IsClosing) i++; // read in the entire tagname while (i < BBCode.Length && char.IsLetter(BBCode[i])) TagName.Append(BBCode[i++]); if (i == BBCode.Length) break; // reached the end of tagname, handle accordingly if (!IsClosing && (BBCode[i] == '=' || BBCode[i] == ']')) { // this fixes exception thrown when user inputs [] if (string.IsNullOrEmpty(TagName.ToString())) continue; var el = new BBCodeNode(TagName.ToString(), "", SingularTags.Contains(TagName.ToString())); nodestack.Peek().AppendChild(el); if (!el.Singular) nodestack.Push(el); if (BBCode[i] == ']') continue; // skip over the equals sign so we don't accidentally read it into the attribute if (BBCode[i] == '=') i++; StringBuilder Attribute = new StringBuilder(); while (i < BBCode.Length && BBCode[i] != ']') Attribute.Append(BBCode[i++]); el.Attribute = Attribute.ToString(); } else if (IsClosing && BBCode[i] == ']') { if (nodestack.Count == 0 || nodestack.Peek().TagName != TagName.ToString()) { if (ThrowOnError) throw new BBCodeParseException("Unmatched closing tag", i); AddPlainText(document, nodestack, "[/" + TagName.ToString() + "]"); continue; } nodestack.Pop(); } else { // edge case where encountering something like: [[i] would cause the [i] tag not to be read properly if (BBCode[i] == '[') i--; // illegal character in tag name if (ThrowOnError) throw new BBCodeParseException("Illegal character in tag name", i); // if ThrowOnError is false, we'll just add it as plain text. AddPlainText(document, nodestack, "[" + TagName.ToString()); // prepend the [ char which started the tag } } } // close up a final text node if it exists: if ((nodestack.Peek() as BBCodeTextNode) != null) nodestack.Pop(); // close the body tag if it's the next one if ((nodestack.Peek() as BBCodeDocument) != null) nodestack.Pop(); if (nodestack.Count > 0 && ThrowOnError) throw new BBCodeParseException("Reached end of document with " + (nodestack.Count-1).ToString() + " unclosed tags", BBCode.Length); return document; }
/// <summary> /// Replaces a specific child node with another /// </summary> /// <param name="Old">The node to remove. This must be a child of this node</param> /// <param name="New">The replacement node. This may not already be childed to another node</param> /// <returns>The removed node</returns> public virtual BBCodeNode ReplaceChild(BBCodeNode Old, BBCodeNode New) { if (Old == null || New == null) throw new ArgumentNullException("Arguments may not be null"); if (Old.Parent != this) throw new ArgumentException("The Old node provided is not a child of this node"); if (New.Parent != null) throw new ArgumentException("The New node provided is a child of another node"); int index = children.IndexOf(Old); children.Remove(Old); children.Insert(index, New); return Old; }
/// <summary> /// Removes a specific child node /// </summary> /// <param name="Node">The child node to remove. This must be a child of the current node.</param> /// <returns>The removed node</returns> public virtual BBCodeNode RemoveChild(BBCodeNode Node) { if (Node == null) throw new ArgumentNullException("Node may not be null"); if (Node.Parent != null) throw new ArgumentException("The BBCodeNode provided is not a child of this node"); children.Remove(Node); return Node; }
/// <summary> /// Adds a new child node at the beginning of this node's descendants /// </summary> /// <param name="Node">The existing BBCodeNode to add. This may not already be childed to another node.</param> /// <returns>The node passed</returns> public virtual BBCodeNode PrependChild(BBCodeNode Node) { if (Singular) throw new InvalidOperationException("Cannot add children to a singular node"); if (Node == null) throw new ArgumentNullException("Node may not be null"); if (Node.Parent != null) throw new ArgumentException("The BBCodeNode provided is already a child of another node"); children.Insert(0, Node); return Node; }
/// <summary> /// Inserts a new child node before the reference node passed. /// </summary> /// <param name="Node">The new child node to add. This may not be already childed to another node</param> /// <param name="After">The reference node. This must be a child of the current node</param> /// <returns>The added node</returns> public virtual BBCodeNode InsertBefore(BBCodeNode Node, BBCodeNode Before) { if (Singular) throw new InvalidOperationException("Cannot add children to a singular node"); if(Node == null) throw new ArgumentNullException("Node may not be null"); if(Before == null) throw new ArgumentNullException("After may not be null"); if (Node.Parent != null) throw new ArgumentException("The Node provided is already a child of another node"); if (Before.Parent == null || Before.Parent != this) throw new ArgumentException("The Before node provided is not a child of this node"); children.Insert(children.IndexOf(Before), Node); return Node; }
/// <summary> /// Creates a recursive copy of the current nodes and its children /// </summary> /// <returns>A deep clone of the current node</returns> public virtual object Clone() { BBCodeNode node = new BBCodeNode(TagName, Attribute); foreach (var Child in Children) node.AppendChild((BBCodeNode)Child.Clone()); return node; }
/// <summary> /// Adds a new child node at the end of this node's descendants /// </summary> /// <param name="TagName">The node's tag name. Mandatory.</param> /// <param name="Attribute">The node's optional attribute. This may be an empty string or null.</param> /// <returns>The newly created child node</returns> public virtual BBCodeNode AppendChild(string TagName, string Attribute) { var node = new BBCodeNode(TagName, Attribute) { Parent = this, }; return AppendChild(node); }
/// <summary> /// Loads a string of BBCode as a BBCodeDocument object /// </summary> /// <param name="BBCode">A string of BBCode text</param> /// <param name="ThrowOnError">Whether to throw an exception on parse error. If false, the error is ignored</param> /// <param name="SingularTags">A list of tags which should be considered singular by the parser. Singular tags are self closing and may not have children.</param> /// <returns>The DOM representation of the text</returns> public static BBCodeDocument Load(string BBCode, bool ThrowOnError, IEnumerable <string> SingularTags) { BBCodeDocument document = new BBCodeDocument(); Stack <BBCodeNode> nodestack = new Stack <BBCodeNode>(); nodestack.Push(document); // iterate through all characters in text for (int i = 0; i < BBCode.Length; i++) { // the character is not a tag if (BBCode[i] != '[') { AddPlainText(document, nodestack, BBCode[i].ToString()); } // beginning of a tag else { // if we have an open text node, close it. if ((nodestack.Peek() as BBCodeTextNode) != null) { nodestack.Pop(); } StringBuilder TagName = new StringBuilder(); i++; // edge case where there is an [ as the last character if (i == BBCode.Length) { if (ThrowOnError) { throw new BBCodeParseException("Reached end of document while reading tag", i); } AddPlainText(document, nodestack, "[" + TagName.ToString()); continue; } bool IsClosing = BBCode[i] == '/'; if (IsClosing) { i++; } // read in the entire tagname while (i < BBCode.Length && char.IsLetter(BBCode[i])) { TagName.Append(BBCode[i++]); } if (i == BBCode.Length) { break; } // reached the end of tagname, handle accordingly if (!IsClosing && (BBCode[i] == '=' || BBCode[i] == ']')) { var el = new BBCodeNode(TagName.ToString(), "", SingularTags.Contains(TagName.ToString())); nodestack.Peek().AppendChild(el); if (!el.Singular) { nodestack.Push(el); } if (BBCode[i] == ']') { continue; } // skip over the equals sign so we don't accidentally read it into the attribute if (BBCode[i] == '=') { i++; } StringBuilder Attribute = new StringBuilder(); while (i < BBCode.Length && BBCode[i] != ']') { Attribute.Append(BBCode[i++]); } el.Attribute = Attribute.ToString(); } else if (IsClosing && BBCode[i] == ']') { if (nodestack.Count == 0 || nodestack.Peek().TagName != TagName.ToString()) { if (ThrowOnError) { throw new BBCodeParseException("Unmatched closing tag", i); } AddPlainText(document, nodestack, "[/" + TagName.ToString() + "]"); continue; } nodestack.Pop(); } else { // edge case where encountering something like: [[i] would cause the [i] tag not to be read properly if (BBCode[i] == '[') { i--; } // illegal character in tag name if (ThrowOnError) { throw new BBCodeParseException("Illegal character in tag name", i); } // if ThrowOnError is false, we'll just add it as plain text. AddPlainText(document, nodestack, "[" + TagName.ToString()); // prepend the [ char which started the tag } } } // close up a final text node if it exists: if ((nodestack.Peek() as BBCodeTextNode) != null) { nodestack.Pop(); } // close the body tag if it's the next one if ((nodestack.Peek() as BBCodeDocument) != null) { nodestack.Pop(); } if (nodestack.Count > 0 && ThrowOnError) { throw new BBCodeParseException("Reached end of document with " + (nodestack.Count - 1).ToString() + " unclosed tags", BBCode.Length); } return(document); }