Skip to content

Commit eafba57

Browse files
committed
Add custom boolean attribute support jhy#503
* Adds BooleanAttribute that writes out itself without a value * Adds API in Element for setting boolean attributes * Update parser to distinguish between no value and empty value
1 parent 373ea35 commit eafba57

File tree

9 files changed

+99
-6
lines changed

9 files changed

+99
-6
lines changed

src/main/java/org/jsoup/nodes/Attribute.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,11 @@ protected boolean isDataAttribute() {
121121
protected final boolean shouldCollapseAttribute(Document.OutputSettings out) {
122122
return ("".equals(value) || value.equalsIgnoreCase(key))
123123
&& out.syntax() == Document.OutputSettings.Syntax.html
124-
&& Arrays.binarySearch(booleanAttributes, key) >= 0;
124+
&& isBooleanAttribute();
125+
}
126+
127+
protected boolean isBooleanAttribute() {
128+
return Arrays.binarySearch(booleanAttributes, key) >= 0;
125129
}
126130

127131
@Override

src/main/java/org/jsoup/nodes/Attributes.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ public void put(String key, String value) {
4848
Attribute attr = new Attribute(key, value);
4949
put(attr);
5050
}
51+
52+
/**
53+
Set a new boolean attribute, remove attribute if value is false.
54+
@param key attribute key
55+
@param value attribute value
56+
*/
57+
public void put(String key, boolean value) {
58+
if (value)
59+
put(new BooleanAttribute(key));
60+
else
61+
remove(key);
62+
}
5163

5264
/**
5365
Set a new attribute, or replace an existing one by key.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.jsoup.nodes;
2+
3+
/**
4+
* A boolean attribute that is written out without any value.
5+
*/
6+
public class BooleanAttribute extends Attribute {
7+
/**
8+
* Create a new boolean attribute from unencoded (raw) key.
9+
* @param key attribute key
10+
*/
11+
public BooleanAttribute(String key) {
12+
super(key, "");
13+
}
14+
15+
@Override
16+
protected boolean isBooleanAttribute() {
17+
return true;
18+
}
19+
}

src/main/java/org/jsoup/nodes/Element.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,21 @@ public Element attr(String attributeKey, String attributeValue) {
116116
super.attr(attributeKey, attributeValue);
117117
return this;
118118
}
119+
120+
/**
121+
* Set a boolean attribute value on this element. Setting to <code>true</code> sets the attribute value to "" and
122+
* marks the attribute as boolean so no value is written out. Setting to <code>false</code> removes the attribute
123+
* with the same key if it exists.
124+
*
125+
* @param attributeKey the attribute key
126+
* @param attributeValue the attribute value
127+
*
128+
* @return this element
129+
*/
130+
public Element attr(String attributeKey, boolean attributeValue) {
131+
attributes.put(attributeKey, attributeValue);
132+
return this;
133+
}
119134

120135
/**
121136
* Get this element's HTML5 custom data attributes. Each attribute in the element that has a key

src/main/java/org/jsoup/parser/Token.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.jsoup.helper.Validate;
44
import org.jsoup.nodes.Attribute;
55
import org.jsoup.nodes.Attributes;
6+
import org.jsoup.nodes.BooleanAttribute;
67

78
/**
89
* Parse tokens for the Tokeniser.
@@ -69,6 +70,7 @@ static abstract class Tag extends Token {
6970
protected String tagName;
7071
private String pendingAttributeName; // attribute names are generally caught in one hop, not accumulated
7172
private StringBuilder pendingAttributeValue = new StringBuilder(); // but values are accumulated, from e.g. & in hrefs
73+
private boolean hasEmptyAttributeValue = false; // distinguish boolean attribute from empty string value
7274
private boolean hasPendingAttributeValue = false;
7375
boolean selfClosing = false;
7476
Attributes attributes; // start tags get attributes on construction. End tags get attributes on first new attribute (but only for parser convenience, not used).
@@ -78,6 +80,7 @@ Tag reset() {
7880
tagName = null;
7981
pendingAttributeName = null;
8082
reset(pendingAttributeValue);
83+
hasEmptyAttributeValue = false;
8184
hasPendingAttributeValue = false;
8285
selfClosing = false;
8386
attributes = null;
@@ -90,13 +93,17 @@ final void newAttribute() {
9093

9194
if (pendingAttributeName != null) {
9295
Attribute attribute;
93-
if (!hasPendingAttributeValue)
96+
if (hasPendingAttributeValue)
97+
attribute = new Attribute(pendingAttributeName, pendingAttributeValue.toString());
98+
else if (hasEmptyAttributeValue)
9499
attribute = new Attribute(pendingAttributeName, "");
95100
else
96-
attribute = new Attribute(pendingAttributeName, pendingAttributeValue.toString());
101+
attribute = new BooleanAttribute(pendingAttributeName);
97102
attributes.put(attribute);
98103
}
99104
pendingAttributeName = null;
105+
hasEmptyAttributeValue = false;
106+
hasPendingAttributeValue = false;
100107
reset(pendingAttributeValue);
101108
}
102109

@@ -158,6 +165,10 @@ final void appendAttributeValue(char[] append) {
158165
ensureAttributeValue();
159166
pendingAttributeValue.append(append);
160167
}
168+
169+
final void setEmptyAttributeValue() {
170+
hasEmptyAttributeValue = true;
171+
}
161172

162173
private void ensureAttributeValue() {
163174
hasPendingAttributeValue = true;

src/main/java/org/jsoup/parser/TokeniserState.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,8 @@ void read(Tokeniser t, CharacterReader r) {
782782
String value = r.consumeToAnySorted(attributeDoubleValueCharsSorted);
783783
if (value.length() > 0)
784784
t.tagPending.appendAttributeValue(value);
785+
else
786+
t.tagPending.setEmptyAttributeValue();
785787

786788
char c = r.consume();
787789
switch (c) {
@@ -812,6 +814,8 @@ void read(Tokeniser t, CharacterReader r) {
812814
String value = r.consumeToAnySorted(attributeSingleValueCharsSorted);
813815
if (value.length() > 0)
814816
t.tagPending.appendAttributeValue(value);
817+
else
818+
t.tagPending.setEmptyAttributeValue();
815819

816820
char c = r.consume();
817821
switch (c) {

src/test/java/org/jsoup/nodes/ElementTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,22 @@ public class ElementTest {
294294
assertEquals(i, ps.get(i).siblingIndex);
295295
}
296296
}
297+
298+
@Test public void testAddBooleanAttribute() {
299+
Element div = new Element(Tag.valueOf("div"), "");
300+
301+
div.attr("true", true);
302+
303+
div.attr("false", "value");
304+
div.attr("false", false);
305+
306+
assertTrue(div.hasAttr("true"));
307+
assertEquals("", div.attr("true"));
308+
309+
assertFalse(div.hasAttr("false"));
310+
311+
assertEquals("<div true></div>", div.outerHtml());
312+
}
297313

298314
@Test public void testAppendRowToTable() {
299315
Document doc = Jsoup.parse("<table><tr><td>1</td></tr></table>");

src/test/java/org/jsoup/parser/AttributeParseTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,16 @@ public class AttributeParseTest {
6666
Elements els = Jsoup.parse(html).select("a");
6767
assertEquals("&wr_id=123&mid-size=true&ok=&wr", els.first().attr("href"));
6868
}
69+
70+
@Test public void parsesBooleanAttributes() {
71+
String html = "<a normal=\"123\" boolean empty=\"\"></a>";
72+
Element el = Jsoup.parse(html).select("a").first();
73+
74+
assertEquals("123", el.attr("normal"));
75+
assertEquals("", el.attr("boolean"));
76+
assertEquals("", el.attr("empty"));
77+
78+
assertEquals(html, el.outerHtml());
79+
}
80+
6981
}

src/test/java/org/jsoup/parser/HtmlParserTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ public class HtmlParserTest {
4343
String html = "<p =a>One<a <p>Something</p>Else";
4444
// this gets a <p> with attr '=a' and an <a tag with an attribue named '<p'; and then auto-recreated
4545
Document doc = Jsoup.parse(html);
46-
assertEquals("<p =a=\"\">One<a <p=\"\">Something</a></p>\n" +
47-
"<a <p=\"\">Else</a>", doc.body().html());
46+
assertEquals("<p =a>One<a <p>Something</a></p>\n" +
47+
"<a <p>Else</a>", doc.body().html());
4848

4949
doc = Jsoup.parse("<p .....>");
50-
assertEquals("<p .....=\"\"></p>", doc.body().html());
50+
assertEquals("<p .....></p>", doc.body().html());
5151
}
5252

5353
@Test public void parsesComments() {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy