Skip to content

Commit 621c1e1

Browse files
committed
fix: normalizer stripping empty list elements
1 parent fde078c commit 621c1e1

4 files changed

Lines changed: 40 additions & 19 deletions

File tree

cpp/parser/GumboNormalizer.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ typedef struct {
546546
GumboNode **nested_lists;
547547
int *nested_count;
548548
int max_nested;
549+
bool has_emitted;
549550
} li_ctx_t;
550551

551552
static void flatten_li_node(GumboNode *node, buffer_t *ib, buffer_t *out,
@@ -562,6 +563,7 @@ static void flush_li_buffer(buffer_t *ib, buffer_t *out, li_ctx_t *ctx) {
562563
emit_styles_close(out, ctx->styles);
563564
buffer_append_str(out, "</li>");
564565
buffer_clear(ib);
566+
ctx->has_emitted = true;
565567
}
566568

567569
static void flatten_li_children(GumboNode *node, buffer_t *ib, buffer_t *out,
@@ -885,6 +887,14 @@ static void walk_node(GumboNode *node, buffer_t *out) {
885887
li_ctx_t ctx = {el, es, nested_lists, &nested_count, 16};
886888
flatten_li_children(node, &li_ib, out, &ctx);
887889
flush_li_buffer(&li_ib, out, &ctx);
890+
891+
/* if nothing emitted - the <li> is empty, we add it manually */
892+
if (!ctx.has_emitted) {
893+
buffer_append_str(out, "<li");
894+
emit_attributes(el, "li", out);
895+
buffer_append_str(out, "></li>");
896+
}
897+
888898
free(li_ib.data);
889899
for (int k = 0; k < nested_count; k++)
890900
walk_children(nested_lists[k], out);

cpp/tests/GumboParserTest.cpp

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -484,19 +484,22 @@ TEST(GumboParserTest, MSWordCheckboxList) {
484484
}
485485

486486
TEST(GumboParserTest, EmptyListItems) {
487-
EXPECT_EQ(GumboParser::normalizeHtml(
488-
"<ul><li></li><li>first</li><li></li><li>second</li><li></li>"
489-
"</ul>"),
490-
"<ul><li>first</li><li>second</li></ul>");
491-
EXPECT_EQ(GumboParser::normalizeHtml(
492-
"<ol><li></li><li>first</li><li></li><li>second</li><li></li>"
493-
"</ol>"),
494-
"<ol><li>first</li><li>second</li></ol>");
495-
EXPECT_EQ(GumboParser::normalizeHtml(
496-
"<ul data-type=\"checkbox\"><li checked></li><li>first</li><li>"
497-
"</li><li checked>second</li><li></li><li></li></ul>"),
498-
"<ul data-type=\"checkbox\"><li>first</li><li "
499-
"checked>second</li></ul>");
487+
EXPECT_EQ(GumboParser::normalizeHtml("<ul><li></li><li>first</li><li></"
488+
"li><li>second</li><li></li><li></li>"
489+
"</ul>"),
490+
"<ul><li></li><li>first</li><li></li><li>second</li><li></li><li></"
491+
"li></ul>");
492+
EXPECT_EQ(GumboParser::normalizeHtml("<ol><li></li><li>first</li><li></"
493+
"li><li>second</li><li></li><li></li>"
494+
"</ol>"),
495+
"<ol><li></li><li>first</li><li></li><li>second</li><li></li><li></"
496+
"li></ol>");
497+
EXPECT_EQ(
498+
GumboParser::normalizeHtml(
499+
"<ul data-type=\"checkbox\"><li checked></li><li>first</li><li>"
500+
"</li><li checked>second</li><li></li><li></li></ul>"),
501+
"<ul data-type=\"checkbox\"><li checked></li><li>first</li><li></li><li "
502+
"checked>second</li><li></li><li></li></ul>");
500503
}
501504

502505
TEST(GumboParserTest, BrRemappings) {

src/web/__tests__/htmlNormalizer.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -420,16 +420,16 @@ describe('htmlNormalizer', () => {
420420
describe('EmptyListItems', () => {
421421
test.each([
422422
[
423-
'<ul><li></li><li>first</li><li></li><li>second</li><li></li></ul>',
424-
'<ul><li>first</li><li>second</li></ul>',
423+
'<ul><li></li><li>first</li><li></li><li>second</li><li></li><li></li></ul>',
424+
'<ul><li></li><li>first</li><li></li><li>second</li><li></li><li></li></ul>',
425425
],
426426
[
427-
'<ol><li></li><li>first</li><li></li><li>second</li><li></li></ol>',
428-
'<ol><li>first</li><li>second</li></ol>',
427+
'<ol><li></li><li>first</li><li></li><li>second</li><li></li><li></li></ol>',
428+
'<ol><li></li><li>first</li><li></li><li>second</li><li></li><li></li></ol>',
429429
],
430430
[
431431
'<ul data-type="checkbox"><li checked></li><li>first</li><li></li><li checked>second</li><li></li><li></li></ul>',
432-
'<ul data-type="checkbox"><li>first</li><li checked>second</li></ul>',
432+
'<ul data-type="checkbox"><li checked></li><li>first</li><li></li><li checked>second</li><li></li><li></li></ul>',
433433
],
434434
])('%s → %s', (input, expected) => {
435435
expect(normalizeHtml(input)).toBe(expected);

src/web/normalization/htmlNormalizer.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ type LiCtx = {
363363
el: Element;
364364
styles: CssStyles;
365365
nestedLists: Element[];
366+
hasEmitted: boolean;
366367
};
367368

368369
function flushLiBuffer(
@@ -377,6 +378,7 @@ function flushLiBuffer(
377378
out.buf += emitStylesClose(ctx.styles);
378379
out.buf += '</li>';
379380
ib.buf = '';
381+
ctx.hasEmitted = true;
380382
}
381383

382384
function flattenLiChildren(
@@ -605,9 +607,15 @@ function walkNode(node: Node, out: { buf: string }): void {
605607
if (outName === 'li') {
606608
const nestedLists: Element[] = [];
607609
const liIb = { buf: '' };
608-
const ctx: LiCtx = { el: node, styles: es, nestedLists };
610+
const ctx: LiCtx = { el: node, styles: es, nestedLists, hasEmitted: false };
609611
flattenLiChildren(node, liIb, out, ctx);
610612
flushLiBuffer(liIb, out, ctx);
613+
614+
// if nothing emitted - the <li> is empty, we add it manually
615+
if (!ctx.hasEmitted) {
616+
out.buf += `<li${emitAttributes(ctx.el, 'li')}></li>`;
617+
}
618+
611619
for (const nl of nestedLists) walkChildren(nl, out);
612620
return;
613621
}

0 commit comments

Comments
 (0)