Element
class Element : Node, IElement, INode, IEventTarget, IMarkupFormattable, IParentNode, IChildNode, INonDocumentTypeChildNode, IElementCssInlineStyle
Represents an element node.
using AngleSharp.Dom.Collections;
using AngleSharp.Dom.Css;
using AngleSharp.Dom.Events;
using AngleSharp.Extensions;
using AngleSharp.Html;
using AngleSharp.Parser.Css;
using AngleSharp.Services.Styling;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
namespace AngleSharp.Dom
{
internal class Element : Node, IElement, INode, IEventTarget, IMarkupFormattable, IParentNode, IChildNode, INonDocumentTypeChildNode, IElementCssInlineStyle
{
private static readonly ConditionalWeakTable<Element, IShadowRoot> ShadowRoots;
private static readonly Dictionary<Type, AttrChangedCallback> RegisteredCallbacks;
private readonly NamedNodeMap _attributes;
private readonly string _namespace;
private readonly string _prefix;
private readonly string _localName;
private HtmlCollection<IElement> _elements;
private ICssStyleDeclaration _style;
private TokenList _classList;
internal NamedNodeMap Attributes => _attributes;
public ICssStyleDeclaration Style => _style ?? (_style = CreateStyle());
public IElement AssignedSlot {
get {
IElement parentElement = base.ParentElement;
if (parentElement.IsShadow())
return parentElement.ShadowRoot.GetAssignedSlot(Slot);
return null;
}
}
public string Slot {
get {
return this.GetOwnAttribute(AttributeNames.Slot);
}
set {
this.SetOwnAttribute(AttributeNames.Slot, value, false);
}
}
public IShadowRoot ShadowRoot {
get {
IShadowRoot value = null;
ShadowRoots.TryGetValue(this, out value);
return value;
}
}
public string Prefix => _prefix;
public string LocalName => _localName;
public string NamespaceUri => _namespace;
public override string TextContent {
get {
StringBuilder stringBuilder = Pool.NewStringBuilder();
foreach (IText item in this.GetDescendants().OfType<IText>()) {
stringBuilder.Append(item.Data);
}
return stringBuilder.ToPool();
}
set {
TextNode node = (!string.IsNullOrEmpty(value)) ? new TextNode(base.Owner, value) : null;
ReplaceAll(node, false);
}
}
public ITokenList ClassList {
get {
if (_classList == null) {
_classList = new TokenList(this.GetOwnAttribute(AttributeNames.Class));
_classList.Changed += delegate(string value) {
UpdateAttribute(AttributeNames.Class, value);
};
}
return _classList;
}
}
public string ClassName {
get {
return this.GetOwnAttribute(AttributeNames.Class);
}
set {
this.SetOwnAttribute(AttributeNames.Class, value, false);
}
}
public string Id {
get {
return this.GetOwnAttribute(AttributeNames.Id);
}
set {
this.SetOwnAttribute(AttributeNames.Id, value, false);
}
}
public string TagName => base.NodeName;
public IElement PreviousElementSibling {
get {
Node parent = base.Parent;
if (parent != null) {
bool flag = false;
for (int num = parent.ChildNodes.Length - 1; num >= 0; num--) {
if (parent.ChildNodes[num] == this)
flag = true;
else if (flag && parent.ChildNodes[num] is IElement) {
return (IElement)parent.ChildNodes[num];
}
}
}
return null;
}
}
public IElement NextElementSibling {
get {
Node parent = base.Parent;
if (parent != null) {
int length = parent.ChildNodes.Length;
bool flag = false;
for (int i = 0; i < length; i++) {
if (parent.ChildNodes[i] == this)
flag = true;
else if (flag && parent.ChildNodes[i] is IElement) {
return (IElement)parent.ChildNodes[i];
}
}
}
return null;
}
}
public int ChildElementCount {
get {
NodeList childNodes = base.ChildNodes;
int length = childNodes.Length;
int num = 0;
for (int i = 0; i < length; i++) {
if (childNodes[i].NodeType == NodeType.Element)
num++;
}
return num;
}
}
public IHtmlCollection<IElement> Children => _elements ?? (_elements = new HtmlCollection<IElement>(this, false, null));
public IElement FirstElementChild {
get {
NodeList childNodes = base.ChildNodes;
int length = childNodes.Length;
for (int i = 0; i < length; i++) {
IElement element = childNodes[i] as IElement;
if (element != null)
return element;
}
return null;
}
}
public IElement LastElementChild {
get {
NodeList childNodes = base.ChildNodes;
for (int num = childNodes.Length - 1; num >= 0; num--) {
IElement element = childNodes[num] as IElement;
if (element != null)
return element;
}
return null;
}
}
public string InnerHtml {
get {
return base.ChildNodes.ToHtml();
}
set {
ReplaceAll(new DocumentFragment(this, value), false);
}
}
public string OuterHtml {
get {
return this.ToHtml();
}
set {
Node parent = base.Parent;
if (parent == null)
throw new DomException(DomError.NotSupported);
Document owner = base.Owner;
if (owner != null && owner.DocumentElement == this)
throw new DomException(DomError.NoModificationAllowed);
parent.InsertChild(parent.IndexOf(this), new DocumentFragment(this, value));
parent.RemoveChild(this);
}
}
INamedNodeMap IElement.Attributes {
get {
return _attributes;
}
}
public bool IsFocused {
get {
Document owner = base.Owner;
if (owner == null)
return false;
return owner.FocusElement == this;
}
protected set {
Document owner = base.Owner;
if (owner != null) {
if (value) {
owner.SetFocus(this);
this.Fire(delegate(FocusEvent m) {
m.Init(EventNames.Focus, false, false);
}, null);
} else {
owner.SetFocus(null);
this.Fire(delegate(FocusEvent m) {
m.Init(EventNames.Blur, false, false);
}, null);
}
}
}
}
static Element()
{
ShadowRoots = new ConditionalWeakTable<Element, IShadowRoot>();
RegisteredCallbacks = new Dictionary<Type, AttrChangedCallback>();
RegisterCallback(AttributeNames.Class, delegate(Element element, string value) {
element._classList?.Update(value);
});
}
public Element(Document owner, string localName, string prefix, string namespaceUri, NodeFlags flags = NodeFlags.None)
: this(owner, (prefix != null) ? (prefix + ":" + localName) : localName, localName, prefix, namespaceUri, flags)
{
}
public Element(Document owner, string name, string localName, string prefix, string namespaceUri, NodeFlags flags = NodeFlags.None)
: base(owner, name, NodeType.Element, flags)
{
_localName = localName;
_prefix = prefix;
_namespace = namespaceUri;
_attributes = new NamedNodeMap(this);
}
public IShadowRoot AttachShadow(ShadowRootMode mode = ShadowRootMode.Open)
{
if (TagNames.AllNoShadowRoot.Contains(_localName))
throw new DomException(DomError.NotSupported);
if (ShadowRoot != null)
throw new DomException(DomError.InvalidState);
ShadowRoot shadowRoot = new ShadowRoot(this, mode);
ShadowRoots.Add(this, shadowRoot);
return shadowRoot;
}
public IElement QuerySelector(string selectors)
{
return base.ChildNodes.QuerySelector(selectors);
}
public IHtmlCollection<IElement> QuerySelectorAll(string selectors)
{
return base.ChildNodes.QuerySelectorAll(selectors);
}
public IHtmlCollection<IElement> GetElementsByClassName(string classNames)
{
return base.ChildNodes.GetElementsByClassName(classNames);
}
public IHtmlCollection<IElement> GetElementsByTagName(string tagName)
{
return base.ChildNodes.GetElementsByTagName(tagName);
}
public IHtmlCollection<IElement> GetElementsByTagNameNS(string namespaceURI, string tagName)
{
return base.ChildNodes.GetElementsByTagName(namespaceURI, tagName);
}
public bool Matches(string selectors)
{
return CssParser.Default.ParseSelector(selectors).Match(this);
}
public override INode Clone(bool deep = true)
{
Element element = new Element(base.Owner, LocalName, _prefix, _namespace, base.Flags);
CloneElement(element, deep);
return element;
}
public IPseudoElement Pseudo(string pseudoElement)
{
return PseudoElement.Create(this, pseudoElement);
}
public bool HasAttribute(string name)
{
if (_namespace.Is(NamespaceNames.HtmlUri))
name = name.ToLowerInvariant();
return _attributes.GetNamedItem(name) != null;
}
public bool HasAttribute(string namespaceUri, string localName)
{
if (string.IsNullOrEmpty(namespaceUri))
namespaceUri = null;
return _attributes.GetNamedItem(namespaceUri, localName) != null;
}
public string GetAttribute(string name)
{
if (_namespace.Is(NamespaceNames.HtmlUri))
name = name.ToLower();
return _attributes.GetNamedItem(name)?.Value;
}
public string GetAttribute(string namespaceUri, string localName)
{
if (string.IsNullOrEmpty(namespaceUri))
namespaceUri = null;
return _attributes.GetNamedItem(namespaceUri, localName)?.Value;
}
public void SetAttribute(string name, string value)
{
if (value != null) {
if (!name.IsXmlName())
throw new DomException(DomError.InvalidCharacter);
if (_namespace.Is(NamespaceNames.HtmlUri))
name = name.ToLowerInvariant();
this.SetOwnAttribute(name, value, false);
} else
RemoveAttribute(name);
}
public void SetAttribute(string namespaceUri, string name, string value)
{
if (value != null) {
string prefix = null;
string localName = null;
Node.GetPrefixAndLocalName(name, ref namespaceUri, out prefix, out localName);
_attributes.SetNamedItem(new Attr(prefix, localName, value, namespaceUri));
} else
RemoveAttribute(namespaceUri, name);
}
public void RemoveAttribute(string name)
{
if (_namespace.Is(NamespaceNames.HtmlUri))
name = name.ToLower();
_attributes.RemoveNamedItemOrDefault(name);
}
public void RemoveAttribute(string namespaceUri, string localName)
{
if (string.IsNullOrEmpty(namespaceUri))
namespaceUri = null;
_attributes.RemoveNamedItemOrDefault(namespaceUri, localName);
}
public void Prepend(params INode[] nodes)
{
this.PrependNodes(nodes);
}
public void Append(params INode[] nodes)
{
this.AppendNodes(nodes);
}
public override bool Equals(INode otherNode)
{
IElement element = otherNode as IElement;
if (element != null) {
if (NamespaceUri.Is(element.NamespaceUri) && _attributes.AreEqual(element.Attributes))
return base.Equals(otherNode);
return false;
}
return false;
}
public void Before(params INode[] nodes)
{
this.InsertBefore(nodes);
}
public void After(params INode[] nodes)
{
this.InsertAfter(nodes);
}
public void Replace(params INode[] nodes)
{
this.ReplaceWith(nodes);
}
public void Remove()
{
this.RemoveFromParent();
}
public void Insert(AdjacentPosition position, string html)
{
DocumentFragment documentFragment = new DocumentFragment((position == AdjacentPosition.BeforeBegin || position == AdjacentPosition.AfterEnd) ? this : (base.Parent as Element), html);
switch (position) {
case AdjacentPosition.BeforeBegin:
base.Parent.InsertBefore(documentFragment, this);
break;
case AdjacentPosition.AfterEnd:
base.Parent.InsertChild(base.Parent.IndexOf(this) + 1, documentFragment);
break;
case AdjacentPosition.AfterBegin:
InsertChild(0, documentFragment);
break;
case AdjacentPosition.BeforeEnd:
AppendChild(documentFragment);
break;
}
}
public override void ToHtml(TextWriter writer, IMarkupFormatter formatter)
{
bool flag = (base.Flags & NodeFlags.SelfClosing) == NodeFlags.SelfClosing;
writer.Write(formatter.OpenTag(this, flag));
if (!flag) {
if ((base.Flags & NodeFlags.LineTolerance) == NodeFlags.LineTolerance && base.FirstChild is IText && ((IText)base.FirstChild).Data.Has('\n', 0))
writer.Write('\n');
foreach (INode childNode in base.ChildNodes) {
childNode.ToHtml(writer, formatter);
}
}
writer.Write(formatter.CloseTag(this, flag));
}
internal virtual void SetupElement()
{
}
internal void AttributeChanged(string localName, string namespaceUri, string oldValue, string newValue)
{
AttrChangedCallback orCreateCallback = GetOrCreateCallback(GetType());
if (namespaceUri == null)
orCreateCallback?.Invoke(this, localName, newValue);
base.Owner.QueueMutation(MutationRecord.Attributes(this, localName, namespaceUri, oldValue));
}
protected ICssStyleDeclaration CreateStyle()
{
if ((base.Flags & (NodeFlags.HtmlMember | NodeFlags.SvgMember)) != 0) {
Document owner = base.Owner;
IConfiguration options = owner.Options;
IBrowsingContext context = owner.Context;
ICssStyleEngine cssStyleEngine = options.GetCssStyleEngine();
if (cssStyleEngine != null) {
string ownAttribute = this.GetOwnAttribute(AttributeNames.Style);
StyleOptions options2 = new StyleOptions(context) {
Element = this
};
ICssStyleDeclaration cssStyleDeclaration = cssStyleEngine.ParseDeclaration(ownAttribute, options2);
IBindable bindable = cssStyleDeclaration as IBindable;
if (bindable != null)
bindable.Changed += delegate(string value) {
UpdateAttribute(AttributeNames.Style, value);
};
return cssStyleDeclaration;
}
}
return null;
}
protected static void RegisterCallback<TElement>(string name, Action<TElement, string> callback) where TElement : Element
{
Type typeFromHandle = typeof(TElement);
AttrChangedCallback orCreateCallback = GetOrCreateCallback(typeFromHandle);
AttrChangedCallback attrChangedCallback = delegate(IElement element, string localName, string newValue) {
if (localName.Is(name))
callback((TElement)element, newValue);
};
if (orCreateCallback != null)
attrChangedCallback = (AttrChangedCallback)Delegate.Combine(orCreateCallback, attrChangedCallback);
RegisteredCallbacks[typeFromHandle] = attrChangedCallback;
}
private static AttrChangedCallback GetOrCreateCallback(Type type)
{
AttrChangedCallback value = null;
if (!RegisteredCallbacks.TryGetValue(type, out value) && (object)type != typeof(Element)) {
AttrChangedCallback orCreateCallback = GetOrCreateCallback(type.GetTypeInfo().get_BaseType());
if (orCreateCallback != null)
RegisteredCallbacks[type] = orCreateCallback;
}
return value;
}
protected void UpdateStyle(string value)
{
IBindable obj = _style as IBindable;
if (string.IsNullOrEmpty(value))
_attributes.RemoveNamedItemOrDefault(AttributeNames.Style, true);
obj?.Update(value);
}
protected void UpdateAttribute(string name, string value)
{
this.SetOwnAttribute(name, value, true);
}
protected sealed override string LocateNamespace(string prefix)
{
return this.LocateNamespaceFor(prefix);
}
protected sealed override string LocatePrefix(string namespaceUri)
{
return this.LocatePrefixFor(namespaceUri);
}
protected void CloneElement(Element element, bool deep)
{
CloneNode(element, deep);
foreach (IAttr attribute in _attributes) {
Attr attr = new Attr(attribute.Prefix, attribute.LocalName, attribute.Value, attribute.NamespaceUri);
element._attributes.FastAddItem(attr);
}
element.SetupElement();
}
}
}