` with
+ * `el.tagName("div");`.
+ *
+ * @param tagName new tag name for this element
+ * @return this element, for chaining
+ * @see Elements.tagName
+ */
+ @JvmOverloads
+ fun tagName(tagName: String, namespace: String = tag.namespace()): Element {
+ Validate.notEmptyParam(tagName, "tagName")
+ Validate.notEmptyParam(namespace, "namespace")
+ tag = Tag.valueOf(
+ tagName,
+ namespace,
+ NodeUtils.parser(this)!!.settings(),
+ ) // maintains the case option of the original parse
+ return this
+ }
+
+ /**
+ * Get the Tag for this element.
+ *
+ * @return the tag object
+ */
+ fun tag(): Tag {
+ return tag
+ }
+
+ fun isBlock(): Boolean = tag.isBlock
+
+ /**
+ * Get the `id` attribute of this element.
+ *
+ * @return The id attribute, if present, or an empty string if not.
+ */
+ fun id(): String {
+ return if (attributes != null) attributes!!.getIgnoreCase("id") else ""
+ }
+
+ /**
+ * Set the `id` attribute of this element.
+ * @param id the ID value to use
+ * @return this Element, for chaining
+ */
+ fun id(id: String?): Element {
+ Validate.notNull(id)
+ attr("id", id)
+ return this
+ }
+
+ /**
+ * Set an attribute value on this element. If this element already has an attribute with the
+ * key, its value is updated; otherwise, a new attribute is added.
+ *
+ * @return this element
+ */
+ override fun attr(attributeKey: String, attributeValue: String?): Element {
+ super.attr(attributeKey, attributeValue)
+ return this
+ }
+
+ /**
+ * Set a boolean attribute value on this element. Setting to `true` sets the attribute value to "" and
+ * marks the attribute as boolean so no value is written out. Setting to `false` removes the attribute
+ * with the same key if it exists.
+ *
+ * @param attributeKey the attribute key
+ * @param attributeValue the attribute value
+ *
+ * @return this element
+ */
+ fun attr(attributeKey: String, attributeValue: Boolean): Element {
+ attributes().put(attributeKey, attributeValue)
+ return this
+ }
+
+ /**
+ * Get this element's HTML5 custom data attributes. Each attribute in the element that has a key
+ * starting with "data-" is included the dataset.
+ *
+ *
+ * E.g., the element `
...` has the dataset
+ * `package=com.fleeksoft.ksoup, language=java`.
+ *
+ *
+ * This map is a filtered view of the element's attribute map. Changes to one map (add, remove, update) are reflected
+ * in the other map.
+ *
+ *
+ * You can find elements that have data attributes using the `[^data-]` attribute key prefix selector.
+ * @return a map of `key=value` custom data attributes.
+ */
+ fun dataset(): Attributes.Dataset {
+ return attributes().dataset()
+ }
+
+ override fun parent(): Element? {
+ return _parentNode as? Element
+ }
+
+ /**
+ * Get this element's parent and ancestors, up to the document root.
+ * @return this element's stack of parents, starting with the closest first.
+ */
+ fun parents(): Elements {
+ val parents = Elements()
+ var parent = parent()
+ while (parent != null && !parent.isNode("#root")) {
+ parents.add(parent)
+ parent = parent.parent()
+ }
+ return parents
+ }
+
+ /**
+ * Get a child element of this element, by its 0-based index number.
+ *
+ *
+ * Note that an element can have both mixed Nodes and Elements as children. This method inspects
+ * a filtered list of children that are elements, and the index is based on that filtered list.
+ *
+ *
+ * @param index the index number of the element to retrieve
+ * @return the child element, if it exists, otherwise throws an `IndexOutOfBoundsException`
+ * @see .childNode
+ */
+ fun child(index: Int): Element {
+ return childElementsList()[index]
+ }
+
+ /**
+ * Get the number of child nodes of this element that are elements.
+ *
+ *
+ * This method works on the same filtered list like [.child]. Use [.childNodes] and [ ][.childNodeSize] to get the unfiltered Nodes (e.g. includes TextNodes etc.)
+ *
+ *
+ * @return the number of child nodes that are elements
+ * @see .children
+ * @see .child
+ */
+ fun childrenSize(): Int {
+ return childElementsList().size
+ }
+
+ /**
+ * Get this element's child elements.
+ *
+ *
+ * This is effectively a filter on [.childNodes] to get Element nodes.
+ *
+ * @return child elements. If this element has no children, returns an empty list.
+ * @see .childNodes
+ */
+ fun children(): Elements {
+ return Elements(childElementsList())
+ }
+
+ /**
+ * Maintains a shadow copy of this element's child elements. If the nodelist is changed, this cache is invalidated.
+ * TODO - think about pulling this out as a helper as there are other shadow lists (like in Attributes) kept around.
+ * @return a list of child elements
+ */
+ fun childElementsList(): List
{
+ if (childNodeSize() == 0) return EmptyChildren // short circuit creating empty
+ var children: MutableList? = null
+ if (shadowChildrenRef != null) {
+ children = shadowChildrenRef!!.toMutableList()
+ }
+ if (shadowChildrenRef == null || children == null) {
+ val size = childNodes.size
+ children = ArrayList(size)
+ for (i in 0 until size) {
+ val node: Node = childNodes[i]
+ if (node is Element) children.add(node)
+ }
+ shadowChildrenRef = children
+ }
+ return children
+ }
+
+ /**
+ * Clears the cached shadow child elements.
+ */
+ override fun nodelistChanged() {
+ super.nodelistChanged()
+ shadowChildrenRef = null
+ }
+
+ /**
+ * Get this element's child text nodes. The list is unmodifiable but the text nodes may be manipulated.
+ *
+ *
+ * This is effectively a filter on [.childNodes] to get Text nodes.
+ * @return child text nodes. If this element has no text nodes, returns an
+ * empty list.
+ *
+ * For example, with the input HTML: `One Two Three
Four
` with the `p` element selected:
+ *
+ * * `p.text()` = `"One Two Three Four"`
+ * * `p.ownText()` = `"One Three Four"`
+ * * `p.children()` = `Elements[,
]`
+ * * `p.childNodes()` = `List["One ", , " Three ",
, " Four"]`
+ * * `p.textNodes()` = `List["One ", " Three ", " Four"]`
+ *
+ */
+ fun textNodes(): List {
+ val textNodes: MutableList = ArrayList()
+ for (node in childNodes) {
+ if (node is TextNode) textNodes.add(node)
+ }
+ return Collections.unmodifiableList(textNodes)
+ }
+
+ /**
+ * Get this element's child data nodes. The list is unmodifiable but the data nodes may be manipulated.
+ *
+ *
+ * This is effectively a filter on [.childNodes] to get Data nodes.
+ *
+ * @return child data nodes. If this element has no data nodes, returns an
+ * empty list.
+ * @see .data
+ */
+ fun dataNodes(): List {
+ val dataNodes: MutableList = ArrayList()
+ for (node in childNodes) {
+ if (node is DataNode) dataNodes.add(node as DataNode)
+ }
+ return Collections.unmodifiableList(dataNodes)
+ }
+
+ /**
+ * Find elements that match the [Selector] CSS query, with this element as the starting context. Matched elements
+ * may include this element, or any of its children.
+ *
+ * This method is generally more powerful to use than the DOM-type `getElementBy*` methods, because
+ * multiple filters can be combined, e.g.:
+ *
+ * * `el.select("a[href]")` - finds links (`a` tags with `href` attributes)
+ * * `el.select("a[href*=example.com]")` - finds links pointing to example.com (loosely)
+ *
+ *
+ * See the query syntax documentation in [com.fleeksoft.ksoup.select.Selector].
+ *
+ * Also known as `querySelectorAll()` in the Web DOM.
+ *
+ * @param cssQuery a [Selector] CSS-like query
+ * @return an [Elements] list containing elements that match the query (empty if none match)
+ * @see Selector selector query syntax
+ *
+ * @see QueryParser.parse
+ * @throws Selector.SelectorParseException (unchecked) on an invalid CSS query.
+ */
+ fun select(cssQuery: String): Elements {
+ return Selector.select(cssQuery, this)
+ }
+
+ /**
+ * Find elements that match the supplied Evaluator. This has the same functionality as [.select], but
+ * may be useful if you are running the same query many times (on many documents) and want to save the overhead of
+ * repeatedly parsing the CSS query.
+ * @param evaluator an element evaluator
+ * @return an [Elements] list containing elements that match the query (empty if none match)
+ */
+ fun select(evaluator: Evaluator): Elements {
+ return Selector.select(evaluator, this)
+ }
+
+ /**
+ * Find the first Element that matches the [Selector] CSS query, with this element as the starting context.
+ *
+ * This is effectively the same as calling `element.select(query).first()`, but is more efficient as query
+ * execution stops on the first hit.
+ *
+ * Also known as `querySelector()` in the Web DOM.
+ * @param cssQuery cssQuery a [Selector] CSS-like query
+ * @return the first matching element, or **`null`** if there is no match.
+ * @see .expectFirst
+ */
+ fun selectFirst(cssQuery: String): Element? {
+ return Selector.selectFirst(cssQuery, this)
+ }
+
+ /**
+ * Finds the first Element that matches the supplied Evaluator, with this element as the starting context, or
+ * `null` if none match.
+ *
+ * @param evaluator an element evaluator
+ * @return the first matching element (walking down the tree, starting from this element), or `null` if none
+ * match.
+ */
+ fun selectFirst(evaluator: Evaluator): Element? {
+ return Collector.findFirst(evaluator, this)
+ }
+
+ /**
+ * Just like [.selectFirst], but if there is no match, throws an [IllegalArgumentException]. This
+ * is useful if you want to simply abort processing on a failed match.
+ * @param cssQuery a [Selector] CSS-like query
+ * @return the first matching element
+ * @throws IllegalArgumentException if no match is found
+ * @since 1.15.2
+ */
+ fun expectFirst(cssQuery: String): Element {
+ return Validate.ensureNotNull(
+ Selector.selectFirst(cssQuery, this),
+ if (parent() != null) "No elements matched the query '$cssQuery' on element '${this.tagName()}'." else "No elements matched the query '$cssQuery' in the document.",
+ ) as Element
+ }
+
+ /**
+ * Checks if this element matches the given [Selector] CSS query. Also knows as `matches()` in the Web
+ * DOM.
+ *
+ * @param cssQuery a [Selector] CSS query
+ * @return if this element matches the query
+ */
+ fun `is`(cssQuery: String): Boolean {
+ return `is`(QueryParser.parse(cssQuery))
+ }
+
+ /**
+ * Check if this element matches the given evaluator.
+ * @param evaluator an element evaluator
+ * @return if this element matches
+ */
+ fun `is`(evaluator: Evaluator?): Boolean {
+ return evaluator!!.matches(root(), this)
+ }
+
+ /**
+ * Find the closest element up the tree of parents that matches the specified CSS query. Will return itself, an
+ * ancestor, or `null` if there is no such matching element.
+ * @param cssQuery a [Selector] CSS query
+ * @return the closest ancestor element (possibly itself) that matches the provided evaluator. `null` if not
+ * found.
+ */
+ fun closest(cssQuery: String): Element? {
+ return closest(QueryParser.parse(cssQuery))
+ }
+
+ /**
+ * Find the closest element up the tree of parents that matches the specified evaluator. Will return itself, an
+ * ancestor, or `null` if there is no such matching element.
+ * @param evaluator a query evaluator
+ * @return the closest ancestor element (possibly itself) that matches the provided evaluator. `null` if not
+ * found.
+ */
+// @Nullable
+ fun closest(evaluator: Evaluator?): Element? {
+ Validate.notNull(evaluator)
+ var el: Element? = this
+ val root = root()
+ do {
+ if (evaluator!!.matches(root, el!!)) return el
+ el = el.parent()
+ } while (el != null)
+ return null
+ }
+
+ /**
+ * Find Elements that match the supplied XPath expression.
+ *
+ * Note that for convenience of writing the Xpath expression, namespaces are disabled, and queries can be
+ * expressed using the element's local name only.
+ *
+ * By default, XPath 1.0 expressions are supported. If you would to use XPath 2.0 or higher, you can provide an
+ * alternate XPathFactory implementation:
+ *
+ * 1. Add the implementation to your classpath. E.g. to use [Saxon-HE](https://www.saxonica.com/products/products.xml), add [net.sf.saxon:Saxon-HE](https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE) to your build.
+ * 1. Set the system property `javax.xml.xpath.XPathFactory:com.fleeksoft.ksoup` to the implementing classname. E.g.:
+ * `System.setProperty(W3CDom.XPathFactoryProperty, "net.sf.saxon.xpath.XPathFactoryImpl");`
+ *
+ *
+ *
+ * @param xpath XPath expression
+ * @return matching elements, or an empty list if none match.
+ * @see .selectXpath
+ * @since 1.14.3
+ */
+ /*fun selectXpath(xpath: String?): Elements {
+ return Elements(NodeUtils.selectXpath(xpath, this, Element::class))
+ }*/
+
+ /**
+ * Find Nodes that match the supplied XPath expression.
+ *
+ * For example, to select TextNodes under `p` elements:
+ * List<TextNode> textNodes = doc.selectXpath("//body//p//text()", TextNode.class);
+ *
+ * Note that in the com.fleeksoft.ksoup DOM, Attribute objects are not Nodes. To directly select attribute values, do something
+ * like:
+ * List<String> hrefs = doc.selectXpath("//a").eachAttr("href");
+ * @param xpath XPath expression
+ * @param nodeType the com.fleeksoft.ksoup node type to return
+ * @see .selectXpath
+ * @return a list of matching nodes
+ * @since 1.14.3
+ */
+ /*fun selectXpath(xpath: String?, nodeType: KClass): List {
+ return NodeUtils.selectXpath(xpath, this, nodeType)
+ }*/
+
+ /**
+ * Insert a node to the end of this Element's children. The incoming node will be re-parented.
+ *
+ * @param child node to add.
+ * @return this Element, for chaining
+ * @see .prependChild
+ * @see .insertChildren
+ */
+ fun appendChild(child: Node): Element {
+ // was - Node#addChildren(child). short-circuits an array create and a loop.
+ reparentChild(child)
+ ensureChildNodes()
+ childNodes.add(child)
+ child.siblingIndex = childNodes.size - 1
+ return this
+ }
+
+ /**
+ * Insert the given nodes to the end of this Element's children.
+ *
+ * @param children nodes to add
+ * @return this Element, for chaining
+ * @see .insertChildren
+ */
+ fun appendChildren(children: Collection): Element {
+ insertChildren(-1, children)
+ return this
+ }
+
+ /**
+ * Add this element to the supplied parent element, as its next child.
+ *
+ * @param parent element to which this element will be appended
+ * @return this element, so that you can continue modifying the element
+ */
+ fun appendTo(parent: Element): Element {
+ Validate.notNull(parent)
+ parent.appendChild(this)
+ return this
+ }
+
+ /**
+ * Add a node to the start of this element's children.
+ *
+ * @param child node to add.
+ * @return this element, so that you can add more child nodes or elements.
+ */
+ fun prependChild(child: Node?): Element {
+ Validate.notNull(child)
+ addChildren(0, child!!)
+ return this
+ }
+
+ /**
+ * Insert the given nodes to the start of this Element's children.
+ *
+ * @param children nodes to add
+ * @return this Element, for chaining
+ * @see .insertChildren
+ */
+ fun prependChildren(children: Collection): Element {
+ insertChildren(0, children)
+ return this
+ }
+
+ /**
+ * Inserts the given child nodes into this element at the specified index. Current nodes will be shifted to the
+ * right. The inserted nodes will be moved from their current parent. To prevent moving, copy the nodes first.
+ *
+ * @param index 0-based index to insert children at. Specify `0` to insert at the start, `-1` at the
+ * end
+ * @param children child nodes to insert
+ * @return this element, for chaining.
+ */
+ fun insertChildren(index: Int, children: Collection): Element {
+ var index = index
+ val currentSize = childNodeSize()
+ if (index < 0) index += currentSize + 1 // roll around
+ Validate.isTrue(index in 0..currentSize, "Insert position out of bounds.")
+ val nodeArray: Array = children.toTypedArray()
+ addChildren(index, *nodeArray)
+ return this
+ }
+
+ /**
+ * Inserts the given child nodes into this element at the specified index. Current nodes will be shifted to the
+ * right. The inserted nodes will be moved from their current parent. To prevent moving, copy the nodes first.
+ *
+ * @param index 0-based index to insert children at. Specify `0` to insert at the start, `-1` at the
+ * end
+ * @param children child nodes to insert
+ * @return this element, for chaining.
+ */
+ fun insertChildren(index: Int, vararg children: Node): Element {
+ var index = index
+ val currentSize = childNodeSize()
+ if (index < 0) index += currentSize + 1 // roll around
+ Validate.isTrue(index in 0..currentSize, "Insert position out of bounds.")
+ addChildren(index, *children)
+ return this
+ }
+
+ /**
+ * Create a new element by tag name, and add it as the last child.
+ *
+ * @param tagName the name of the tag (e.g. `div`).
+ * @return the new element, to allow you to add content to it, e.g.:
+ * `parent.appendElement("h1").attr("id", "header").text("Welcome");`
+ */
+ @JvmOverloads
+ fun appendElement(tagName: String, namespace: String = tag.namespace()): Element {
+ val child = Element(
+ Tag.valueOf(
+ tagName,
+ namespace,
+ NodeUtils.parser(this)!!
+ .settings(),
+ ),
+ baseUri(),
+ )
+ appendChild(child)
+ return child
+ }
+
+ /**
+ * Create a new element by tag name, and add it as the first child.
+ *
+ * @param tagName the name of the tag (e.g. `div`).
+ * @return the new element, to allow you to add content to it, e.g.:
+ * `parent.prependElement("h1").attr("id", "header").text("Welcome");`
+ */
+ @JvmOverloads
+ fun prependElement(tagName: String, namespace: String = tag.namespace()): Element {
+ val child = Element(
+ Tag.valueOf(
+ tagName,
+ namespace,
+ NodeUtils.parser(this)!!
+ .settings(),
+ ),
+ baseUri(),
+ )
+ prependChild(child)
+ return child
+ }
+
+ /**
+ * Create and append a new TextNode to this element.
+ *
+ * @param text the (un-encoded) text to add
+ * @return this element
+ */
+ fun appendText(text: String): Element {
+ Validate.notNull(text)
+ val node = TextNode(text)
+ appendChild(node)
+ return this
+ }
+
+ /**
+ * Create and prepend a new TextNode to this element.
+ *
+ * @param text the decoded text to add
+ * @return this element
+ */
+ fun prependText(text: String): Element {
+ Validate.notNull(text)
+ val node = TextNode(text)
+ prependChild(node)
+ return this
+ }
+
+ /**
+ * Add inner HTML to this element. The supplied HTML will be parsed, and each node appended to the end of the children.
+ * @param html HTML to add inside this element, after the existing HTML
+ * @return this element
+ * @see .html
+ */
+ fun append(html: String): Element {
+ val nodes: List = NodeUtils.parser(this)!!.parseFragmentInput(html, this, baseUri())
+ addChildren(*nodes.toTypedArray())
+ return this
+ }
+
+ /**
+ * Add inner HTML into this element. The supplied HTML will be parsed, and each node prepended to the start of the element's children.
+ * @param html HTML to add inside this element, before the existing HTML
+ * @return this element
+ * @see .html
+ */
+ fun prepend(html: String): Element {
+ val nodes: List = NodeUtils.parser(this)!!.parseFragmentInput(html, this, baseUri())
+ addChildren(0, *nodes.toTypedArray())
+ return this
+ }
+
+ /**
+ * Insert the specified HTML into the DOM before this element (as a preceding sibling).
+ *
+ * @param html HTML to add before this element
+ * @return this element, for chaining
+ * @see .after
+ */
+ override fun before(html: String): Element {
+ return super.before(html) as Element
+ }
+
+ /**
+ * Insert the specified node into the DOM before this node (as a preceding sibling).
+ * @param node to add before this element
+ * @return this Element, for chaining
+ * @see .after
+ */
+ override fun before(node: Node?): Element {
+ return super.before(node) as Element
+ }
+
+ /**
+ * Insert the specified HTML into the DOM after this element (as a following sibling).
+ *
+ * @param html HTML to add after this element
+ * @return this element, for chaining
+ * @see .before
+ */
+ override fun after(html: String): Element {
+ return super.after(html) as Element
+ }
+
+ /**
+ * Insert the specified node into the DOM after this node (as a following sibling).
+ * @param node to add after this element
+ * @return this element, for chaining
+ * @see .before
+ */
+ override fun after(node: Node): Element {
+ return super.after(node) as Element
+ }
+
+ /**
+ * Remove all the element's child nodes. Any attributes are left as-is. Each child node has its parent set to
+ * `null`.
+ * @return this element
+ */
+ override fun empty(): Element {
+ // Detach each of the children -> parent links:
+ for (child in childNodes) {
+ child._parentNode = null
+ }
+ childNodes.clear()
+ return this
+ }
+
+ /**
+ * Wrap the supplied HTML around this element.
+ *
+ * @param html HTML to wrap around this element, e.g. ``. Can be arbitrarily deep.
+ * @return this element, for chaining.
+ */
+ override fun wrap(html: String): Element {
+ return super.wrap(html) as Element
+ }
+
+ /**
+ * Get a CSS selector that will uniquely select this element.
+ *
+ *
+ * If the element has an ID, returns #id;
+ * otherwise returns the parent (if any) CSS selector, followed by '>',
+ * followed by a unique selector for the element (tag.class.class:nth-child(n)).
+ *
+ *
+ * @return the CSS Path that can be used to retrieve the element in a selector.
+ */
+ fun cssSelector(): String {
+ if (id().isNotEmpty()) {
+ // prefer to return the ID - but check that it's actually unique first!
+ val idSel = "#" + escapeCssIdentifier(id())
+ val doc: Document? = ownerDocument()
+ if (doc != null) {
+ val els: Elements = doc.select(idSel)
+ if (els.size === 1 && els[0] === this) {
+ // otherwise, continue to the nth-child impl
+ return idSel
+ }
+ } else {
+ return idSel // no ownerdoc, return the ID selector
+ }
+ }
+ val selector: StringBuilder = StringUtil.borrowBuilder()
+ var el: Element? = this
+ while (el != null && el !is Document) {
+ selector.insert(0, el.cssSelectorComponent())
+ el = el.parent()
+ }
+ return StringUtil.releaseBuilder(selector)
+ }
+
+ private fun cssSelectorComponent(): String {
+ // Escape tagname, and translate HTML namespace ns:tag to CSS namespace syntax ns|tag
+ val tagName: String = escapeCssIdentifier(tagName()).replace("\\:", "|")
+ val selector: StringBuilder = StringUtil.borrowBuilder().append(tagName)
+ // String classes = StringUtil.join(classNames().stream().map(TokenQueue::escapeCssIdentifier).iterator(), ".");
+ // todo - replace with ^^ in 1.16.1 when we enable Android support for stream etc
+ val escapedClasses: StringUtil.StringJoiner = StringUtil.StringJoiner(".")
+ for (name in classNames()) escapedClasses.add(escapeCssIdentifier(name))
+ val classes: String = escapedClasses.complete()
+ if (classes.isNotEmpty()) selector.append('.').append(classes)
+ val parent: Element? = parent()
+ if (parent == null || parent is Document) {
+ // don't add Document to selector, as will always have a html node
+ return StringUtil.releaseBuilder(selector)
+ }
+ selector.insert(0, " > ")
+ if (parent.select(selector.toString()).size > 1) {
+ selector.append(":nth-child(${elementSiblingIndex() + 1})")
+ }
+ return StringUtil.releaseBuilder(selector)
+ }
+
+ /**
+ * Get sibling elements. If the element has no sibling elements, returns an empty list. An element is not a sibling
+ * of itself, so will not be included in the returned list.
+ * @return sibling elements
+ */
+ fun siblingElements(): Elements {
+ if (_parentNode == null) return Elements()
+ val elements = (_parentNode as Element).childElementsList()
+ val siblings = Elements()
+ for (el in elements) if (el !== this) siblings.add(el)
+ return siblings
+ }
+
+ /**
+ * Gets the next sibling element of this element. E.g., if a `div` contains two `p`s,
+ * the `nextElementSibling` of the first `p` is the second `p`.
+ *
+ *
+ * This is similar to [.nextSibling], but specifically finds only Elements
+ *
+ * @return the next element, or null if there is no next element
+ * @see .previousElementSibling
+ */
+// @Nullable
+ fun nextElementSibling(): Element? {
+ var next: Node = this
+ while (next.nextSibling()?.also { next = it } != null) {
+ if (next is Element) return next as Element
+ }
+ return null
+ }
+
+ /**
+ * Get each of the sibling elements that come after this element.
+ *
+ * @return each of the element siblings after this element, or an empty list if there are no next sibling elements
+ */
+ fun nextElementSiblings(): Elements {
+ return nextElementSiblings(true)
+ }
+
+ /**
+ * Gets the previous element sibling of this element.
+ * @return the previous element, or null if there is no previous element
+ * @see .nextElementSibling
+ */
+// @Nullable
+ fun previousElementSibling(): Element? {
+ var prev: Node = this
+ while (prev.previousSibling()?.also { prev = it } != null) {
+ if (prev is Element) return prev as Element
+ }
+ return null
+ }
+
+ /**
+ * Get each of the element siblings before this element.
+ *
+ * @return the previous element siblings, or an empty list if there are none.
+ */
+ fun previousElementSiblings(): Elements {
+ return nextElementSiblings(false)
+ }
+
+ private fun nextElementSiblings(next: Boolean): Elements {
+ val els = Elements()
+ if (_parentNode == null) return els
+ els.add(this)
+ return if (next) els.nextAll() else els.prevAll()
+ }
+
+ /**
+ * Gets the first Element sibling of this element. That may be this element.
+ * @return the first sibling that is an element (aka the parent's first element child)
+ */
+ fun firstElementSibling(): Element? {
+ val parent: Element? = parent()
+ return if (parent != null) {
+ parent.firstElementChild()
+ } else {
+ this // orphan is its own first sibling
+ }
+ }
+
+ /**
+ * Get the list index of this element in its element sibling list. I.e. if this is the first element
+ * sibling, returns 0.
+ * @return position in element sibling list
+ */
+ fun elementSiblingIndex(): Int {
+ val parent: Element? = parent()
+ return if (parent == null) {
+ 0
+ } else {
+ indexInList(
+ this,
+ parent.childElementsList(),
+ )
+ }
+ }
+
+ /**
+ * Gets the last element sibling of this element. That may be this element.
+ * @return the last sibling that is an element (aka the parent's last element child)
+ */
+ fun lastElementSibling(): Element? {
+ val parent: Element? = parent()
+ return if (parent != null) {
+ parent.lastElementChild()
+ } else {
+ this
+ }
+ }
+
+ /**
+ * Gets the first child of this Element that is an Element, or `null` if there is none.
+ * @return the first Element child node, or null.
+ * @see .firstChild
+ * @see .lastElementChild
+ * @since 1.15.2
+ */
+// @Nullable
+ fun firstElementChild(): Element? {
+ var child: Node? = firstChild()
+ while (child != null) {
+ if (child is Element) return child
+ child = child.nextSibling()
+ }
+ return null
+ }
+
+ /**
+ * Gets the last child of this Element that is an Element, or @{code null} if there is none.
+ * @return the last Element child node, or null.
+ * @see .lastChild
+ * @see .firstElementChild
+ * @since 1.15.2
+ */
+// @Nullable
+ fun lastElementChild(): Element? {
+ var child: Node? = lastChild()
+ while (child != null) {
+ if (child is Element) return child
+ child = child.previousSibling()
+ }
+ return null
+ }
+ // DOM type methods
+ /**
+ * Finds elements, including and recursively under this element, with the specified tag name.
+ * @param tagName The tag name to search for (case insensitively).
+ * @return a matching unmodifiable list of elements. Will be empty if this element and none of its children match.
+ */
+ fun getElementsByTag(tagName: String?): Elements {
+ var tagName = tagName
+ Validate.notEmpty(tagName)
+ tagName = normalize(tagName)
+ return Collector.collect(Evaluator.Tag(tagName), this)
+ }
+
+ /**
+ * Find an element by ID, including or under this element.
+ *
+ *
+ * Note that this finds the first matching ID, starting with this element. If you search down from a different
+ * starting point, it is possible to find a different element by ID. For unique element by ID within a Document,
+ * use [Document.getElementById]
+ * @param id The ID to search for.
+ * @return The first matching element by ID, starting with this element, or null if none found.
+ */
+// @Nullable
+ fun getElementById(id: String): Element? {
+ Validate.notEmpty(id)
+ val elements: Elements = Collector.collect(Evaluator.Id(id), this)
+ return if (elements.size > 0) elements[0] else null
+ }
+
+ /**
+ * Find elements that have this class, including or under this element. Case-insensitive.
+ *
+ *
+ * Elements can have multiple classes (e.g. `