Parsing splits one text run into several Text nodes at each character reference (`<p>a &amp; b</p>` gives 3 child nodes)

Ouverte Adaptée aux débutants
#353 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 5 jours

@johnhldiver-beep y travaille déjà.

Depuis le 30/10/2026.

  • #361 par @johnhldiver-beep — ouverte

Évaluation

Difficulté
2/5
Temps estimé
1-3 heures
Accessibilité débutants
68/100
Type d'issue
Bug
Clarté
Plutôt claire
Activité
Active
Stack technique
html, javascript
Domaine
backend, web-dev

Piste de recherche

Commence par le gestionnaire ontext dans esm/shared/parse-from-string.js, qui, selon l’issue, ajoute un nœud Text pour chaque callback du parseur. Compare son comportement avec celui de DomHandler de htmlparser2, puis ajoute un test de régression pour du balisage contenant des références de caractères. Le travail est terminé lorsque l’analyse de l’exemple produit un nœud Text avec les données "a & b" et que la couverture existante reste à 100 %.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

Parsing <p>a &amp; b</p> gives the <p> three Text nodes ("a ", "&", " b"). The HTML parsing algorithm appends each character to the previous Text node, and jsdom gives one Text node, "a & b". Code that walks childNodes and treats each Text node as one run sees extra runs wherever the source has an entity. normalize() merges them afterwards, but every consumer of parsed output has to know to call it on every subtree, and a spec parser never produces the split in the first place ("insert a character": https://html.spec.whatwg.org/multipage/parsing.html#insert-a-character).

Asks:

  1. Should the parser merge text into the previous Text node when the previous sibling under the same parent is a Text node, as the HTML parsing algorithm does?
  2. If yes, I can open a PR that changes esm/ only, with a test, keeping coverage at 100%.

Environment: linkedom 0.18.14 (npm, latest) and main at e2ee208, htmlparser2 10.1.0, Node 24.14.1, macOS.

Test case:

import { DOMParser } from 'linkedom'

const doc = new DOMParser().parseFromString('<!doctype html><html><body><p>a &amp; b</p></body></html>', 'text/html')
const p = doc.querySelector('p')
console.log('textContent:', JSON.stringify(p.textContent))
console.log('childNodes.length:', p.childNodes.length)
console.log('childNodes data:', JSON.stringify([...p.childNodes].map((n) => n.data)))

Output:

textContent: "a & b"
childNodes.length: 3
childNodes data: ["a ","&"," b"]

Expected: childNodes.length: 1, data ["a & b"] (what jsdom 29.1.1 returns for the same markup).

Input linkedom Text nodes jsdom Text nodes
<p>a and b</p> 1 1
<p>a &amp; b</p> 3 1
<p>x &lt; y &gt; z &#169;</p> 6 1

parseHTML gives the same result as DOMParser (3 nodes for <p>a &amp; b</p>).

Raw output (npm 0.18.14, main e2ee208, variants, htmlparser2)
$ node repro.mjs   # linkedom 0.18.14 from npm
textContent: "a & b"
childNodes.length: 3
childNodes data: ["a ","&"," b"]
$ node repro-main.mjs   # main e2ee208 esm/
textContent: "a & b"
childNodes.length: 3
childNodes data: ["a ","&"," b"]
$ node variants.mjs
no entity:       1 ["a and b"]
&amp;:           3 ["a ","&"," b"]
&lt; &gt; &#169;: 6 ["x ","<"," y ",">"," z ","©"]
parseHTML:       3
$ node htmlparser2-ontext.mjs   # htmlparser2 10.1.0, decodeEntities: true
htmlparser2 ontext calls: ["a ","&"," b"]
$ node jsdom-control.mjs
jsdom childNodes.length: 1 ["a & b"]

Where it comes from: htmlparser2 with decodeEntities: true calls ontext once per piece around each character reference. The ontext handler in parseFromString appends a new Text node for every call:

https://github.com/WebReflection/linkedom/blob/e2ee20813e4b3ae946d7ba41621ddf3698431989/esm/shared/parse-from-string.js#L92-L98

htmlparser2's own DomHandler merges each ontext piece into the previous Text node (domhandler 5.0.3, lib/index.js, ontext), so with the same callbacks it yields one node for <p>a &amp; b</p>.

No existing issue found for this; the closest are #62 and #71, which are about serializing entities, not the node split.

Langage dominant
HTML
Étoiles
2.1k
Forks
117
Merge moyen
4 j 13 h
PR mergées (30 j)
2

Préparer son environnement

Ce projet ne fournit ni conteneur de développement, ni Dockerfile, ni guide de contribution : l'installation est à votre charge. Commencez par son README, et consultez notre guide de la première contribution pour les étapes générales.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de WebReflection/linkedom

Toutes les issues de WebReflection/linkedom

Issues similaires

Plus d'issues Backend & API Design

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.