mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-02 21:22:51 +00:00
fix(jobdanmark-search): drop a malformed closing tag the parser left as text (#526)
A live page wrote its Jobtype item as "Fuldtid </ li >". node-html-parser consumes well-formed markup but does not recognise a closing tag with a space after the "<" or the slash, so the fragment became a text node and overviewValue kept it: employmentType: ["Fuldtid </ li >"], matching none of the documented job-type vocabulary. The same cleanText feeds every text field on both branches. Strip only the closing-tag shape in cleanText before collapsing whitespace. The parser decodes entities before cleanText runs, so author text the page encoded as <div> or <T> arrives as a literal <div> / <T> and must stay; a probe of what the parser leaks shows only malformed closing shapes (</ li >, </ li>, < /li>). Two fixtures: the stray tag in both the Jobtype item and a description paragraph (fails on master), and an entity-encoded <div> that survives. Observed by the maintainer in the #516 merge note.
This commit is contained in:
1 parent
e475d355ae
commit
a644df0625
3 files changed
+68
-1
No files matched your search
@@ -52,8 +52,18 @@ interface DetailResult {
|
||||
applyUrl: string | null
|
||||
}
|
||||
|
||||
// A malformed closing tag that reached the text layer. node-html-parser
|
||||
// consumes well-formed markup (`<strong>`, `<br/>`, `</li >`) but does not
|
||||
// recognise a closing tag with a space after the `<` or the slash, so `</ li >`
|
||||
// survives as text; a live jobdanmark page emitted
|
||||
// `employmentType: ["Fuldtid </ li >"]` that way. Only the closing shape is
|
||||
// stripped: the parser decodes entities before this runs, so author text the
|
||||
// page encoded as `<div>` or `<T>` arrives here as a literal
|
||||
// `<div>` / `<T>` and must stay.
|
||||
const STRAY_CLOSING_TAG = /<\s*\/\s*[a-z][a-z0-9-]*\s*>/gi
|
||||
|
||||
function cleanText(text: string): string {
|
||||
return text.replace(/\s+/g, " ").trim()
|
||||
return text.replace(STRAY_CLOSING_TAG, " ").replace(/\s+/g, " ").trim()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,6 +65,50 @@ describe("parseJobPostingFromHtml", () => {
|
||||
expect(parsed.validThrough).toBeNull();
|
||||
});
|
||||
|
||||
test("drops a malformed closing tag that the parser left as text (#525)", () => {
|
||||
// A live page (konsulent-til-renoveringsprogram) wrote its Jobtype item as
|
||||
// `Fuldtid </ li >`; node-html-parser does not recognise `</ li >` as a tag,
|
||||
// so it reached employmentType verbatim. The same cleanText feeds the title,
|
||||
// company, workplace and description on this branch.
|
||||
const html = HTML_WITHOUT_JSON_LD.replace(
|
||||
"<li><strong>Jobtype:</strong> Fuldtid</li>",
|
||||
"<li><strong>Jobtype:</strong> Fuldtid </ li ></li>",
|
||||
).replace(
|
||||
"<p>Hvad nu hvis du med få klik kunne undersøge dit lokalområde?</p>",
|
||||
"<p>Hvad nu hvis du med få klik kunne undersøge dit lokalområde?</ p ></p>",
|
||||
);
|
||||
|
||||
const parsed = parseJobPostingFromHtml(
|
||||
html,
|
||||
"journalistisk-udvikler",
|
||||
"https://jobdanmark.dk/job/journalistisk-udvikler",
|
||||
);
|
||||
|
||||
expect(parsed.employmentType).toEqual(["Fuldtid"]);
|
||||
expect(parsed.description.split("\n")[0]).toBe("Hvad nu hvis du med få klik kunne undersøge dit lokalområde?");
|
||||
expect(JSON.stringify(parsed)).not.toMatch(/<\s*\/?\s*[a-z]+\s*>/i);
|
||||
});
|
||||
|
||||
test("keeps author text the page entity-encoded as a literal tag (#525)", () => {
|
||||
// node-html-parser decodes entities before cleanText runs, so `<div>`
|
||||
// in a description arrives as a literal `<div>`. Only the malformed
|
||||
// closing-tag shape is stripped; an opening tag or a type parameter stays.
|
||||
const html = HTML_WITHOUT_JSON_LD.replace(
|
||||
"<p>Hvad nu hvis du med få klik kunne undersøge dit lokalområde?</p>",
|
||||
"<p>Erfaring med <div> og <span> i HTML, og <T> generics; x < y > z.</p>",
|
||||
);
|
||||
|
||||
const parsed = parseJobPostingFromHtml(
|
||||
html,
|
||||
"journalistisk-udvikler",
|
||||
"https://jobdanmark.dk/job/journalistisk-udvikler",
|
||||
);
|
||||
|
||||
expect(parsed.description.split("\n")[0]).toBe(
|
||||
"Erfaring med <div> og <span> i HTML, og <T> generics; x < y > z.",
|
||||
);
|
||||
});
|
||||
|
||||
test("does not reject titles containing '404' mid-phrase", () => {
|
||||
const htmlWith404InTitle = HTML_WITHOUT_JSON_LD.replace(
|
||||
"<title>Journalistisk udvikler søges | jobdanmark</title>",
|
||||
|
||||
@@ -29,6 +29,19 @@ per-file diff commands.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`jobdanmark-search detail` no longer keeps a malformed closing tag as text on the
|
||||
rendered-HTML branch** (#525, `detail.ts`, `tests/detail-parsing.test.ts`) - a live page wrote
|
||||
its Jobtype item as `Fuldtid </ li >`, and because `node-html-parser` does not recognise a
|
||||
closing tag with a space after the slash, the fragment reached the output verbatim:
|
||||
`employmentType: ["Fuldtid </ li >"]`, a value that matches none of the documented job-type
|
||||
vocabulary. The same `cleanText` feeds every text field on both branches, so any of them
|
||||
could carry it. `cleanText` now drops the closing-tag shape (`</ li >`, `</ li>`, `< /li>`)
|
||||
before collapsing whitespace - and only that shape, because the parser decodes entities
|
||||
before `cleanText` runs, so author text the page encoded as `<div>` or `<T>`
|
||||
arrives as a literal `<div>` / `<T>` and must survive. Two fixtures: the stray tag in both
|
||||
the Jobtype item and a description paragraph (fails on master), and an entity-encoded
|
||||
`<div>` in a description that stays in the text.
|
||||
|
||||
- **`/add-portal` includes the `.agents` prefix in generated `allowed-tools`** (#545,
|
||||
`.claude/commands/add-portal.md`, `tests/test_add_portal_command.py`) - the specification template
|
||||
for the new portal's `SKILL.md` frontmatter wrote `allowed-tools: Bash(bun run skills/<name>/cli/src/cli.ts *)`
|
||||
|
||||
Reference in new issue
Block a user