@amermathsoc/texml-to-html 15.0.0 → 15.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +11 -4
- package/lib/elements/label.js +18 -15
- package/package.json +5 -5
- package/test/element-book-ref-list.js +1 -1
- package/test/element-disp-formula-group.js +1 -1
- package/test/element-sec-app-front-matter-part-dedication-title-label.js +16 -16
- package/test/element-sec-meta.js +1 -1
- package/test/element-statement-label-title.js +6 -6
- package/test/element-stripEmptyLabel.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [15.1.0](https://github.com/AmerMathSoc/texml-to-html/compare/v15.0.0...v15.1.0) (2023-11-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **label.js:** preserve title and label ([5dae34f](https://github.com/AmerMathSoc/texml-to-html/commit/5dae34f1dfeaf8fa884303f7de2518b35d52c38b)), closes [#327](https://github.com/AmerMathSoc/texml-to-html/issues/327)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **package:** upgrade linkedom ([34ae8a7](https://github.com/AmerMathSoc/texml-to-html/commit/34ae8a7f48c2d6f2744e22c9f07aed4159a1f896))
|
|
16
|
+
|
|
5
17
|
## [15.0.0](https://github.com/AmerMathSoc/texml-to-html/compare/v14.0.1...v15.0.0) (2023-11-02)
|
|
6
18
|
|
|
7
19
|
|
package/README.md
CHANGED
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Converting texml-generated JATS/BITS-like XML to HTML.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
### Quick example
|
|
8
|
+
|
|
9
|
+
For a first test run, try [an example](./examples), e.g.,
|
|
10
|
+
|
|
11
|
+
* Install via npm: `$ npm i @amermathsoc/texml-to-html`
|
|
12
|
+
* Process a test file: `$ node node_modules/@amermathsoc/texml-to-html/examples/cli.js node_modules/@amermathsoc/texml-to-html/text/article.xml > htmlOutput.html`
|
|
13
|
+
|
|
14
|
+
### Basic usage
|
|
6
15
|
|
|
7
16
|
```js
|
|
8
17
|
import fs from 'fs';
|
|
9
18
|
import path from 'path';
|
|
10
|
-
import xml2html from '
|
|
19
|
+
import xml2html from '@amermathsoc/texml-to-html';
|
|
11
20
|
|
|
12
21
|
const article = xml2html(fs.readFileSync(path.resolve(process.argv[2])).toString()).window.document;
|
|
13
22
|
console.log(article.toString());
|
|
14
23
|
```
|
|
15
|
-
|
|
16
|
-
See also [the examples](./examples).
|
package/lib/elements/label.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import getParentLevel from '../helpers/getParentLevel.js';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* label and title elements
|
|
20
|
+
* label and title elements. If both are present, expects label+title (not title+label)
|
|
21
21
|
* @param {HTMLElement} htmlParentNode
|
|
22
22
|
* @param {Element} xmlnode
|
|
23
23
|
*/
|
|
@@ -41,7 +41,8 @@ export default function (htmlParentNode, xmlnode) {
|
|
|
41
41
|
this.passThrough(htmlParentNode, xmlnode);
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
// CASE fig, fig-group, table-wrap, table-wrap-group via caption
|
|
44
|
+
// CASE fig, fig-group, table-wrap, table-wrap-group via caption
|
|
45
|
+
// TODO: could we just return?
|
|
45
46
|
if (
|
|
46
47
|
xmlnode.parentNode.tagName === 'table-wrap' ||
|
|
47
48
|
xmlnode.parentNode.tagName === 'table-wrap-group' ||
|
|
@@ -54,33 +55,33 @@ export default function (htmlParentNode, xmlnode) {
|
|
|
54
55
|
// CASE label followed by a title -- we skip (and pull in the label later on when processing title)
|
|
55
56
|
if (xmlnode.nextElementSibling?.tagName === 'title') return;
|
|
56
57
|
// CASE empty label
|
|
57
|
-
if (xmlnode.tagName === 'label' && xmlnode.innerHTML.trim() === '') return;
|
|
58
|
+
if (xmlnode.tagName === 'label' && xmlnode.innerHTML.trim() === '') return; // TODO: I think texml is better about this now (only found in mcl11)
|
|
58
59
|
|
|
59
60
|
// complex cases
|
|
60
61
|
|
|
61
|
-
// Decide
|
|
62
|
+
// Decide container (h* or figcaption; wrapping header for subtitles)
|
|
62
63
|
const isStatement = xmlnode.parentNode.tagName === 'statement';
|
|
63
64
|
const isDispFormulaGroup = xmlnode.parentNode.tagName === 'disp-formula-group';
|
|
64
65
|
const level = getParentLevel(htmlParentNode) + 1;
|
|
65
|
-
const
|
|
66
|
-
htmlParentNode.appendChild(
|
|
66
|
+
const container = (isStatement || isDispFormulaGroup) ? this.createNode('figcaption', '') : this.createNode(`h${level}`, '');
|
|
67
|
+
htmlParentNode.appendChild(container);
|
|
67
68
|
|
|
68
|
-
// subtitle handling (assumes
|
|
69
|
+
// subtitle handling (assumes container is not figcaption)
|
|
69
70
|
const subtitleSibling = xmlnode.parentNode.querySelector(':scope>subtitle');
|
|
70
71
|
if (subtitleSibling && subtitleSibling.innerHTML.trim() !== '') {
|
|
71
72
|
// wrap heading in header
|
|
72
73
|
const header = this.createNode('header');
|
|
73
74
|
htmlParentNode.appendChild(header);
|
|
74
|
-
header.appendChild(
|
|
75
|
+
header.appendChild(container);
|
|
75
76
|
// recurse subtitle
|
|
76
77
|
this.recurseTheDom(header, subtitleSibling);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
// Pull in label (if title+label and we're processing title)
|
|
80
81
|
const previousSibling = xmlnode.previousElementSibling;
|
|
81
|
-
if (previousSibling?.tagName === 'label' && previousSibling.innerHTML.trim() !== '') {
|
|
82
|
+
if (previousSibling?.tagName === 'label' && previousSibling.innerHTML.trim() !== '') { //TODO: empty label check unnecessary? cf. earlier TODO re empty label
|
|
82
83
|
const labelSpan = this.createNode('span', '', { 'data-ams-doc': 'label' });
|
|
83
|
-
|
|
84
|
+
container.appendChild(labelSpan);
|
|
84
85
|
this.passThrough(labelSpan, previousSibling);
|
|
85
86
|
const labelSeparatorString = isStatement ? ' ' : '. ';
|
|
86
87
|
labelSpan.insertAdjacentText('beforeend', labelSeparatorString);
|
|
@@ -90,12 +91,14 @@ export default function (htmlParentNode, xmlnode) {
|
|
|
90
91
|
const altTitle = xmlnode.parentNode.querySelector(':scope>alt-title');
|
|
91
92
|
const hasAltTitle = altTitle && altTitle.innerHTML !== xmlnode.innerHTML;
|
|
92
93
|
if (this.isBook && hasAltTitle) {
|
|
93
|
-
|
|
94
|
+
container.setAttribute('data-ams-doc-alttitle', container.textContent + altTitle.innerHTML); // NOTE assumes previousSibling handled first
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
//
|
|
97
|
-
this.
|
|
97
|
+
// add main node and recurse
|
|
98
|
+
const actualSpan = this.createNode('span', '', { 'data-ams-doc': xmlnode.tagName });
|
|
99
|
+
container.appendChild(actualSpan);
|
|
100
|
+
this.passThrough(actualSpan, xmlnode);
|
|
98
101
|
|
|
99
|
-
// faking TeX's punctutation-at-end logic
|
|
100
|
-
if (isStatement)
|
|
102
|
+
// faking TeX's punctutation-at-end logic // TODO: should we move punctuation out of the title/label?
|
|
103
|
+
if (isStatement) container.insertAdjacentText('beforeend', container.textContent.endsWith('.') ? ' ' : '. ');
|
|
101
104
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amermathsoc/texml-to-html",
|
|
3
|
-
"version": "15.
|
|
3
|
+
"version": "15.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A NodeJS library for converting AMS-style JATS XML to HTML",
|
|
6
6
|
"scripts": {
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/AmerMathSoc/texml-to-html#readme",
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"c8": "8.0.
|
|
33
|
-
"standard-version": "9.
|
|
34
|
-
"tape": "5.
|
|
32
|
+
"c8": "8.0.1",
|
|
33
|
+
"standard-version": "9.5.0",
|
|
34
|
+
"tape": "5.7.2"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"linkedom": "0.
|
|
37
|
+
"linkedom": "0.16.4"
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -25,7 +25,7 @@ tape('Template: book-back/ref-list ', async function(t) {
|
|
|
25
25
|
const bibl = document.querySelector('section#reflist[role="doc-bibliography"]');
|
|
26
26
|
t.ok(bibl, 'ref-list becomes section with id, role, doc-level');
|
|
27
27
|
const title = bibl.querySelector('h1');
|
|
28
|
-
t.equal(title.outerHTML, '<h1>title</h1>', 'ref-list title as heading');
|
|
28
|
+
t.equal(title.outerHTML, '<h1><span data-ams-doc="title">title</span></h1>', 'ref-list title as heading');
|
|
29
29
|
const list = bibl.querySelector('dl');
|
|
30
30
|
t.ok(list, 'dl created for ref items');
|
|
31
31
|
t.ok(document.querySelector('section#reflistchapter [role="doc-bibliography"] h2'), 'Chapter-level ref-list title gets h2')
|
|
@@ -23,5 +23,5 @@ tape('disp-formula-group', async function(t) {
|
|
|
23
23
|
const groupChild = document.querySelector('#equations figure[data-ams-doc="statement"]#disp-formula-group');
|
|
24
24
|
t.ok(groupChild, 'disp-formula-group as figure with data-ams-doc="statement" with id')
|
|
25
25
|
t.equal(groupChild.firstElementChild.tagName, 'FIGCAPTION', 'disp-formula-group label');
|
|
26
|
-
t.equal(groupChild.firstElementChild.innerHTML, 'Formula Group', 'disp-formula-group passes label along');
|
|
26
|
+
t.equal(groupChild.firstElementChild.innerHTML, '<span data-ams-doc="label">Formula Group</span>', 'disp-formula-group passes label along');
|
|
27
27
|
});
|
|
@@ -29,14 +29,14 @@ tape('sec, app, front-matter-part, dedication, title, label', async function(t)
|
|
|
29
29
|
t.ok(document.querySelector('#intro2[role="doc-introduction"]'), 'sec with matching title text to role doc-introduction');
|
|
30
30
|
t.ok(document.querySelector('#ded[role="doc-dedication"]'), 'dedication to role doc-dedication');
|
|
31
31
|
|
|
32
|
-
t.equal(document.querySelector('#subsec h3').innerHTML, 'Subsection', 'subsection without wrapping section gets correct level');
|
|
32
|
+
t.equal(document.querySelector('#subsec h3').innerHTML, '<span data-ams-doc="label">Subsection</span>', 'subsection without wrapping section gets correct level');
|
|
33
33
|
|
|
34
|
-
t.equal(document.querySelector('#seclabeltitle h2').innerHTML, '<span data-ams-doc="label">Label. </span>Title', 'sec with label+title: heading level and content');
|
|
35
|
-
t.equal(document.querySelector('#sectitle h2').innerHTML, 'Title', 'sec with title: heading level and content');
|
|
36
|
-
t.equal(document.querySelector('#seclabel h2').innerHTML, 'Label', 'sec with label: heading level and content');
|
|
37
|
-
t.equal(document.querySelector('#applabeltitle h2').innerHTML, '<span data-ams-doc="label">Label. </span>Title', 'app with label+title: heading level and content');
|
|
38
|
-
t.equal(document.querySelector('#apptitle h2').innerHTML, 'Title', 'app with title: heading level and content');
|
|
39
|
-
t.equal(document.querySelector('#applabel h2').innerHTML, 'Label', 'app with label: heading level and content');
|
|
34
|
+
t.equal(document.querySelector('#seclabeltitle h2').innerHTML, '<span data-ams-doc="label">Label. </span><span data-ams-doc="title">Title</span>', 'sec with label+title: heading level and content');
|
|
35
|
+
t.equal(document.querySelector('#sectitle h2').innerHTML, '<span data-ams-doc="title">Title</span>', 'sec with title: heading level and content');
|
|
36
|
+
t.equal(document.querySelector('#seclabel h2').innerHTML, '<span data-ams-doc="label">Label</span>', 'sec with label: heading level and content');
|
|
37
|
+
t.equal(document.querySelector('#applabeltitle h2').innerHTML, '<span data-ams-doc="label">Label. </span><span data-ams-doc="title">Title</span>', 'app with label+title: heading level and content');
|
|
38
|
+
t.equal(document.querySelector('#apptitle h2').innerHTML, '<span data-ams-doc="title">Title</span>', 'app with title: heading level and content');
|
|
39
|
+
t.equal(document.querySelector('#applabel h2').innerHTML, '<span data-ams-doc="label">Label</span>', 'app with label: heading level and content');
|
|
40
40
|
|
|
41
41
|
t.ok(document.querySelector('#sectitle header p[data-ams-doc="subtitle"]'), 'sec with title: subtitle to p with data-ams-doc');
|
|
42
42
|
t.ok(document.querySelector('#seclabel header p[data-ams-doc="subtitle"]'), 'sec with title: subtitle to p with data-ams-doc');
|
|
@@ -52,15 +52,15 @@ tape('sec, app, front-matter-part, dedication, title, label', async function(t)
|
|
|
52
52
|
const document2 = book;
|
|
53
53
|
t.ok(document2.querySelector('#chapter[role="doc-chapter"]'), 'sec with specific-use chapter to role doc-chapter'); // NOTE so far, chapters only occur in books but the xslt doesn't check for it
|
|
54
54
|
|
|
55
|
-
t.equal(document2.querySelector('#seclabeltitle h2').innerHTML, '<span data-ams-doc="label">Label. </span>Title', 'sec with label+title: heading level and content');
|
|
56
|
-
t.equal(document2.querySelector('#sectitle h2').innerHTML, 'Title', 'sec with title: heading level and content');
|
|
57
|
-
t.equal(document2.querySelector('#seclabel h2').innerHTML, 'Label', 'sec with label: heading level and content');
|
|
58
|
-
t.equal(document2.querySelector('#fmlabeltitle h1').innerHTML, '<span data-ams-doc="label">Label. </span>Title', 'front-matter-part with label+title: heading level and content');
|
|
59
|
-
t.equal(document2.querySelector('#fmtitle h1').innerHTML, 'Title', 'front-matter-part with title: heading level and content');
|
|
60
|
-
t.equal(document2.querySelector('#fmlabel h1').innerHTML, 'Label', 'front-matter-part with label: heading level and content');
|
|
61
|
-
t.equal(document2.querySelector('#applabeltitle h1').innerHTML, '<span data-ams-doc="label">Label. </span>Title', 'app with label+title: heading level and content');
|
|
62
|
-
t.equal(document2.querySelector('#apptitle h1').innerHTML, 'Title', 'app with title: heading level and content');
|
|
63
|
-
t.equal(document2.querySelector('#applabel h1').innerHTML, 'Label', 'app with label: heading level and content');
|
|
55
|
+
t.equal(document2.querySelector('#seclabeltitle h2').innerHTML, '<span data-ams-doc="label">Label. </span><span data-ams-doc="title">Title</span>', 'sec with label+title: heading level and content');
|
|
56
|
+
t.equal(document2.querySelector('#sectitle h2').innerHTML, '<span data-ams-doc="title">Title</span>', 'sec with title: heading level and content');
|
|
57
|
+
t.equal(document2.querySelector('#seclabel h2').innerHTML, '<span data-ams-doc="label">Label</span>', 'sec with label: heading level and content');
|
|
58
|
+
t.equal(document2.querySelector('#fmlabeltitle h1').innerHTML, '<span data-ams-doc="label">Label. </span><span data-ams-doc="title">Title</span>', 'front-matter-part with label+title: heading level and content');
|
|
59
|
+
t.equal(document2.querySelector('#fmtitle h1').innerHTML, '<span data-ams-doc="title">Title</span>', 'front-matter-part with title: heading level and content');
|
|
60
|
+
t.equal(document2.querySelector('#fmlabel h1').innerHTML, '<span data-ams-doc="label">Label</span>', 'front-matter-part with label: heading level and content');
|
|
61
|
+
t.equal(document2.querySelector('#applabeltitle h1').innerHTML, '<span data-ams-doc="label">Label. </span><span data-ams-doc="title">Title</span>', 'app with label+title: heading level and content');
|
|
62
|
+
t.equal(document2.querySelector('#apptitle h1').innerHTML, '<span data-ams-doc="title">Title</span>', 'app with title: heading level and content');
|
|
63
|
+
t.equal(document2.querySelector('#applabel h1').innerHTML, '<span data-ams-doc="label">Label</span>', 'app with label: heading level and content');
|
|
64
64
|
|
|
65
65
|
t.equal(document2.querySelector('#part').getAttribute('role'), 'doc-part', 'part role');
|
|
66
66
|
t.ok(document2.querySelector('#inparttitle h2'), 'sec with title in chapter in part: heading level reduced');
|
package/test/element-sec-meta.js
CHANGED
|
@@ -25,7 +25,7 @@ tape('Template: sec-meta => contrib-group, author-comment, abstract/title', asyn
|
|
|
25
25
|
const secMeta = section.querySelector('section[data-ams-doc="sec-meta"]');
|
|
26
26
|
t.equal(secMeta.getAttribute('data-ams-contributors'), '{"null":[]}', 'book sec-meta');
|
|
27
27
|
t.ok(section.querySelector('h1'), 'title becomes h1');
|
|
28
|
-
t.equal(section.querySelector('h1').innerHTML, 'Title', 'title content in heading');
|
|
28
|
+
t.equal(section.querySelector('h1').innerHTML, '<span data-ams-doc="title">Title</span>', 'title content in heading');
|
|
29
29
|
t.ok(section.querySelector('[role="doc-abstract"] h2'), 'abstract title becomes h2');
|
|
30
30
|
|
|
31
31
|
const document2 = article;
|
|
@@ -29,7 +29,7 @@ tape('Template: statement, label, title', async function(t) {
|
|
|
29
29
|
t.ok(statement1Heading, 'statement heading level in article');
|
|
30
30
|
t.equal(
|
|
31
31
|
statement1Heading.innerHTML,
|
|
32
|
-
'<span data-ams-doc="label">Label 1 </span>Title 1
|
|
32
|
+
'<span data-ams-doc="label">Label 1 </span><span data-ams-doc="title">Title 1</span>. ',
|
|
33
33
|
'statement with label+title creates space before and period after title'
|
|
34
34
|
);
|
|
35
35
|
const statement2 = document.querySelector(
|
|
@@ -38,7 +38,7 @@ tape('Template: statement, label, title', async function(t) {
|
|
|
38
38
|
t.ok(statement2, 'statement 2');
|
|
39
39
|
t.equal(
|
|
40
40
|
statement2.querySelector('figcaption').innerHTML,
|
|
41
|
-
'Label 2
|
|
41
|
+
'<span data-ams-doc="label">Label 2</span>. ',
|
|
42
42
|
'statement with label creates period after'
|
|
43
43
|
);
|
|
44
44
|
const statement3 = document.querySelector(
|
|
@@ -47,7 +47,7 @@ tape('Template: statement, label, title', async function(t) {
|
|
|
47
47
|
t.ok(statement3, 'statement 3');
|
|
48
48
|
t.equal(
|
|
49
49
|
statement3.querySelector('figcaption').innerHTML,
|
|
50
|
-
'Title 3
|
|
50
|
+
'<span data-ams-doc="title">Title 3</span>. ',
|
|
51
51
|
'statement with title creates period after'
|
|
52
52
|
);
|
|
53
53
|
const statementTitlePeriod = document.querySelector(
|
|
@@ -55,7 +55,7 @@ tape('Template: statement, label, title', async function(t) {
|
|
|
55
55
|
);
|
|
56
56
|
t.equal(
|
|
57
57
|
statementTitlePeriod.querySelector('figcaption').innerHTML,
|
|
58
|
-
'Title 4
|
|
58
|
+
'<span data-ams-doc="title">Title 4.</span> ',
|
|
59
59
|
'statement with title with period does not have extra period after'
|
|
60
60
|
);
|
|
61
61
|
const statement4 = document.querySelector(
|
|
@@ -64,7 +64,7 @@ tape('Template: statement, label, title', async function(t) {
|
|
|
64
64
|
t.ok(statement4, 'statement 4');
|
|
65
65
|
t.equal(
|
|
66
66
|
statement4.querySelector('figcaption').innerHTML,
|
|
67
|
-
'Proof
|
|
67
|
+
'<span data-ams-doc="title">Proof</span>. ',
|
|
68
68
|
'title in proof statement now also gets extra period'
|
|
69
69
|
);
|
|
70
70
|
const statement5 = document.querySelector(
|
|
@@ -73,7 +73,7 @@ tape('Template: statement, label, title', async function(t) {
|
|
|
73
73
|
t.ok(statement5, 'statement 5');
|
|
74
74
|
t.equal(
|
|
75
75
|
statement5.querySelector('figcaption').innerHTML,
|
|
76
|
-
'<span data-ams-doc="label">Label 5 </span>Proof
|
|
76
|
+
'<span data-ams-doc="label">Label 5 </span><span data-ams-doc="title">Proof</span>. ',
|
|
77
77
|
'proof statement with label+title'
|
|
78
78
|
);
|
|
79
79
|
t.equal(
|
|
@@ -22,5 +22,5 @@ tape('Empty Labels should be stripped', async function(t) {
|
|
|
22
22
|
t.plan(2);
|
|
23
23
|
const document = article;
|
|
24
24
|
t.equal(document.querySelector('#emptyLabel').innerHTML.trim(), '', 'Statement with no title and empty label');
|
|
25
|
-
t.equal(document.querySelector('#titleEmptyLabel').innerHTML.trim(), '<figcaption>Title
|
|
25
|
+
t.equal(document.querySelector('#titleEmptyLabel').innerHTML.trim(), '<figcaption><span data-ams-doc="title">Title</span>. </figcaption>', 'Statement with no title and empty label');
|
|
26
26
|
});
|