Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion src/elements/math.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ export interface MathData {
isBlock: boolean;
}

const DATA_FORMULA_BLOCK_TAGS = new Set([
'address', 'article', 'aside', 'blockquote', 'div', 'figure',
'footer', 'header', 'main', 'p', 'section'
]);

/**
* WeChat/mdnice renders the same formula in nested wrappers. Only the
* outermost data-formula element represents a formula in document flow;
* processing descendants as well would emit duplicate Markdown equations.
*/
export const isNestedDataFormula = (el: Element): boolean =>
el.hasAttribute('data-formula') && Boolean(el.parentElement?.closest('[data-formula]'));

// --- MathJax CHTML (mjx-*) → MathML reconstruction -------------------------
//
// When MathJax v3 renders with the CHTML output and the page ships no
Expand Down Expand Up @@ -304,6 +317,10 @@ export const getBasicLatexFromElement = (el: Element): string | null => {
if (dataMath) {
return dataMath;
}
const dataFormula = el.getAttribute('data-formula');
if (dataFormula) {
return dataFormula;
}
const parentDataEntry = el.parentElement?.classList.contains('hurmet-tex')
? el.parentElement.getAttribute('data-entry')
: null;
Expand Down Expand Up @@ -375,6 +392,22 @@ export const isBlockDisplay = (el: Element): boolean => {
return true;
}

// WeChat/mdnice uses semantic block wrappers (commonly section/div) for
// display equations and span wrappers for inline equations. Explicit
// display hints take precedence over the wrapper tag.
if (el.hasAttribute('data-formula')) {
const dataDisplayAttr = el.getAttribute('data-display');
if (displayAttr === 'inline' || displayAttr === 'false' || dataDisplayAttr === 'inline' || dataDisplayAttr === 'false') {
return false;
}
if (displayAttr === 'true' || dataDisplayAttr === 'block' || dataDisplayAttr === 'true') {
return true;
}
if (DATA_FORMULA_BLOCK_TAGS.has(el.tagName.toLowerCase())) {
return true;
}
}

// Check common class names
const classNames = getClassName(el).toLowerCase();
if (classNames.includes('display') || classNames.includes('block')) {
Expand Down Expand Up @@ -426,7 +459,7 @@ export const isBlockDisplay = (el: Element): boolean => {

// Cheap presence check before running the full mathSelectors scan.
// Must remain a subset of mathSelectors — every selector here should also appear there.
export const mathFastCheck = 'math, mjx-container, .MathJax, .katex, img.latex, [data-math], [data-latex], script[type^="math/"]';
export const mathFastCheck = 'math, mjx-container, .MathJax, .katex, img.latex, [data-math], [data-latex], [data-formula], script[type^="math/"]';

// Shared selector for math elements
export const mathSelectors = [
Expand Down Expand Up @@ -462,6 +495,7 @@ export const mathSelectors = [
'math',
'[data-math]',
'[data-latex]',
'[data-formula]',
'[data-tex]',
'script[type^="math/"]',
'annotation[encoding="application/x-tex"]'
Expand Down
4 changes: 3 additions & 1 deletion src/elements/math.core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getMathMLFromElement,
getBasicLatexFromElement as getLatexFromElement,
isBlockDisplay,
isNestedDataFormula,
mathSelectors,
mathFastCheck
} from './math.base';
Expand Down Expand Up @@ -43,6 +44,7 @@ export const mathRules = [
fastCheck: mathFastCheck,
transform: (el: Element, doc: Document): Element => {
if (!hasHTMLElementProps(el)) return el;
if (isNestedDataFormula(el)) return el;

const mathData = getMathMLFromElement(el);
const latex = getLatexFromElement(el);
Expand All @@ -62,4 +64,4 @@ export const mathRules = [
return cleanMathEl;
}
}
];
];
4 changes: 3 additions & 1 deletion src/elements/math.full.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getMathMLFromElement,
getBasicLatexFromElement,
isBlockDisplay,
isNestedDataFormula,
mathSelectors,
mathFastCheck
} from './math.base';
Expand Down Expand Up @@ -79,6 +80,7 @@ export const mathRules = [
if (!('classList' in el) || !('getAttribute' in el) || !('querySelector' in el)) {
return el;
}
if (isNestedDataFormula(el)) return el;

const mathData = getMathMLFromElement(el);
const latex = getLatexFromElement(el);
Expand All @@ -98,4 +100,4 @@ export const mathRules = [
return cleanMathEl;
}
}
];
];
106 changes: 106 additions & 0 deletions tests/expected/math--wechat-data-formula.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
```json
{
"title": "WeChat data-formula",
"author": "",
"site": "",
"published": ""
}
```

Inline formula $x_1$ should appear once.

Equation 1.

$$
E_1 = x_1 \tag{1}
$$

Equation 2.

$$
E_2 = x_2 \tag{2}
$$

Equation 3.

$$
E_3 = x_3 \tag{3}
$$

Equation 4.

$$
E_4 = x_4 \tag{4}
$$

Equation 5.

$$
E_5 = x_5 \tag{5}
$$

Equation 6.

$$
E_6 = x_6 \tag{6}
$$

Equation 7.

$$
E_7 = x_7 \tag{7}
$$

Equation 8.

$$
E_8 = x_8 \tag{8}
$$

Equation 9.

$$
E_9 = x_9 \tag{9}
$$

Equation 10.

$$
E_{10} = x_{10} \tag{10}
$$

Equation 11.

$$
E_{11} = x_{11} \tag{11}
$$

Equation 12.

$$
E_{12} = x_{12} \tag{12}
$$

Equation 13.

$$
E_{13} = x_{13} \tag{13}
$$

Equation 14.

$$
E_{14} = x_{14} \tag{14}
$$

Equation 15.

$$
E_{15} = x_{15} \tag{15}
$$

Equation 16.

$$
E_{16} = x_{16} \tag{16}
$$
84 changes: 84 additions & 0 deletions tests/fixtures/math--wechat-data-formula.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!-- {"url": "https://mp.weixin.qq.com/s/example"} -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>WeChat data-formula</title>
</head>
<body>
<article>
<h1>WeChat data-formula</h1>
<p>
Inline formula
<span data-formula="x_1">
<span data-formula="x_1"><svg aria-hidden="true"></svg></span>
</span>
should appear once.
</p>
<div>
<p>Equation 1.</p>
<section data-formula="E_1 = x_1 \tag{1}"><span data-formula="E_1 = x_1 \tag{1}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 2.</p>
<section data-formula="E_2 = x_2 \tag{2}"><span data-formula="E_2 = x_2 \tag{2}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 3.</p>
<section data-formula="E_3 = x_3 \tag{3}"><span data-formula="E_3 = x_3 \tag{3}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 4.</p>
<section data-formula="E_4 = x_4 \tag{4}"><span data-formula="E_4 = x_4 \tag{4}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 5.</p>
<section data-formula="E_5 = x_5 \tag{5}"><span data-formula="E_5 = x_5 \tag{5}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 6.</p>
<section data-formula="E_6 = x_6 \tag{6}"><span data-formula="E_6 = x_6 \tag{6}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 7.</p>
<section data-formula="E_7 = x_7 \tag{7}"><span data-formula="E_7 = x_7 \tag{7}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 8.</p>
<section data-formula="E_8 = x_8 \tag{8}"><span data-formula="E_8 = x_8 \tag{8}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 9.</p>
<section data-formula="E_9 = x_9 \tag{9}"><span data-formula="E_9 = x_9 \tag{9}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 10.</p>
<section data-formula="E_{10} = x_{10} \tag{10}"><span data-formula="E_{10} = x_{10} \tag{10}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 11.</p>
<section data-formula="E_{11} = x_{11} \tag{11}"><span data-formula="E_{11} = x_{11} \tag{11}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 12.</p>
<section data-formula="E_{12} = x_{12} \tag{12}"><span data-formula="E_{12} = x_{12} \tag{12}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 13.</p>
<section data-formula="E_{13} = x_{13} \tag{13}"><span data-formula="E_{13} = x_{13} \tag{13}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 14.</p>
<section data-formula="E_{14} = x_{14} \tag{14}"><span data-formula="E_{14} = x_{14} \tag{14}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 15.</p>
<section data-formula="E_{15} = x_{15} \tag{15}"><span data-formula="E_{15} = x_{15} \tag{15}"><svg aria-hidden="true"></svg></span></section>
</div>
<div>
<p>Equation 16.</p>
<section data-formula="E_{16} = x_{16} \tag{16}"><span data-formula="E_{16} = x_{16} \tag{16}"><svg aria-hidden="true"></svg></span></section>
</div>
</article>
</body>
</html>