1 // HtmlAgilityPack V1.0 - Simon Mourier <simon underscore mourier at hotmail dot com>
3 using System
.Diagnostics
;
7 using System
.Xml
.XPath
;
9 namespace HtmlAgilityPack
12 /// Represents an HTML navigator on an HTML document seen as a data store.
14 public class HtmlNodeNavigator
: XPathNavigator
18 private int _attindex
;
19 private HtmlNode _currentnode
;
20 private HtmlDocument _doc
= new HtmlDocument();
21 private HtmlNameTable _nametable
= new HtmlNameTable();
29 internal HtmlNodeNavigator()
34 internal HtmlNodeNavigator(HtmlDocument doc
, HtmlNode currentNode
)
36 if (currentNode
== null)
38 throw new ArgumentNullException("currentNode");
40 if (currentNode
.OwnerDocument
!= doc
)
42 throw new ArgumentException(HtmlDocument
.HtmlExceptionRefNotChild
);
48 _currentnode
= currentNode
;
51 private HtmlNodeNavigator(HtmlNodeNavigator nav
)
55 throw new ArgumentNullException("nav");
60 _currentnode
= nav
._currentnode
;
61 _attindex
= nav
._attindex
;
62 _nametable
= nav
._nametable
; // REVIEW: should we do this?
66 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a stream.
68 /// <param name="stream">The input stream.</param>
69 public HtmlNodeNavigator(Stream stream
)
76 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a stream.
78 /// <param name="stream">The input stream.</param>
79 /// <param name="detectEncodingFromByteOrderMarks">Indicates whether to look for byte order marks at the beginning of the stream.</param>
80 public HtmlNodeNavigator(Stream stream
, bool detectEncodingFromByteOrderMarks
)
82 _doc
.Load(stream
, detectEncodingFromByteOrderMarks
);
87 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a stream.
89 /// <param name="stream">The input stream.</param>
90 /// <param name="encoding">The character encoding to use.</param>
91 public HtmlNodeNavigator(Stream stream
, Encoding encoding
)
93 _doc
.Load(stream
, encoding
);
98 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a stream.
100 /// <param name="stream">The input stream.</param>
101 /// <param name="encoding">The character encoding to use.</param>
102 /// <param name="detectEncodingFromByteOrderMarks">Indicates whether to look for byte order marks at the beginning of the stream.</param>
103 public HtmlNodeNavigator(Stream stream
, Encoding encoding
, bool detectEncodingFromByteOrderMarks
)
105 _doc
.Load(stream
, encoding
, detectEncodingFromByteOrderMarks
);
110 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a stream.
112 /// <param name="stream">The input stream.</param>
113 /// <param name="encoding">The character encoding to use.</param>
114 /// <param name="detectEncodingFromByteOrderMarks">Indicates whether to look for byte order marks at the beginning of the stream.</param>
115 /// <param name="buffersize">The minimum buffer size.</param>
116 public HtmlNodeNavigator(Stream stream
, Encoding encoding
, bool detectEncodingFromByteOrderMarks
, int buffersize
)
118 _doc
.Load(stream
, encoding
, detectEncodingFromByteOrderMarks
, buffersize
);
123 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a TextReader.
125 /// <param name="reader">The TextReader used to feed the HTML data into the document.</param>
126 public HtmlNodeNavigator(TextReader reader
)
133 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a file.
135 /// <param name="path">The complete file path to be read.</param>
136 public HtmlNodeNavigator(string path
)
143 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a file.
145 /// <param name="path">The complete file path to be read.</param>
146 /// <param name="detectEncodingFromByteOrderMarks">Indicates whether to look for byte order marks at the beginning of the file.</param>
147 public HtmlNodeNavigator(string path
, bool detectEncodingFromByteOrderMarks
)
149 _doc
.Load(path
, detectEncodingFromByteOrderMarks
);
154 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a file.
156 /// <param name="path">The complete file path to be read.</param>
157 /// <param name="encoding">The character encoding to use.</param>
158 public HtmlNodeNavigator(string path
, Encoding encoding
)
160 _doc
.Load(path
, encoding
);
165 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a file.
167 /// <param name="path">The complete file path to be read.</param>
168 /// <param name="encoding">The character encoding to use.</param>
169 /// <param name="detectEncodingFromByteOrderMarks">Indicates whether to look for byte order marks at the beginning of the file.</param>
170 public HtmlNodeNavigator(string path
, Encoding encoding
, bool detectEncodingFromByteOrderMarks
)
172 _doc
.Load(path
, encoding
, detectEncodingFromByteOrderMarks
);
177 /// Initializes a new instance of the HtmlNavigator and loads an HTML document from a file.
179 /// <param name="path">The complete file path to be read.</param>
180 /// <param name="encoding">The character encoding to use.</param>
181 /// <param name="detectEncodingFromByteOrderMarks">Indicates whether to look for byte order marks at the beginning of the file.</param>
182 /// <param name="buffersize">The minimum buffer size.</param>
183 public HtmlNodeNavigator(string path
, Encoding encoding
, bool detectEncodingFromByteOrderMarks
, int buffersize
)
185 _doc
.Load(path
, encoding
, detectEncodingFromByteOrderMarks
, buffersize
);
194 /// Gets the base URI for the current node.
195 /// Always returns string.Empty in the case of HtmlNavigator implementation.
197 public override string BaseURI
202 return _nametable
.GetOrAdd(string.Empty
);
207 /// Gets the current HTML document.
209 public HtmlDocument CurrentDocument
215 /// Gets the current HTML node.
217 public HtmlNode CurrentNode
219 get { return _currentnode; }
223 /// Gets a value indicating whether the current node has child nodes.
225 public override bool HasAttributes
229 InternalTrace(">" + (_currentnode
.Attributes
.Count
> 0));
230 return (_currentnode
.Attributes
.Count
> 0);
235 /// Gets a value indicating whether the current node has child nodes.
237 public override bool HasChildren
241 InternalTrace(">" + (_currentnode
.ChildNodes
.Count
> 0));
242 return (_currentnode
.ChildNodes
.Count
> 0);
247 /// Gets a value indicating whether the current node is an empty element.
249 public override bool IsEmptyElement
253 InternalTrace(">" + !HasChildren
);
254 // REVIEW: is this ok?
260 /// Gets the name of the current HTML node without the namespace prefix.
262 public override string LocalName
268 InternalTrace("att>" + _currentnode
.Attributes
[_attindex
].Name
);
269 return _nametable
.GetOrAdd(_currentnode
.Attributes
[_attindex
].Name
);
271 InternalTrace("node>" + _currentnode
.Name
);
272 return _nametable
.GetOrAdd(_currentnode
.Name
);
277 /// Gets the qualified name of the current node.
279 public override string Name
283 InternalTrace(">" + _currentnode
.Name
);
284 return _nametable
.GetOrAdd(_currentnode
.Name
);
289 /// Gets the namespace URI (as defined in the W3C Namespace Specification) of the current node.
290 /// Always returns string.Empty in the case of HtmlNavigator implementation.
292 public override string NamespaceURI
297 return _nametable
.GetOrAdd(string.Empty
);
302 /// Gets the <see cref="XmlNameTable"/> associated with this implementation.
304 public override XmlNameTable NameTable
314 /// Gets the type of the current node.
316 public override XPathNodeType NodeType
320 switch (_currentnode
.NodeType
)
322 case HtmlNodeType
.Comment
:
323 InternalTrace(">" + XPathNodeType
.Comment
);
324 return XPathNodeType
.Comment
;
326 case HtmlNodeType
.Document
:
327 InternalTrace(">" + XPathNodeType
.Root
);
328 return XPathNodeType
.Root
;
330 case HtmlNodeType
.Text
:
331 InternalTrace(">" + XPathNodeType
.Text
);
332 return XPathNodeType
.Text
;
334 case HtmlNodeType
.Element
:
338 InternalTrace(">" + XPathNodeType
.Attribute
);
339 return XPathNodeType
.Attribute
;
341 InternalTrace(">" + XPathNodeType
.Element
);
342 return XPathNodeType
.Element
;
346 throw new NotImplementedException("Internal error: Unhandled HtmlNodeType: " +
347 _currentnode
.NodeType
);
353 /// Gets the prefix associated with the current node.
354 /// Always returns string.Empty in the case of HtmlNavigator implementation.
356 public override string Prefix
361 return _nametable
.GetOrAdd(string.Empty
);
366 /// Gets the text value of the current node.
368 public override string Value
372 InternalTrace("nt=" + _currentnode
.NodeType
);
373 switch (_currentnode
.NodeType
)
375 case HtmlNodeType
.Comment
:
376 InternalTrace(">" + ((HtmlCommentNode
) _currentnode
).Comment
);
377 return ((HtmlCommentNode
) _currentnode
).Comment
;
379 case HtmlNodeType
.Document
:
383 case HtmlNodeType
.Text
:
384 InternalTrace(">" + ((HtmlTextNode
) _currentnode
).Text
);
385 return ((HtmlTextNode
) _currentnode
).Text
;
387 case HtmlNodeType
.Element
:
391 InternalTrace(">" + _currentnode
.Attributes
[_attindex
].Value
);
392 return _currentnode
.Attributes
[_attindex
].Value
;
394 return _currentnode
.InnerText
;
398 throw new NotImplementedException("Internal error: Unhandled HtmlNodeType: " +
399 _currentnode
.NodeType
);
405 /// Gets the xml:lang scope for the current node.
406 /// Always returns string.Empty in the case of HtmlNavigator implementation.
408 public override string XmlLang
413 return _nametable
.GetOrAdd(string.Empty
);
419 #region Public Methods
422 /// Creates a new HtmlNavigator positioned at the same node as this HtmlNavigator.
424 /// <returns>A new HtmlNavigator object positioned at the same node as the original HtmlNavigator.</returns>
425 public override XPathNavigator
Clone()
428 return new HtmlNodeNavigator(this);
432 /// Gets the value of the HTML attribute with the specified LocalName and NamespaceURI.
434 /// <param name="localName">The local name of the HTML attribute.</param>
435 /// <param name="namespaceURI">The namespace URI of the attribute. Unsupported with the HtmlNavigator implementation.</param>
436 /// <returns>The value of the specified HTML attribute. String.Empty or null if a matching attribute is not found or if the navigator is not positioned on an element node.</returns>
437 public override string GetAttribute(string localName
, string namespaceURI
)
439 InternalTrace("localName=" + localName
+ ", namespaceURI=" + namespaceURI
);
440 HtmlAttribute att
= _currentnode
.Attributes
[localName
];
443 InternalTrace(">null");
446 InternalTrace(">" + att
.Value
);
451 /// Returns the value of the namespace node corresponding to the specified local name.
452 /// Always returns string.Empty for the HtmlNavigator implementation.
454 /// <param name="name">The local name of the namespace node.</param>
455 /// <returns>Always returns string.Empty for the HtmlNavigator implementation.</returns>
456 public override string GetNamespace(string name
)
458 InternalTrace("name=" + name
);
463 /// Determines whether the current HtmlNavigator is at the same position as the specified HtmlNavigator.
465 /// <param name="other">The HtmlNavigator that you want to compare against.</param>
466 /// <returns>true if the two navigators have the same position, otherwise, false.</returns>
467 public override bool IsSamePosition(XPathNavigator other
)
469 HtmlNodeNavigator nav
= other
as HtmlNodeNavigator
;
472 InternalTrace(">false");
475 InternalTrace(">" + (nav
._currentnode
== _currentnode
));
476 return (nav
._currentnode
== _currentnode
);
480 /// Moves to the same position as the specified HtmlNavigator.
482 /// <param name="other">The HtmlNavigator positioned on the node that you want to move to.</param>
483 /// <returns>true if successful, otherwise false. If false, the position of the navigator is unchanged.</returns>
484 public override bool MoveTo(XPathNavigator other
)
486 HtmlNodeNavigator nav
= other
as HtmlNodeNavigator
;
489 InternalTrace(">false (nav is not an HtmlNodeNavigator)");
492 InternalTrace("moveto oid=" + nav
.GetHashCode()
493 + ", n:" + nav
._currentnode
.Name
494 + ", a:" + nav
._attindex
);
496 if (nav
._doc
== _doc
)
498 _currentnode
= nav
._currentnode
;
499 _attindex
= nav
._attindex
;
500 InternalTrace(">true");
503 // we don't know how to handle that
504 InternalTrace(">false (???)");
509 /// Moves to the HTML attribute with matching LocalName and NamespaceURI.
511 /// <param name="localName">The local name of the HTML attribute.</param>
512 /// <param name="namespaceURI">The namespace URI of the attribute. Unsupported with the HtmlNavigator implementation.</param>
513 /// <returns>true if the HTML attribute is found, otherwise, false. If false, the position of the navigator does not change.</returns>
514 public override bool MoveToAttribute(string localName
, string namespaceURI
)
516 InternalTrace("localName=" + localName
+ ", namespaceURI=" + namespaceURI
);
517 int index
= _currentnode
.Attributes
.GetAttributeIndex(localName
);
520 InternalTrace(">false");
524 InternalTrace(">true");
529 /// Moves to the first sibling of the current node.
531 /// <returns>true if the navigator is successful moving to the first sibling node, false if there is no first sibling or if the navigator is currently positioned on an attribute node.</returns>
532 public override bool MoveToFirst()
534 if (_currentnode
.ParentNode
== null)
536 InternalTrace(">false");
539 if (_currentnode
.ParentNode
.FirstChild
== null)
541 InternalTrace(">false");
544 _currentnode
= _currentnode
.ParentNode
.FirstChild
;
545 InternalTrace(">true");
550 /// Moves to the first HTML attribute.
552 /// <returns>true if the navigator is successful moving to the first HTML attribute, otherwise, false.</returns>
553 public override bool MoveToFirstAttribute()
557 InternalTrace(">false");
561 InternalTrace(">true");
566 /// Moves to the first child of the current node.
568 /// <returns>true if there is a first child node, otherwise false.</returns>
569 public override bool MoveToFirstChild()
571 if (!_currentnode
.HasChildNodes
)
573 InternalTrace(">false");
576 _currentnode
= _currentnode
.ChildNodes
[0];
577 InternalTrace(">true");
582 /// Moves the XPathNavigator to the first namespace node of the current element.
583 /// Always returns false for the HtmlNavigator implementation.
585 /// <param name="scope">An XPathNamespaceScope value describing the namespace scope.</param>
586 /// <returns>Always returns false for the HtmlNavigator implementation.</returns>
587 public override bool MoveToFirstNamespace(XPathNamespaceScope scope
)
594 /// Moves to the node that has an attribute of type ID whose value matches the specified string.
596 /// <param name="id">A string representing the ID value of the node to which you want to move. This argument does not need to be atomized.</param>
597 /// <returns>true if the move was successful, otherwise false. If false, the position of the navigator is unchanged.</returns>
598 public override bool MoveToId(string id
)
600 InternalTrace("id=" + id
);
601 HtmlNode node
= _doc
.GetElementbyId(id
);
604 InternalTrace(">false");
608 InternalTrace(">true");
613 /// Moves the XPathNavigator to the namespace node with the specified local name.
614 /// Always returns false for the HtmlNavigator implementation.
616 /// <param name="name">The local name of the namespace node.</param>
617 /// <returns>Always returns false for the HtmlNavigator implementation.</returns>
618 public override bool MoveToNamespace(string name
)
620 InternalTrace("name=" + name
);
625 /// Moves to the next sibling of the current node.
627 /// <returns>true if the navigator is successful moving to the next sibling node, false if there are no more siblings or if the navigator is currently positioned on an attribute node. If false, the position of the navigator is unchanged.</returns>
628 public override bool MoveToNext()
630 if (_currentnode
.NextSibling
== null)
632 InternalTrace(">false");
635 InternalTrace("_c=" + _currentnode
.CloneNode(false).OuterHtml
);
636 InternalTrace("_n=" + _currentnode
.NextSibling
.CloneNode(false).OuterHtml
);
637 _currentnode
= _currentnode
.NextSibling
;
638 InternalTrace(">true");
643 /// Moves to the next HTML attribute.
645 /// <returns></returns>
646 public override bool MoveToNextAttribute()
649 if (_attindex
>= (_currentnode
.Attributes
.Count
- 1))
651 InternalTrace(">false");
655 InternalTrace(">true");
660 /// Moves the XPathNavigator to the next namespace node.
661 /// Always returns falsefor the HtmlNavigator implementation.
663 /// <param name="scope">An XPathNamespaceScope value describing the namespace scope.</param>
664 /// <returns>Always returns false for the HtmlNavigator implementation.</returns>
665 public override bool MoveToNextNamespace(XPathNamespaceScope scope
)
672 /// Moves to the parent of the current node.
674 /// <returns>true if there is a parent node, otherwise false.</returns>
675 public override bool MoveToParent()
677 if (_currentnode
.ParentNode
== null)
679 InternalTrace(">false");
682 _currentnode
= _currentnode
.ParentNode
;
683 InternalTrace(">true");
688 /// Moves to the previous sibling of the current node.
690 /// <returns>true if the navigator is successful moving to the previous sibling node, false if there is no previous sibling or if the navigator is currently positioned on an attribute node.</returns>
691 public override bool MoveToPrevious()
693 if (_currentnode
.PreviousSibling
== null)
695 InternalTrace(">false");
698 _currentnode
= _currentnode
.PreviousSibling
;
699 InternalTrace(">true");
704 /// Moves to the root node to which the current node belongs.
706 public override void MoveToRoot()
708 _currentnode
= _doc
.DocumentNode
;
714 #region Internal Methods
716 [Conditional("TRACE")]
717 internal void InternalTrace(object traceValue
)
723 StackFrame sf
= new StackFrame(1, true);
724 string name
= sf
.GetMethod().Name
;
725 string nodename
= _currentnode
== null ? "(null)" : _currentnode
.Name
;
727 if (_currentnode
== null)
729 nodevalue
= "(null)";
733 switch (_currentnode
.NodeType
)
735 case HtmlNodeType
.Comment
:
736 nodevalue
= ((HtmlCommentNode
) _currentnode
).Comment
;
739 case HtmlNodeType
.Document
:
743 case HtmlNodeType
.Text
:
744 nodevalue
= ((HtmlTextNode
) _currentnode
).Text
;
748 nodevalue
= _currentnode
.CloneNode(false).OuterHtml
;
752 System
.Diagnostics
.Trace
.WriteLine(string.Format("oid={0},n={1},a={2},v={3},{4}", GetHashCode(), nodename
, _attindex
, nodevalue
, traceValue
), "N!" + name
);
757 #region Private Methods
762 _currentnode
= _doc
.DocumentNode
;