@manuscripts/transform 4.3.53 → 4.4.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.
Files changed (172) hide show
  1. package/dist/cjs/jats/__tests__/jats-roundtrip.test.js +3 -3
  2. package/dist/cjs/version.js +1 -1
  3. package/dist/es/jats/__tests__/jats-roundtrip.test.js +3 -3
  4. package/dist/es/version.js +1 -1
  5. package/dist/types/jats/__tests__/utils.d.ts +1 -1
  6. package/dist/types/version.d.ts +1 -1
  7. package/package.json +22 -21
  8. package/src/errors.ts +28 -0
  9. package/src/getVersion.ts +21 -0
  10. package/src/index.ts +28 -0
  11. package/src/jats/__tests__/__fixtures__/debug-example.xml +136 -0
  12. package/src/jats/__tests__/__fixtures__/jats-abstract-no-body.xml +163 -0
  13. package/src/jats/__tests__/__fixtures__/jats-citations.xml +472 -0
  14. package/src/jats/__tests__/__fixtures__/jats-comments.xml +46 -0
  15. package/src/jats/__tests__/__fixtures__/jats-document.xml +134 -0
  16. package/src/jats/__tests__/__fixtures__/jats-example-doc.xml +1235 -0
  17. package/src/jats/__tests__/__fixtures__/jats-example-front-only.xml +26 -0
  18. package/src/jats/__tests__/__fixtures__/jats-example-full.xml +209 -0
  19. package/src/jats/__tests__/__fixtures__/jats-example-no-back.xml +156 -0
  20. package/src/jats/__tests__/__fixtures__/jats-example-no-body.xml +91 -0
  21. package/src/jats/__tests__/__fixtures__/jats-example.xml +221 -0
  22. package/src/jats/__tests__/__fixtures__/jats-fn-group.xml +60 -0
  23. package/src/jats/__tests__/__fixtures__/jats-import-no-refs.xml +1077 -0
  24. package/src/jats/__tests__/__fixtures__/jats-import.xml +1561 -0
  25. package/src/jats/__tests__/__fixtures__/jats-roundtrip.xml +133 -0
  26. package/src/jats/__tests__/__fixtures__/jats-tables-example.xml +2043 -0
  27. package/src/jats/__tests__/__fixtures__/math-fragment.xml +12 -0
  28. package/src/jats/__tests__/__snapshots__/citeproc.test.ts.snap +57 -0
  29. package/src/jats/__tests__/__snapshots__/create-article-node.test.ts.snap +65 -0
  30. package/src/jats/__tests__/__snapshots__/jats-exporter.test.ts.snap +7 -0
  31. package/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap +59605 -0
  32. package/src/jats/__tests__/__snapshots__/jats-roundtrip.test.ts.snap +18 -0
  33. package/src/jats/__tests__/citations.ts +25 -0
  34. package/src/jats/__tests__/citeproc.test.ts +195 -0
  35. package/src/jats/__tests__/create-article-node.test.ts +29 -0
  36. package/src/jats/__tests__/data/section-categories.ts +212 -0
  37. package/src/jats/__tests__/files.ts +24 -0
  38. package/src/jats/__tests__/jats-exporter.test.ts +223 -0
  39. package/src/jats/__tests__/jats-importer.test.ts +567 -0
  40. package/src/jats/__tests__/jats-roundtrip.test.ts +66 -0
  41. package/src/jats/__tests__/utils.ts +90 -0
  42. package/src/jats/exporter/citeproc.ts +265 -0
  43. package/src/jats/exporter/jats-exporter.ts +2090 -0
  44. package/src/jats/exporter/jats-versions.ts +50 -0
  45. package/src/jats/exporter/labels.ts +101 -0
  46. package/src/jats/importer/create-article-node.ts +52 -0
  47. package/src/jats/importer/jats-comments.ts +111 -0
  48. package/src/jats/importer/jats-dom-parser.ts +1260 -0
  49. package/src/jats/importer/jats-parser-utils.ts +234 -0
  50. package/src/jats/importer/jats-transformations.ts +591 -0
  51. package/src/jats/importer/parse-jats-article.ts +106 -0
  52. package/src/jats/index.ts +21 -0
  53. package/src/jats/types.ts +16 -0
  54. package/src/lib/__tests__/footnotes.test.ts +36 -0
  55. package/src/lib/citeproc.ts +30 -0
  56. package/src/lib/credit-roles.ts +91 -0
  57. package/src/lib/deafults.ts +17 -0
  58. package/src/lib/footnotes.ts +85 -0
  59. package/src/lib/html.ts +56 -0
  60. package/src/lib/section-categories.ts +27 -0
  61. package/src/lib/utils.ts +145 -0
  62. package/src/lib/xml.ts +28 -0
  63. package/src/schema/__tests__/docs.ts +1911 -0
  64. package/src/schema/__tests__/groups.test.ts +61 -0
  65. package/src/schema/__tests__/migration.test.ts +23 -0
  66. package/src/schema/groups.ts +36 -0
  67. package/src/schema/index.ts +286 -0
  68. package/src/schema/marks.ts +254 -0
  69. package/src/schema/migration/migrate.ts +77 -0
  70. package/src/schema/migration/migration-script.ts +26 -0
  71. package/src/schema/migration/migration-scripts/1.2.5.ts +44 -0
  72. package/src/schema/migration/migration-scripts/2.3.22.ts +52 -0
  73. package/src/schema/migration/migration-scripts/3.0.12.ts +33 -0
  74. package/src/schema/migration/migration-scripts/3.0.21.ts +50 -0
  75. package/src/schema/migration/migration-scripts/3.0.30.ts +65 -0
  76. package/src/schema/migration/migration-scripts/3.0.31.ts +40 -0
  77. package/src/schema/migration/migration-scripts/3.0.41.ts +37 -0
  78. package/src/schema/migration/migration-scripts/3.0.55.ts +42 -0
  79. package/src/schema/migration/migration-scripts/3.0.56.ts +114 -0
  80. package/src/schema/migration/migration-scripts/4.2.13.ts +37 -0
  81. package/src/schema/migration/migration-scripts/4.2.15.ts +48 -0
  82. package/src/schema/migration/migration-scripts/4.3.23.ts +55 -0
  83. package/src/schema/migration/migration-scripts/4.3.34.ts +132 -0
  84. package/src/schema/migration/migration-scripts/4.3.35.ts +41 -0
  85. package/src/schema/migration/migration-scripts/index.ts +49 -0
  86. package/src/schema/nodes/abstracts.ts +29 -0
  87. package/src/schema/nodes/affiliation.ts +88 -0
  88. package/src/schema/nodes/affiliations.ts +30 -0
  89. package/src/schema/nodes/alt_text.ts +36 -0
  90. package/src/schema/nodes/alt_title.ts +40 -0
  91. package/src/schema/nodes/alt_titles_section.ts +58 -0
  92. package/src/schema/nodes/attachment.ts +46 -0
  93. package/src/schema/nodes/attachments.ts +45 -0
  94. package/src/schema/nodes/attribution.ts +38 -0
  95. package/src/schema/nodes/author_notes.ts +44 -0
  96. package/src/schema/nodes/award.ts +52 -0
  97. package/src/schema/nodes/awards.ts +45 -0
  98. package/src/schema/nodes/backmatter.ts +31 -0
  99. package/src/schema/nodes/bibliography_element.ts +58 -0
  100. package/src/schema/nodes/bibliography_item.ts +108 -0
  101. package/src/schema/nodes/bibliography_section.ts +60 -0
  102. package/src/schema/nodes/blockquote_element.ts +79 -0
  103. package/src/schema/nodes/body.ts +29 -0
  104. package/src/schema/nodes/box_element.ts +67 -0
  105. package/src/schema/nodes/caption.ts +67 -0
  106. package/src/schema/nodes/caption_title.ts +67 -0
  107. package/src/schema/nodes/citation.ts +70 -0
  108. package/src/schema/nodes/comment.ts +50 -0
  109. package/src/schema/nodes/comments.ts +28 -0
  110. package/src/schema/nodes/contributor.ts +87 -0
  111. package/src/schema/nodes/contributors.ts +40 -0
  112. package/src/schema/nodes/core_section.ts +27 -0
  113. package/src/schema/nodes/corresp.ts +49 -0
  114. package/src/schema/nodes/cross_reference.ts +64 -0
  115. package/src/schema/nodes/doc.ts +21 -0
  116. package/src/schema/nodes/embed.ts +55 -0
  117. package/src/schema/nodes/equation.ts +65 -0
  118. package/src/schema/nodes/equation_element.ts +61 -0
  119. package/src/schema/nodes/figure.ts +65 -0
  120. package/src/schema/nodes/figure_element.ts +73 -0
  121. package/src/schema/nodes/footnote.ts +95 -0
  122. package/src/schema/nodes/footnotes_element.ts +69 -0
  123. package/src/schema/nodes/footnotes_section.ts +61 -0
  124. package/src/schema/nodes/general_table_footnote.ts +42 -0
  125. package/src/schema/nodes/graphical_abstract_section.ts +62 -0
  126. package/src/schema/nodes/hard_break.ts +36 -0
  127. package/src/schema/nodes/hero_image.ts +46 -0
  128. package/src/schema/nodes/highlight_marker.ts +68 -0
  129. package/src/schema/nodes/image_element.ts +49 -0
  130. package/src/schema/nodes/inline_equation.ts +68 -0
  131. package/src/schema/nodes/inline_footnote.ts +66 -0
  132. package/src/schema/nodes/keyword.ts +61 -0
  133. package/src/schema/nodes/keyword_group.ts +63 -0
  134. package/src/schema/nodes/keywords.ts +59 -0
  135. package/src/schema/nodes/keywords_element.ts +64 -0
  136. package/src/schema/nodes/link.ts +79 -0
  137. package/src/schema/nodes/list.ts +134 -0
  138. package/src/schema/nodes/listing.ts +91 -0
  139. package/src/schema/nodes/listing_element.ts +61 -0
  140. package/src/schema/nodes/long_desc.ts +36 -0
  141. package/src/schema/nodes/manuscript.ts +86 -0
  142. package/src/schema/nodes/missing_figure.ts +59 -0
  143. package/src/schema/nodes/paragraph.ts +77 -0
  144. package/src/schema/nodes/placeholder.ts +62 -0
  145. package/src/schema/nodes/placeholder_element.ts +60 -0
  146. package/src/schema/nodes/pullquote_element.ts +81 -0
  147. package/src/schema/nodes/quote_image.ts +64 -0
  148. package/src/schema/nodes/section.ts +69 -0
  149. package/src/schema/nodes/section_label.ts +39 -0
  150. package/src/schema/nodes/section_title.ts +58 -0
  151. package/src/schema/nodes/subtitle.ts +40 -0
  152. package/src/schema/nodes/subtitles.ts +51 -0
  153. package/src/schema/nodes/supplement.ts +77 -0
  154. package/src/schema/nodes/supplements.ts +60 -0
  155. package/src/schema/nodes/table.ts +144 -0
  156. package/src/schema/nodes/table_col.ts +76 -0
  157. package/src/schema/nodes/table_element.ts +67 -0
  158. package/src/schema/nodes/table_element_footer.ts +42 -0
  159. package/src/schema/nodes/text.ts +31 -0
  160. package/src/schema/nodes/title.ts +43 -0
  161. package/src/schema/nodes/trans_abstract.ts +58 -0
  162. package/src/schema/nodes/trans_graphical_abstract.ts +67 -0
  163. package/src/schema/types.ts +240 -0
  164. package/src/tests.ts +33 -0
  165. package/src/transformer/id.ts +23 -0
  166. package/src/transformer/index.ts +21 -0
  167. package/src/transformer/node-names.ts +73 -0
  168. package/src/transformer/node-title.ts +111 -0
  169. package/src/transformer/node-types.ts +39 -0
  170. package/src/transformer/node-validator.ts +82 -0
  171. package/src/types.ts +25 -0
  172. package/src/version.ts +1 -0
@@ -0,0 +1,591 @@
1
+ /*!
2
+ * © 2020 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { defaultTitle } from '../../lib/deafults'
18
+ import { XML_NAMESPACE } from '../../lib/xml'
19
+ import { SectionCategory, SectionGroup } from '../../schema'
20
+ import { htmlFromJatsNode } from './jats-parser-utils'
21
+
22
+ export type CreateElement = (tagName: string) => HTMLElement
23
+
24
+ const removeNodeFromParent = (node: Element) =>
25
+ node.parentNode && node.parentNode.removeChild(node)
26
+
27
+ const capitalizeFirstLetter = (str: string) =>
28
+ str.charAt(0).toUpperCase() + str.slice(1)
29
+
30
+ const createSectionGroup = (
31
+ type: SectionGroup,
32
+ createElement: CreateElement
33
+ ) => {
34
+ const sec = createElement('sec')
35
+ sec.setAttribute('sec-type', type)
36
+ return sec
37
+ }
38
+
39
+ export const addMissingCaptions = (
40
+ doc: Document,
41
+ createElement: CreateElement
42
+ ) => {
43
+ const elements = doc.querySelectorAll(
44
+ 'fig, table-wrap, media, supplementary-material, boxed-text'
45
+ )
46
+ for (const element of elements) {
47
+ let caption: Element | null = element.querySelector('caption')
48
+ if (!caption) {
49
+ caption = createElement('caption')
50
+ element.nodeName === 'fig'
51
+ ? element.appendChild(caption)
52
+ : element.prepend(caption)
53
+ }
54
+ if (!caption.querySelector('title') && element.nodeName !== 'fig') {
55
+ caption.prepend(createElement('title'))
56
+ }
57
+ if (
58
+ !caption.querySelector('p') &&
59
+ element.nodeName !== 'boxed-text' &&
60
+ element.nodeName !== 'table-wrap'
61
+ ) {
62
+ caption.appendChild(createElement('p'))
63
+ }
64
+ }
65
+ }
66
+ export const createBoxedElementSection = (
67
+ doc: Document,
68
+ createElement: CreateElement
69
+ ) => {
70
+ const boxedTexts = doc.querySelectorAll('boxed-text')
71
+ for (const boxedText of boxedTexts) {
72
+ const containerSec = createElement('sec')
73
+ const children = Array.from(boxedText.children).filter(
74
+ (child) => child.localName !== 'label' && child.localName !== 'caption'
75
+ )
76
+ containerSec.append(...children)
77
+ boxedText.appendChild(containerSec)
78
+ }
79
+ }
80
+
81
+ export const createTitles = (front: Element, createElement: CreateElement) => {
82
+ const titles = createElement('titles')
83
+ let title = front.querySelector('article-meta > title-group > article-title')
84
+ if (title) {
85
+ title.innerHTML = htmlFromJatsNode(title, createElement) ?? defaultTitle
86
+ } else {
87
+ title = createElement('article-title')
88
+ title.innerHTML = defaultTitle
89
+ }
90
+ titles.appendChild(title)
91
+
92
+ const subtitles = front.querySelectorAll('subtitle')
93
+ subtitles.forEach((subtitle) => {
94
+ subtitle.innerHTML = htmlFromJatsNode(subtitle, createElement) ?? ''
95
+ titles.appendChild(subtitle)
96
+ })
97
+
98
+ const altTitles = front.querySelectorAll(
99
+ 'article-meta > title-group > alt-title'
100
+ )
101
+ altTitles.forEach((altTitle) => {
102
+ altTitle.innerHTML = htmlFromJatsNode(altTitle, createElement) ?? ''
103
+ titles.appendChild(altTitle)
104
+ })
105
+ front.parentNode?.insertBefore(titles, front)
106
+ }
107
+
108
+ export const moveAuthorNotes = (
109
+ front: Element,
110
+ createElement: CreateElement
111
+ ) => {
112
+ const authorNotes = front.querySelector('article-meta > author-notes')
113
+ if (authorNotes) {
114
+ const sectionTitle = createElement('title')
115
+ authorNotes.prepend(sectionTitle)
116
+ front.parentNode?.insertBefore(authorNotes, front)
117
+ }
118
+ }
119
+ export const moveAwards = (front: Element) => {
120
+ const awards = front.querySelector('article-meta > funding-group')
121
+ if (awards) {
122
+ front.parentNode?.insertBefore(awards, front)
123
+ }
124
+ }
125
+
126
+ export const moveContributors = (
127
+ front: Element,
128
+ createElement: CreateElement
129
+ ) => {
130
+ const contribs = front.querySelectorAll(
131
+ 'contrib-group > contrib[contrib-type="author"]'
132
+ )
133
+
134
+ if (contribs.length) {
135
+ const contributors = createElement('contributors')
136
+ contribs.forEach((c) => contributors.appendChild(c))
137
+ front.parentNode?.insertBefore(contributors, front)
138
+ }
139
+ }
140
+
141
+ export const moveAffiliations = (
142
+ front: Element,
143
+ createElement: CreateElement
144
+ ) => {
145
+ const affs = front.querySelectorAll('article-meta > contrib-group > aff')
146
+ if (affs.length) {
147
+ const affiliations = createElement('affiliations')
148
+ affs.forEach((a) => affiliations.appendChild(a))
149
+ front.parentNode?.insertBefore(affiliations, front)
150
+ }
151
+ }
152
+
153
+ export const moveAbstracts = (
154
+ front: Element,
155
+ group: Element,
156
+ createElement: CreateElement,
157
+ sectionCategories?: SectionCategory[]
158
+ ) => {
159
+ const abstracts = front.querySelectorAll(
160
+ 'article-meta > abstract, article-meta > trans-abstract'
161
+ )
162
+ abstracts.forEach((abstract) => {
163
+ const sec = createAbstractSection(
164
+ abstract,
165
+ createElement,
166
+ sectionCategories
167
+ )
168
+ removeNodeFromParent(abstract)
169
+ group.appendChild(sec)
170
+ })
171
+ }
172
+
173
+ export const moveHeroImage = (doc: Document) => {
174
+ const heroImage = doc.querySelector('graphic[content-type="leading"]')
175
+ if (!heroImage) {
176
+ return
177
+ }
178
+ const back = doc.querySelector('back')
179
+ if (back) {
180
+ back.parentNode?.insertBefore(heroImage, back.nextSibling)
181
+ } else {
182
+ doc.documentElement.appendChild(heroImage)
183
+ }
184
+ }
185
+
186
+ export const createBody = (
187
+ doc: Document,
188
+ body: Element,
189
+ createElement: CreateElement
190
+ ) => {
191
+ const group = createSectionGroup('body', createElement)
192
+ const elements = body.querySelectorAll(
193
+ ':scope > *:not(sec), :scope > sec:not([sec-type="backmatter"]), :scope > sec:not([sec-type])'
194
+ )
195
+ elements.forEach((element) => {
196
+ removeNodeFromParent(element)
197
+ group.appendChild(element)
198
+ })
199
+ body.append(group)
200
+ }
201
+
202
+ export const createAbstracts = (
203
+ front: Element,
204
+ body: Element,
205
+ createElement: CreateElement,
206
+ sectionCategories?: SectionCategory[]
207
+ ) => {
208
+ const group = createSectionGroup('abstracts', createElement)
209
+ moveAbstracts(front, group, createElement, sectionCategories)
210
+ body.insertBefore(group, body.lastElementChild)
211
+ }
212
+
213
+ export const createBackmatter = (
214
+ doc: Document,
215
+ body: Element,
216
+ sectionCategories: SectionCategory[],
217
+ createElement: CreateElement
218
+ ) => {
219
+ const group = createSectionGroup('backmatter', createElement)
220
+ moveBackSections(doc, group)
221
+ moveAppendices(doc, group, createElement)
222
+ moveSpecialFootnotes(doc, group, sectionCategories, createElement)
223
+ moveAcknowledgments(doc, group, createElement)
224
+ moveFootnotes(doc, group, createElement)
225
+ body.append(group)
226
+ }
227
+
228
+ const moveFootnotes = (
229
+ doc: Document,
230
+ group: Element,
231
+ createElement: CreateElement
232
+ ) => {
233
+ const fns = [
234
+ ...doc.querySelectorAll('fn:not(table-wrap-foot fn, author-notes fn)'),
235
+ ]
236
+ let section = doc.querySelector('sec[sec-type="endnotes"]')
237
+ const fnGroup =
238
+ section?.querySelector('fn-group') || createElement('fn-group')
239
+ fns.forEach((fn) => {
240
+ if (!fn.getAttribute('fn-type')) {
241
+ fnGroup.appendChild(fn)
242
+ }
243
+ })
244
+ if (!section && fnGroup.innerHTML) {
245
+ section = createFootnotesSection([fnGroup], createElement)
246
+ }
247
+ if (section) {
248
+ group.appendChild(section)
249
+ }
250
+ }
251
+
252
+ // process footnotes with special meaning to
253
+ const moveSpecialFootnotes = (
254
+ doc: Document,
255
+ group: Element,
256
+ sectionCategories: SectionCategory[],
257
+ createElement: CreateElement
258
+ ) => {
259
+ const fns = [...doc.querySelectorAll('fn[fn-type]')]
260
+ for (const fn of fns) {
261
+ const type = fn.getAttribute('fn-type') || '' //Cannot be null since it is queried above
262
+ const category = sectionCategories.find((category) =>
263
+ category.synonyms.includes(type)
264
+ )
265
+ if (category) {
266
+ const section = createElement('sec')
267
+ const fnTitle =
268
+ fn.querySelector('label') ||
269
+ fn.querySelector('p[content-type="fn-title"]')
270
+ const title = createElement('title')
271
+ if (fnTitle) {
272
+ const titleText = fnTitle.textContent?.trim()
273
+ if (titleText) {
274
+ title.textContent = titleText
275
+ }
276
+ removeNodeFromParent(fnTitle)
277
+ } else {
278
+ title.textContent = category.titles[0]
279
+ }
280
+ section.append(title)
281
+ section.append(...fn.children)
282
+ removeNodeFromParent(fn)
283
+ section.setAttribute('sec-type', category.id)
284
+ group.append(section)
285
+ }
286
+ }
287
+ }
288
+ // move captions to the end of their containers
289
+ export const moveCaptionsToEnd = (body: Element) => {
290
+ const captions = body.querySelectorAll('caption')
291
+
292
+ for (const caption of captions) {
293
+ if (
294
+ caption.parentNode &&
295
+ caption.parentNode.nodeName !== 'table-wrap' &&
296
+ caption.parentNode.nodeName !== 'boxed-text'
297
+ ) {
298
+ caption.parentNode.appendChild(caption)
299
+ }
300
+ }
301
+ }
302
+
303
+ const createAbstractSection = (
304
+ abstract: Element,
305
+ createElement: CreateElement,
306
+ sectionCategories?: SectionCategory[]
307
+ ) => {
308
+ const abstractType = abstract.getAttribute('abstract-type')
309
+ const sectionType = abstractType ? `abstract-${abstractType}` : 'abstract'
310
+ let section = createElement('sec')
311
+ if (abstract.nodeName === 'trans-abstract') {
312
+ section = createElement('trans-abstract')
313
+ const lang = abstract.getAttributeNS(XML_NAMESPACE, 'lang')
314
+ if (lang) {
315
+ section.setAttributeNS(XML_NAMESPACE, 'lang', lang)
316
+ }
317
+ }
318
+ section.setAttribute('sec-type', sectionType)
319
+ if (!abstract.querySelector(':scope > title')) {
320
+ const title = createElement('title')
321
+ const category = sectionCategories?.find(
322
+ (c) => c.id === sectionType || c.synonyms.includes(sectionType)
323
+ )
324
+ if (category?.titles[0]) {
325
+ title.textContent = category.titles[0]
326
+ } else {
327
+ title.textContent = abstractType
328
+ ? `${capitalizeFirstLetter(abstractType.split('-').join(' '))} Abstract`
329
+ : 'Abstract'
330
+ }
331
+ section.appendChild(title)
332
+ }
333
+
334
+ while (abstract.firstChild) {
335
+ section.appendChild(abstract.firstChild)
336
+ }
337
+ return section
338
+ }
339
+
340
+ const createAcknowledgmentsSection = (
341
+ ack: Element,
342
+ createElement: CreateElement
343
+ ) => {
344
+ const section = createElement('sec')
345
+ section.setAttribute('sec-type', 'acknowledgments')
346
+
347
+ if (!ack.querySelector('title')) {
348
+ const title = createElement('title')
349
+ title.textContent = 'Acknowledgements'
350
+ section.appendChild(title)
351
+ }
352
+
353
+ while (ack.firstChild) {
354
+ section.appendChild(ack.firstChild)
355
+ }
356
+ return section
357
+ }
358
+
359
+ const createFootnotesSection = (
360
+ fnGroups: Element[],
361
+ createElement: CreateElement
362
+ ) => {
363
+ const section = createElement('sec')
364
+ section.setAttribute('sec-type', 'endnotes')
365
+
366
+ const titleNode = fnGroups
367
+ .map((g) => g.querySelector('title'))
368
+ .filter((t) => t !== null)[0]
369
+
370
+ if (titleNode) {
371
+ section.appendChild(titleNode)
372
+ } else {
373
+ const title = createElement('title')
374
+ title.textContent = 'Footnotes'
375
+ section.appendChild(title)
376
+ }
377
+
378
+ for (const fnGroup of fnGroups) {
379
+ section.appendChild(fnGroup)
380
+ }
381
+
382
+ return section
383
+ }
384
+
385
+ const createAppendicesSection = (
386
+ app: Element,
387
+ createElement: CreateElement
388
+ ) => {
389
+ const section = createElement('sec')
390
+ section.setAttribute('sec-type', 'appendices')
391
+ section.append(...app.children)
392
+ return section
393
+ }
394
+
395
+ const moveBackSections = (doc: Document, group: Element) => {
396
+ for (const section of doc.querySelectorAll('back > sec')) {
397
+ removeNodeFromParent(section)
398
+ group.appendChild(section)
399
+ }
400
+ }
401
+
402
+ const moveAcknowledgments = (
403
+ doc: Document,
404
+ group: Element,
405
+ createElement: CreateElement
406
+ ) => {
407
+ const ack = doc.querySelector('back > ack')
408
+ if (ack) {
409
+ const section = createAcknowledgmentsSection(ack, createElement)
410
+ removeNodeFromParent(ack)
411
+ group.appendChild(section)
412
+ }
413
+ }
414
+ const moveAppendices = (
415
+ doc: Document,
416
+ group: Element,
417
+ createElement: CreateElement
418
+ ) => {
419
+ const apps = doc.querySelectorAll('back > app-group > app')
420
+ for (const app of apps) {
421
+ const section = createAppendicesSection(app, createElement)
422
+ removeNodeFromParent(app)
423
+ group.appendChild(section)
424
+ }
425
+ }
426
+
427
+ export const createKeywordsSection = (
428
+ document: Document,
429
+ body: Element,
430
+ createElement: CreateElement
431
+ ) => {
432
+ const kwdGroups = [...document.querySelectorAll('kwd-group')]
433
+ if (kwdGroups.length > 0) {
434
+ const section = createElement('sec')
435
+ section.setAttribute('sec-type', 'keywords')
436
+ const title = createElement('title')
437
+ title.textContent = 'Keywords'
438
+ section.append(title)
439
+ const kwdGroupList = createElement('kwd-group-list')
440
+ // Using the first kwd-group since for the moment we only support single kwd-group
441
+ kwdGroupList.append(kwdGroups[0])
442
+ section.append(kwdGroupList)
443
+ body.prepend(section)
444
+ }
445
+ }
446
+
447
+ export const createSupplementaryMaterialsSection = (
448
+ document: Document,
449
+ body: Element,
450
+ createElement: CreateElement
451
+ ) => {
452
+ const suppls = [
453
+ ...document.querySelectorAll('article-meta > supplementary-material'),
454
+ ]
455
+ if (suppls.length) {
456
+ const section = createElement('sec')
457
+ section.setAttribute('sec-type', 'supplementary-material')
458
+ const title = createElement('title')
459
+ title.textContent = 'Supplements'
460
+ section.append(title)
461
+ section.append(...suppls)
462
+ body.append(section)
463
+ }
464
+ }
465
+
466
+ export const createAccessibilityItems = (
467
+ doc: Document,
468
+ createElement: CreateElement
469
+ ) => {
470
+ const resolveA11yNode = (item: Element, tagName: string) => {
471
+ if (item.nodeName.toLowerCase() === 'fig') {
472
+ const graphicNode = item.querySelector(`graphic ${tagName}`)
473
+ const figNode = item.querySelector(`:scope > ${tagName}`)
474
+ if (graphicNode && figNode) {
475
+ figNode.remove()
476
+ }
477
+ return graphicNode || figNode || createElement(tagName)
478
+ }
479
+ return item.querySelector(tagName) || createElement(tagName)
480
+ }
481
+
482
+ doc
483
+ .querySelectorAll('media, fig, table-wrap, graphic:not(fig graphic)')
484
+ .forEach((item) => {
485
+ item.appendChild(resolveA11yNode(item, 'alt-text'))
486
+ item.appendChild(resolveA11yNode(item, 'long-desc'))
487
+ })
488
+ }
489
+
490
+ export const moveReferencesToBackmatter = (
491
+ body: Element,
492
+ back: Element,
493
+ createElement: CreateElement
494
+ ) => {
495
+ const backmatter = body.querySelector('sec[sec-type="backmatter"]')
496
+ const refList = back.querySelector('ref-list')
497
+ if (!backmatter || !refList) {
498
+ return
499
+ }
500
+
501
+ removeNodeFromParent(refList)
502
+
503
+ const section = createElement('sec')
504
+ section.setAttribute('sec-type', 'bibliography')
505
+ const title = createElement('title')
506
+ title.textContent = 'References'
507
+ section.appendChild(title)
508
+ section.appendChild(refList)
509
+
510
+ backmatter.appendChild(section)
511
+ }
512
+
513
+ export const orderTableFootnote = (doc: Document, body: Element) => {
514
+ const rids = new Set(
515
+ [...body.querySelectorAll('tbody > xref[ref-type="fn"]')].map((xref) =>
516
+ xref.getAttribute('rid')
517
+ )
518
+ )
519
+
520
+ const fnGroups = doc.querySelectorAll('table-wrap-foot > fn-group')
521
+ fnGroups.forEach((fnGroup) => {
522
+ // sort the un-cited table footnote at the end of list
523
+ const fns = [...fnGroup.querySelectorAll('fn')].sort((fn) =>
524
+ rids.has(fn.getAttribute('id')) ? -1 : 0
525
+ )
526
+ fnGroup.replaceChildren(...fns)
527
+ })
528
+ }
529
+
530
+ export const createAttachments = (
531
+ doc: Document,
532
+ createElement: CreateElement
533
+ ) => {
534
+ const attachments = createElement('attachments')
535
+ doc
536
+ .querySelectorAll('self-uri')
537
+ .forEach((attachment) => attachments.appendChild(attachment))
538
+ if (attachments.children.length > 0) {
539
+ doc.documentElement.appendChild(attachments)
540
+ }
541
+ }
542
+
543
+ export const fixTables = (
544
+ doc: Document,
545
+ body: Element,
546
+ createElement: CreateElement
547
+ ) => {
548
+ const tableWraps = body.querySelectorAll('table-wrap')
549
+ tableWraps.forEach((tableWrap) => {
550
+ // Move cols into a colgroup if they are not already
551
+ // This more closely maps how they exist in HTML and, subsequently, in ManuscriptJSON
552
+ const table = tableWrap.querySelector('table')
553
+ if (!table) {
554
+ return
555
+ }
556
+ // Merge tfoot rows into tbody to preserve correct row order when parsing.
557
+ // In JATS, tfoot appears before tbody in the DOM, which would
558
+ // cause the last row to appear first after roundtripping.
559
+ const tfoot = table.querySelector('tfoot')
560
+ const tbody = table.querySelector('tbody')
561
+ if (tfoot && tbody) {
562
+ const tfootRows = Array.from(tfoot.querySelectorAll(':scope > tr'))
563
+ for (const row of tfootRows) {
564
+ removeNodeFromParent(row)
565
+ tbody.appendChild(row)
566
+ }
567
+ removeNodeFromParent(tfoot)
568
+ }
569
+ const colgroup = table.querySelector('colgroup')
570
+ const cols = table.querySelectorAll('col')
571
+ if (!colgroup && table.firstChild && cols.length > 0) {
572
+ const colgroup = createElement('colgroup')
573
+ for (const col of cols) {
574
+ colgroup.appendChild(col)
575
+ }
576
+ tableWrap.insertBefore(colgroup, table.nextSibling)
577
+ }
578
+ const tableFootWrap = tableWrap.querySelector('table-wrap-foot')
579
+ if (tableFootWrap) {
580
+ const paragraphs = tableFootWrap.querySelectorAll(':scope > p')
581
+ if (paragraphs.length) {
582
+ const generalTableFootnote = createElement('general-table-footnote')
583
+ for (const paragraph of paragraphs) {
584
+ removeNodeFromParent(paragraph)
585
+ generalTableFootnote.append(paragraph)
586
+ }
587
+ tableFootWrap.prepend(generalTableFootnote)
588
+ }
589
+ }
590
+ })
591
+ }
@@ -0,0 +1,106 @@
1
+ /*!
2
+ * © 2020 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { ActualManuscriptNode, schema, SectionCategory } from '../../schema'
18
+ import { markComments } from './jats-comments'
19
+ import { JATSDOMParser } from './jats-dom-parser'
20
+ import { updateDocumentIDs } from './jats-parser-utils'
21
+ import {
22
+ addMissingCaptions,
23
+ createAbstracts,
24
+ createAccessibilityItems,
25
+ createAttachments,
26
+ createBackmatter,
27
+ createBody,
28
+ createBoxedElementSection,
29
+ createKeywordsSection,
30
+ createSupplementaryMaterialsSection,
31
+ createTitles,
32
+ fixTables,
33
+ moveAffiliations,
34
+ moveAuthorNotes,
35
+ moveAwards,
36
+ moveCaptionsToEnd,
37
+ moveContributors,
38
+ moveHeroImage,
39
+ moveReferencesToBackmatter,
40
+ orderTableFootnote,
41
+ } from './jats-transformations'
42
+
43
+ const processJATS = (doc: Document, sectionCategories: SectionCategory[]) => {
44
+ const createElement = createElementFn(doc)
45
+ markComments(doc)
46
+
47
+ const front = doc.querySelector('front')
48
+ if (!front) {
49
+ return
50
+ }
51
+ addMissingCaptions(doc, createElement)
52
+ createBoxedElementSection(doc, createElement)
53
+ createTitles(front, createElement)
54
+ moveContributors(front, createElement)
55
+ moveAffiliations(front, createElement)
56
+ moveAuthorNotes(front, createElement)
57
+ moveAwards(front)
58
+
59
+ let body = doc.querySelector('body')
60
+ if (!body) {
61
+ body = createElement('body') as HTMLBodyElement
62
+ const article = doc.querySelector('article')
63
+ if (!article) {
64
+ return
65
+ }
66
+ article.append(body)
67
+ }
68
+
69
+ moveCaptionsToEnd(body)
70
+ createBody(doc, body, createElement)
71
+ createAbstracts(front, body, createElement, sectionCategories)
72
+ createBackmatter(doc, body, sectionCategories, createElement)
73
+ createSupplementaryMaterialsSection(doc, body, createElement)
74
+ createKeywordsSection(doc, body, createElement)
75
+ fixTables(doc, body, createElement)
76
+ orderTableFootnote(doc, body)
77
+ moveHeroImage(doc)
78
+ createAttachments(doc, createElement)
79
+ const back = doc.querySelector('back')
80
+ if (!back) {
81
+ return
82
+ }
83
+ moveReferencesToBackmatter(body, back, createElement)
84
+ createAccessibilityItems(doc, createElement)
85
+ }
86
+
87
+ const createElementFn = (doc: Document) => (tagName: string) =>
88
+ doc.createElement(tagName)
89
+
90
+ export const parseJATSArticle = (
91
+ doc: Document,
92
+ sectionCategories: SectionCategory[],
93
+ template?: string
94
+ ) => {
95
+ processJATS(doc, sectionCategories)
96
+ const node = new JATSDOMParser(sectionCategories, schema).parse(doc)
97
+ .firstChild as ActualManuscriptNode
98
+ if (!node) {
99
+ throw new Error('No content was parsed from the JATS article body')
100
+ }
101
+ updateDocumentIDs(node)
102
+ if (template) {
103
+ node.attrs.prototype = template
104
+ }
105
+ return node
106
+ }
@@ -0,0 +1,21 @@
1
+ /*!
2
+ * © 2020 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ export * from './exporter/jats-exporter'
18
+ export * from './exporter/jats-versions'
19
+ export * from './exporter/labels'
20
+ export * from './importer/create-article-node'
21
+ export { parseJATSArticle } from './importer/parse-jats-article'