Skip to content

fix(mermaid): resolve C4Context white-page caused by missing xmlns:xlink namespace declaration#140

Merged
mgks merged 1 commit into
docmd-io:mainfrom
sinsombat:main
May 30, 2026
Merged

fix(mermaid): resolve C4Context white-page caused by missing xmlns:xlink namespace declaration#140
mgks merged 1 commit into
docmd-io:mainfrom
sinsombat:main

Conversation

@sinsombat
Copy link
Copy Markdown
Contributor

Problem

C4Context diagrams render as a blank white box instead of showing the diagram.

Root cause

Commit 45b056a (v0.8.3) changed SVG insertion from wrapper.innerHTML = svg to DOMParser.parseFromString(svg, 'image/svg+xml') to avoid an innerHTML XSS warning.

DOMParser with 'image/svg+xml' is a strict XML parser. It fails with a <parsererror> root when it encounters an undeclared namespace prefix.

mermaid.render() serialises its output via innerHTML which omits XML namespace declarations — specifically xmlns:xlink — even when xlink:href attributes are present.

C4Context always adds person icons via <image xlink:href="data:..."/> (every Person() / ExternalPerson() shape calls drawImage2() unconditionally):

mermaid.render()       →  SVG with xlink:href but no xmlns:xlink
DOMParser (XML strict) →  <parsererror>  (not <svg>)
querySelector('svg')   →  null
initView()             →  early return, no dimensions set
diagram area           →  empty white box

Why only C4Context?

Other diagram types use xlink:href only for optional features (clickable links, explicit image nodes). C4Context requires person icons unconditionally, so every diagram hits the issue.

mermaid.run() (used by the live editor) does include xmlns:xlink because its code path passes XMLNS_XLINK_STD to appendDivSvgG. Only mermaid.render() (used by init-mermaid.js) omits it — a mermaid v11 inconsistency between the two APIs.

Fix

Extract fixSvgNamespaces(svg) — a pure string utility that injects the missing xmlns:xlink declaration into the SVG string before it reaches DOMParser:

export function fixSvgNamespaces(svg: string): string {
  if (svg.includes('xlink:') && !svg.includes('xmlns:xlink')) {
    return svg.replace(/(<svg\b)/, '$1 xmlns:xlink="http://www.w3.org/1999/xlink"');
  }
  return svg;
}

The DOMParser approach introduced in v0.8.3 is kept intact — only the input string is patched. No XSS regression.

The function is idempotent: if xmlns:xlink is already present, or if no xlink: prefix is used, the string is returned unchanged.

Tests

Adds vitest + happy-dom test suite for the new utility (src/svg-utils.test.ts, 10 tests):

Group Coverage
C4Context (root cause) adds xmlns:xlink when missing; placed on <svg> tag; existing content preserved
Idempotency no modification when declaration already present; no duplication on double-call
Non-C4 diagrams no-op for SVG with no xlink: usage; fix applied for flowchart-with-links
DOM integration DOMParser returns <svg> root after fix; querySelector('svg') is non-null
Test Files  1 passed (1)
      Tests  10 passed (10)

Files changed

File Change
src/svg-utils.ts new — fixSvgNamespaces utility
src/svg-utils.test.ts new — 10 unit tests
src/client.ts apply fixSvgNamespaces before DOMParser
vitest.config.ts new — test runner config (vitest + happy-dom)
package.json add vitest + happy-dom dev deps; add test script

…ink before DOMParser

mermaid.render() serialises SVG via innerHTML which omits the
xmlns:xlink namespace declaration even when xlink:href attributes
are present (e.g. person-icon <image> elements in C4Context).
DOMParser.parseFromString(svg, 'image/svg+xml') is a strict XML
parser; an undeclared 'xlink:' prefix causes it to return a
<parsererror> root instead of <svg>.  The subsequent
wrapper.querySelector('svg') then returns null, initView() exits
early, and the diagram area renders as an empty white box.

Root cause is specific to C4Context because every Person() /
ExternalPerson() shape unconditionally calls drawImage2() which
sets xlink:href.  Other diagram types only use xlink:href for
optional features (clickable links, image nodes) so most users
never hit it.

Fix: extract fixSvgNamespaces() utility that injects the
xmlns:xlink declaration into the SVG string before it reaches
DOMParser.  The function is a no-op when the declaration is
already present (idempotent) and does not touch SVGs that have
no xlink: usage — so non-C4 diagrams are unaffected.

The DOMParser approach (introduced in v0.8.3 to avoid innerHTML
XSS warnings) is kept intact; only the input string is patched.

Adds vitest + happy-dom test suite for the new utility (10 tests):
- xmlns:xlink is added when missing
- existing declaration is not duplicated
- no-op for diagrams without xlink: usage
- DOM integration: querySelector('svg') is non-null after fix
@mgks mgks merged commit 010eccd into docmd-io:main May 30, 2026
1 check passed
@mgks
Copy link
Copy Markdown
Member

mgks commented May 30, 2026

Merged into dev-0.8.5

Review Summary:

  • All 10 tests passing
  • TypeScript compilation successful
  • No security vulnerabilities
  • Clean, minimal implementation
  • Idempotent function with proper fallbacks

The fix correctly handles the missing xmlns:xlink namespace declaration issue. Thanks for the comprehensive test suite and clear root cause analysis!

This will be included in the v0.8.5 release.

@mgks mgks mentioned this pull request May 31, 2026
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants