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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/perfect-numbers-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"crossref-utils": patch
---

Add chair contributor_type option
5 changes: 5 additions & 0 deletions .changeset/pretty-otters-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"crossref-utils": patch
---

Remove xrefs from abstract tree
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ This will prompt the user to select new DOIs, if they are not present in MyST me
- `--name`, `--email`: Depositor name and email. Default Depositor is Curvenote.
- `--registrant`: Registrant organization. Default is `Crossref` - likely this should not be changed.
- `--id`: Unique ID for the deposit. By default, a random ID will be autogenerated - likely this should not be changed.
- `--contributor-type`: For **conference** deposits only, sets the `contributor_role` for people listed under MyST `editors` in the proceedings metadata. Values are `editor` (default) or `chair`. Other deposit types ignore this flag.

### Deposit types

Expand Down Expand Up @@ -85,7 +86,8 @@ You may also specify in the frontmatter:
- `venue.issn` - series issn
- `venue.doi` - series doi
- `volume.subject` - proceedings subject
- `editors` - proceedings editors
- `volume.doi` - proceedings doi
- `editors` - proceedings editors (or "chairs" if `--contributor-type chair` is specified)

#### Dataset

Expand Down
45 changes: 34 additions & 11 deletions src/cli/deposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
transformCiteToText,
transformNewlineToSpace,
transformXrefToLink,
unwrapJatsXrefElements,
} from './utils.js';
import type { ProjectFrontmatter } from 'myst-frontmatter';
import { selectNewDois } from './generate.js';
Expand All @@ -57,6 +58,7 @@ type DepositOptions = {
journalAbbr?: string;
journalDoi?: string;
prefix?: string;
contributorType?: 'editor' | 'chair';
};

type DepositSource = {
Expand Down Expand Up @@ -130,11 +132,13 @@ export async function depositArticleFromSource(session: ISession, depositSource:
transformNewlineToSpace(abstractPart);
const serializer = new JatsSerializer(new VFile(), abstractPart as any);
const jats = serializer.render(true).elements();
abstract = u(
'element',
{ name: 'jats:abstract' },
jats.map((e) => element2JatsUnist(e)),
) as Element;
abstract = unwrapJatsXrefElements(
u(
'element',
{ name: 'jats:abstract' },
jats.map((e) => element2JatsUnist(e)),
) as Element,
);
} else if (description) {
// Use the project description as the fallback for the abstract
abstractPart = {
Expand All @@ -144,11 +148,13 @@ export async function depositArticleFromSource(session: ISession, depositSource:
transformNewlineToSpace(abstractPart);
const serializer = new JatsSerializer(new VFile(), abstractPart as any);
const jats = serializer.render(true).elements();
abstract = u(
'element',
{ name: 'jats:abstract' },
jats.map((e) => element2JatsUnist(e)),
) as Element;
abstract = unwrapJatsXrefElements(
u(
'element',
{ name: 'jats:abstract' },
jats.map((e) => element2JatsUnist(e)),
) as Element,
);
}
return { frontmatter: frontmatter ?? {}, dois, abstract, configFile };
}
Expand Down Expand Up @@ -369,7 +375,11 @@ function issueDataFromArticles(
}
}
if (editors?.length && !proceedingsEditors) {
proceedingsEditors = contributorsXmlFromMystEditors(frontmatter);
const proceedingsEditorRole =
opts.type === 'conference' && opts.contributorType === 'chair' ? 'chair' : 'editor';
proceedingsEditors = contributorsXmlFromMystEditors(frontmatter, {
contributor_role: proceedingsEditorRole,
});
}
});
if (!publicationDate && (volumeNumber || volumeDoi || issueNumber || issueDoi)) {
Expand Down Expand Up @@ -419,6 +429,11 @@ export async function deposit(session: ISession, opts: DepositOptions) {
if (!depositType) {
throw new Error('No deposit type specified');
}
if (depositType !== 'conference' && opts.contributorType === 'chair') {
session.log.warn(
'`--contributor-type chair` only applies to conference deposits; using editor role for this deposit type.',
);
}
if (!name) {
const resp = await inquirer.prompt([
{
Expand Down Expand Up @@ -661,6 +676,14 @@ function makeDepositCLI(program: Command) {
.addOption(new Option('--registrant <value>', 'Registrant organization').default('Crossref'))
.addOption(new Option('-o, --output <value>', 'Output file'))
.addOption(new Option('--prefix <value>', 'Prefix for new DOIs'))
.addOption(
new Option(
'--contributor-type <value>',
'Contributor role for myst `editors` in conference proceedings',
)
.choices(['editor', 'chair'])
.default('editor'),
)
.action(clirun(deposit, { program, getSession: (logger) => new Session({ logger }) }));
return command;
}
Expand Down
24 changes: 24 additions & 0 deletions src/cli/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from 'node:fs';
import { u } from 'unist-builder';
import { selectAll } from 'unist-util-select';
import { liftChildren, type GenericNode, type GenericParent } from 'myst-common';
import type { Element, ElementContent } from 'xast';

type JatsAttributes = Record<string, string | undefined>;

Expand Down Expand Up @@ -30,6 +31,29 @@ export function element2JatsUnist(element: JatsElement): Node {
throw new Error(`Invalid Jats element: ${element}`);
}

/**
* Remove `jats:xref` wrappers from a JATS xast subtree, keeping only their children.
*/
export function unwrapJatsXrefElements(node: Element): Element {
const children = node.children ?? [];
const newChildren: ElementContent[] = [];
for (const child of children) {
if (child.type === 'element') {
const processed = unwrapJatsXrefElements(child);
if (processed.name === 'jats:xref') {
for (const inner of processed.children) {
newChildren.push(inner.type === 'element' ? unwrapJatsXrefElements(inner) : inner);
}
} else {
newChildren.push(processed);
}
} else {
newChildren.push(child);
}
}
return { ...node, children: newChildren };
}

/**
* Transform to handle xrefs that resolve to external sites
*
Expand Down
8 changes: 6 additions & 2 deletions src/contributors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ export function contributorsXmlFromMystAuthors(
);
}

export function contributorsXmlFromMystEditors(myst: PageFrontmatter): Element | undefined {
export function contributorsXmlFromMystEditors(
myst: PageFrontmatter,
opts?: { contributor_role?: ContributorOptions['contributor_role'] },
): Element | undefined {
const contributor_role = opts?.contributor_role ?? 'editor';
const editors =
myst.editors
?.map((editor) => myst.contributors?.find(({ id }) => editor === id))
Expand All @@ -88,7 +92,7 @@ export function contributorsXmlFromMystEditors(myst: PageFrontmatter): Element |
contributorXml({
...(editor as ContributorOptions),
sequence: index === 0 ? 'first' : 'additional',
contributor_role: 'editor',
contributor_role,
}),
),
);
Expand Down
26 changes: 26 additions & 0 deletions tests/contributors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, test, expect } from 'vitest';
import { toXml } from 'xast-util-to-xml';
import type { PageFrontmatter } from 'myst-frontmatter';
import { contributorsXmlFromMystEditors } from '../src/contributors.js';

describe('contributorsXmlFromMystEditors', () => {
const baseMyst = {
editors: ['ed1'],
contributors: [
{
id: 'ed1',
nameParsed: { given: 'Pat', family: 'Organizer', literal: 'Pat Organizer' },
},
],
} as unknown as PageFrontmatter;

test('defaults myst editors to Crossref contributor_role editor', () => {
const el = contributorsXmlFromMystEditors(baseMyst);
expect(toXml(el!)).toContain('contributor_role="editor"');
});

test('supports chair role for proceedings editors', () => {
const el = contributorsXmlFromMystEditors(baseMyst, { contributor_role: 'chair' });
expect(toXml(el!)).toContain('contributor_role="chair"');
});
});
30 changes: 30 additions & 0 deletions tests/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, test, expect } from 'vitest';
import { u } from 'unist-builder';
import { toXml } from 'xast-util-to-xml';
import { publicationDateXml } from '../src';
import { unwrapJatsXrefElements } from '../src/cli/utils.js';
import type { Element } from 'xast';

describe('CrossRef Utilities', () => {
Expand Down Expand Up @@ -29,3 +31,31 @@ describe('CrossRef Utilities', () => {
}
});
});

describe('unwrapJatsXrefElements', () => {
test('removes jats:xref wrapper, keeps children', () => {
const tree = u('element', { name: 'jats:p', attributes: {} }, [
u('text', 'See '),
u('element', { name: 'jats:xref', attributes: { 'ref-type': 'fig', rid: 'f1' } }, [
u('element', { name: 'jats:bold', attributes: {} }, [u('text', 'Figure 1')]),
]),
u('text', ' for details.'),
]) as Element;
const out = unwrapJatsXrefElements(tree);
const xml = toXml(out);
expect(xml).not.toContain('xref');
expect(xml).toContain('Figure 1');
expect(xml).toContain('See ');
expect(xml).toContain('for details.');
});

test('unwraps nested jats:xref', () => {
const tree = u('element', { name: 'jats:p', attributes: {} }, [
u('element', { name: 'jats:xref', attributes: {} }, [
u('element', { name: 'jats:xref', attributes: {} }, [u('text', 'inner')]),
]),
]) as Element;
const out = unwrapJatsXrefElements(tree);
expect(toXml(out)).toBe('<jats:p>inner</jats:p>');
});
});
Loading