@uniweb/scholar 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -32
- package/package.json +19 -6
- package/src/bibliography/formatters/index.js +62 -23
- package/src/bibliography/index.js +8 -29
- package/src/bibliography/normalize.js +234 -0
- package/src/bibliography/parsers/index.js +35 -1
- package/src/citations/Bibliography.jsx +26 -54
- package/src/citations/Citation.jsx +35 -145
- package/src/citations/CitationProvider.jsx +83 -85
- package/src/components/AuthorList.jsx +65 -40
- package/src/components/BibtexBlock.jsx +1 -1
- package/src/components/CiteButton.jsx +78 -45
- package/src/components/DoiLink.jsx +27 -21
- package/src/index.js +15 -5
- package/src/math/Equation.jsx +45 -75
- package/src/math/index.js +19 -4
- package/src/bibliography/formatters/apa.js +0 -352
- package/src/bibliography/formatters/chicago.js +0 -339
- package/src/bibliography/formatters/ieee.js +0 -332
- package/src/bibliography/formatters/mla.js +0 -335
- package/src/bibliography/parsers/bibtex.js +0 -405
- package/src/bibliography/utils/authors.js +0 -277
- package/src/bibliography/utils/dates.js +0 -228
- package/src/bibliography/utils/index.js +0 -8
- package/src/math/Math.jsx +0 -115
- package/src/math/katex-loader.js +0 -105
package/README.md
CHANGED
|
@@ -12,28 +12,21 @@ pnpm add @uniweb/scholar
|
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
|
-
- **
|
|
15
|
+
- **Numbered Equations** - Cross-referenceable display equations
|
|
16
16
|
- **Bibliography** - Format references in APA, MLA, Chicago, and IEEE styles
|
|
17
17
|
- **Citations** - Track inline citations with automatic bibliography generation
|
|
18
18
|
- **BibTeX** - Parse and export BibTeX format
|
|
19
19
|
- **UI Components** - Ready-to-use citation buttons, author lists, and DOI links
|
|
20
20
|
|
|
21
|
+
> **Plain LaTeX math** (inline `$x$`, display `$$x$$`, fenced ```` ```math ````) is handled by Uniweb's content pipeline — the markdown parser compiles LaTeX to MathML Core at build time and the browser renders it natively. No runtime math library needed; no `<Math>` component required.
|
|
22
|
+
|
|
21
23
|
## Quick Start
|
|
22
24
|
|
|
23
|
-
###
|
|
25
|
+
### Numbered Equations
|
|
24
26
|
|
|
25
27
|
```jsx
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
// Inline math
|
|
29
|
-
<p>The equation <Math>E = mc^2</Math> shows mass-energy equivalence.</p>
|
|
28
|
+
import { Equation, EquationRef, EquationProvider } from '@uniweb/scholar/math'
|
|
30
29
|
|
|
31
|
-
// Display (block) math
|
|
32
|
-
<Math display>
|
|
33
|
-
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
|
|
34
|
-
</Math>
|
|
35
|
-
|
|
36
|
-
// Numbered equations with cross-references
|
|
37
30
|
<EquationProvider>
|
|
38
31
|
<Equation id="einstein">E = mc^2</Equation>
|
|
39
32
|
<p>As shown in <EquationRef id="einstein" />, energy equals mass times the speed of light squared.</p>
|
|
@@ -97,29 +90,49 @@ const bibtex = `
|
|
|
97
90
|
|
|
98
91
|
### Math Module
|
|
99
92
|
|
|
100
|
-
|
|
93
|
+
Scholar does not ship a math renderer. Plain LaTeX in markdown is compiled to MathML by Uniweb's content pipeline and rendered natively by the browser — see the framework docs for the `$x$`, `$$x$$`, and ```math fence syntax.
|
|
101
94
|
|
|
102
|
-
|
|
95
|
+
`<Equation>` and `<EquationRef>` cover the distinct feature of **numbered cross-referenceable equations**. They consume pre-compiled MathML from `content.math` and are fully SSR-safe.
|
|
103
96
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
97
|
+
Label an equation in markdown with a `:<id>` suffix on the fence:
|
|
98
|
+
|
|
99
|
+
````markdown
|
|
100
|
+
```math:einstein
|
|
101
|
+
E = mc^2
|
|
102
|
+
```
|
|
103
|
+
````
|
|
104
|
+
|
|
105
|
+
Then in a foundation's section component:
|
|
106
|
+
|
|
107
|
+
```jsx
|
|
108
|
+
import { Equation, EquationProvider, EquationRef } from '@uniweb/scholar/math'
|
|
109
|
+
|
|
110
|
+
function MyBlock({ content }) {
|
|
111
|
+
return (
|
|
112
|
+
<EquationProvider>
|
|
113
|
+
{content.math?.filter(m => m.id).map(m => (
|
|
114
|
+
<Equation key={m.id} id={m.id} mathml={m.mathml} />
|
|
115
|
+
))}
|
|
116
|
+
<p>As shown in <EquationRef id="einstein" />, energy equals mc².</p>
|
|
117
|
+
</EquationProvider>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
```
|
|
109
121
|
|
|
110
122
|
#### `<Equation>`
|
|
111
123
|
|
|
112
|
-
|
|
124
|
+
Numbered display equation rendered from pre-compiled MathML.
|
|
113
125
|
|
|
114
126
|
| Prop | Type | Default | Description |
|
|
115
127
|
|------|------|---------|-------------|
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `label` | `string` | - |
|
|
128
|
+
| `id` | `string` | auto | Identifier for cross-referencing |
|
|
129
|
+
| `mathml` | `string` | - | Pre-compiled MathML from `content.math[i].mathml` |
|
|
130
|
+
| `label` | `string` | - | Override the auto-number with a custom label |
|
|
131
|
+
| `className` | `string` | - | CSS classes on the wrapper |
|
|
119
132
|
|
|
120
133
|
#### `<EquationRef>`
|
|
121
134
|
|
|
122
|
-
|
|
135
|
+
Link to a numbered equation within the same `<EquationProvider>`.
|
|
123
136
|
|
|
124
137
|
| Prop | Type | Default | Description |
|
|
125
138
|
|------|------|---------|-------------|
|
|
@@ -128,7 +141,7 @@ Creates a reference link to a numbered equation.
|
|
|
128
141
|
|
|
129
142
|
#### `<EquationProvider>`
|
|
130
143
|
|
|
131
|
-
Context provider for equation numbering. Wrap content containing equations.
|
|
144
|
+
Context provider for equation numbering. Wrap content containing equations and their references.
|
|
132
145
|
|
|
133
146
|
### Citations Module
|
|
134
147
|
|
|
@@ -280,17 +293,14 @@ import { DoiLink } from '@uniweb/scholar/components'
|
|
|
280
293
|
|
|
281
294
|
## Bundle Size
|
|
282
295
|
|
|
283
|
-
|
|
296
|
+
Scholar ships no math renderer — MathML is pre-compiled upstream and rendered natively by the browser. Approximate subpath sizes:
|
|
284
297
|
|
|
285
298
|
| Module | Size |
|
|
286
299
|
|--------|------|
|
|
287
300
|
| bibliography (formatters, parsers) | ~15KB |
|
|
288
|
-
| math (
|
|
289
|
-
|
|
|
290
|
-
|
|
|
291
|
-
| components | ~8KB |
|
|
292
|
-
|
|
293
|
-
KaTeX CSS is automatically injected when math rendering is first used.
|
|
301
|
+
| math (numbered equations, refs) | ~5KB |
|
|
302
|
+
| citations | ~10KB |
|
|
303
|
+
| components | ~8KB |
|
|
294
304
|
|
|
295
305
|
## License
|
|
296
306
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/scholar",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Academic content utilities for Uniweb -
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Academic content utilities for Uniweb - numbered equations with cross-references, bibliographic references, and inline citations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.js",
|
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
"uniweb",
|
|
19
19
|
"scholar",
|
|
20
20
|
"academic",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"latex",
|
|
21
|
+
"equations",
|
|
22
|
+
"mathml",
|
|
24
23
|
"bibliography",
|
|
25
24
|
"citations",
|
|
26
25
|
"bibtex",
|
|
@@ -41,9 +40,23 @@
|
|
|
41
40
|
"node": ">=20.19"
|
|
42
41
|
},
|
|
43
42
|
"dependencies": {
|
|
44
|
-
"
|
|
43
|
+
"@citestyle/bibtex": "^1.0.0",
|
|
44
|
+
"@citestyle/core": "^1.0.0",
|
|
45
|
+
"@citestyle/registry": "^1.0.0",
|
|
46
|
+
"@citestyle/styles": "^1.0.0"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
47
49
|
"react": "^18.0.0 || ^19.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@testing-library/react": "^16.0.0",
|
|
53
|
+
"jsdom": "^25.0.0",
|
|
54
|
+
"react": "^19.0.0",
|
|
55
|
+
"react-dom": "^19.0.0",
|
|
56
|
+
"vitest": "^2.0.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest"
|
|
48
61
|
}
|
|
49
62
|
}
|
|
@@ -1,39 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bibliography Formatters
|
|
3
3
|
*
|
|
4
|
+
* Delegates to @citestyle/* compiled styles for reference formatting.
|
|
5
|
+
* Returns structured output: { html, text, parts, links }.
|
|
6
|
+
*
|
|
4
7
|
* @module @uniweb/scholar/bibliography/formatters
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
import * as apa from '@citestyle/styles/apa'
|
|
11
|
+
import * as mla from '@citestyle/styles/mla'
|
|
12
|
+
import * as chicagoAD from '@citestyle/styles/chicago-author-date'
|
|
13
|
+
import * as ieee from '@citestyle/styles/ieee'
|
|
14
|
+
import * as harvard from '@citestyle/styles/harvard'
|
|
15
|
+
import * as vancouver from '@citestyle/styles/vancouver'
|
|
16
|
+
import { toCslJson } from '../normalize.js'
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Compiled styles keyed by short name.
|
|
20
|
+
* Each value is a compiled style module with bibliography(), citation(), meta.
|
|
21
|
+
*/
|
|
22
|
+
export const compiledStyles = {
|
|
23
|
+
apa,
|
|
24
|
+
mla,
|
|
25
|
+
chicago: chicagoAD,
|
|
26
|
+
ieee,
|
|
27
|
+
harvard,
|
|
28
|
+
vancouver,
|
|
29
|
+
}
|
|
16
30
|
|
|
17
31
|
/**
|
|
18
|
-
*
|
|
32
|
+
* Human-readable labels for each style.
|
|
19
33
|
*/
|
|
20
|
-
|
|
21
|
-
apa:
|
|
22
|
-
mla:
|
|
23
|
-
chicago:
|
|
24
|
-
ieee:
|
|
34
|
+
const styleLabels = {
|
|
35
|
+
apa: 'APA (7th ed.)',
|
|
36
|
+
mla: 'MLA (9th ed.)',
|
|
37
|
+
chicago: 'Chicago (Author-Date)',
|
|
38
|
+
ieee: 'IEEE',
|
|
39
|
+
harvard: 'Harvard',
|
|
40
|
+
vancouver: 'Vancouver',
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
/**
|
|
28
|
-
* Format a reference in a specific style
|
|
44
|
+
* Format a reference in a specific citation style.
|
|
45
|
+
*
|
|
46
|
+
* Accepts Scholar-shaped publication objects OR CSL-JSON items.
|
|
47
|
+
* Returns structured output from @citestyle — { html, text, parts, links }.
|
|
29
48
|
*
|
|
30
|
-
* @param {Object} publication - Publication data
|
|
49
|
+
* @param {Object} publication - Publication data (Scholar or CSL-JSON)
|
|
31
50
|
* @param {Object} [options]
|
|
32
|
-
* @param {string} [options.style='apa'] - Citation style
|
|
33
|
-
* @returns {string
|
|
51
|
+
* @param {string} [options.style='apa'] - Citation style name
|
|
52
|
+
* @returns {{ html: string, text: string, parts: Object, links: Object }}
|
|
34
53
|
*
|
|
35
54
|
* @example
|
|
36
|
-
* const
|
|
55
|
+
* const entry = formatReference({
|
|
37
56
|
* type: 'article',
|
|
38
57
|
* authors: [{ family: 'Smith', given: 'John' }],
|
|
39
58
|
* year: 2024,
|
|
@@ -43,14 +62,34 @@ export const formatters = {
|
|
|
43
62
|
* pages: '1-10',
|
|
44
63
|
* doi: '10.1234/example'
|
|
45
64
|
* }, { style: 'apa' })
|
|
65
|
+
*
|
|
66
|
+
* entry.text // "Smith, J. (2024). A Study of Something. ..."
|
|
67
|
+
* entry.html // "<span class=\"csl-entry\">Smith, J. (2024)..."
|
|
46
68
|
*/
|
|
47
69
|
export function formatReference(publication, options = {}) {
|
|
48
70
|
const { style = 'apa' } = options
|
|
49
71
|
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
52
|
-
throw new Error(
|
|
72
|
+
const compiled = compiledStyles[style.toLowerCase()]
|
|
73
|
+
if (!compiled) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Unknown citation style: ${style}. Available: ${Object.keys(compiledStyles).join(', ')}`
|
|
76
|
+
)
|
|
53
77
|
}
|
|
54
78
|
|
|
55
|
-
|
|
79
|
+
const item = toCslJson(publication)
|
|
80
|
+
return compiled.bibliography(item, {})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get metadata for all available citation styles.
|
|
85
|
+
* Useful for building UI style pickers.
|
|
86
|
+
*
|
|
87
|
+
* @returns {Array<{ id: string, label: string, class: string }>}
|
|
88
|
+
*/
|
|
89
|
+
export function getAvailableStyles() {
|
|
90
|
+
return Object.entries(compiledStyles).map(([id, style]) => ({
|
|
91
|
+
id,
|
|
92
|
+
label: styleLabels[id] || style.meta?.title || id,
|
|
93
|
+
class: style.meta?.class || 'in-text',
|
|
94
|
+
}))
|
|
56
95
|
}
|
|
@@ -1,42 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bibliography Module
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Reference formatting powered by @citestyle/* compiled styles,
|
|
5
|
+
* BibTeX parsing and export, and data normalization utilities.
|
|
5
6
|
*
|
|
6
7
|
* @module @uniweb/scholar/bibliography
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
// Formatters
|
|
10
|
+
// Formatters — citestyle-powered
|
|
10
11
|
export {
|
|
11
12
|
formatReference,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
formatChicago,
|
|
15
|
-
formatIeee,
|
|
16
|
-
formatters,
|
|
13
|
+
getAvailableStyles,
|
|
14
|
+
compiledStyles,
|
|
17
15
|
} from './formatters/index.js'
|
|
18
16
|
|
|
19
|
-
// Parsers
|
|
17
|
+
// Parsers — delegated to @citestyle/bibtex
|
|
20
18
|
export { parseBibtex, exportBibtex } from './parsers/index.js'
|
|
21
19
|
|
|
22
|
-
//
|
|
23
|
-
export {
|
|
24
|
-
formatAuthors,
|
|
25
|
-
parseAuthorName,
|
|
26
|
-
parseAuthorString,
|
|
27
|
-
getInitials,
|
|
28
|
-
formatApaAuthors,
|
|
29
|
-
formatMlaAuthors,
|
|
30
|
-
formatChicagoAuthors,
|
|
31
|
-
formatIeeeAuthors,
|
|
32
|
-
} from './utils/authors.js'
|
|
33
|
-
|
|
34
|
-
export {
|
|
35
|
-
formatDate,
|
|
36
|
-
parseDate,
|
|
37
|
-
getYear,
|
|
38
|
-
formatApaDate,
|
|
39
|
-
formatMlaDate,
|
|
40
|
-
formatChicagoDate,
|
|
41
|
-
formatIeeeDate,
|
|
42
|
-
} from './utils/dates.js'
|
|
20
|
+
// Normalization — Scholar ↔ CSL-JSON bridge
|
|
21
|
+
export { toCslJson, fromCslJson, mapType, unmapType } from './normalize.js'
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Normalization Layer
|
|
3
|
+
*
|
|
4
|
+
* Bidirectional bridge between Scholar's publication objects (BibTeX-style
|
|
5
|
+
* field names) and CSL-JSON items (the format @citestyle/* expects).
|
|
6
|
+
*
|
|
7
|
+
* @module @uniweb/scholar/bibliography/normalize
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Scholar type → CSL-JSON type
|
|
11
|
+
const TYPE_TO_CSL = {
|
|
12
|
+
article: 'article-journal',
|
|
13
|
+
journal: 'article-journal',
|
|
14
|
+
book: 'book',
|
|
15
|
+
inbook: 'chapter',
|
|
16
|
+
incollection: 'chapter',
|
|
17
|
+
chapter: 'chapter',
|
|
18
|
+
inproceedings: 'paper-conference',
|
|
19
|
+
conference: 'paper-conference',
|
|
20
|
+
webpage: 'webpage',
|
|
21
|
+
website: 'webpage',
|
|
22
|
+
online: 'webpage',
|
|
23
|
+
thesis: 'thesis',
|
|
24
|
+
phdthesis: 'thesis',
|
|
25
|
+
mastersthesis: 'thesis',
|
|
26
|
+
report: 'report',
|
|
27
|
+
techreport: 'report',
|
|
28
|
+
misc: 'article', // sensible default
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// CSL-JSON type → Scholar type (reverse mapping, picks first match)
|
|
32
|
+
const TYPE_FROM_CSL = {
|
|
33
|
+
'article-journal': 'article',
|
|
34
|
+
book: 'book',
|
|
35
|
+
chapter: 'incollection',
|
|
36
|
+
'paper-conference': 'inproceedings',
|
|
37
|
+
webpage: 'webpage',
|
|
38
|
+
thesis: 'thesis',
|
|
39
|
+
report: 'report',
|
|
40
|
+
article: 'article',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Map a Scholar entry type to a CSL-JSON type.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} scholarType
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function mapType(scholarType) {
|
|
50
|
+
if (!scholarType) return 'article-journal'
|
|
51
|
+
return TYPE_TO_CSL[scholarType.toLowerCase()] || 'article-journal'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Map a CSL-JSON type back to a Scholar entry type.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} cslType
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
export function unmapType(cslType) {
|
|
61
|
+
if (!cslType) return 'article'
|
|
62
|
+
return TYPE_FROM_CSL[cslType] || cslType
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Convert a Scholar publication object to a CSL-JSON item.
|
|
67
|
+
*
|
|
68
|
+
* Scholar uses BibTeX-style field names (`authors`, `journal`, `year`).
|
|
69
|
+
* Citestyle expects CSL-JSON (`author`, `container-title`, `issued`).
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} pub - Scholar publication object
|
|
72
|
+
* @returns {Object} CSL-JSON item
|
|
73
|
+
*/
|
|
74
|
+
export function toCslJson(pub) {
|
|
75
|
+
if (!pub) return {}
|
|
76
|
+
|
|
77
|
+
// If it already looks like CSL-JSON (has `issued` or `container-title`),
|
|
78
|
+
// pass through with minimal normalization
|
|
79
|
+
if (pub.issued || pub['container-title']) {
|
|
80
|
+
return { id: pub.id || pub.citationKey || 'unknown', ...pub }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const item = {
|
|
84
|
+
id: pub.id || pub.citationKey || 'unknown',
|
|
85
|
+
type: mapType(pub.type),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Authors — Scholar uses `authors` (array), CSL-JSON uses `author`
|
|
89
|
+
if (pub.authors?.length) {
|
|
90
|
+
item.author = pub.authors.map(normalizeName)
|
|
91
|
+
} else if (pub.author) {
|
|
92
|
+
// Already CSL-JSON shape
|
|
93
|
+
item.author = Array.isArray(pub.author)
|
|
94
|
+
? pub.author.map(normalizeName)
|
|
95
|
+
: undefined
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Editors
|
|
99
|
+
if (pub.editors?.length) {
|
|
100
|
+
item.editor = pub.editors.map(normalizeName)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Title
|
|
104
|
+
if (pub.title) item.title = pub.title
|
|
105
|
+
|
|
106
|
+
// Container title — Scholar uses `journal` or `bookTitle`
|
|
107
|
+
const container = pub.journal || pub.bookTitle || pub.containerTitle
|
|
108
|
+
if (container) item['container-title'] = container
|
|
109
|
+
|
|
110
|
+
// Date — Scholar uses `year` (number), CSL-JSON uses `issued`
|
|
111
|
+
if (pub.year) {
|
|
112
|
+
item.issued = { 'date-parts': [[Number(pub.year)]] }
|
|
113
|
+
} else if (pub.date) {
|
|
114
|
+
// Try to parse an ISO date string
|
|
115
|
+
const parts = String(pub.date).split('-').map(Number)
|
|
116
|
+
item.issued = { 'date-parts': [parts.filter((n) => !isNaN(n))] }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Standard fields
|
|
120
|
+
if (pub.volume) item.volume = String(pub.volume)
|
|
121
|
+
if (pub.issue) item.issue = String(pub.issue)
|
|
122
|
+
if (pub.pages) item.page = pub.pages.replace('–', '-')
|
|
123
|
+
if (pub.page) item.page = pub.page
|
|
124
|
+
if (pub.doi) item.DOI = pub.doi.replace(/^https?:\/\/doi\.org\//, '')
|
|
125
|
+
if (pub.DOI) item.DOI = pub.DOI
|
|
126
|
+
if (pub.url) item.URL = pub.url
|
|
127
|
+
if (pub.URL) item.URL = pub.URL
|
|
128
|
+
if (pub.publisher) item.publisher = pub.publisher
|
|
129
|
+
if (pub.isbn) item.ISBN = pub.isbn
|
|
130
|
+
if (pub.issn) item.ISSN = pub.issn
|
|
131
|
+
if (pub.abstract) item.abstract = pub.abstract
|
|
132
|
+
if (pub.edition) item.edition = pub.edition
|
|
133
|
+
|
|
134
|
+
// Thesis-specific
|
|
135
|
+
if (pub.school || pub.institution) {
|
|
136
|
+
item.publisher = pub.school || pub.institution
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return item
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Convert a CSL-JSON item to a Scholar publication object.
|
|
144
|
+
*
|
|
145
|
+
* Used for backward compatibility when components expect Scholar-shaped
|
|
146
|
+
* objects (`pub.authors`, `pub.journal`, `pub.year`).
|
|
147
|
+
*
|
|
148
|
+
* @param {Object} item - CSL-JSON item
|
|
149
|
+
* @returns {Object} Scholar publication object
|
|
150
|
+
*/
|
|
151
|
+
export function fromCslJson(item) {
|
|
152
|
+
if (!item) return {}
|
|
153
|
+
|
|
154
|
+
const pub = {
|
|
155
|
+
id: item.id,
|
|
156
|
+
type: unmapType(item.type),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Authors
|
|
160
|
+
if (item.author?.length) {
|
|
161
|
+
pub.authors = item.author.map((a) => ({
|
|
162
|
+
family: a.family || '',
|
|
163
|
+
given: a.given || '',
|
|
164
|
+
...(a.suffix ? { suffix: a.suffix } : {}),
|
|
165
|
+
}))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Editors
|
|
169
|
+
if (item.editor?.length) {
|
|
170
|
+
pub.editors = item.editor.map((a) => ({
|
|
171
|
+
family: a.family || '',
|
|
172
|
+
given: a.given || '',
|
|
173
|
+
}))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Title
|
|
177
|
+
if (item.title) pub.title = item.title
|
|
178
|
+
|
|
179
|
+
// Container title → journal or bookTitle
|
|
180
|
+
if (item['container-title']) {
|
|
181
|
+
if (pub.type === 'article' || pub.type === 'journal') {
|
|
182
|
+
pub.journal = item['container-title']
|
|
183
|
+
} else {
|
|
184
|
+
pub.bookTitle = item['container-title']
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Date
|
|
189
|
+
if (item.issued?.['date-parts']?.[0]) {
|
|
190
|
+
pub.year = item.issued['date-parts'][0][0]
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Standard fields
|
|
194
|
+
if (item.volume) pub.volume = item.volume
|
|
195
|
+
if (item.issue) pub.issue = item.issue
|
|
196
|
+
if (item.page) pub.pages = item.page
|
|
197
|
+
if (item.DOI) pub.doi = item.DOI
|
|
198
|
+
if (item.URL) pub.url = item.URL
|
|
199
|
+
if (item.publisher) pub.publisher = item.publisher
|
|
200
|
+
if (item.ISBN) pub.isbn = item.ISBN
|
|
201
|
+
if (item.ISSN) pub.issn = item.ISSN
|
|
202
|
+
if (item.abstract) pub.abstract = item.abstract
|
|
203
|
+
if (item.edition) pub.edition = item.edition
|
|
204
|
+
|
|
205
|
+
return pub
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Normalize a name object — ensures {family, given} shape.
|
|
210
|
+
* Handles strings, objects with various key patterns.
|
|
211
|
+
*
|
|
212
|
+
* @param {Object|string} name
|
|
213
|
+
* @returns {{ family: string, given: string, suffix?: string }}
|
|
214
|
+
*/
|
|
215
|
+
function normalizeName(name) {
|
|
216
|
+
if (typeof name === 'string') {
|
|
217
|
+
// "Last, First" or "First Last"
|
|
218
|
+
if (name.includes(',')) {
|
|
219
|
+
const [family, given] = name.split(',').map((s) => s.trim())
|
|
220
|
+
return { family, given }
|
|
221
|
+
}
|
|
222
|
+
const parts = name.trim().split(/\s+/)
|
|
223
|
+
return {
|
|
224
|
+
family: parts[parts.length - 1],
|
|
225
|
+
given: parts.slice(0, -1).join(' '),
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
family: name.family || name.lastName || '',
|
|
231
|
+
given: name.given || name.firstName || '',
|
|
232
|
+
...(name.suffix ? { suffix: name.suffix } : {}),
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bibliography Parsers
|
|
3
3
|
*
|
|
4
|
+
* Delegates to @citestyle/bibtex for BibTeX parsing and export.
|
|
5
|
+
* Returns CSL-JSON items directly — no scholar-shaped wrapper.
|
|
6
|
+
*
|
|
4
7
|
* @module @uniweb/scholar/bibliography/parsers
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
import {
|
|
11
|
+
parseBibtex as csParseBibtex,
|
|
12
|
+
exportBibtex as csExportBibtex,
|
|
13
|
+
} from '@citestyle/bibtex'
|
|
14
|
+
import { toCslJson } from '../normalize.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse a BibTeX string into an array of CSL-JSON items.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} bibtex - BibTeX source string
|
|
20
|
+
* @returns {Array<Object>} Array of CSL-JSON items
|
|
21
|
+
*/
|
|
22
|
+
export const parseBibtex = csParseBibtex
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Export a single publication (Scholar or CSL-JSON) to BibTeX format.
|
|
26
|
+
*
|
|
27
|
+
* Wraps @citestyle/bibtex's exportBibtex, which expects an array of
|
|
28
|
+
* CSL-JSON items. This accepts a single Scholar-shaped pub for convenience.
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} publication - Publication data (Scholar or CSL-JSON)
|
|
31
|
+
* @returns {string} BibTeX string
|
|
32
|
+
*/
|
|
33
|
+
export function exportBibtex(publication) {
|
|
34
|
+
if (!publication) return ''
|
|
35
|
+
|
|
36
|
+
// If it's a string, it's already BibTeX — pass through
|
|
37
|
+
if (typeof publication === 'string') return publication
|
|
38
|
+
|
|
39
|
+
const item = toCslJson(publication)
|
|
40
|
+
return csExportBibtex([item])
|
|
41
|
+
}
|