Skip to content

Commit 9e965c9

Browse files
fix(web): parsing whitespaces in normalizer
1 parent 566900a commit 9e965c9

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

src/web/__tests__/htmlNormalizer.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,4 +497,32 @@ describe('htmlNormalizer', () => {
497497
expect(normalizeHtml(input)).toBe(expected);
498498
});
499499
});
500+
501+
describe('InterBlockWhitespace', () => {
502+
// Pretty-printed consecutive paragraphs must not gain empty <p>s from the
503+
// newlines between them (those would later serialize as extra <br>s).
504+
test.each([
505+
[
506+
'<p>Asdasd</p>\n<p>Asdasd</p>\n<p>Asdasda</p>',
507+
'<p>Asdasd</p><p>Asdasd</p><p>Asdasda</p>',
508+
],
509+
[
510+
'<p>Asdasd</p>\n\n<p>Asdasd</p>\n\n<p>Asdasda</p>',
511+
'<p>Asdasd</p><p>Asdasd</p><p>Asdasda</p>',
512+
],
513+
[
514+
'<html>\n<p>Asdasd</p>\n<p>Asdasd</p>\n<p>Asdasda</p>\n</html>',
515+
'<p>Asdasd</p><p>Asdasd</p><p>Asdasda</p>',
516+
],
517+
['<p>Asdasd</p> <p>Asdasd</p>', '<p>Asdasd</p><p>Asdasd</p>'],
518+
// Significant inline content between blocks is still wrapped in <p>.
519+
['<p>a</p> hello <p>b</p>', '<p>a</p><p> hello </p><p>b</p>'],
520+
// Spaces inside text / between inlines must be preserved.
521+
['hello world', 'hello world'],
522+
['<p>hello world</p>', '<p>hello world</p>'],
523+
['<b>hello</b> <i>world</i>', '<b>hello</b> <i>world</i>'],
524+
])('%s → %s', (input, expected) => {
525+
expect(normalizeHtml(input)).toBe(expected);
526+
});
527+
});
500528
});

src/web/normalization/htmlNormalizer.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,27 @@ function escapeText(s: string): string {
323323

324324
// --- Blockquote content flattening ---
325325

326+
function isWhitespaceOnly(value: string): boolean {
327+
for (let i = 0; i < value.length; i++) {
328+
const c = value.charCodeAt(i);
329+
// space, tab, LF, CR, FF — mirrors GumboNormalizer.c
330+
if (c !== 0x20 && c !== 0x09 && c !== 0x0a && c !== 0x0d && c !== 0x0c) {
331+
return false;
332+
}
333+
}
334+
return true;
335+
}
336+
337+
/**
338+
* Flush buffered inline content as a <p>. Inter-block whitespace (newlines /
339+
* spaces between block tags in pretty-printed HTML) is discarded so it does
340+
* not become empty paragraphs that later serialize as extra <br>s.
341+
*/
326342
function flushInlineP(ib: { buf: string }, out: { buf: string }): void {
327-
if (ib.buf.length > 0) {
343+
if (ib.buf.length > 0 && !isWhitespaceOnly(ib.buf)) {
328344
out.buf += `<p>${ib.buf}</p>`;
329-
ib.buf = '';
330345
}
346+
ib.buf = '';
331347
}
332348

333349
function flattenBqChildren(
@@ -493,9 +509,11 @@ function walkChildren(node: Element, out: { buf: string }): void {
493509
break;
494510
}
495511
if (isElement(cur) && isBrNode(cur)) {
496-
if (ib.buf.length > 0) {
512+
// Whitespace-only buffer is layout noise; treat like empty → <br>
513+
if (ib.buf.length > 0 && !isWhitespaceOnly(ib.buf)) {
497514
flushInlineP(ib, out);
498515
} else {
516+
ib.buf = '';
499517
out.buf += '<br>';
500518
}
501519
i++;

0 commit comments

Comments
 (0)