Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/format/html/format-html-meta.ts
6450 views
1
/*
2
* format-html-meta.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { kHtmlEmptyPostProcessResult } from "../../command/render/constants.ts";
8
import {
9
PandocInputTraits,
10
RenderedFormat,
11
} from "../../command/render/types.ts";
12
import { kCanonicalUrl } from "../../config/constants.ts";
13
import { Format, Metadata } from "../../config/types.ts";
14
import { bibliographyCslJson } from "../../core/bibliography.ts";
15
import {
16
CSL,
17
cslDateToEDTFDate,
18
CSLExtras,
19
kAbstractUrl,
20
kEIssn,
21
kPdfUrl,
22
} from "../../core/csl.ts";
23
import { Document } from "../../core/deno-dom.ts";
24
import { encodeAttributeValue } from "../../core/html.ts";
25
import { kWebsite } from "../../project/types/website/website-constants.ts";
26
import {
27
documentCSL,
28
synthesizeCitationUrl,
29
} from "../../quarto-core/attribution/document.ts";
30
import { writeLinkTag, writeMetaTag } from "./format-html-shared.ts";
31
32
export const kGoogleScholar = "google-scholar";
33
34
export function metadataPostProcessor(
35
input: string,
36
format: Format,
37
offset?: string,
38
) {
39
return async (doc: Document, options: {
40
inputMetadata: Metadata;
41
inputTraits: PandocInputTraits;
42
renderedFormats: RenderedFormat[];
43
}) => {
44
// Generate a canonical tag if requested
45
if (format.render[kCanonicalUrl]) {
46
writeCanonicalUrl(
47
doc,
48
format.render[kCanonicalUrl],
49
input,
50
options.inputMetadata,
51
format.pandoc["output-file"],
52
offset,
53
);
54
}
55
56
if (googleScholarEnabled(format)) {
57
const { csl, extras } = documentCSL(
58
input,
59
options.inputMetadata,
60
"webpage",
61
format.pandoc["output-file"],
62
offset,
63
);
64
const documentMetadata = googleScholarMeta(csl, extras);
65
const referenceMetadata = await googleScholarReferences(
66
input,
67
options.inputMetadata,
68
);
69
[...documentMetadata, ...referenceMetadata].forEach((meta) => {
70
writeMetaTag(meta.name, meta.content, doc);
71
});
72
}
73
// no resource refs
74
return Promise.resolve(kHtmlEmptyPostProcessResult);
75
};
76
}
77
78
function googleScholarEnabled(format: Format) {
79
// Enabled by the format / document
80
if (format.metadata[kGoogleScholar] === true) {
81
return true;
82
}
83
// Disabled for the site
84
const siteMeta = format.metadata[kWebsite] as Metadata;
85
if (siteMeta && siteMeta[kGoogleScholar] === true) {
86
return true;
87
}
88
return false;
89
}
90
91
interface MetaTagData {
92
name: string;
93
content: string;
94
}
95
96
function googleScholarMeta(
97
csl: CSL,
98
extras: CSLExtras,
99
): MetaTagData[] {
100
// The scholar metadata that we'll generate into
101
const scholarMeta: MetaTagData[] = [];
102
const write = metadataWriter(scholarMeta);
103
104
// Process title
105
if (csl.title) {
106
write("citation_title", csl.title);
107
}
108
109
if (csl.abstract) {
110
write("citation_abstract", csl.abstract);
111
}
112
113
if (extras.keywords) {
114
write("citation_keywords", extras.keywords);
115
}
116
117
// Authors
118
if (csl.author) {
119
csl.author.forEach((author) => {
120
write(
121
"citation_author",
122
author.literal || `${author.given} ${author.family}`,
123
);
124
});
125
}
126
127
// Editors
128
if (csl.editor) {
129
csl.editor.forEach((editor) => {
130
write(
131
"citation_editor",
132
editor.literal || `${editor.given} ${editor.family}`,
133
);
134
});
135
}
136
137
if (csl.issued) {
138
const edtfIssued = cslDateToEDTFDate(csl.issued);
139
if (edtfIssued) {
140
write("citation_publication_date", edtfIssued);
141
write("citation_cover_date", edtfIssued);
142
}
143
const parts = csl.issued["date-parts"];
144
if (parts && parts.length > 0) {
145
write("citation_year", parts[0][0]);
146
}
147
}
148
149
if (csl["available-date"]) {
150
const edtfAvailable = cslDateToEDTFDate(csl["available-date"]);
151
if (edtfAvailable) {
152
write("citation_online_date", edtfAvailable);
153
}
154
}
155
156
if (csl.URL) {
157
write(
158
"citation_fulltext_html_url",
159
csl.URL,
160
);
161
}
162
163
if (extras[kPdfUrl]) {
164
write("citation_pdf_url", extras[kPdfUrl]);
165
}
166
167
if (extras[kAbstractUrl]) {
168
write("citation_abstract_html_url", extras[kAbstractUrl]);
169
}
170
171
if (csl.issue) {
172
write("citation_issue", csl.issue);
173
}
174
175
if (csl.DOI) {
176
write("citation_doi", csl.DOI);
177
}
178
179
if (csl.ISBN) {
180
write("citation_isbn", csl.ISBN);
181
}
182
183
if (csl.ISSN) {
184
write("citation_issn", csl.ISSN);
185
}
186
187
if (extras[kEIssn]) {
188
write("citation_eissn", extras[kEIssn]);
189
}
190
191
if (csl.PMID) {
192
write("citation_pmid", csl.PMID);
193
}
194
195
if (csl.volume) {
196
write("citation_volume", csl.volume);
197
}
198
199
if (csl.language) {
200
write("citation_language", csl.language);
201
}
202
203
if (csl["page-first"]) {
204
write("citation_firstpage", csl["page-first"]);
205
}
206
207
if (csl["page-last"]) {
208
write("citation_lastpage", csl["page-last"]);
209
}
210
211
const type = csl.type;
212
if (type === "paper-conference") {
213
if (csl["container-title"]) {
214
write("citation_conference_title", csl["container-title"]);
215
}
216
217
if (csl.publisher) {
218
write("citation_conference", csl.publisher);
219
}
220
} else if (type === "thesis") {
221
if (csl.publisher) {
222
write("citation_dissertation_institution", csl.publisher);
223
}
224
} else if (type === "report") {
225
if (csl.publisher) {
226
write(
227
"citation_technical_report_institution",
228
csl.publisher,
229
);
230
}
231
if (csl.number) {
232
write(
233
"citation_technical_report_number",
234
csl.number,
235
);
236
}
237
} else if (type === "book") {
238
if (csl["container-title"]) {
239
write("citation_book_title", csl["container-title"]);
240
}
241
} else if (type === "chapter") {
242
write("citation_inbook_title", csl["container-title"]);
243
} else {
244
if (csl["container-title"]) {
245
write("citation_journal_title", csl["container-title"]);
246
}
247
248
if (csl["container-title-short"]) {
249
write("citation_journal_abbrev", csl["container-title-short"]);
250
}
251
252
if (csl.publisher) {
253
write("citation_publisher", csl.publisher);
254
}
255
}
256
257
if (csl["collection-title"]) {
258
write("citation_series_title", csl["collection-title"]);
259
}
260
261
return scholarMeta;
262
}
263
264
async function googleScholarReferences(input: string, metadata: Metadata) {
265
const scholarMeta: MetaTagData[] = [];
266
const write = metadataWriter(scholarMeta);
267
268
// Generate the references by reading the bibliography and parsing the html
269
const references = await bibliographyCslJson(input, metadata);
270
271
if (references) {
272
references.forEach((reference) => {
273
const refMetas = googleScholarMeta(reference, {});
274
const metaStrs = refMetas.map((refMeta) => {
275
return `${refMeta.name}=${refMeta.content};`;
276
});
277
write("citation_reference", metaStrs.join());
278
});
279
}
280
return scholarMeta;
281
}
282
283
function metadataWriter(metadata: MetaTagData[]) {
284
const write = (key: string, value: unknown) => {
285
metadata.push({
286
name: key,
287
content: encodeAttributeValue(value as string),
288
});
289
};
290
return write;
291
}
292
293
function writeCanonicalUrl(
294
doc: Document,
295
url: string | boolean,
296
input: string,
297
inputMetadata: Metadata,
298
outputFile?: string,
299
offset?: string,
300
) {
301
if (typeof url === "string") {
302
// Use the explicitly provided URL
303
writeLinkTag("canonical", url, doc);
304
} else if (url) {
305
// Compute a canonical url and include that
306
const canonicalUrl = synthesizeCitationUrl(
307
input,
308
inputMetadata,
309
outputFile,
310
offset,
311
);
312
if (canonicalUrl) {
313
writeLinkTag("canonical", canonicalUrl, doc);
314
}
315
}
316
}
317
318