Skip to content

Commit 2d9fb31

Browse files
committed
improved a inside a handling
1 parent 2057637 commit 2d9fb31

13 files changed

Lines changed: 256 additions & 2 deletions

src/main/java/org/htmlunit/cyberneko/HTMLTagBalancer.java

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,19 @@ public void startElement(final QName elem, XMLAttributes attrs, final Augmentati
647647
final HTMLElements.Element element = getElement(elem);
648648
final short elementCode = element.code;
649649

650+
// HTML5 special case: a new <a> start tag closes an <a> that is
651+
// still open, even across intervening block elements (e.g.
652+
// <a href="1">1<div><a href="2">2</a></div> ). Real browsers
653+
// un-nest the <div> so it becomes a sibling of the first <a>,
654+
// with an empty clone of that <a> reopened as the div's first
655+
// child. This mirrors that behavior for the common case.
656+
if (elementCode == HTMLElements.A) {
657+
final int aPos = findLastOpen(HTMLElements.A);
658+
if (aPos != -1) {
659+
closeAndReopenThroughAnchor(aPos);
660+
}
661+
}
662+
650663
reopenFormattingElements(null);
651664

652665
if (elementCode == HTMLElements.TEMPLATE) {
@@ -923,8 +936,7 @@ else if (!fTemplateFragment && elementCode == HTMLElements.SELECT) {
923936
}
924937
}
925938
else {
926-
final boolean inline = element.isInline();
927-
fElementStack.push(new Info(element, elem, inline ? attrs : null));
939+
fElementStack.push(new Info(element, elem, attrs));
928940
if (attrs == null) {
929941
attrs = fEmptyXMLAttributes;
930942
}
@@ -1259,6 +1271,69 @@ private void closeTopElements(final QName triggeringElement, final int depth, fi
12591271
}
12601272
}
12611273

1274+
/**
1275+
* @return the stack index of the topmost currently-open element with the
1276+
* given code, or -1 if none is open.
1277+
*/
1278+
private int findLastOpen(final short elementCode) {
1279+
if (!fElementStack.isOpen(elementCode)) {
1280+
return -1;
1281+
}
1282+
for (int i = fElementStack.length - 1; i >= 0; i--) {
1283+
if (fElementStack.data[i].element.code == elementCode) {
1284+
return i;
1285+
}
1286+
}
1287+
return -1;
1288+
}
1289+
1290+
/**
1291+
* Closes every open element from the top of the stack down to and
1292+
* including the element at {@code anchorPos} (which must be an open
1293+
* {@code <a>}), then reopens the elements that were above it as empty
1294+
* elements/clones, innermost being the {@code <a>} itself, so that the
1295+
* caller can push the new {@code <a>} as a child of the reopened chain.
1296+
* This approximates the HTML5 adoption agency algorithm for the common
1297+
* "single furthest block" case; it does not implement the full
1298+
* multi-iteration algorithm.
1299+
*
1300+
* @param anchorPos stack index of the still-open {@code <a>} to un-nest.
1301+
*/
1302+
private void closeAndReopenThroughAnchor(final int anchorPos) {
1303+
final int count = fElementStack.length - anchorPos;
1304+
final Info[] popped = new Info[count];
1305+
for (int i = 0; i < count; i++) {
1306+
popped[i] = fElementStack.data[anchorPos + i];
1307+
}
1308+
1309+
if (fReportErrors) {
1310+
fErrorReporter.reportWarning("HTML2008", new Object[]{popped[0].qname.getRawname()});
1311+
}
1312+
1313+
// close everything from the top of the stack down through the <a>
1314+
for (int i = count - 1; i >= 0; i--) {
1315+
final Info info = fElementStack.pop();
1316+
if (documentHandler_ != null) {
1317+
callEndElement(info.qname, synthesizedAugs());
1318+
}
1319+
}
1320+
1321+
// reopen the ancestors that were above the <a>, outermost first
1322+
for (int i = 1; i < count; i++) {
1323+
forceStartElement(popped[i].qname, popped[i].attributes, synthesizedAugs());
1324+
}
1325+
1326+
// if there were intervening ancestors, reopen an empty clone of the
1327+
// <a> as their innermost child, then close it immediately: this is
1328+
// what browsers show as the empty "<a href=...></a>" left behind.
1329+
// With no intervening ancestors (direct <a>...<a> nesting) the new
1330+
// <a> simply becomes a sibling of the old one, so no clone is needed.
1331+
if (count > 1) {
1332+
forceStartElement(popped[0].qname, popped[0].attributes, synthesizedAugs());
1333+
endElement(popped[0].qname, synthesizedAugs());
1334+
}
1335+
}
1336+
12621337
// re-open inline elements
12631338
protected boolean reopenFormattingElements(final HTMLElements.Element element) {
12641339
if (fFormattingStack.length == 0) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<a href="1">1<div><a href="2">2</a></div>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(html
2+
(head
3+
)head
4+
(body
5+
(a
6+
Ahref 1
7+
"1
8+
)a
9+
(div
10+
(a
11+
Ahref 1
12+
)a
13+
(a
14+
Ahref 2
15+
"2
16+
)a
17+
)div
18+
)body
19+
)html
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
(html
2+
(head
3+
)head
4+
(body
5+
(a
6+
Ahref 1
7+
"1
8+
(div
9+
)div
10+
)a
11+
(div
12+
(a
13+
Ahref 1
14+
)a
15+
(a
16+
Ahref 2
17+
"2
18+
)a
19+
)div
20+
)body
21+
)html
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
(html
2+
(head
3+
)head
4+
(body
5+
(a
6+
Ahref 1
7+
"1
8+
(div
9+
)div
10+
)a
11+
(div
12+
(a
13+
Ahref 1
14+
)a
15+
(a
16+
Ahref 2
17+
"2
18+
)a
19+
)div
20+
)body
21+
)html
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
(a
2+
Ahref 1
3+
"1
4+
(div
5+
)div
6+
)a
7+
(div
8+
(a
9+
Ahref 1
10+
)a
11+
(a
12+
Ahref 2
13+
"2
14+
)a
15+
)div
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
(a
2+
Ahref 1
3+
"1
4+
(div
5+
)div
6+
)a
7+
(div
8+
(a
9+
Ahref 1
10+
)a
11+
(a
12+
Ahref 2
13+
"2
14+
)a
15+
)div
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<html><head></head><body><a href="1">1</a><div><a href="1"></a><a href="2">2</a></div></body></html>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<html><head></head><body><a href="1">1<div></div></a><div><a href="1"></a><a href="2">2</a></div></body></html>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
(html
2+
(head
3+
)head
4+
(body
5+
(a
6+
Ahref 1
7+
"1
8+
(div
9+
)div
10+
)a
11+
(div
12+
(a
13+
Ahref 1
14+
)a
15+
(a
16+
Ahref 2
17+
"2
18+
)a
19+
)div
20+
)body
21+
)html

0 commit comments

Comments
 (0)