diff options
Diffstat (limited to 'src/org/jsoup/nodes/Element.java')
-rw-r--r-- | src/org/jsoup/nodes/Element.java | 1119 |
1 files changed, 0 insertions, 1119 deletions
diff --git a/src/org/jsoup/nodes/Element.java b/src/org/jsoup/nodes/Element.java deleted file mode 100644 index 5c1894c934..0000000000 --- a/src/org/jsoup/nodes/Element.java +++ /dev/null @@ -1,1119 +0,0 @@ -package org.jsoup.nodes; - -import org.jsoup.helper.StringUtil; -import org.jsoup.helper.Validate; -import org.jsoup.parser.Parser; -import org.jsoup.parser.Tag; -import org.jsoup.select.Collector; -import org.jsoup.select.Elements; -import org.jsoup.select.Evaluator; -import org.jsoup.select.Selector; - -import java.util.*; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -/** - * A HTML element consists of a tag name, attributes, and child nodes (including text nodes and - * other elements). - * - * From an Element, you can extract data, traverse the node graph, and manipulate the HTML. - * - * @author Jonathan Hedley, jonathan@hedley.net - */ -public class Element extends Node { - private Tag tag; - private Set<String> classNames; - - /** - * Create a new, standalone Element. (Standalone in that is has no parent.) - * - * @param tag tag of this element - * @param baseUri the base URI - * @param attributes initial attributes - * @see #appendChild(Node) - * @see #appendElement(String) - */ - public Element(Tag tag, String baseUri, Attributes attributes) { - super(baseUri, attributes); - - Validate.notNull(tag); - this.tag = tag; - } - - /** - * Create a new Element from a tag and a base URI. - * - * @param tag element tag - * @param baseUri the base URI of this element. It is acceptable for the base URI to be an empty - * string, but not null. - * @see Tag#valueOf(String) - */ - public Element(Tag tag, String baseUri) { - this(tag, baseUri, new Attributes()); - } - - @Override - public String nodeName() { - return tag.getName(); - } - - /** - * Get the name of the tag for this element. E.g. {@code div} - * - * @return the tag name - */ - public String tagName() { - return tag.getName(); - } - - /** - * Change the tag of this element. For example, convert a {@code <span>} to a {@code <div>} with - * {@code el.tagName("div");}. - * - * @param tagName new tag name for this element - * @return this element, for chaining - */ - public Element tagName(String tagName) { - Validate.notEmpty(tagName, "Tag name must not be empty."); - tag = Tag.valueOf(tagName); - return this; - } - - /** - * Get the Tag for this element. - * - * @return the tag object - */ - public Tag tag() { - return tag; - } - - /** - * Test if this element is a block-level element. (E.g. {@code <div> == true} or an inline element - * {@code <p> == false}). - * - * @return true if block, false if not (and thus inline) - */ - public boolean isBlock() { - return tag.isBlock(); - } - - /** - * Get the {@code id} attribute of this element. - * - * @return The id attribute, if present, or an empty string if not. - */ - public String id() { - String id = attr("id"); - return id == null ? "" : id; - } - - /** - * 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 - */ - public Element attr(String attributeKey, String attributeValue) { - super.attr(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. - * <p> - * E.g., the element {@code <div data-package="jsoup" data-language="Java" class="group">...} has the dataset - * {@code package=jsoup, language=java}. - * <p> - * 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. - * <p> - * You can find elements that have data attributes using the {@code [^data-]} attribute key prefix selector. - * @return a map of {@code key=value} custom data attributes. - */ - public Map<String, String> dataset() { - return attributes.dataset(); - } - - @Override - public final Element parent() { - return (Element) parentNode; - } - - /** - * Get this element's parent and ancestors, up to the document root. - * @return this element's stack of parents, closest first. - */ - public Elements parents() { - Elements parents = new Elements(); - accumulateParents(this, parents); - return parents; - } - - private static void accumulateParents(Element el, Elements parents) { - Element parent = el.parent(); - if (parent != null && !parent.tagName().equals("#root")) { - parents.add(parent); - accumulateParents(parent, parents); - } - } - - /** - * Get a child element of this element, by its 0-based index number. - * <p/> - * 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, or {@code null} if absent. - * @see #childNode(int) - */ - public Element child(int index) { - return children().get(index); - } - - /** - * Get this element's child elements. - * <p/> - * This is effectively a filter on {@link #childNodes()} to get Element nodes. - * @return child elements. If this element has no children, returns an - * empty list. - * @see #childNodes() - */ - public Elements children() { - // create on the fly rather than maintaining two lists. if gets slow, memoize, and mark dirty on change - List<Element> elements = new ArrayList<Element>(); - for (Node node : childNodes) { - if (node instanceof Element) - elements.add((Element) node); - } - return new Elements(elements); - } - - /** - * Get this element's child text nodes. The list is unmodifiable but the text nodes may be manipulated. - * <p/> - * This is effectively a filter on {@link #childNodes()} to get Text nodes. - * @return child text nodes. If this element has no text nodes, returns an - * empty list. - * <p/> - * For example, with the input HTML: {@code <p>One <span>Two</span> Three <br> Four</p>} with the {@code p} element selected: - * <ul> - * <li>{@code p.text()} = {@code "One Two Three Four"}</li> - * <li>{@code p.ownText()} = {@code "One Three Four"}</li> - * <li>{@code p.children()} = {@code Elements[<span>, <br>]}</li> - * <li>{@code p.childNodes()} = {@code List<Node>["One ", <span>, " Three ", <br>, " Four"]}</li> - * <li>{@code p.textNodes()} = {@code List<TextNode>["One ", " Three ", " Four"]}</li> - * </ul> - */ - public List<TextNode> textNodes() { - List<TextNode> textNodes = new ArrayList<TextNode>(); - for (Node node : childNodes) { - if (node instanceof TextNode) - textNodes.add((TextNode) node); - } - return Collections.unmodifiableList(textNodes); - } - - /** - * Get this element's child data nodes. The list is unmodifiable but the data nodes may be manipulated. - * <p/> - * This is effectively a filter on {@link #childNodes()} to get Data nodes. - * @return child data nodes. If this element has no data nodes, returns an - * empty list. - * @see #data() - */ - public List<DataNode> dataNodes() { - List<DataNode> dataNodes = new ArrayList<DataNode>(); - for (Node node : childNodes) { - if (node instanceof DataNode) - dataNodes.add((DataNode) node); - } - return Collections.unmodifiableList(dataNodes); - } - - /** - * Find elements that match the {@link Selector} CSS query, with this element as the starting context. Matched elements - * may include this element, or any of its children. - * <p/> - * This method is generally more powerful to use than the DOM-type {@code getElementBy*} methods, because - * multiple filters can be combined, e.g.: - * <ul> - * <li>{@code el.select("a[href]")} - finds links ({@code a} tags with {@code href} attributes) - * <li>{@code el.select("a[href*=example.com]")} - finds links pointing to example.com (loosely) - * </ul> - * <p/> - * See the query syntax documentation in {@link org.jsoup.select.Selector}. - * - * @param cssQuery a {@link Selector} CSS-like query - * @return elements that match the query (empty if none match) - * @see org.jsoup.select.Selector - */ - public Elements select(String cssQuery) { - return Selector.select(cssQuery, this); - } - - /** - * Add a node child node to this element. - * - * @param child node to add. Must not already have a parent. - * @return this element, so that you can add more child nodes or elements. - */ - public Element appendChild(Node child) { - Validate.notNull(child); - - addChildren(child); - return this; - } - - /** - * Add a node to the start of this element's children. - * - * @param child node to add. Must not already have a parent. - * @return this element, so that you can add more child nodes or elements. - */ - public Element prependChild(Node child) { - Validate.notNull(child); - - addChildren(0, child); - 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. {@code div}). - * @return the new element, to allow you to add content to it, e.g.: - * {@code parent.appendElement("h1").attr("id", "header").text("Welcome");} - */ - public Element appendElement(String tagName) { - Element child = new Element(Tag.valueOf(tagName), 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. {@code div}). - * @return the new element, to allow you to add content to it, e.g.: - * {@code parent.prependElement("h1").attr("id", "header").text("Welcome");} - */ - public Element prependElement(String tagName) { - Element child = new Element(Tag.valueOf(tagName), baseUri()); - prependChild(child); - return child; - } - - /** - * Create and append a new TextNode to this element. - * - * @param text the unencoded text to add - * @return this element - */ - public Element appendText(String text) { - TextNode node = new TextNode(text, baseUri()); - appendChild(node); - return this; - } - - /** - * Create and prepend a new TextNode to this element. - * - * @param text the unencoded text to add - * @return this element - */ - public Element prependText(String text) { - TextNode node = new TextNode(text, baseUri()); - 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(String) - */ - public Element append(String html) { - Validate.notNull(html); - - List<Node> nodes = Parser.parseFragment(html, this, baseUri()); - addChildren(nodes.toArray(new Node[nodes.size()])); - 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(String) - */ - public Element prepend(String html) { - Validate.notNull(html); - - List<Node> nodes = Parser.parseFragment(html, this, baseUri()); - addChildren(0, nodes.toArray(new Node[nodes.size()])); - return this; - } - - /** - * Insert the specified HTML into the DOM before this element (i.e. as a preceding sibling). - * - * @param html HTML to add before this element - * @return this element, for chaining - * @see #after(String) - */ - @Override - public Element before(String html) { - return (Element) super.before(html); - } - - /** - * Insert the specified node into the DOM before this node (i.e. as a preceding sibling). - * @param node to add before this element - * @return this Element, for chaining - * @see #after(Node) - */ - @Override - public Element before(Node node) { - return (Element) super.before(node); - } - - /** - * Insert the specified HTML into the DOM after this element (i.e. as a following sibling). - * - * @param html HTML to add after this element - * @return this element, for chaining - * @see #before(String) - */ - @Override - public Element after(String html) { - return (Element) super.after(html); - } - - /** - * Insert the specified node into the DOM after this node (i.e. as a following sibling). - * @param node to add after this element - * @return this element, for chaining - * @see #before(Node) - */ - @Override - public Element after(Node node) { - return (Element) super.after(node); - } - - /** - * Remove all of the element's child nodes. Any attributes are left as-is. - * @return this element - */ - public Element empty() { - childNodes.clear(); - return this; - } - - /** - * Wrap the supplied HTML around this element. - * - * @param html HTML to wrap around this element, e.g. {@code <div class="head"></div>}. Can be arbitrarily deep. - * @return this element, for chaining. - */ - @Override - public Element wrap(String html) { - return (Element) super.wrap(html); - } - - /** - * 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 - */ - public Elements siblingElements() { - if (parentNode == null) - return new Elements(0); - - List<Element> elements = parent().children(); - Elements siblings = new Elements(elements.size() - 1); - for (Element el: elements) - if (el != this) - siblings.add(el); - return siblings; - } - - /** - * Gets the next sibling element of this element. E.g., if a {@code div} contains two {@code p}s, - * the {@code nextElementSibling} of the first {@code p} is the second {@code p}. - * <p/> - * This is similar to {@link #nextSibling()}, but specifically finds only Elements - * @return the next element, or null if there is no next element - * @see #previousElementSibling() - */ - public Element nextElementSibling() { - if (parentNode == null) return null; - List<Element> siblings = parent().children(); - Integer index = indexInList(this, siblings); - Validate.notNull(index); - if (siblings.size() > index+1) - return siblings.get(index+1); - else - return null; - } - - /** - * Gets the previous element sibling of this element. - * @return the previous element, or null if there is no previous element - * @see #nextElementSibling() - */ - public Element previousElementSibling() { - if (parentNode == null) return null; - List<Element> siblings = parent().children(); - Integer index = indexInList(this, siblings); - Validate.notNull(index); - if (index > 0) - return siblings.get(index-1); - else - return null; - } - - /** - * Gets the first element sibling of this element. - * @return the first sibling that is an element (aka the parent's first element child) - */ - public Element firstElementSibling() { - // todo: should firstSibling() exclude this? - List<Element> siblings = parent().children(); - return siblings.size() > 1 ? siblings.get(0) : null; - } - - /** - * 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 - */ - public Integer elementSiblingIndex() { - if (parent() == null) return 0; - return indexInList(this, parent().children()); - } - - /** - * Gets the last element sibling of this element - * @return the last sibling that is an element (aka the parent's last element child) - */ - public Element lastElementSibling() { - List<Element> siblings = parent().children(); - return siblings.size() > 1 ? siblings.get(siblings.size() - 1) : null; - } - - private static <E extends Element> Integer indexInList(Element search, List<E> elements) { - Validate.notNull(search); - Validate.notNull(elements); - - for (int i = 0; i < elements.size(); i++) { - E element = elements.get(i); - if (element.equals(search)) - return i; - } - 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. - */ - public Elements getElementsByTag(String tagName) { - Validate.notEmpty(tagName); - tagName = tagName.toLowerCase().trim(); - - return Collector.collect(new Evaluator.Tag(tagName), this); - } - - /** - * Find an element by ID, including or under this element. - * <p> - * 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 {@link Document#getElementById(String)} - * @param id The ID to search for. - * @return The first matching element by ID, starting with this element, or null if none found. - */ - public Element getElementById(String id) { - Validate.notEmpty(id); - - Elements elements = Collector.collect(new Evaluator.Id(id), this); - if (elements.size() > 0) - return elements.get(0); - else - return null; - } - - /** - * Find elements that have this class, including or under this element. Case insensitive. - * <p> - * Elements can have multiple classes (e.g. {@code <div class="header round first">}. This method - * checks each class, so you can find the above with {@code el.getElementsByClass("header");}. - * - * @param className the name of the class to search for. - * @return elements with the supplied class name, empty if none - * @see #hasClass(String) - * @see #classNames() - */ - public Elements getElementsByClass(String className) { - Validate.notEmpty(className); - - return Collector.collect(new Evaluator.Class(className), this); - } - - /** - * Find elements that have a named attribute set. Case insensitive. - * - * @param key name of the attribute, e.g. {@code href} - * @return elements that have this attribute, empty if none - */ - public Elements getElementsByAttribute(String key) { - Validate.notEmpty(key); - key = key.trim().toLowerCase(); - - return Collector.collect(new Evaluator.Attribute(key), this); - } - - /** - * Find elements that have an attribute name starting with the supplied prefix. Use {@code data-} to find elements - * that have HTML5 datasets. - * @param keyPrefix name prefix of the attribute e.g. {@code data-} - * @return elements that have attribute names that start with with the prefix, empty if none. - */ - public Elements getElementsByAttributeStarting(String keyPrefix) { - Validate.notEmpty(keyPrefix); - keyPrefix = keyPrefix.trim().toLowerCase(); - - return Collector.collect(new Evaluator.AttributeStarting(keyPrefix), this); - } - - /** - * Find elements that have an attribute with the specific value. Case insensitive. - * - * @param key name of the attribute - * @param value value of the attribute - * @return elements that have this attribute with this value, empty if none - */ - public Elements getElementsByAttributeValue(String key, String value) { - return Collector.collect(new Evaluator.AttributeWithValue(key, value), this); - } - - /** - * Find elements that either do not have this attribute, or have it with a different value. Case insensitive. - * - * @param key name of the attribute - * @param value value of the attribute - * @return elements that do not have a matching attribute - */ - public Elements getElementsByAttributeValueNot(String key, String value) { - return Collector.collect(new Evaluator.AttributeWithValueNot(key, value), this); - } - - /** - * Find elements that have attributes that start with the value prefix. Case insensitive. - * - * @param key name of the attribute - * @param valuePrefix start of attribute value - * @return elements that have attributes that start with the value prefix - */ - public Elements getElementsByAttributeValueStarting(String key, String valuePrefix) { - return Collector.collect(new Evaluator.AttributeWithValueStarting(key, valuePrefix), this); - } - - /** - * Find elements that have attributes that end with the value suffix. Case insensitive. - * - * @param key name of the attribute - * @param valueSuffix end of the attribute value - * @return elements that have attributes that end with the value suffix - */ - public Elements getElementsByAttributeValueEnding(String key, String valueSuffix) { - return Collector.collect(new Evaluator.AttributeWithValueEnding(key, valueSuffix), this); - } - - /** - * Find elements that have attributes whose value contains the match string. Case insensitive. - * - * @param key name of the attribute - * @param match substring of value to search for - * @return elements that have attributes containing this text - */ - public Elements getElementsByAttributeValueContaining(String key, String match) { - return Collector.collect(new Evaluator.AttributeWithValueContaining(key, match), this); - } - - /** - * Find elements that have attributes whose values match the supplied regular expression. - * @param key name of the attribute - * @param pattern compiled regular expression to match against attribute values - * @return elements that have attributes matching this regular expression - */ - public Elements getElementsByAttributeValueMatching(String key, Pattern pattern) { - return Collector.collect(new Evaluator.AttributeWithValueMatching(key, pattern), this); - - } - - /** - * Find elements that have attributes whose values match the supplied regular expression. - * @param key name of the attribute - * @param regex regular expression to match against attribute values. You can use <a href="http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded">embedded flags</a> (such as (?i) and (?m) to control regex options. - * @return elements that have attributes matching this regular expression - */ - public Elements getElementsByAttributeValueMatching(String key, String regex) { - Pattern pattern; - try { - pattern = Pattern.compile(regex); - } catch (PatternSyntaxException e) { - throw new IllegalArgumentException("Pattern syntax error: " + regex, e); - } - return getElementsByAttributeValueMatching(key, pattern); - } - - /** - * Find elements whose sibling index is less than the supplied index. - * @param index 0-based index - * @return elements less than index - */ - public Elements getElementsByIndexLessThan(int index) { - return Collector.collect(new Evaluator.IndexLessThan(index), this); - } - - /** - * Find elements whose sibling index is greater than the supplied index. - * @param index 0-based index - * @return elements greater than index - */ - public Elements getElementsByIndexGreaterThan(int index) { - return Collector.collect(new Evaluator.IndexGreaterThan(index), this); - } - - /** - * Find elements whose sibling index is equal to the supplied index. - * @param index 0-based index - * @return elements equal to index - */ - public Elements getElementsByIndexEquals(int index) { - return Collector.collect(new Evaluator.IndexEquals(index), this); - } - - /** - * Find elements that contain the specified string. The search is case insensitive. The text may appear directly - * in the element, or in any of its descendants. - * @param searchText to look for in the element's text - * @return elements that contain the string, case insensitive. - * @see Element#text() - */ - public Elements getElementsContainingText(String searchText) { - return Collector.collect(new Evaluator.ContainsText(searchText), this); - } - - /** - * Find elements that directly contain the specified string. The search is case insensitive. The text must appear directly - * in the element, not in any of its descendants. - * @param searchText to look for in the element's own text - * @return elements that contain the string, case insensitive. - * @see Element#ownText() - */ - public Elements getElementsContainingOwnText(String searchText) { - return Collector.collect(new Evaluator.ContainsOwnText(searchText), this); - } - - /** - * Find elements whose text matches the supplied regular expression. - * @param pattern regular expression to match text against - * @return elements matching the supplied regular expression. - * @see Element#text() - */ - public Elements getElementsMatchingText(Pattern pattern) { - return Collector.collect(new Evaluator.Matches(pattern), this); - } - - /** - * Find elements whose text matches the supplied regular expression. - * @param regex regular expression to match text against. You can use <a href="http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded">embedded flags</a> (such as (?i) and (?m) to control regex options. - * @return elements matching the supplied regular expression. - * @see Element#text() - */ - public Elements getElementsMatchingText(String regex) { - Pattern pattern; - try { - pattern = Pattern.compile(regex); - } catch (PatternSyntaxException e) { - throw new IllegalArgumentException("Pattern syntax error: " + regex, e); - } - return getElementsMatchingText(pattern); - } - - /** - * Find elements whose own text matches the supplied regular expression. - * @param pattern regular expression to match text against - * @return elements matching the supplied regular expression. - * @see Element#ownText() - */ - public Elements getElementsMatchingOwnText(Pattern pattern) { - return Collector.collect(new Evaluator.MatchesOwn(pattern), this); - } - - /** - * Find elements whose text matches the supplied regular expression. - * @param regex regular expression to match text against. You can use <a href="http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded">embedded flags</a> (such as (?i) and (?m) to control regex options. - * @return elements matching the supplied regular expression. - * @see Element#ownText() - */ - public Elements getElementsMatchingOwnText(String regex) { - Pattern pattern; - try { - pattern = Pattern.compile(regex); - } catch (PatternSyntaxException e) { - throw new IllegalArgumentException("Pattern syntax error: " + regex, e); - } - return getElementsMatchingOwnText(pattern); - } - - /** - * Find all elements under this element (including self, and children of children). - * - * @return all elements - */ - public Elements getAllElements() { - return Collector.collect(new Evaluator.AllElements(), this); - } - - /** - * Gets the combined text of this element and all its children. - * <p> - * For example, given HTML {@code <p>Hello <b>there</b> now!</p>}, {@code p.text()} returns {@code "Hello there now!"} - * - * @return unencoded text, or empty string if none. - * @see #ownText() - * @see #textNodes() - */ - public String text() { - StringBuilder sb = new StringBuilder(); - text(sb); - return sb.toString().trim(); - } - - private void text(StringBuilder accum) { - appendWhitespaceIfBr(this, accum); - - for (Node child : childNodes) { - if (child instanceof TextNode) { - TextNode textNode = (TextNode) child; - appendNormalisedText(accum, textNode); - } else if (child instanceof Element) { - Element element = (Element) child; - if (accum.length() > 0 && element.isBlock() && !TextNode.lastCharIsWhitespace(accum)) - accum.append(" "); - element.text(accum); - } - } - } - - /** - * Gets the text owned by this element only; does not get the combined text of all children. - * <p> - * For example, given HTML {@code <p>Hello <b>there</b> now!</p>}, {@code p.ownText()} returns {@code "Hello now!"}, - * whereas {@code p.text()} returns {@code "Hello there now!"}. - * Note that the text within the {@code b} element is not returned, as it is not a direct child of the {@code p} element. - * - * @return unencoded text, or empty string if none. - * @see #text() - * @see #textNodes() - */ - public String ownText() { - StringBuilder sb = new StringBuilder(); - ownText(sb); - return sb.toString().trim(); - } - - private void ownText(StringBuilder accum) { - for (Node child : childNodes) { - if (child instanceof TextNode) { - TextNode textNode = (TextNode) child; - appendNormalisedText(accum, textNode); - } else if (child instanceof Element) { - appendWhitespaceIfBr((Element) child, accum); - } - } - } - - private void appendNormalisedText(StringBuilder accum, TextNode textNode) { - String text = textNode.getWholeText(); - - if (!preserveWhitespace()) { - text = TextNode.normaliseWhitespace(text); - if (TextNode.lastCharIsWhitespace(accum)) - text = TextNode.stripLeadingWhitespace(text); - } - accum.append(text); - } - - private static void appendWhitespaceIfBr(Element element, StringBuilder accum) { - if (element.tag.getName().equals("br") && !TextNode.lastCharIsWhitespace(accum)) - accum.append(" "); - } - - boolean preserveWhitespace() { - return tag.preserveWhitespace() || parent() != null && parent().preserveWhitespace(); - } - - /** - * Set the text of this element. Any existing contents (text or elements) will be cleared - * @param text unencoded text - * @return this element - */ - public Element text(String text) { - Validate.notNull(text); - - empty(); - TextNode textNode = new TextNode(text, baseUri); - appendChild(textNode); - - return this; - } - - /** - Test if this element has any text content (that is not just whitespace). - @return true if element has non-blank text content. - */ - public boolean hasText() { - for (Node child: childNodes) { - if (child instanceof TextNode) { - TextNode textNode = (TextNode) child; - if (!textNode.isBlank()) - return true; - } else if (child instanceof Element) { - Element el = (Element) child; - if (el.hasText()) - return true; - } - } - return false; - } - - /** - * Get the combined data of this element. Data is e.g. the inside of a {@code script} tag. - * @return the data, or empty string if none - * - * @see #dataNodes() - */ - public String data() { - StringBuilder sb = new StringBuilder(); - - for (Node childNode : childNodes) { - if (childNode instanceof DataNode) { - DataNode data = (DataNode) childNode; - sb.append(data.getWholeData()); - } else if (childNode instanceof Element) { - Element element = (Element) childNode; - String elementData = element.data(); - sb.append(elementData); - } - } - return sb.toString(); - } - - /** - * Gets the literal value of this element's "class" attribute, which may include multiple class names, space - * separated. (E.g. on <code><div class="header gray"></code> returns, "<code>header gray</code>") - * @return The literal class attribute, or <b>empty string</b> if no class attribute set. - */ - public String className() { - return attr("class"); - } - - /** - * Get all of the element's class names. E.g. on element {@code <div class="header gray"}>}, - * returns a set of two elements {@code "header", "gray"}. Note that modifications to this set are not pushed to - * the backing {@code class} attribute; use the {@link #classNames(java.util.Set)} method to persist them. - * @return set of classnames, empty if no class attribute - */ - public Set<String> classNames() { - if (classNames == null) { - String[] names = className().split("\\s+"); - classNames = new LinkedHashSet<String>(Arrays.asList(names)); - } - return classNames; - } - - /** - Set the element's {@code class} attribute to the supplied class names. - @param classNames set of classes - @return this element, for chaining - */ - public Element classNames(Set<String> classNames) { - Validate.notNull(classNames); - attributes.put("class", StringUtil.join(classNames, " ")); - return this; - } - - /** - * Tests if this element has a class. Case insensitive. - * @param className name of class to check for - * @return true if it does, false if not - */ - public boolean hasClass(String className) { - Set<String> classNames = classNames(); - for (String name : classNames) { - if (className.equalsIgnoreCase(name)) - return true; - } - return false; - } - - /** - Add a class name to this element's {@code class} attribute. - @param className class name to add - @return this element - */ - public Element addClass(String className) { - Validate.notNull(className); - - Set<String> classes = classNames(); - classes.add(className); - classNames(classes); - - return this; - } - - /** - Remove a class name from this element's {@code class} attribute. - @param className class name to remove - @return this element - */ - public Element removeClass(String className) { - Validate.notNull(className); - - Set<String> classes = classNames(); - classes.remove(className); - classNames(classes); - - return this; - } - - /** - Toggle a class name on this element's {@code class} attribute: if present, remove it; otherwise add it. - @param className class name to toggle - @return this element - */ - public Element toggleClass(String className) { - Validate.notNull(className); - - Set<String> classes = classNames(); - if (classes.contains(className)) - classes.remove(className); - else - classes.add(className); - classNames(classes); - - return this; - } - - /** - * Get the value of a form element (input, textarea, etc). - * @return the value of the form element, or empty string if not set. - */ - public String val() { - if (tagName().equals("textarea")) - return text(); - else - return attr("value"); - } - - /** - * Set the value of a form element (input, textarea, etc). - * @param value value to set - * @return this element (for chaining) - */ - public Element val(String value) { - if (tagName().equals("textarea")) - text(value); - else - attr("value", value); - return this; - } - - void outerHtmlHead(StringBuilder accum, int depth, Document.OutputSettings out) { - if (accum.length() > 0 && out.prettyPrint() && (tag.formatAsBlock() || (parent() != null && parent().tag().formatAsBlock()))) - indent(accum, depth, out); - accum - .append("<") - .append(tagName()); - attributes.html(accum, out); - - if (childNodes.isEmpty() && tag.isSelfClosing()) - accum.append(" />"); - else - accum.append(">"); - } - - void outerHtmlTail(StringBuilder accum, int depth, Document.OutputSettings out) { - if (!(childNodes.isEmpty() && tag.isSelfClosing())) { - if (out.prettyPrint() && !childNodes.isEmpty() && tag.formatAsBlock()) - indent(accum, depth, out); - accum.append("</").append(tagName()).append(">"); - } - } - - /** - * Retrieves the element's inner HTML. E.g. on a {@code <div>} with one empty {@code <p>}, would return - * {@code <p></p>}. (Whereas {@link #outerHtml()} would return {@code <div><p></p></div>}.) - * - * @return String of HTML. - * @see #outerHtml() - */ - public String html() { - StringBuilder accum = new StringBuilder(); - html(accum); - return accum.toString().trim(); - } - - private void html(StringBuilder accum) { - for (Node node : childNodes) - node.outerHtml(accum); - } - - /** - * Set this element's inner HTML. Clears the existing HTML first. - * @param html HTML to parse and set into this element - * @return this element - * @see #append(String) - */ - public Element html(String html) { - empty(); - append(html); - return this; - } - - public String toString() { - return outerHtml(); - } - - @Override - public boolean equals(Object o) { - return this == o; - } - - @Override - public int hashCode() { - // todo: fixup, not very useful - int result = super.hashCode(); - result = 31 * result + (tag != null ? tag.hashCode() : 0); - return result; - } - - @Override - public Element clone() { - Element clone = (Element) super.clone(); - clone.classNames(); // creates linked set of class names from class attribute - return clone; - } -} |