From 69ee61b4eb8ef6ee78189ed948405897a8507c54 Mon Sep 17 00:00:00 2001 From: asouqi Date: Thu, 10 Sep 2026 13:34:24 +0300 Subject: [PATCH 1/9] [FGA-020] Image Attribution Support (LEAN-6032, LEAN-6036) --- .../__snapshots__/jats-importer.test.ts.snap | 26 ------------- src/jats/exporter/jats-exporter.ts | 17 ++++++--- src/jats/importer/jats-dom-parser.ts | 37 ++++++++++++++----- src/schema/nodes/figure_element.ts | 3 -- src/schema/nodes/hero_image.ts | 2 +- src/schema/nodes/image_element.ts | 2 +- 6 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap b/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap index f872d2d5..57e5cde9 100644 --- a/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap +++ b/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap @@ -3163,7 +3163,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -18051,7 +18050,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -18279,7 +18277,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -18964,7 +18961,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -23392,7 +23388,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -27254,7 +27249,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -27410,7 +27404,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -31390,9 +31383,6 @@ Object { }, Object { "attrs": Object { - "attribution": Object { - "literal": "Source: Wikipedia.", - }, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -31494,9 +31484,6 @@ Object { }, Object { "attrs": Object { - "attribution": Object { - "literal": "Source: Wikipedia.", - }, "dataTracked": null, "id": "fig1", }, @@ -33365,7 +33352,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -33445,7 +33431,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -35130,7 +35115,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -39016,7 +39000,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -39172,7 +39155,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -45227,7 +45209,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -51855,7 +51836,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -52372,7 +52352,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -52934,7 +52913,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -53590,7 +53568,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -54000,7 +53977,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -54504,7 +54480,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, @@ -54950,7 +54925,6 @@ Object { }, Object { "attrs": Object { - "attribution": undefined, "dataTracked": null, "id": "MPFigureElement:test", }, diff --git a/src/jats/exporter/jats-exporter.ts b/src/jats/exporter/jats-exporter.ts index 6a2ae73b..0738f5a9 100644 --- a/src/jats/exporter/jats-exporter.ts +++ b/src/jats/exporter/jats-exporter.ts @@ -1151,11 +1151,15 @@ export class JATSExporter { } } const appendAttributions = ($element: Element, node: ManuscriptNode) => { - if (node.attrs.attribution) { - const $attrib = this.createElement('attrib') - $attrib.textContent = node.attrs.attribution.literal - $element.appendChild($attrib) - } + const attributions = this.getChildrenOfType( + schema.nodes.attribution, + node + ) + attributions.forEach((attribution) => { + if (attribution.content.size > 0) { + $element.appendChild(this.serializeNode(attribution)) + } + }) } const appendTable = ($element: Element, node: ManuscriptNode) => { @@ -1254,6 +1258,7 @@ export class JATSExporter { this.appendCaption($graphic, node) this.appendChildNodeOfType($graphic, node, schema.nodes.alt_text) this.appendChildNodeOfType($graphic, node, schema.nodes.long_desc) + appendAttributions($graphic, node) return $graphic } @@ -1292,11 +1297,11 @@ export class JATSExporter { node.type.schema.nodes.footnotes_element ) processChildNodes($fig, node, contentNodeType) - appendAttributions($fig, node) if (isExecutableNodeType(node.type)) { processExecutableNode(node, $fig) } moveAltTextAndLongDescToGraphics($fig) + appendAttributions($fig, node) return $fig } diff --git a/src/jats/importer/jats-dom-parser.ts b/src/jats/importer/jats-dom-parser.ts index df8c0214..f5115eee 100644 --- a/src/jats/importer/jats-dom-parser.ts +++ b/src/jats/importer/jats-dom-parser.ts @@ -35,6 +35,7 @@ import { SectionCategory, } from '../../schema' import { DEFAULT_PROFILE_ID } from './jats-comments' +import findLastIndex from 'lodash/findLastIndex' export class JATSDOMParser { private parser: DOMParser @@ -276,6 +277,14 @@ export class JATSDOMParser { private getFigContent = (node: Node) => { const element = node as HTMLElement const content = [this.schema.nodes.figure.create(this.getFigAttrs(element))] + const attributions = element.querySelectorAll('attrib') + Array.from(attributions).forEach((attribution) => { + content.push( + this.parse(attribution, { + topNode: this.schema.nodes.attribution.create(), + }) + ) + }) const altText = element.querySelector('alt-text') if (altText) { const altTextNode = this.schema.nodes.alt_text.create() @@ -608,10 +617,18 @@ export class JATSDOMParser { return Fragment.from(this.schema.text('_')) }, }, - { tag: 'attrib', node: 'attribution', + getAttrs: (node) => { + const element = node as HTMLElement + const isInTargetContext = !!element.closest('fig, disp-quote') + return isInTargetContext ? {} : false + }, + }, + { + tag: 'attrib', + skip: true, }, { tag: 'back', @@ -859,7 +876,16 @@ export class JATSDOMParser { this.getCaptionContent(node) ) const fig = this.getFigContent(element).content - return Fragment.from([...fig.slice(0, 1), caption, ...fig.slice(1)]) + const attributionIndex = findLastIndex( + fig, + (node) => node.type === this.schema.nodes.attribution + ) + const splitIndex = attributionIndex === -1 ? 1 : attributionIndex + 1 + return Fragment.from([ + ...fig.slice(0, splitIndex), + caption, + ...fig.slice(splitIndex), + ]) }, getAttrs: this.getFigAttrs, }, @@ -868,15 +894,8 @@ export class JATSDOMParser { node: 'figure_element', getAttrs: (node) => { const element = node as HTMLElement - const attrib = element.querySelector('attrib') - const attribution = attrib - ? { - literal: getTrimmedTextContent(attrib) ?? '', - } - : undefined return { id: element.getAttribute('id'), - attribution, } }, }, diff --git a/src/schema/nodes/figure_element.ts b/src/schema/nodes/figure_element.ts index 69b94604..b7120590 100644 --- a/src/schema/nodes/figure_element.ts +++ b/src/schema/nodes/figure_element.ts @@ -17,11 +17,9 @@ import { NodeSpec } from 'prosemirror-model' import { ManuscriptNode } from '../types' -import { AttributionNode } from './attribution' export interface FigureElementAttrs { id: string - attribution?: AttributionNode } export interface FigureElementNode extends ManuscriptNode { @@ -33,7 +31,6 @@ export const figureElement: NodeSpec = { '(paragraph | figure | missing_figure | placeholder)+ attribution* caption alt_text long_desc (listing | placeholder)', attrs: { id: { default: '' }, - attribution: { default: undefined }, dataTracked: { default: null }, }, selectable: false, diff --git a/src/schema/nodes/hero_image.ts b/src/schema/nodes/hero_image.ts index 75bf14e4..ba66625b 100644 --- a/src/schema/nodes/hero_image.ts +++ b/src/schema/nodes/hero_image.ts @@ -26,7 +26,7 @@ export interface HeroImageNode extends ManuscriptNode { } export const heroImage: NodeSpec = { - content: 'figure? alt_text long_desc', + content: 'figure? attribution* alt_text long_desc', attrs: { id: { default: '' }, type: { default: 'leading' }, diff --git a/src/schema/nodes/image_element.ts b/src/schema/nodes/image_element.ts index 204d7be8..49de9228 100644 --- a/src/schema/nodes/image_element.ts +++ b/src/schema/nodes/image_element.ts @@ -25,7 +25,7 @@ export interface ImageElementNode extends ManuscriptNode { } export const imageElement: NodeSpec = { - content: 'figure? caption alt_text long_desc', + content: 'figure? attribution* caption alt_text long_desc', attrs: { id: { default: '' }, extLink: { default: '' }, From 2d9a3f7e7ba6a3845d7919c17e28264cc6fb5958 Mon Sep 17 00:00:00 2001 From: asouqi Date: Thu, 10 Sep 2026 15:32:41 +0300 Subject: [PATCH 2/9] [FGA-020] Image Attribution Support (LEAN-6032, LEAN-6036) --- src/jats/importer/jats-dom-parser.ts | 8 ++++++-- src/schema/nodes/attribution.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/jats/importer/jats-dom-parser.ts b/src/jats/importer/jats-dom-parser.ts index f5115eee..b53c67da 100644 --- a/src/jats/importer/jats-dom-parser.ts +++ b/src/jats/importer/jats-dom-parser.ts @@ -277,7 +277,7 @@ export class JATSDOMParser { private getFigContent = (node: Node) => { const element = node as HTMLElement const content = [this.schema.nodes.figure.create(this.getFigAttrs(element))] - const attributions = element.querySelectorAll('attrib') + const attributions = element.querySelectorAll(':scope > attrib') Array.from(attributions).forEach((attribution) => { content.push( this.parse(attribution, { @@ -620,9 +620,13 @@ export class JATSDOMParser { { tag: 'attrib', node: 'attribution', + // we use closest() instead of `context` as it matches ProseMirror's parse-time node stack, not real DOM ancestry. + // Non-matching (e.g. in ) falls to `skip: true` below. getAttrs: (node) => { const element = node as HTMLElement - const isInTargetContext = !!element.closest('fig, disp-quote') + const isInTargetContext = !!element.closest( + 'fig, graphic:not(fig graphic), disp-quote' + ) return isInTargetContext ? {} : false }, }, diff --git a/src/schema/nodes/attribution.ts b/src/schema/nodes/attribution.ts index 28b73cb8..4b8c3b0f 100644 --- a/src/schema/nodes/attribution.ts +++ b/src/schema/nodes/attribution.ts @@ -34,5 +34,5 @@ export const attribution: NodeSpec = { context: 'blockquote_element/|pullquote_element/', }, ], - toDOM: () => ['footer', 0], + toDOM: () => ['footer', { class: 'attribution' }, 0], } From 89b028835e1eb9b0818dac8e93626a5478311d48 Mon Sep 17 00:00:00 2001 From: asouqi Date: Mon, 14 Sep 2026 14:31:31 +0300 Subject: [PATCH 3/9] review update --- .../__snapshots__/jats-importer.test.ts.snap | 48 ++++++------- src/jats/importer/jats-dom-parser.ts | 51 ++++++-------- src/jats/importer/jats-transformations.ts | 31 +++++++++ src/jats/importer/parse-jats-article.ts | 2 + src/schema/__tests__/docs.ts | 27 ++++++++ .../migration/migration-scripts/4.5.7.ts | 69 +++++++++++++++++++ .../migration/migration-scripts/index.ts | 2 + src/schema/nodes/figure_element.ts | 2 +- src/schema/nodes/hero_image.ts | 2 +- src/schema/nodes/image_element.ts | 2 +- 10 files changed, 180 insertions(+), 56 deletions(-) create mode 100644 src/schema/migration/migration-scripts/4.5.7.ts diff --git a/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap b/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap index 57e5cde9..a416332d 100644 --- a/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap +++ b/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap @@ -31410,18 +31410,6 @@ Object { }, "type": "figure", }, - Object { - "attrs": Object { - "dataTracked": null, - }, - "content": Array [ - Object { - "text": "Source: Wikipedia.", - "type": "text", - }, - ], - "type": "attribution", - }, Object { "attrs": Object { "dataTracked": null, @@ -31455,6 +31443,18 @@ Object { ], "type": "caption", }, + Object { + "attrs": Object { + "dataTracked": null, + }, + "content": Array [ + Object { + "text": "Source: Wikipedia.", + "type": "text", + }, + ], + "type": "attribution", + }, Object { "attrs": Object { "id": "MPAltText:test", @@ -31509,18 +31509,6 @@ Object { }, "type": "missing_figure", }, - Object { - "attrs": Object { - "dataTracked": null, - }, - "content": Array [ - Object { - "text": "Source: Wikipedia.", - "type": "text", - }, - ], - "type": "attribution", - }, Object { "attrs": Object { "dataTracked": null, @@ -31554,6 +31542,18 @@ Object { ], "type": "caption", }, + Object { + "attrs": Object { + "dataTracked": null, + }, + "content": Array [ + Object { + "text": "Source: Wikipedia.", + "type": "text", + }, + ], + "type": "attribution", + }, Object { "attrs": Object { "id": "MPAltText:test", diff --git a/src/jats/importer/jats-dom-parser.ts b/src/jats/importer/jats-dom-parser.ts index b53c67da..f85cd52d 100644 --- a/src/jats/importer/jats-dom-parser.ts +++ b/src/jats/importer/jats-dom-parser.ts @@ -35,7 +35,6 @@ import { SectionCategory, } from '../../schema' import { DEFAULT_PROFILE_ID } from './jats-comments' -import findLastIndex from 'lodash/findLastIndex' export class JATSDOMParser { private parser: DOMParser @@ -277,14 +276,10 @@ export class JATSDOMParser { private getFigContent = (node: Node) => { const element = node as HTMLElement const content = [this.schema.nodes.figure.create(this.getFigAttrs(element))] - const attributions = element.querySelectorAll(':scope > attrib') - Array.from(attributions).forEach((attribution) => { - content.push( - this.parse(attribution, { - topNode: this.schema.nodes.attribution.create(), - }) - ) - }) + const attributions = Array.from(element.querySelectorAll(':scope > attrib')) + if (attributions.length > 0) { + content.push(this.mergeAttributions(attributions)) + } const altText = element.querySelector('alt-text') if (altText) { const altTextNode = this.schema.nodes.alt_text.create() @@ -308,6 +303,22 @@ export class JATSDOMParser { ) } + private mergeAttributions = (attribs: Element[]) => { + const doc = attribs[0].ownerDocument + const wrapper = doc.createElement('attrib') + attribs.forEach((attrib, index) => { + if (index > 0) { + wrapper.append(doc.createTextNode(' ')) + } + wrapper.append( + ...Array.from(attrib.childNodes).map((n) => n.cloneNode(true)) + ) + }) + return this.parse(wrapper, { + topNode: this.schema.nodes.attribution.create(), + }) + } + private parseRefPages = (element: Element) => { const fpage = getTrimmedTextContent(element, 'fpage') const lpage = getTrimmedTextContent(element, 'lpage') @@ -618,17 +629,8 @@ export class JATSDOMParser { }, }, { - tag: 'attrib', + tag: 'fig attrib, disp-quote attrib, graphic:not(fig graphic) attrib', node: 'attribution', - // we use closest() instead of `context` as it matches ProseMirror's parse-time node stack, not real DOM ancestry. - // Non-matching (e.g. in ) falls to `skip: true` below. - getAttrs: (node) => { - const element = node as HTMLElement - const isInTargetContext = !!element.closest( - 'fig, graphic:not(fig graphic), disp-quote' - ) - return isInTargetContext ? {} : false - }, }, { tag: 'attrib', @@ -880,16 +882,7 @@ export class JATSDOMParser { this.getCaptionContent(node) ) const fig = this.getFigContent(element).content - const attributionIndex = findLastIndex( - fig, - (node) => node.type === this.schema.nodes.attribution - ) - const splitIndex = attributionIndex === -1 ? 1 : attributionIndex + 1 - return Fragment.from([ - ...fig.slice(0, splitIndex), - caption, - ...fig.slice(splitIndex), - ]) + return Fragment.from([...fig.slice(0, 1), caption, ...fig.slice(1)]) }, getAttrs: this.getFigAttrs, }, diff --git a/src/jats/importer/jats-transformations.ts b/src/jats/importer/jats-transformations.ts index 24c70037..5755b50a 100644 --- a/src/jats/importer/jats-transformations.ts +++ b/src/jats/importer/jats-transformations.ts @@ -296,6 +296,37 @@ export const moveCaptionsToEnd = (body: Element) => { } } +export const mergeAttributions = ( + doc: Document, + body: Element, + createElement: CreateElement +) => { + const attributions = [ + ...body.querySelectorAll('fig > attrib, graphic:not(fig graphic) attrib'), + ] + const attribsByParent = new Map() + attributions.forEach((attrib) => { + if (!attrib.parentElement) { + return + } + const group = attribsByParent.get(attrib.parentElement) ?? [] + attribsByParent.set(attrib.parentElement, [...group, attrib]) + }) + attribsByParent.forEach((group, parent) => { + const merged = createElement('attrib') + group.forEach((attrib, index) => { + if (index > 0) { + merged.append(doc.createTextNode(' ')) + } + merged.append( + ...Array.from(attrib.childNodes).map((n) => n.cloneNode(true)) + ) + removeNodeFromParent(attrib) + }) + parent.appendChild(merged) + }) +} + const prepareAbstract = ( abstract: Element, createElement: CreateElement, diff --git a/src/jats/importer/parse-jats-article.ts b/src/jats/importer/parse-jats-article.ts index ced78376..7403387e 100644 --- a/src/jats/importer/parse-jats-article.ts +++ b/src/jats/importer/parse-jats-article.ts @@ -30,6 +30,7 @@ import { createSupplementaryMaterialsSection, createTitles, fixTables, + mergeAttributions, moveAffiliations, moveAuthorNotes, moveAwards, @@ -67,6 +68,7 @@ const processJATS = (doc: Document, sectionCategories: SectionCategory[]) => { } moveCaptionsToEnd(body) + mergeAttributions(doc, body, createElement) createBody(doc, body, createElement) createAbstracts(front, body, createElement, sectionCategories) createBackmatter(doc, body, sectionCategories, createElement) diff --git a/src/schema/__tests__/docs.ts b/src/schema/__tests__/docs.ts index 8cd66b01..ee28d049 100644 --- a/src/schema/__tests__/docs.ts +++ b/src/schema/__tests__/docs.ts @@ -544,7 +544,34 @@ export const v2_3_20 = { src: 'attachment:figure1', contentType: 'image/png', dataTracked: null, + attribution: { + literal: 'Source: Wikipedia.', + }, + }, + }, + { + type: 'attribution', + attrs: { + dataTracked: null, }, + content: [ + { + type: 'text', + text: 'Source: Wikipedia.', + }, + ], + }, + { + type: 'attribution', + attrs: { + dataTracked: null, + }, + content: [ + { + type: 'text', + text: 'Source: Wiley Online Library.', + }, + ], }, { type: 'figcaption', diff --git a/src/schema/migration/migration-scripts/4.5.7.ts b/src/schema/migration/migration-scripts/4.5.7.ts new file mode 100644 index 00000000..a66decdc --- /dev/null +++ b/src/schema/migration/migration-scripts/4.5.7.ts @@ -0,0 +1,69 @@ +/*! + * © 2026 Atypon Systems LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { JSONProsemirrorNode } from '../../../types' +import { MigrationScript } from '../migration-script' + +class Migration457 implements MigrationScript { + fromVersion = '4.5.6' + toVersion = '4.5.7' + + migrateNode(node: JSONProsemirrorNode): JSONProsemirrorNode { + if (node.type !== 'figure_element') { + return node + } + + const attributionIndex = node.content?.findIndex( + (n) => n.type === 'attribution' + ) + + if (!attributionIndex || attributionIndex === -1) { + return node + } + + const attribution = node.content + ?.filter((n) => n.type === 'attribution') + .reduce( + (n, attrib) => ({ + ...attrib, + content: [...(attrib.content || []), ...(n.content || [])], + }), + { type: 'attribution', attrs: {} } + ) + + if (!attribution) { + return node + } + + const captionIndex = node.content?.findIndex((n) => n.type === 'caption') + + if (!captionIndex || captionIndex === -1) { + return node + } + + return { + ...node, + content: [ + ...(node.content?.slice(0, attributionIndex) || []), + ...(node.content?.slice(captionIndex, captionIndex + 1) || []), + attribution, + ...(node.content?.slice(captionIndex + 1) || []), + ], + } + } +} + +export default Migration457 diff --git a/src/schema/migration/migration-scripts/index.ts b/src/schema/migration/migration-scripts/index.ts index f3ea23b3..b8d97188 100644 --- a/src/schema/migration/migration-scripts/index.ts +++ b/src/schema/migration/migration-scripts/index.ts @@ -31,6 +31,7 @@ import Migration4335 from './4.3.35' import Migration442 from './4.4.2' import Migration447 from './4.4.7' import Migration453 from './4.5.3' +import Migration457 from './4.5.7' const migrations = [ new Migration125(), @@ -50,6 +51,7 @@ const migrations = [ new Migration442(), new Migration447(), new Migration453(), + new Migration457(), ] export default migrations diff --git a/src/schema/nodes/figure_element.ts b/src/schema/nodes/figure_element.ts index b7120590..db4587f5 100644 --- a/src/schema/nodes/figure_element.ts +++ b/src/schema/nodes/figure_element.ts @@ -28,7 +28,7 @@ export interface FigureElementNode extends ManuscriptNode { export const figureElement: NodeSpec = { content: - '(paragraph | figure | missing_figure | placeholder)+ attribution* caption alt_text long_desc (listing | placeholder)', + '(paragraph | figure | missing_figure | placeholder)+ caption attribution? alt_text long_desc (listing | placeholder)', attrs: { id: { default: '' }, dataTracked: { default: null }, diff --git a/src/schema/nodes/hero_image.ts b/src/schema/nodes/hero_image.ts index ba66625b..db3c674d 100644 --- a/src/schema/nodes/hero_image.ts +++ b/src/schema/nodes/hero_image.ts @@ -26,7 +26,7 @@ export interface HeroImageNode extends ManuscriptNode { } export const heroImage: NodeSpec = { - content: 'figure? attribution* alt_text long_desc', + content: 'figure? attribution? alt_text long_desc', attrs: { id: { default: '' }, type: { default: 'leading' }, diff --git a/src/schema/nodes/image_element.ts b/src/schema/nodes/image_element.ts index 49de9228..dd0d11de 100644 --- a/src/schema/nodes/image_element.ts +++ b/src/schema/nodes/image_element.ts @@ -25,7 +25,7 @@ export interface ImageElementNode extends ManuscriptNode { } export const imageElement: NodeSpec = { - content: 'figure? attribution* caption alt_text long_desc', + content: 'figure? caption attribution? alt_text long_desc', attrs: { id: { default: '' }, extLink: { default: '' }, From 89cdb8ed79b85950f56c19f494573ebb2addd0c1 Mon Sep 17 00:00:00 2001 From: asouqi Date: Mon, 14 Sep 2026 15:03:33 +0300 Subject: [PATCH 4/9] review update --- src/jats/exporter/jats-exporter.ts | 14 ++++++-------- src/jats/importer/jats-dom-parser.ts | 23 ++++------------------- src/jats/importer/jats-transformations.ts | 5 ++--- src/jats/importer/parse-jats-article.ts | 2 +- 4 files changed, 13 insertions(+), 31 deletions(-) diff --git a/src/jats/exporter/jats-exporter.ts b/src/jats/exporter/jats-exporter.ts index 0738f5a9..e52844f1 100644 --- a/src/jats/exporter/jats-exporter.ts +++ b/src/jats/exporter/jats-exporter.ts @@ -1150,16 +1150,14 @@ export class JATSExporter { } } } - const appendAttributions = ($element: Element, node: ManuscriptNode) => { - const attributions = this.getChildrenOfType( + const appendAttribution = ($element: Element, node: ManuscriptNode) => { + const attribution = this.getFirstChildOfType( schema.nodes.attribution, node ) - attributions.forEach((attribution) => { - if (attribution.content.size > 0) { + if (attribution && attribution.content.size > 0) { $element.appendChild(this.serializeNode(attribution)) - } - }) + } } const appendTable = ($element: Element, node: ManuscriptNode) => { @@ -1258,7 +1256,7 @@ export class JATSExporter { this.appendCaption($graphic, node) this.appendChildNodeOfType($graphic, node, schema.nodes.alt_text) this.appendChildNodeOfType($graphic, node, schema.nodes.long_desc) - appendAttributions($graphic, node) + appendAttribution($graphic, node) return $graphic } @@ -1301,7 +1299,7 @@ export class JATSExporter { processExecutableNode(node, $fig) } moveAltTextAndLongDescToGraphics($fig) - appendAttributions($fig, node) + appendAttribution($fig, node) return $fig } diff --git a/src/jats/importer/jats-dom-parser.ts b/src/jats/importer/jats-dom-parser.ts index f85cd52d..88a0f414 100644 --- a/src/jats/importer/jats-dom-parser.ts +++ b/src/jats/importer/jats-dom-parser.ts @@ -276,9 +276,10 @@ export class JATSDOMParser { private getFigContent = (node: Node) => { const element = node as HTMLElement const content = [this.schema.nodes.figure.create(this.getFigAttrs(element))] - const attributions = Array.from(element.querySelectorAll(':scope > attrib')) - if (attributions.length > 0) { - content.push(this.mergeAttributions(attributions)) + const attribution = element.querySelector('attrib') + if (attribution) { + const attributionNode = this.schema.nodes.attribution.create() + content.push(this.parse(attribution, { topNode: attributionNode })) } const altText = element.querySelector('alt-text') if (altText) { @@ -303,22 +304,6 @@ export class JATSDOMParser { ) } - private mergeAttributions = (attribs: Element[]) => { - const doc = attribs[0].ownerDocument - const wrapper = doc.createElement('attrib') - attribs.forEach((attrib, index) => { - if (index > 0) { - wrapper.append(doc.createTextNode(' ')) - } - wrapper.append( - ...Array.from(attrib.childNodes).map((n) => n.cloneNode(true)) - ) - }) - return this.parse(wrapper, { - topNode: this.schema.nodes.attribution.create(), - }) - } - private parseRefPages = (element: Element) => { const fpage = getTrimmedTextContent(element, 'fpage') const lpage = getTrimmedTextContent(element, 'lpage') diff --git a/src/jats/importer/jats-transformations.ts b/src/jats/importer/jats-transformations.ts index 5755b50a..203196bb 100644 --- a/src/jats/importer/jats-transformations.ts +++ b/src/jats/importer/jats-transformations.ts @@ -298,11 +298,10 @@ export const moveCaptionsToEnd = (body: Element) => { export const mergeAttributions = ( doc: Document, - body: Element, createElement: CreateElement ) => { const attributions = [ - ...body.querySelectorAll('fig > attrib, graphic:not(fig graphic) attrib'), + ...doc.querySelectorAll('fig > attrib, graphic:not(fig graphic) attrib'), ] const attribsByParent = new Map() attributions.forEach((attrib) => { @@ -316,7 +315,7 @@ export const mergeAttributions = ( const merged = createElement('attrib') group.forEach((attrib, index) => { if (index > 0) { - merged.append(doc.createTextNode(' ')) + merged.append(doc.createTextNode(', ')) } merged.append( ...Array.from(attrib.childNodes).map((n) => n.cloneNode(true)) diff --git a/src/jats/importer/parse-jats-article.ts b/src/jats/importer/parse-jats-article.ts index 7403387e..5ab07740 100644 --- a/src/jats/importer/parse-jats-article.ts +++ b/src/jats/importer/parse-jats-article.ts @@ -68,7 +68,7 @@ const processJATS = (doc: Document, sectionCategories: SectionCategory[]) => { } moveCaptionsToEnd(body) - mergeAttributions(doc, body, createElement) + mergeAttributions(doc, createElement) createBody(doc, body, createElement) createAbstracts(front, body, createElement, sectionCategories) createBackmatter(doc, body, sectionCategories, createElement) From fd97e35d8cfe77252dbb7f3a02f275e17ada8251 Mon Sep 17 00:00:00 2001 From: asouqi Date: Mon, 14 Sep 2026 15:50:44 +0300 Subject: [PATCH 5/9] review update --- src/jats/importer/jats-dom-parser.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/jats/importer/jats-dom-parser.ts b/src/jats/importer/jats-dom-parser.ts index 88a0f414..01a13313 100644 --- a/src/jats/importer/jats-dom-parser.ts +++ b/src/jats/importer/jats-dom-parser.ts @@ -613,13 +613,11 @@ export class JATSDOMParser { return Fragment.from(this.schema.text('_')) }, }, - { - tag: 'fig attrib, disp-quote attrib, graphic:not(fig graphic) attrib', - node: 'attribution', - }, { tag: 'attrib', - skip: true, + node: 'attribution', + context: + 'figure_element/|image_element/|hero_image/|blockquote_element/|pullquote_element/', }, { tag: 'back', @@ -964,6 +962,11 @@ export class JATSDOMParser { tag: 'p[content-type="headshots"]', node: 'headshot_grid', }, + { + tag: 'p', + node: 'text_block', + context: 'pullquote_element/|blockquote_element/', + }, { tag: 'p', node: 'paragraph', From c7647c2f0cb7e27ac5a33f8a6b37ad6d6ec1e15c Mon Sep 17 00:00:00 2001 From: asouqi Date: Tue, 15 Sep 2026 14:01:25 +0300 Subject: [PATCH 6/9] review update --- src/jats/exporter/jats-exporter.ts | 18 ++++++------------ src/jats/importer/jats-transformations.ts | 20 ++++++++------------ 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/jats/exporter/jats-exporter.ts b/src/jats/exporter/jats-exporter.ts index e52844f1..633ef02c 100644 --- a/src/jats/exporter/jats-exporter.ts +++ b/src/jats/exporter/jats-exporter.ts @@ -231,7 +231,7 @@ export class JATSExporter { type: ManuscriptNodeType ) => { const childNode = this.getFirstChildOfType(type, node) - if (childNode) { + if (childNode && childNode.content.size > 0) { $element.appendChild(this.serializeNode(childNode)) } } @@ -1150,15 +1150,6 @@ export class JATSExporter { } } } - const appendAttribution = ($element: Element, node: ManuscriptNode) => { - const attribution = this.getFirstChildOfType( - schema.nodes.attribution, - node - ) - if (attribution && attribution.content.size > 0) { - $element.appendChild(this.serializeNode(attribution)) - } - } const appendTable = ($element: Element, node: ManuscriptNode) => { const tableNode = this.getFirstChildOfType(schema.nodes.table, node) @@ -1256,7 +1247,7 @@ export class JATSExporter { this.appendCaption($graphic, node) this.appendChildNodeOfType($graphic, node, schema.nodes.alt_text) this.appendChildNodeOfType($graphic, node, schema.nodes.long_desc) - appendAttribution($graphic, node) + this.appendChildNodeOfType($graphic, node, schema.nodes.attribution) return $graphic } @@ -1299,7 +1290,7 @@ export class JATSExporter { processExecutableNode(node, $fig) } moveAltTextAndLongDescToGraphics($fig) - appendAttribution($fig, node) + this.appendChildNodeOfType($fig, node, schema.nodes.attribution) return $fig } @@ -1803,6 +1794,9 @@ export class JATSExporter { $graphic = this.serializeNode(node) as Element $floatsGroup.appendChild($graphic) } else { + if (node.type === schema.nodes.attribution && node.content.size === 0) { + return + } const $serializedNode = this.serializeNode(node) $graphic?.appendChild($serializedNode) } diff --git a/src/jats/importer/jats-transformations.ts b/src/jats/importer/jats-transformations.ts index 203196bb..6c71c885 100644 --- a/src/jats/importer/jats-transformations.ts +++ b/src/jats/importer/jats-transformations.ts @@ -300,20 +300,16 @@ export const mergeAttributions = ( doc: Document, createElement: CreateElement ) => { - const attributions = [ - ...doc.querySelectorAll('fig > attrib, graphic:not(fig graphic) attrib'), + const parents = [ + ...doc.querySelectorAll( + 'fig:has(> attrib), graphic:not(fig graphic):has( > attrib)' + ), ] - const attribsByParent = new Map() - attributions.forEach((attrib) => { - if (!attrib.parentElement) { - return - } - const group = attribsByParent.get(attrib.parentElement) ?? [] - attribsByParent.set(attrib.parentElement, [...group, attrib]) - }) - attribsByParent.forEach((group, parent) => { + + parents.forEach((parent) => { const merged = createElement('attrib') - group.forEach((attrib, index) => { + const attributions = parent.querySelectorAll(':scope > attrib') + attributions.forEach((attrib, index) => { if (index > 0) { merged.append(doc.createTextNode(', ')) } From 5c337a9218d2447f4d488ba825406aa1cade2a8a Mon Sep 17 00:00:00 2001 From: asouqi Date: Tue, 15 Sep 2026 14:09:39 +0300 Subject: [PATCH 7/9] lint --- src/jats/importer/jats-transformations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jats/importer/jats-transformations.ts b/src/jats/importer/jats-transformations.ts index 6c71c885..a691213f 100644 --- a/src/jats/importer/jats-transformations.ts +++ b/src/jats/importer/jats-transformations.ts @@ -302,7 +302,7 @@ export const mergeAttributions = ( ) => { const parents = [ ...doc.querySelectorAll( - 'fig:has(> attrib), graphic:not(fig graphic):has( > attrib)' + 'fig:has(> attrib), graphic:not(fig graphic):has(> attrib)' ), ] @@ -311,7 +311,7 @@ export const mergeAttributions = ( const attributions = parent.querySelectorAll(':scope > attrib') attributions.forEach((attrib, index) => { if (index > 0) { - merged.append(doc.createTextNode(', ')) + merged.append(doc.createTextNode(' ')) } merged.append( ...Array.from(attrib.childNodes).map((n) => n.cloneNode(true)) From 09a22725955e723cc3a09cfdc0503865878e1939 Mon Sep 17 00:00:00 2001 From: asouqi Date: Wed, 16 Sep 2026 12:22:34 +0300 Subject: [PATCH 8/9] review updates --- src/jats/__tests__/__snapshots__/jats-exporter.test.ts.snap | 6 +++--- src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap | 2 +- src/jats/importer/jats-transformations.ts | 6 +----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/jats/__tests__/__snapshots__/jats-exporter.test.ts.snap b/src/jats/__tests__/__snapshots__/jats-exporter.test.ts.snap index ef53ab1c..4c6e5d15 100644 --- a/src/jats/__tests__/__snapshots__/jats-exporter.test.ts.snap +++ b/src/jats/__tests__/__snapshots__/jats-exporter.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`JATS exporter > export latest version > jats-export 1`] = `"
This is the Article TitleWhich has a SubtitleMember, IEEEDayNathan123University of Life, Houston, TX, Houston, TX, USACorrespondence: nday@inera.com.

Author_Footnote.

DisclaimerThis is an unlabelled author footnote with a separate title.

This is information about Competing Interests.

120520090104200902052009

The direct interaction of phenobarbital with the epidermal growth factor receptor reveals a new network interface in nuclear receptor signaling.

Introduction

This is a sectionalized abstract.

Results

These are the results of the experiment.

ParagraphsexportingtestingNLM DTD
IntroductionA level 2 subheadA level 3 subheadA level 4 subheadA level 5 subhead

Materials and Methods

Some more text

Results

Some text that introduces a poem:

Poetry Source: Wikipedia

You might1 peruse, e.g., Box 1. See also .

Boxed boxes.Box_Head_1Box_Head_2

Box_Textc

See also, 1 and . Kinds of tables This is a table title continued paragraph Table_Head Table_Body* * Table_Footnote Block_Quote Source: Wikipedia Paragraph_Continued with a graphic in the middlewhere the paragraph continues.

Num_Bul_List_1

List_1_Continued

Num_Bul_List_2

List_2_Continued

level 1, bullet 2

Discussion

ParagraphUnnumbered_List_1 Another unnumbered itemwith another paragraph that continues after a list.

Conclusions

Paragraph

This is an appendixAppendix_Head_1Appendix_Head_2

Appendix_Text

Acknowledgements

Acknowledgement

Data availability

This is some information about data availability.

Ethics statement

Paragraph, seeb and Figure 1.

A picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

A missing picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

Author Contibutions are described here.

This is a Financial Disclosure.

Footnotes

Box Note

This is a footnote.

Footnote called out in a box

Day RN. This is the article title. EDCC. 2008;3(2):123-134.
"`; +exports[`JATS exporter > export latest version > jats-export 1`] = `"
This is the Article TitleWhich has a SubtitleMember, IEEEDayNathan123University of Life, Houston, TX, Houston, TX, USACorrespondence: nday@inera.com.

Author_Footnote.

DisclaimerThis is an unlabelled author footnote with a separate title.

This is information about Competing Interests.

120520090104200902052009

The direct interaction of phenobarbital with the epidermal growth factor receptor reveals a new network interface in nuclear receptor signaling.

Introduction

This is a sectionalized abstract.

Results

These are the results of the experiment.

ParagraphsexportingtestingNLM DTD
IntroductionA level 2 subheadA level 3 subheadA level 4 subheadA level 5 subhead

Materials and Methods

Some more text

Results

Some text that introduces a poem:

Poetry Source: Wikipedia

You might1 peruse, e.g., Box 1. See also .

Boxed boxes.Box_Head_1Box_Head_2

Box_Textc

See also, 1 and . Kinds of tables This is a table title continued paragraph Table_Head Table_Body* * Table_Footnote Block_Quote Source: WikipediaParagraph_Continued with a graphic in the middlewhere the paragraph continues.

Num_Bul_List_1

List_1_Continued

Num_Bul_List_2

List_2_Continued

level 1, bullet 2

Discussion

ParagraphUnnumbered_List_1 Another unnumbered itemwith another paragraph that continues after a list.

Conclusions

Paragraph

This is an appendixAppendix_Head_1Appendix_Head_2

Appendix_Text

Acknowledgements

Acknowledgement

Data availability

This is some information about data availability.

Ethics statement

Paragraph, seeb and Figure 1.

A picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

A missing picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

Author Contibutions are described here.

This is a Financial Disclosure.

Footnotes

Box Note

This is a footnote.

Footnote called out in a box

Day RN. This is the article title. EDCC. 2008;3(2):123-134.
"`; -exports[`JATS exporter > export v1.1 > jats-export-1.1 1`] = `"
This is the Article TitleWhich has a SubtitleMember, IEEEDayNathan123University of Life, Houston, TX, Houston, TX, USACorrespondence: nday@inera.com.

Author_Footnote.

DisclaimerThis is an unlabelled author footnote with a separate title.

This is information about Competing Interests.

120520090104200902052009

The direct interaction of phenobarbital with the epidermal growth factor receptor reveals a new network interface in nuclear receptor signaling.

Introduction

This is a sectionalized abstract.

Results

These are the results of the experiment.

ParagraphsexportingtestingNLM DTD
IntroductionA level 2 subheadA level 3 subheadA level 4 subheadA level 5 subhead

Materials and Methods

Some more text

Results

Some text that introduces a poem:

Poetry Source: Wikipedia

You might1 peruse, e.g., Box 1. See also .

Boxed boxes.Box_Head_1Box_Head_2

Box_Textc

See also, 1 and . Kinds of tables This is a table title continued paragraph Table_Head Table_Body* * Table_Footnote Block_Quote Source: Wikipedia Paragraph_Continued with a graphic in the middlewhere the paragraph continues.

Num_Bul_List_1

List_1_Continued

Num_Bul_List_2

List_2_Continued

level 1, bullet 2

Discussion

ParagraphUnnumbered_List_1 Another unnumbered itemwith another paragraph that continues after a list.

Conclusions

Paragraph

This is an appendixAppendix_Head_1Appendix_Head_2

Appendix_Text

Acknowledgements

Acknowledgement

Data availability

This is some information about data availability.

Ethics statement

Paragraph, seeb and Figure 1.

A picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

A missing picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

Author Contibutions are described here.

This is a Financial Disclosure.

Footnotes

Box Note

This is a footnote.

Footnote called out in a box

Day RN. This is the article title. EDCC. 2008;3(2):123-134.
"`; +exports[`JATS exporter > export v1.1 > jats-export-1.1 1`] = `"
This is the Article TitleWhich has a SubtitleMember, IEEEDayNathan123University of Life, Houston, TX, Houston, TX, USACorrespondence: nday@inera.com.

Author_Footnote.

DisclaimerThis is an unlabelled author footnote with a separate title.

This is information about Competing Interests.

120520090104200902052009

The direct interaction of phenobarbital with the epidermal growth factor receptor reveals a new network interface in nuclear receptor signaling.

Introduction

This is a sectionalized abstract.

Results

These are the results of the experiment.

ParagraphsexportingtestingNLM DTD
IntroductionA level 2 subheadA level 3 subheadA level 4 subheadA level 5 subhead

Materials and Methods

Some more text

Results

Some text that introduces a poem:

Poetry Source: Wikipedia

You might1 peruse, e.g., Box 1. See also .

Boxed boxes.Box_Head_1Box_Head_2

Box_Textc

See also, 1 and . Kinds of tables This is a table title continued paragraph Table_Head Table_Body* * Table_Footnote Block_Quote Source: WikipediaParagraph_Continued with a graphic in the middlewhere the paragraph continues.

Num_Bul_List_1

List_1_Continued

Num_Bul_List_2

List_2_Continued

level 1, bullet 2

Discussion

ParagraphUnnumbered_List_1 Another unnumbered itemwith another paragraph that continues after a list.

Conclusions

Paragraph

This is an appendixAppendix_Head_1Appendix_Head_2

Appendix_Text

Acknowledgements

Acknowledgement

Data availability

This is some information about data availability.

Ethics statement

Paragraph, seeb and Figure 1.

A picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

A missing picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

Author Contibutions are described here.

This is a Financial Disclosure.

Footnotes

Box Note

This is a footnote.

Footnote called out in a box

Day RN. This is the article title. EDCC. 2008;3(2):123-134.
"`; -exports[`JATS exporter > export with & and < in bibliography metadata > jats-export-with-xml-unsafe-in-biblios 1`] = `"
This is the Article TitleWhich has a SubtitleMember, IEEEDayNathan123University of Life, Houston, TX, Houston, TX, USACorrespondence: nday@inera.com.

Author_Footnote.

DisclaimerThis is an unlabelled author footnote with a separate title.

This is information about Competing Interests.

120520090104200902052009

The direct interaction of phenobarbital with the epidermal growth factor receptor reveals a new network interface in nuclear receptor signaling.

Introduction

This is a sectionalized abstract.

Results

These are the results of the experiment.

ParagraphsexportingtestingNLM DTD
IntroductionA level 2 subheadA level 3 subheadA level 4 subheadA level 5 subhead

Materials and Methods

Some more text

Results

Some text that introduces a poem:

Poetry Source: Wikipedia

You might1 peruse, e.g., Box 1. See also .

Boxed boxes.Box_Head_1Box_Head_2

Box_Textc

See also, 1 and . Kinds of tables This is a table title continued paragraph Table_Head Table_Body* * Table_Footnote Block_Quote Source: Wikipedia Paragraph_Continued with a graphic in the middlewhere the paragraph continues.

Num_Bul_List_1

List_1_Continued

Num_Bul_List_2

List_2_Continued

level 1, bullet 2

Discussion

ParagraphUnnumbered_List_1 Another unnumbered itemwith another paragraph that continues after a list.

Conclusions

Paragraph

This is an appendixAppendix_Head_1Appendix_Head_2

Appendix_Text

Acknowledgements

Acknowledgement

Data availability

This is some information about data availability.

Ethics statement

Paragraph, seeb and Figure 1.

A picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

A missing picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

Author Contibutions are described here.

This is a Financial Disclosure.

Footnotes

Box Note

This is a footnote.

Footnote called out in a box

Day RN. This is the article title. & Sons 55 < 135. EDCC. 2008;3(2):123-134.
"`; +exports[`JATS exporter > export with & and < in bibliography metadata > jats-export-with-xml-unsafe-in-biblios 1`] = `"
This is the Article TitleWhich has a SubtitleMember, IEEEDayNathan123University of Life, Houston, TX, Houston, TX, USACorrespondence: nday@inera.com.

Author_Footnote.

DisclaimerThis is an unlabelled author footnote with a separate title.

This is information about Competing Interests.

120520090104200902052009

The direct interaction of phenobarbital with the epidermal growth factor receptor reveals a new network interface in nuclear receptor signaling.

Introduction

This is a sectionalized abstract.

Results

These are the results of the experiment.

ParagraphsexportingtestingNLM DTD
IntroductionA level 2 subheadA level 3 subheadA level 4 subheadA level 5 subhead

Materials and Methods

Some more text

Results

Some text that introduces a poem:

Poetry Source: Wikipedia

You might1 peruse, e.g., Box 1. See also .

Boxed boxes.Box_Head_1Box_Head_2

Box_Textc

See also, 1 and . Kinds of tables This is a table title continued paragraph Table_Head Table_Body* * Table_Footnote Block_Quote Source: WikipediaParagraph_Continued with a graphic in the middlewhere the paragraph continues.

Num_Bul_List_1

List_1_Continued

Num_Bul_List_2

List_2_Continued

level 1, bullet 2

Discussion

ParagraphUnnumbered_List_1 Another unnumbered itemwith another paragraph that continues after a list.

Conclusions

Paragraph

This is an appendixAppendix_Head_1Appendix_Head_2

Appendix_Text

Acknowledgements

Acknowledgement

Data availability

This is some information about data availability.

Ethics statement

Paragraph, seeb and Figure 1.

A picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

A missing picture on the kinds of figures.

The Fig Caption continues.

test paragraph

Source: Wikipedia.

Author Contibutions are described here.

This is a Financial Disclosure.

Footnotes

Box Note

This is a footnote.

Footnote called out in a box

Day RN. This is the article title. & Sons 55 < 135. EDCC. 2008;3(2):123-134.
"`; diff --git a/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap b/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap index a416332d..375ef745 100644 --- a/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap +++ b/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap @@ -31855,7 +31855,7 @@ Object { "type": "cross_reference", }, Object { - "text": ". Kinds of tables This is a table title continued paragraph Table_Head Table_Body* * Table_Footnote Block_Quote Source: Wikipedia Paragraph_Continued with a graphic in the middlewhere the paragraph continues.", + "text": ". Kinds of tables This is a table title continued paragraph Table_Head Table_Body* * Table_Footnote Block_Quote Source: WikipediaParagraph_Continued with a graphic in the middlewhere the paragraph continues.", "type": "text", }, ], diff --git a/src/jats/importer/jats-transformations.ts b/src/jats/importer/jats-transformations.ts index a691213f..4e3fa24f 100644 --- a/src/jats/importer/jats-transformations.ts +++ b/src/jats/importer/jats-transformations.ts @@ -300,11 +300,7 @@ export const mergeAttributions = ( doc: Document, createElement: CreateElement ) => { - const parents = [ - ...doc.querySelectorAll( - 'fig:has(> attrib), graphic:not(fig graphic):has(> attrib)' - ), - ] + const parents = [...doc.querySelectorAll(':has(> attrib)')] parents.forEach((parent) => { const merged = createElement('attrib') From be4b54aeba0c05c1139aaa120f0c6781dc19d9d9 Mon Sep 17 00:00:00 2001 From: "zax-assistant[bot]" <103364958+zax-assistant[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:54:59 +0000 Subject: [PATCH 9/9] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b889eb9..d06d84a3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@manuscripts/transform", "description": "ProseMirror transformer for Manuscripts applications", - "version": "4.5.7", + "version": "4.5.8-LEAN-6032.0", "repository": "github:Atypon-OpenSource/manuscripts-transform", "license": "Apache-2.0", "main": "dist/cjs",