/// <summary> /// Internal remove a node. /// </summary> internal void RemoveNode(HNode node) { // If node is alone, reset the list if (node.nextNode == node) { this.content = null; } else { // Find previous node var prev = node.nextNode; while (prev.nextNode != node) { prev = prev.nextNode; } // Clean the list prev.nextNode = node.nextNode; // If the node is the last, then prev become the last if (this.content == node) { this.content = prev; } } // Detach node node.parent = null; node.nextNode = null; }
/// <summary> /// Enumerate deeper child nodes /// </summary> protected IEnumerable <HNode> GetDescendantNodes(bool self) { if (self) { yield return(this); } HNode n = this; while (true) { HContainer c = n as HContainer; HNode first; if (c != null && (first = c.FirstNode) != null) { n = first; } else { while (n != null && n != this && n == n.parent.content) { n = n.parent; } if (n == null || n == this) { break; } n = n.nextNode; } yield return(n); } }
/// <summary> /// Serialize a node /// </summary> protected virtual void SerializeNode(HNode node, TextWriter writer) { if (node is HDocument) { SerializeDocument((HDocument)node, writer); } else if (node is HDocumentType) { SerializeDocumentType((HDocumentType)node, writer); } else if (node is HXmlDeclaration) { SerializeXmlDeclaration((HXmlDeclaration)node, writer); } else if (node is HComment) { SerializeComment((HComment)node, writer); } else if (node is HCData) { SerializeCData((HCData)node, writer); } else if (node is HText) { SerializeText((HText)node, writer); } else if (node is HElement) { SerializeElement((HElement)node, writer); } }
/// <summary> /// Internal insertion of string. /// </summary> void InsertString(HNode before, String s) { // Valid the string ValidateString(s); // If no content the we affect the texte if (content == null) { content = s; } else if (s.Length > 0) { if (before == null) { // If the content is a string, we concat them if (content is string) { content = (string)content + s; } else { // If the content is an HText node the we adding the string to the node HText tn = content as HText; if (tn != null && !(tn is HCData)) { tn.value += s; } else { AppendNode(new HText(s)); } } } else if (before == FirstNodeRef) { // If the content is a string, we concat them if (content is string) { content = s + (string)content; } else { // If the content is an HText node the we adding the string to the node HText tn = content as HText; if (tn != null && !(tn is HCData)) { tn.value = s + tn.value; } else { InsertNode(FirstNodeRef, new HText(s)); } } } else { InsertNode(before, new HText(s)); } } }
/// <summary> /// Insert element after the target /// </summary> public static T InsertAfter <T>(this T element, HNode target) where T : HNode { if (element != null && target != null) { target.AddAfter(element); } return(element); }
/// <summary> /// Insert the set of elements after the target /// </summary> public static IEnumerable <T> InsertAfter <T>(this IEnumerable <T> elements, HNode target) where T : HNode { if (elements != null && target != null) { target.AddAfter(elements.ToArray()); } return(elements); }
/// <summary> /// Insert <paramref name="node"/> before the <paramref name="before"/> node. /// </summary> /// <remarks> /// If previous is null then insert the node at the end of the list. /// </remarks> void InsertNode(HNode before, HNode node) { // Validate the node ValidateNode(node, this); // If the node have a parent we clone it if (node.Parent != null) { node = node.CloneNode(); } else { // If node is the parent of this container, then we clone it HNode p = this; while (p.parent != null) { p = p.Parent; } if (node == p) { node = node.CloneNode(); } } // Set the parent node.parent = this; // Content empty ? if (content == null) { node.nextNode = node; content = node; } else if (before == null) { ConvertContentTextToNode(); // Add at the end of the list node.nextNode = ((HNode)content).nextNode; ((HNode)content).nextNode = node; content = node; } else if (before == FirstNodeRef) { ConvertContentTextToNode(); // Add at the beginning of the list node.nextNode = ((HNode)content).nextNode; ((HNode)content).nextNode = node; } else { // Search the 'previous' before node var previousBefore = before; while (previousBefore.nextNode != before) { previousBefore = previousBefore.nextNode; } // Insert the node to the list node.nextNode = before; previousBefore.nextNode = node; } }
/// <summary> /// Can't accept a document or document type nodes /// </summary> internal override void ValidateNode(HNode node, HNode previous) { base.ValidateNode(node, previous); if (node is HDocument) { throw new ArgumentException("Can't add a document in a element."); } if (node is HDocumentType) { throw new ArgumentException("Can't add a document type in a element."); } }
/// <summary> /// Serialize a node /// </summary> public String SerializeNode(HNode node) { if (node == null) { throw new ArgumentNullException("node"); } StringBuilder html = new StringBuilder(); using (var writer = new StringWriter(html)) SerializeNode(node, writer); return(html.ToString()); }
/// <summary> /// Returns the nodes contained in this element. /// </summary> public IEnumerable <HNode> Nodes() { HNode n = LastNode; if (n != null) { do { n = n.nextNode; yield return(n); } while (n.parent == this && n != content); } }
/// <summary> /// Get all precedings siblings of the element /// </summary> public static IEnumerable <HNode> PrevAll(this HNode element) { if (element != null) { var p = element.PreviousNode; while (p != null) { yield return(p); p = p.PreviousNode; } } }
/// <summary> /// Get all next siblings of the element /// </summary> public static IEnumerable <HNode> NextAll(this HNode element) { if (element != null) { var p = element.NextNode; while (p != null) { yield return(p); p = p.NextNode; } } }
/// <summary> /// Insert a content before the <param name="insert" /> node. /// </summary> internal void Insert(HNode insert, object content) { if (content == null) { return; } HNode n = content as HNode; if (n != null) { InsertNode(insert, n); return; } string s = content as string; if (s != null) { InsertString(insert, s); return; } HAttribute a = content as HAttribute; if (a != null) { AddAttribute(a); return; } IEnumerable e = content as IEnumerable; if (e != null) { if (insert == FirstNodeRef) { foreach (object obj in e.Cast <Object>().Reverse()) { Insert(insert, obj); } } else { foreach (object obj in e) { Insert(insert, obj); } } return; } InsertString(insert, content.ToString()); }
/// <summary> /// Remove all nodes /// </summary> public void RemoveNodes() { if (this.content is HNode) { HNode node = (HNode)this.content; while (node != null) { node.parent = null; var n = node.nextNode; node.nextNode = null; node = n; } } this.content = null; }
/// <summary> /// Enumerate all child elements /// </summary> protected IEnumerable <HElement> GetElements(String name) { HNode n = content as HNode; if (n != null) { do { n = n.nextNode; HElement e = n as HElement; if (e != null && (name == null || String.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase))) { yield return(e); } } while (n.parent == this && n != content); } }
/// <summary> /// Find the node of a type /// </summary> /// <remarks> /// This is just an helper more fast than an GetElements().FirstOrDefault(). /// </remarks> T FindFirstNode <T>() where T : HNode { HNode n = content as HNode; if (n != null) { do { n = n.nextNode; T e = n as T; if (e != null) { return(e); } } while (n != content); } return(null); }
internal HContainer(HContainer other) { if (other.content is string) { this.content = other.content; } else { HNode n = (HNode)other.content; if (n != null) { do { n = n.nextNode; AppendNode(n.CloneNode()); } while (n != other.content); } } }
/// <summary> /// Enumerate deeper child elements /// </summary> protected IEnumerable <HElement> GetDescendants(String name, bool self) { if (self) { HElement e = (HElement)this; if (name == null || String.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase)) { yield return(e); } } HNode n = this; HContainer c = this; while (true) { if (c != null && c.content is HNode) { n = ((HNode)c.content).nextNode; } else { while (n != this && n == n.parent.content) { n = n.parent; } if (n == this) { break; } n = n.nextNode; } HElement e = n as HElement; if (e != null && (name == null || String.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase))) { yield return(e); } c = e; } }
internal override void ValidateNode(HNode node, HNode previous) { base.ValidateNode(node, previous); // Can't accept CData if (node is HCData) { throw new ArgumentException("Can't add CData in a document"); } if (node is HDocument) { throw new ArgumentException("Can't add a document in a document"); } // If node is a text, validate the string if (node is HText) { ValidateString(((HText)node).Value); } else if (node is HXmlDeclaration) { // If the xml declaration is defined, we can't add an another if (XmlDeclaration != null) { throw new ArgumentException("Xml declaration is alreay defined."); } // We can't add a xml declaration after the document type if (DocumentType != null) { throw new ArgumentException("Can't add a xml declaration after the document type."); } // We can't add a xml declaration after the root if (Root != null) { throw new ArgumentException("Can't add a xml declaration after the root node."); } } else if (node is HDocumentType) { // If the document type is defined, we can't add an another if (DocumentType != null) { throw new ArgumentException("Document type is alreay defined."); } // We can't add a document type after the root if (Root != null) { throw new ArgumentException("Can't add a document type after the root node."); } } else if (node is HElement) { // If root is defined, then we can't add a new element if (Root != null) { throw new ArgumentException("Root is already defined."); } } }
/// <summary> /// Add node at the end of the list. /// </summary> void AppendNode(HNode n) { InsertNode(null, n); }
/// <summary> /// Deserialize as a list of nodes /// </summary> public IEnumerable <HNode> Deserialize(TextReader reader, Func <Exception, bool> errorHandler = null) { if (reader == null) { throw new ArgumentNullException("reader"); } // Create the parser var parser = new HParser(reader); parser.RemoveUnknownOrInvalidEntities = this.RemoveUnknownOrInvalidEntities; var token = ParseNext(parser, errorHandler); Stack <HElement> opened = new Stack <HElement>(); HXmlDeclaration currentXDecl = null; String tag; HNode tokenToReturns = null; while (token != null) { switch (token.TokenType) { case ParsedTokenType.Text: var htxt = new HText(HEntity.HtmlDecode(((ParsedText)token).Text, RemoveUnknownOrInvalidEntities)); if (opened.Count > 0) { ProtectAddOnPeek(opened, htxt, errorHandler); } else { tokenToReturns = htxt; } break; case ParsedTokenType.CData: var hcd = new HCData(((ParsedCData)token).Text); if (opened.Count > 0) { ProtectAddOnPeek(opened, hcd, errorHandler); } else { tokenToReturns = hcd; } break; case ParsedTokenType.Comment: var hcom = new HComment(HEntity.HtmlDecode(((ParsedComment)token).Text, RemoveUnknownOrInvalidEntities)); if (opened.Count > 0) { ProtectAddOnPeek(opened, hcom, errorHandler); } else { tokenToReturns = hcom; } break; case ParsedTokenType.OpenTag: opened.Push(new HElement(((ParsedTag)token).TagName)); break; case ParsedTokenType.AutoClosedTag: System.Diagnostics.Debug.Assert(opened.Count > 0, "Opened tags are empty when receiving AutoClosedTag."); System.Diagnostics.Debug.Assert(opened.Peek().Name == ((ParsedTag)token).TagName, "AutoClosedTag and opened element are not same tag name."); var actag = opened.Pop(); if (opened.Count > 0) { ProtectAddOnPeek(opened, actag, errorHandler); } else { tokenToReturns = actag; } break; case ParsedTokenType.CloseTag: System.Diagnostics.Debug.Assert(opened.Count > 0, "Opened tags are empty when receiving CloseTag."); System.Diagnostics.Debug.Assert(opened.Peek().Name == ((ParsedTag)token).TagName, "CloseTag and opened element are not same tag name."); // Tag with text content String tagName = opened.Peek().Name; if (IsRawElement(tagName) || IsEscapableRawElement(tagName)) { token = ParseContentTextNext(tagName, parser, errorHandler); continue; } break; case ParsedTokenType.EndTag: tag = ((ParsedTag)token).TagName; HElement helm; // Close all elements that not matching the tag while (opened.Count > 0 && !String.Equals(opened.Peek().Name, tag, StringComparison.OrdinalIgnoreCase)) { helm = opened.Pop(); if (opened.Count > 0) { ProtectAddOnPeek(opened, helm, errorHandler); } else { yield return(helm); } } // If we are opened tag, then close it because we find our element if (opened.Count > 0) { helm = opened.Pop(); if (opened.Count > 0) { ProtectAddOnPeek(opened, helm, errorHandler); } else { yield return(helm); } } break; case ParsedTokenType.OpenProcessInstruction: if (currentXDecl != null) { while ((token = ParseNext(parser, errorHandler)) != null && token.TokenType != ParsedTokenType.CloseProcessInstruction) { ; } ProcessError(new ParseError("XML declaration already opened."), errorHandler); } tag = ((ParsedTag)token).TagName; if (!String.Equals("xml", tag, StringComparison.OrdinalIgnoreCase)) { while ((token = ParseNext(parser, errorHandler)) != null && token.TokenType != ParsedTokenType.CloseProcessInstruction) { ; } ProcessError(new ParseError(String.Format("Unexpected '{0}' process instruction.", tag)), errorHandler); } currentXDecl = new HXmlDeclaration(null, null, null); break; case ParsedTokenType.CloseProcessInstruction: if (currentXDecl == null) { ProcessError(new ParseError("No XML declaration opened."), errorHandler); } else { if (opened.Count > 0) { ProtectAddOnPeek(opened, currentXDecl, errorHandler); } else { tokenToReturns = currentXDecl; } } currentXDecl = null; break; case ParsedTokenType.Doctype: var vs = ((ParsedDoctype)token).Values ?? new String[0]; var hdt = new HDocumentType( vs.Length > 0 ? vs[0] : null, vs.Length > 1 ? vs[1] : null, vs.Length > 2 ? vs[2] : null, vs.Length > 3 ? vs[3] : null ); if (opened.Count > 0) { ProtectAddOnPeek(opened, hdt, errorHandler); } else { tokenToReturns = hdt; } break; case ParsedTokenType.Attribute: var attr = (ParsedAttribute)token; // Xml declaration ? if (currentXDecl != null) { if (String.Equals("version", attr.Name, StringComparison.OrdinalIgnoreCase)) { currentXDecl.Version = attr.Value; } else if (String.Equals("encoding", attr.Name, StringComparison.OrdinalIgnoreCase)) { currentXDecl.Encoding = attr.Value; } else if (String.Equals("standalone", attr.Name, StringComparison.OrdinalIgnoreCase)) { currentXDecl.Standalone = attr.Value; } else { ProcessError(new ParseError(String.Format("Invalid XML declaration attribute : ''", attr.Name)), errorHandler); } } System.Diagnostics.Debug.Assert(opened.Count > 0, "No element opened for the attribtue."); ProtectAddOnPeek(opened, new HAttribute(attr.Name, attr.Value), errorHandler); break; //default: // break; } // Returns a token if we have one if (tokenToReturns != null) { yield return(tokenToReturns); tokenToReturns = null; } // Next token token = ParseNext(parser, errorHandler); } // Close all opened elements while (opened.Count > 0) { yield return(opened.Pop()); } }
/// <remarks> /// Validate insertion of the given node. previous is the node after which insertion /// will occur. previous == null means at beginning, previous == this means at end. /// </remarks> internal virtual void ValidateNode(HNode node, HNode previous) { }