Skip to content

Commit 93a526c

Browse files
committed
DOMTokenList simplify impl and fix removal of duplicate entries; replace() method implemented
1 parent 22000c9 commit 93a526c

File tree

3 files changed

+224
-93
lines changed

3 files changed

+224
-93
lines changed

src/changes/changes.xml

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
<body>
1010
<release version="4.5.0" date="xxxx, 2024" description="WebWorker, Bugfixes">
1111
<action type="add" dev="rbri">
12-
DOMTokenList is now iterable, keys(), entries() method implemented.
12+
DOMTokenList simplify impl and fix removal of duplicate entries.
13+
</action>
14+
<action type="add" dev="rbri">
15+
DOMTokenList is now iterable, keys(), entries(), and replace() method implemented.
1316
</action>
1417
<action type="add" dev="Markus Winter" issue="#848">
1518
Implementation of DOMTokenList.forEach() added.

src/main/java/org/htmlunit/javascript/host/dom/DOMTokenList.java

+62-84
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
*/
1515
package org.htmlunit.javascript.host.dom;
1616

17-
import java.util.Arrays;
18-
import java.util.HashSet;
17+
import java.util.ArrayList;
18+
import java.util.List;
1919

2020
import org.apache.commons.lang3.StringUtils;
2121
import org.htmlunit.WebClient;
@@ -88,10 +88,7 @@ public int getLength() {
8888
return 0;
8989
}
9090

91-
final String[] parts = StringUtils.split(value, WHITESPACE_CHARS);
92-
final HashSet<String> elements = new HashSet<>(parts.length);
93-
elements.addAll(Arrays.asList(parts));
94-
return elements.size();
91+
return split(value).size();
9592
}
9693

9794
private String getAttribValue() {
@@ -134,24 +131,12 @@ public void add(final String token) {
134131
throw JavaScriptEngine.reportRuntimeError("Empty input not allowed");
135132
}
136133

137-
String value = getAttribValue();
138-
if (StringUtils.isEmpty(value)) {
139-
value = token;
140-
}
141-
else {
142-
value = String.join(" ", StringUtils.split(value, WHITESPACE_CHARS));
143-
if (position(value, token) < 0) {
144-
if (value.length() != 0 && !isWhitespace(value.charAt(value.length() - 1))) {
145-
value = value + " ";
146-
}
147-
value = value + token;
148-
}
149-
else {
150-
value = String.join(" ", StringUtils.split(value, WHITESPACE_CHARS));
151-
}
134+
final List<String> parts = split(getAttribValue());
135+
if (!parts.contains(token)) {
136+
parts.add(token);
152137
}
138+
updateAttribute(String.join(" ", parts));
153139

154-
updateAttribute(value);
155140
}
156141

157142
/**
@@ -167,39 +152,43 @@ public void remove(final String token) {
167152
throw JavaScriptEngine.reportRuntimeError("Empty input not allowed");
168153
}
169154

170-
final String oldValue = getAttribValue();
171-
if (oldValue == null) {
172-
return;
173-
}
174-
175-
String value = String.join(" ", StringUtils.split(oldValue, WHITESPACE_CHARS));
176-
int pos = position(value, token);
177-
while (pos != -1) {
178-
int from = pos;
179-
int to = pos + token.length();
155+
final List<String> parts = split(getAttribValue());
156+
parts.remove(token);
157+
updateAttribute(String.join(" ", parts));
158+
}
180159

181-
while (from > 0 && isWhitespace(value.charAt(from - 1))) {
182-
from = from - 1;
183-
}
184-
while (to < value.length() - 1 && isWhitespace(value.charAt(to))) {
185-
to = to + 1;
186-
}
160+
/**
161+
* Replaces an existing token with a new token. If the first token doesn't exist, replace()
162+
* returns false immediately, without adding the new token to the token list.
163+
* @param oldToken a string representing the token you want to replace
164+
* @param newToken a string representing the token you want to replace oldToken with
165+
* @return true if oldToken was successfully replaced, or false if not
166+
*/
167+
@JsxFunction
168+
public boolean replace(final String oldToken, final String newToken) {
169+
if (StringUtils.isEmpty(oldToken)) {
170+
throw JavaScriptEngine.reportRuntimeError("Empty oldToken not allowed");
171+
}
172+
if (StringUtils.containsAny(oldToken, WHITESPACE_CHARS)) {
173+
throw JavaScriptEngine.reportRuntimeError("oldToken contains whitespace");
174+
}
187175

188-
final StringBuilder result = new StringBuilder();
189-
if (from > 0) {
190-
result.append(value, 0, from);
191-
if (to < value.length()) {
192-
result.append(' ');
193-
}
194-
}
195-
result.append(value, to, value.length());
196-
value = result.toString();
176+
if (StringUtils.isEmpty(newToken)) {
177+
throw JavaScriptEngine.reportRuntimeError("Empty newToken not allowed");
178+
}
179+
if (StringUtils.containsAny(newToken, WHITESPACE_CHARS)) {
180+
throw JavaScriptEngine.reportRuntimeError("newToken contains whitespace");
181+
}
197182

198-
pos = position(value, token);
183+
final List<String> parts = split(getAttribValue());
184+
final int pos = parts.indexOf(oldToken);
185+
while (pos == -1) {
186+
return false;
199187
}
200188

201-
value = String.join(" ", StringUtils.split(value, WHITESPACE_CHARS));
202-
updateAttribute(value);
189+
parts.set(pos, newToken);
190+
updateAttribute(String.join(" ", parts));
191+
return true;
203192
}
204193

205194
/**
@@ -235,13 +224,8 @@ public boolean contains(final String token) {
235224
throw JavaScriptEngine.reportRuntimeError("Empty input not allowed");
236225
}
237226

238-
String value = getAttribValue();
239-
if (StringUtils.isEmpty(value)) {
240-
return false;
241-
}
242-
243-
value = String.join(" ", StringUtils.split(value, WHITESPACE_CHARS));
244-
return position(value, token) > -1;
227+
final List<String> parts = split(getAttribValue());
228+
return parts.contains(token);
245229
}
246230

247231
/**
@@ -260,9 +244,9 @@ public Object item(final int index) {
260244
return null;
261245
}
262246

263-
final String[] values = StringUtils.split(value, WHITESPACE_CHARS);
264-
if (index < values.length) {
265-
return values[index];
247+
final List<String> parts = split(getAttribValue());
248+
if (index < parts.size()) {
249+
return parts.get(index);
266250
}
267251

268252
return null;
@@ -289,12 +273,12 @@ public Object[] getIds() {
289273
return normalIds;
290274
}
291275

292-
final String[] values = StringUtils.split(value, WHITESPACE_CHARS);
293-
final Object[] ids = new Object[values.length + normalIds.length];
294-
for (int i = 0; i < values.length; i++) {
276+
final List<String> parts = split(getAttribValue());
277+
final Object[] ids = new Object[parts.size() + normalIds.length];
278+
for (int i = 0; i < parts.size(); i++) {
295279
ids[i] = i;
296280
}
297-
System.arraycopy(normalIds, 0, ids, values.length, normalIds.length);
281+
System.arraycopy(normalIds, 0, ids, parts.size(), normalIds.length);
298282

299283
return ids;
300284
}
@@ -340,9 +324,9 @@ public void forEach(final Object callback) {
340324
final ContextAction<Object> contextAction = cx -> {
341325
final Function function = (Function) callback;
342326
final Scriptable scope = getParentScope();
343-
final String[] values = StringUtils.split(value, WHITESPACE_CHARS);
344-
for (int i = 0; i < values.length; i++) {
345-
function.call(cx, scope, this, new Object[] {values[i], i, this});
327+
final List<String> parts = split(getAttribValue());
328+
for (int i = 0; i < parts.size(); i++) {
329+
function.call(cx, scope, this, new Object[] {parts.get(i), i, this});
346330
}
347331
return null;
348332
};
@@ -371,26 +355,20 @@ private void updateAttribute(final String value) {
371355
domNode.setAttributeNode(attr);
372356
}
373357

374-
private static int position(final String value, final String token) {
375-
final int pos = value.indexOf(token);
376-
if (pos < 0) {
377-
return -1;
358+
private static List<String> split(final String value) {
359+
if (StringUtils.isEmpty(value)) {
360+
return new ArrayList<>();
378361
}
379362

380-
// whitespace before
381-
if (pos != 0 && !isWhitespace(value.charAt(pos - 1))) {
382-
return -1;
383-
}
363+
final String[] parts = StringUtils.split(value, WHITESPACE_CHARS);
384364

385-
// whitespace after
386-
final int end = pos + token.length();
387-
if (end != value.length() && !isWhitespace(value.charAt(end))) {
388-
return -1;
365+
// usually a short list, no index needed
366+
final List<String> result = new ArrayList<>();
367+
for (final String part : parts) {
368+
if (!result.contains(part)) {
369+
result.add(part);
370+
}
389371
}
390-
return pos;
391-
}
392-
393-
private static boolean isWhitespace(final int ch) {
394-
return WHITESPACE_CHARS.indexOf(ch) > -1;
372+
return result;
395373
}
396374
}

0 commit comments

Comments
 (0)