pandoc-jats-ruby 0.0.3

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.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "pandoc/jats/ruby"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,193 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Publishing DTD v1.0 20120330//EN"
3
+ "JATS-journalpublishing1.dtd">
4
+ $if(article.type)$
5
+ <article xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink" dtd-version="1.0" article-type="$article.type$">
6
+ $else$
7
+ <article xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink" dtd-version="1.0" article-type="other">
8
+ $endif$
9
+ <front>
10
+ <journal-meta>
11
+ $if(journal.publisher-id)$
12
+ <journal-id journal-id-type="publisher-id">$journal.publisher-id$</journal-id>
13
+ $endif$
14
+ $if(journal.nlm-ta)$
15
+ <journal-id journal-id-type="nlm-ta">$journal.nlm-ta$</journal-id>
16
+ $endif$
17
+ $if(journal.pmc)$
18
+ <journal-id journal-id-type="pmc">$journal.pmc$</journal-id>
19
+ $endif$
20
+ <journal-title-group>
21
+ $if(journal.title)$
22
+ <journal-title>$journal.title$</journal-title>
23
+ $endif$
24
+ $if(journal.abbrev-title)$
25
+ <abbrev-journal-title>$journal.abbrev-title$</abbrev-journal-title>
26
+ $endif$
27
+ </journal-title-group>
28
+ $if(journal.pissn)$
29
+ <issn pub-type="ppub">$journal.pissn$</issn>
30
+ $endif$
31
+ $if(journal.eissn)$
32
+ <issn pub-type="epub">$journal.eissn$</issn>
33
+ $endif$
34
+ <publisher>
35
+ <publisher-name>$journal.publisher-name$</publisher-name>
36
+ $if(journal.publisher-loc)$
37
+ <publisher-loc>$journal.publisher-loc$</publisher-loc>
38
+ $endif$
39
+ </publisher>
40
+ </journal-meta>
41
+ <article-meta>
42
+ $if(article.publisher-id)$
43
+ <article-id pub-id-type="publisher-id">$article.publisher-id$</article-id>
44
+ $endif$
45
+ $if(article.doi)$
46
+ <article-id pub-id-type="doi">$article.doi$</article-id>
47
+ $endif$
48
+ $if(article.pmid)$
49
+ <article-id pub-id-type="pmid">$article.pmid$</article-id>
50
+ $endif$
51
+ $if(article.pmcid)$
52
+ <article-id pub-id-type="pmcid">$article.pmcid$</article-id>
53
+ $endif$
54
+ $if(article.art-access-id)$
55
+ <article-id pub-id-type="art-access-id">$article.art-access-id$</article-id>
56
+ $endif$
57
+ $if(article.heading)$
58
+ <article-categories>
59
+ <subj-group subj-group-type="heading">
60
+ <subject>$article.heading$</subject>
61
+ </subj-group>
62
+ $if(article.categories)$
63
+ <subj-group subj-group-type="categories">
64
+ $for(article.categories)$
65
+ <subject>$article.categories$</subject>
66
+ $endfor$
67
+ </subj-group>
68
+ $endif$
69
+ </article-categories>
70
+ $endif$
71
+ $if(title)$
72
+ <title-group>
73
+ <article-title>$title$</article-title>
74
+ </title-group>
75
+ $endif$
76
+ $if(author)$
77
+ <contrib-group>
78
+ $for(author)$
79
+ <contrib contrib-type="author">
80
+ $if(author.orcid)$
81
+ <contrib-id contrib-id-type="orcid">$author.orcid$</contrib-id>
82
+ $endif$
83
+ <name>
84
+ $if(author.surname)$
85
+ <surname>$author.surname$</surname>
86
+ <given-names>$author.given-names$</given-names>
87
+ $else$
88
+ <string-name>$author$</string-name>
89
+ $endif$
90
+ </name>
91
+ $if(author.email)$
92
+ <email>$author.email$</email>
93
+ $endif$
94
+ $if(author.aff-id)$
95
+ <xref ref-type="aff" rid="aff-$contrib.aff-id$"/>
96
+ $endif$
97
+ $if(author.cor-id)$
98
+ <xref ref-type="corresp" rid="cor-$author.cor-id$"><sup>*</sup></xref>
99
+ $endif$
100
+ </contrib>
101
+ $endfor$
102
+ </contrib-group>
103
+ $endif$
104
+ $if(article.author-notes)$
105
+ <author-notes>
106
+ $if(article.author-notes.corresp)$
107
+ $for(article.author-notes.corresp)$
108
+ <corresp id="cor-$article.author-notes.corresp.id$">* E-mail: <email>$article.author-notes.corresp.email$</email></corresp>
109
+ $endfor$
110
+ $endif$
111
+ $if(article.author-notes.conflict)$
112
+ <fn fn-type="conflict"><p>$article.author-notes.conflict$</p></fn>
113
+ $endif$
114
+ $if(article.author-notes.con)$
115
+ <fn fn-type="con"><p>$article.author-notes.con$</p></fn>
116
+ $endif$
117
+ </author-notes>
118
+ $endif$
119
+ $if(date)$
120
+ $if(date.iso-8601)$
121
+ <pub-date pub-type="epub" iso-8601-date="$date.iso-8601$">
122
+ $else$
123
+ <pub-date pub-type="epub">
124
+ $endif$
125
+ $if(date.day)$
126
+ <day>$pub-date.day$</day>
127
+ $endif$
128
+ $if(date.month)$
129
+ <month>$pub-date.month$</month>
130
+ $endif$
131
+ $if(date.year)$
132
+ <year>$pub-date.year$</year>
133
+ $else$
134
+ <string-date>$date$</string-date>
135
+ $endif$
136
+ </pub-date>
137
+ $endif$
138
+ $if(article.volume)$
139
+ <volume>$article.volume$</volume>
140
+ $endif$
141
+ $if(article.issue)$
142
+ <issue>$article.issue$</issue>
143
+ $endif$
144
+ $if(article.fpage)$
145
+ <fpage>$article.fpage$</fpage>
146
+ $endif$
147
+ $if(article.lpage)$
148
+ <lpage>$article.lpage$</lpage>
149
+ $endif$
150
+ $if(article.elocation-id)$
151
+ <elocation-id>$article.elocation-id$</elocation-id>
152
+ $endif$
153
+ $if(history)$
154
+ <history>
155
+ </history>
156
+ $endif$
157
+ $if(copyright)$
158
+ <permissions>
159
+ $if(copyright.statement)$
160
+ <copyright-statement>$copyright.statement$</copyright-statement>
161
+ $endif$
162
+ $if(copyright.year)$
163
+ <copyright-year>$copyright.year$</copyright-year>
164
+ $endif$
165
+ $if(copyright.holder)$
166
+ <copyright-holder>$copyright.holder$</copyright-holder>
167
+ $endif$
168
+ $if(copyright.text)$
169
+ <license license-type="$copyright.type$" xlink:href="$copyright.link$">
170
+ <license-p>$copyright.text$</license-p>
171
+ </license>
172
+ </permissions>
173
+ $endif$
174
+ $endif$
175
+ $if(tags)$
176
+ <kwd-group kwd-group-type="author">
177
+ $for(tags)$
178
+ <kwd>$tags$</kwd>
179
+ $endfor$
180
+ </kwd-group>
181
+ $endif$
182
+ $if(article.funding-statement)$
183
+ <funding-group>
184
+ <funding-statement>$article.funding-statement$</funding-statement>
185
+ </funding-group>
186
+ $endif$
187
+ </article-meta>
188
+ $if(notes)$
189
+ <notes>$notes$</notes>
190
+ $endif$
191
+ </front>
192
+ $body$
193
+ </article>
@@ -0,0 +1,662 @@
1
+ -- This is a JATS custom writer for pandoc. It produces output
2
+ -- that tries to conform to the JATS 1.0 specification
3
+ -- http://jats.nlm.nih.gov/archiving/tag-library/1.0/index.html
4
+ --
5
+ -- Invoke with: pandoc -t jats.lua
6
+ --
7
+ -- Note: you need not have lua installed on your system to use this
8
+ -- custom writer. However, if you do have lua installed, you can
9
+ -- use it to test changes to the script. 'lua JATS.lua' will
10
+ -- produce informative error messages if your code contains
11
+ -- syntax errors.
12
+ --
13
+ -- Released under the GPL, version 2 or greater. See LICENSE for more info.
14
+
15
+ -- Tables to store metadata, headers, sections, back sections, references, figures and footnotes
16
+ local meta = {}
17
+ local headers = {}
18
+ local sections = {}
19
+ local back = {}
20
+ local references = {}
21
+ local figures = {}
22
+
23
+ -- This function is called once for the whole document. Parameters:
24
+ -- body is a string, metadata is a table, variables is a table.
25
+ -- This gives you a fragment. You could use the metadata table to
26
+ -- fill variables in a custom lua template. Or, pass `--template=...`
27
+ -- to pandoc, and pandoc will do the template processing as
28
+ -- usual.
29
+ function Doc(body, metadata, variables)
30
+ meta = metadata or {}
31
+
32
+ -- if document doesn't start with section, add top-level section without title
33
+ if string.sub(body, 1, 6) ~= '</sec>' then
34
+ body = Header(1, '') .. '\n' .. body
35
+ end
36
+
37
+ -- strip closing section tag from beginning, add to end of document
38
+ body = string.sub(body, 7) .. '</sec>'
39
+
40
+ -- parse sections, turn body into table of sections
41
+ for lev, title, content in string.gmatch(body, '<sec.-lev="(.-)".->%s<title>(.-)</title>(.-)</sec>') do
42
+ attr = section_helper(tonumber(lev), content, title)
43
+ end
44
+
45
+ body = xml('body', '\n' .. table.concat(sections, '\n') .. '\n')
46
+
47
+ if #back > 0 then
48
+ body = body .. '\n' .. xml('back', '\n' .. table.concat(back, '\n'))
49
+ end
50
+
51
+ return body
52
+ end
53
+
54
+ -- XML character entity escaping and unescaping
55
+ function escape(s)
56
+ local map = { ['<'] = '&lt;',
57
+ ['>'] = '&gt;',
58
+ ['&'] = '&amp;',
59
+ ['"'] = '&quot;',
60
+ ['\'']= '&#39;' }
61
+ return s:gsub("[<>&\"']", function(x) return map[x] end)
62
+ end
63
+
64
+ function unescape(s)
65
+ local map = { ['&lt;'] = '<',
66
+ ['&gt;'] = '>',
67
+ ['&amp;'] = '&',
68
+ ['&quot;'] = '"',
69
+ ['&#39;']= '\'' }
70
+ return s:gsub('(&(#?)([%d%a]+);)', function(x) return map[x] end)
71
+ end
72
+
73
+ -- Helper function to convert an attributes table into
74
+ -- a string that can be put into XML elements.
75
+ function attributes(attr)
76
+ local attr_table = {}
77
+ for x, y in pairsByKeys(attr) do
78
+ if y and y ~= '' then
79
+ table.insert(attr_table, string.format(' %s="%s"', x, escape(y)))
80
+ end
81
+ end
82
+ return table.concat(attr_table)
83
+ end
84
+
85
+ -- sort table, so that attributes are in consistent order
86
+ function pairsByKeys (t, f)
87
+ local a = {}
88
+ for n in pairs(t) do table.insert(a, n) end
89
+ table.sort(a, f)
90
+ local i = 0 -- iterator variable
91
+ local iter = function () -- iterator function
92
+ i = i + 1
93
+ if a[i] == nil then return nil
94
+ else return a[i], t[a[i]]
95
+ end
96
+ end
97
+ return iter
98
+ end
99
+
100
+ -- generic xml builder
101
+ function xml(tag, s, attr)
102
+ attr = attr and attributes(attr) or ''
103
+ s = s and '>' .. s .. '</' .. tag .. '>' or '/>'
104
+ return '<' .. tag .. attr .. s
105
+ end
106
+
107
+ -- Flatten nested table, needed for nested YAML metadata['
108
+ -- We only flatten associative arrays and create composite key,
109
+ -- numbered arrays and flat tables are left intact.
110
+ -- We also convert all hyphens in keys to underscores,
111
+ -- so that they are proper variable names
112
+ function flatten_table(tbl)
113
+ local result = {}
114
+
115
+ local function flatten(tbl, key)
116
+ for k, v in pairs(tbl) do
117
+ if type(k) == 'number' and k > 0 and k <= #tbl then
118
+ result[key] = tbl
119
+ break
120
+ else
121
+ k = (key and key .. '-' or '') .. k
122
+ if type(v) == 'table' then
123
+ flatten(v, k)
124
+ else
125
+ result[k] = v
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ flatten(tbl)
132
+ return result
133
+ end
134
+
135
+ -- Read a file from the working directory and
136
+ -- return its contents (or nil if not found).
137
+ function read_file(name)
138
+ local base, ext = name:match("([^%.]*)(.*)")
139
+ local fname = base .. ext
140
+ local file = io.open(fname, "r")
141
+ if not file then return nil end
142
+ return file:read("*all")
143
+ end
144
+
145
+ -- Parse YAML string and return table.
146
+ -- We only understand a subset.
147
+ function parse_yaml(s)
148
+ local l = {}
149
+ local c = {}
150
+ local i = 0
151
+ local k = nil
152
+
153
+ -- patterns
154
+ line_pattern = '(.-)\r?\n'
155
+ config_pattern = '^(%s*)([%w%-]+):%s*(.-)$'
156
+
157
+ -- First split string into lines
158
+ local function lines(line)
159
+ table.insert(l, line)
160
+ return ""
161
+ end
162
+
163
+ lines((s:gsub(line_pattern, lines)))
164
+
165
+ -- Then go over each line and check value and indentation
166
+ for _, v in ipairs(l) do
167
+ v:gsub(config_pattern, function(indent, tag, v)
168
+ if (v == '') then
169
+ i, k = string.len(indent), tag
170
+ c[tag] = {}
171
+ else
172
+ -- check whether value is enclosed by brackets, i.e. an array
173
+ if v:find('^%[(.-)%]$') then
174
+ arr = {};
175
+ for match in (v:sub(2, -2) .. ','):gmatch('(.-)' .. ',%s*') do
176
+ table.insert(arr, match);
177
+ end
178
+ v = arr;
179
+ else
180
+ -- if it is a string, remove optional enclosing quotes
181
+ v = v:match('^["\']*(.-)["\']*$')
182
+ end
183
+
184
+ if string.len(indent) == i + 2 and k then
185
+ c[k][tag] = v
186
+ else
187
+ c[tag] = v
188
+ end
189
+ end
190
+ end)
191
+ end
192
+
193
+ return c
194
+ end
195
+
196
+ -- add appropriate sec-type attribute
197
+ function sec_type_helper(s)
198
+ local map = { ['Abstract']= 'abstract',
199
+ ['Acknowledgments']= 'acknowledgements',
200
+ ['Author Summary']= 'author-summary',
201
+ ['Conclusions'] = 'conclusions',
202
+ ['Discussion'] = 'discussion',
203
+ ['Glossary'] = 'glossary',
204
+ ['Introduction'] = 'intro',
205
+ ['Materials and Methods'] = 'materials|methods',
206
+ ['Notes'] = 'notes',
207
+ ['References']= 'references',
208
+ ['Results']= 'results',
209
+ ['Supporting Information']= 'supplementary-material',
210
+ ['Supplementary Information']= 'supplementary-material' }
211
+ return map[s]
212
+ end
213
+
214
+ function section_helper(lev, s, title)
215
+ local attr = { ['sec-type'] = sec_type_helper(title) }
216
+
217
+ if attr['sec-type'] == "acknowledgements" then
218
+ table.insert(back, Ack(s, title))
219
+ elseif attr['sec-type'] == "references" then
220
+ table.insert(back, RefList(s, title))
221
+ elseif attr['sec-type'] == "notes" then
222
+ table.insert(back, Note(s, title))
223
+ elseif attr['sec-type'] == "glossary" then
224
+ table.insert(back, Glossary(s, title))
225
+ elseif attr['sec-type'] == "abstract" or attr['sec-type'] == "author-summary" then
226
+ -- discard, should be provided via metadata
227
+ elseif attr['sec-type'] == "supplementary-material" then
228
+ table.insert(sections, SupplementaryMaterial(s, title))
229
+ else
230
+ table.insert(sections, Section(lev, s, title, attr))
231
+ end
232
+
233
+ return attr
234
+ end
235
+
236
+ -- Create table with year, month, day and iso8601-formatted date
237
+ -- Input is iso8601-formatted date as string
238
+ -- Return nil if input is not a valid date
239
+ function date_helper(iso_date)
240
+ if not iso_date or string.len(iso_date) ~= 10 then return nil end
241
+
242
+ _,_,y,m,d = string.find(iso_date, '(%d+)-(%d+)-(%d+)')
243
+ time = os.time({ year = y, month = m, day = d })
244
+ date = os.date('*t', time)
245
+ date.iso8601 = string.format('%04d-%02d-%02d', date.year, date.month, date.day)
246
+ return date
247
+ end
248
+
249
+ -- Create affiliation table, linked to authors via aff-id
250
+ function affiliation_helper(tbl)
251
+
252
+ set = {}
253
+ i = 0
254
+ for _,author in ipairs(tbl.author) do
255
+ if author.affiliation then
256
+ if not set[author.affiliation] then
257
+ i = i + 1
258
+ set[author.affiliation] = i
259
+ end
260
+ author['aff-id'] = set[author.affiliation]
261
+ end
262
+ end
263
+
264
+ tbl.aff = {}
265
+ for k,v in pairs(set) do
266
+ aff = { id = v, name = k }
267
+ table.insert(tbl.aff, aff)
268
+ end
269
+
270
+ return tbl
271
+ end
272
+
273
+ -- Create corresponding author table, linked to authors via cor-id
274
+ function corresp_helper(tbl)
275
+
276
+ set = {}
277
+ i = 0
278
+ for _,author in ipairs(tbl.author) do
279
+ if author.corresp and author.email then
280
+ i = i + 1
281
+ set[i] = author.email
282
+ author['cor-id'] = i
283
+ end
284
+ end
285
+
286
+ tbl.corresp = {}
287
+ for k,v in pairs(set) do
288
+ corresp = { id = k, email = v }
289
+ table.insert(tbl.corresp, corresp)
290
+ end
291
+
292
+ return tbl
293
+ end
294
+
295
+ -- temporary fix
296
+ function fix_citeproc(s)
297
+ s = s:gsub('</surname>, ', '</surname>')
298
+ s = s:gsub('</name></name><name>','</name>')
299
+ return s
300
+ end
301
+
302
+ -- Convert pandoc alignment to something HTML can use.
303
+ -- align is AlignLeft, AlignRight, AlignCenter, or AlignDefault.
304
+ function html_align(align)
305
+ local map = { ['AlignRight']= 'right',
306
+ ['AlignCenter']= 'center' }
307
+ return map[align] or 'left'
308
+ end
309
+
310
+ -- Blocksep is used to separate block elements.
311
+ function Blocksep()
312
+ return "\n"
313
+ end
314
+
315
+ -- The functions that follow render corresponding pandoc elements.
316
+ -- s is always a string, attr is always a table of attributes, and
317
+ -- items is always an array of strings (the items in a list).
318
+ -- Comments indicate the types of other variables.
319
+ -- Defined at https://github.com/jgm/pandoc/blob/master/src/Text/Pandoc/Writers/Custom.hs
320
+
321
+ -- block elements
322
+
323
+ function Plain(s)
324
+ return s
325
+ end
326
+
327
+ function Para(s)
328
+ return xml('p', s)
329
+ end
330
+
331
+ function RawBlock(s)
332
+ return xml('preformat', s)
333
+ end
334
+
335
+ -- JATS restricts use to inside table cells (<td> and <th>)
336
+ function HorizontalRule()
337
+ return '<hr/>'
338
+ end
339
+
340
+ -- lev is an integer, the header level.
341
+ -- we can't use closing tags, as we don't know the end of the section
342
+ function Header(lev, s, attr)
343
+ attr = attr or {}
344
+ attr['lev'] = '' .. lev
345
+ return '</sec>\n<sec' .. attributes(attr) .. '>\n' .. xml('title', s)
346
+ end
347
+
348
+ function Note(s)
349
+ return s
350
+ end
351
+
352
+ function CodeBlock(s, attr)
353
+ -- If code block has class 'dot', pipe the contents through dot
354
+ -- and base64, and include the base64-encoded png as a data: URL.
355
+ if attr.class and string.match(' ' .. attr.class .. ' ',' dot ') then
356
+ local png = pipe("base64", pipe("dot -Tpng", s))
357
+ return '<img src="data:image/png;base64,' .. png .. '"/>'
358
+ -- otherwise treat as code (one could pipe through a highlighter)
359
+ else
360
+ return "<pre><code" .. attributes(attr) .. ">" .. escape(s) ..
361
+ "</code></pre>"
362
+ end
363
+ end
364
+
365
+ function BlockQuote(s)
366
+ xml('boxed-text', s)
367
+ end
368
+
369
+ -- Caption is a string, aligns is an array of strings,
370
+ -- widths is an array of floats, headers is an array of
371
+ -- strings, rows is an array of arrays of strings.
372
+ function Table(caption, aligns, widths, headers, rows)
373
+ local buffer = {}
374
+ local function add(s)
375
+ table.insert(buffer, s)
376
+ end
377
+ table.insert(buffer, '<table-wrap>')
378
+ if caption ~= '' then
379
+ -- if caption begins with <bold> text, make it the <title>
380
+ caption = string.gsub('<p>' .. caption, "^<p><bold>(.-)</bold>%s", "<title>%1</title>\n<p>")
381
+ add(xml('caption>', caption))
382
+ end
383
+ add("<table>")
384
+ if widths and widths[1] ~= 0 then
385
+ for _, w in pairs(widths) do
386
+ add('<col width="' .. string.format("%d%%", w * 100) .. '" />')
387
+ end
388
+ end
389
+ local header_row = {}
390
+ local empty_header = true
391
+ for i, h in pairs(headers) do
392
+ local align = html_align(aligns[i])
393
+
394
+ -- remove <p> tag
395
+ h = h:gsub("^<p>(.-)</p>", "%1")
396
+
397
+ table.insert(header_row,'<th align="' .. align .. '">' .. h .. '</th>')
398
+ empty_header = empty_header and h == ""
399
+ end
400
+ if empty_header then
401
+ head = ""
402
+ else
403
+ add('<tr>')
404
+ for _,h in pairs(header_row) do
405
+ add(h)
406
+ end
407
+ add('</tr>')
408
+ end
409
+ for _, row in pairs(rows) do
410
+ add('<tr>')
411
+ for i,c in pairs(row) do
412
+ -- remove <p> tag
413
+ c = c:gsub("^<p>(.-)</p>", "%1")
414
+ add('<td align="' .. html_align(aligns[i]) .. '">' .. c .. '</td>')
415
+ end
416
+ add('</tr>')
417
+ end
418
+ add('</table>\n</table-wrap>')
419
+ return table.concat(buffer,'\n')
420
+ end
421
+
422
+ function BulletList(items)
423
+ local attr = { ['list-type'] = 'bullet' }
424
+ return List(items, attr)
425
+ end
426
+
427
+ function OrderedList(items)
428
+ local attr = { ['list-type'] = 'order' }
429
+ return List(items, attr)
430
+ end
431
+
432
+ function List(items, attr)
433
+ local buffer = {}
434
+ for _, item in pairs(items) do
435
+ table.insert(buffer, xml('list-item', item))
436
+ end
437
+ return xml('list', '\n' .. table.concat(buffer, '\n') .. '\n', attr)
438
+ end
439
+
440
+ -- Revisit association list StackValue instance.
441
+ -- items is a table of tables
442
+ function DefinitionList(items)
443
+ local buffer = {}
444
+ for _,item in pairs(items) do
445
+ for k, v in pairs(item) do
446
+ local term = xml('term', k)
447
+ local def = xml('def', table.concat(v,'</def><def>'))
448
+ table.insert(buffer, xml('def-item', term .. def))
449
+ end
450
+ end
451
+ return xml('def-list', '\n' .. table.concat(buffer, '\n') .. '\n')
452
+ end
453
+
454
+ function Div(s, attr)
455
+ return s
456
+ end
457
+
458
+ -- custom block elements for JATS
459
+
460
+ -- section is generated after header to allow reordering
461
+ function Section(lev, s, title, attr)
462
+ local last = headers[#headers]
463
+ local h = last and last.h or {}
464
+ h[lev] = (h[lev] or 0) + 1
465
+ for i = lev + 1, #headers do
466
+ table.remove(h, i)
467
+ end
468
+
469
+ local header = { ['h'] = h,
470
+ ['title'] = title,
471
+ ['id'] = 'sec-' .. table.concat(h,'.'),
472
+ ['sec-type'] = attr['sec-type'] }
473
+
474
+ table.insert(headers, header)
475
+
476
+ attr = { ['id'] = header['id'], ['sec-type'] = header['sec-type'] }
477
+ title = xml('title', title ~= '' and title or nil)
478
+ return xml('sec', '\n' .. title .. s, attr)
479
+ end
480
+
481
+ function SupplementaryMaterial(s, title, attr)
482
+ attr = {}
483
+ title = xml('title', title)
484
+ local caption = xml('caption', title .. s)
485
+ return xml('supplementary-material', '\n' .. caption .. '\n', attr)
486
+ end
487
+
488
+ function Ack(s, title)
489
+ title = title and '\n' .. xml('title', title) or ''
490
+ return xml('ack', title .. s)
491
+ end
492
+
493
+ function Glossary(s, title, attr)
494
+ title = xml('title', title)
495
+ return xml('glossary', title .. s, attr)
496
+ end
497
+
498
+ function RefList(s, title)
499
+ s = fix_citeproc(s)
500
+
501
+ -- format ids
502
+ s = string.gsub(s, '<ref id="(%d+)">', function (r)
503
+ local attr = { ['id'] = string.format('r%03d', tonumber(r)) }
504
+ return '<ref ' .. attributes(attr) .. '>'
505
+ end)
506
+
507
+ for ref in string.gmatch(s, '(<ref.-</ref>)') do
508
+ Ref(ref)
509
+ end
510
+
511
+ if #references > 0 then
512
+ title = xml('title', title)
513
+ return xml('ref-list', title .. table.concat(references, '\n'), attr)
514
+ else
515
+ return ''
516
+ end
517
+ end
518
+
519
+ function Ref(s)
520
+ table.insert(references, s)
521
+ return #references
522
+ end
523
+
524
+ -- inline elements
525
+
526
+ function Str(s)
527
+ return s
528
+ end
529
+
530
+ function Space()
531
+ return ' '
532
+ end
533
+
534
+ function Emph(s)
535
+ return xml('italic', s)
536
+ end
537
+
538
+ function Strong(s)
539
+ return xml('bold', s)
540
+ end
541
+
542
+ function Strikeout(s)
543
+ return xml('strike', s)
544
+ end
545
+
546
+ function Superscript(s)
547
+ return xml('sup', s)
548
+ end
549
+
550
+ function Subscript(s)
551
+ return xml('sub', s)
552
+ end
553
+
554
+ function SmallCaps(s)
555
+ return xml('sc', s)
556
+ end
557
+
558
+ function SingleQuoted(s)
559
+ return "'" .. s .. "'"
560
+ end
561
+
562
+ function DoubleQuoted(s)
563
+ return '"' .. s .. '"'
564
+ end
565
+
566
+ -- format in-text citation
567
+ function Cite(s)
568
+ local ids = {}
569
+ for id in string.gmatch(s, '(%d+)') do
570
+ id = tonumber(id)
571
+ -- workaround to discard year mistakenly taken for key
572
+ if id and id < 1000 then
573
+ local attr = { ['ref-type'] = 'bibr',
574
+ ['rid'] = string.format("r%03d", id) }
575
+ table.insert(ids, xml('xref', '[' .. id .. ']', attr))
576
+ end
577
+ end
578
+ if #ids > 0 then
579
+ return table.concat(ids)
580
+ else
581
+ -- return original key for backwards compatibility
582
+ return s
583
+ end
584
+ end
585
+
586
+ function Code(s, attr)
587
+ return xml('preformat', s, attr)
588
+ end
589
+
590
+ function DisplayMath(s)
591
+ return xml('disp-formula', s)
592
+ end
593
+
594
+ function InlineMath(s)
595
+ return xml('inline-formula', s)
596
+ end
597
+
598
+ function RawInline(s)
599
+ return xml('preformat', s)
600
+ end
601
+
602
+ function LineBreak()
603
+ return ' '
604
+ end
605
+
606
+ function Link(s, src, title)
607
+ if src ~= '' and s ~= '' then
608
+ attr = { ['ext-link-type'] = 'uri',
609
+ ['xlink:href'] = escape(src),
610
+ ['xlink:title'] = escape(title),
611
+ ['xlink:type'] = 'simple' }
612
+
613
+ return xml('ext-link', s, attr)
614
+ else
615
+ return s
616
+ end
617
+ end
618
+
619
+ function CaptionedImage(s, src, title)
620
+ -- if title begins with <bold> text, make it the <title>
621
+ title = string.gsub(title, "^<bold>(.-)</bold>%s", function(t) xml('title', t) end)
622
+ local num = #figures + 1
623
+ local attr = { ['id'] = string.format("g%03d", num) }
624
+ local caption = xml('caption', s)
625
+ local fig = xml('fig', caption .. Image(nil, src, title), attr)
626
+
627
+ table.insert(figures, fig)
628
+ return fig
629
+ end
630
+
631
+ function Image(s, src, title)
632
+ local attr = { ['mimetype'] = 'image',
633
+ ['xlink:href'] = escape(src),
634
+ ['xlink:title'] = escape(title),
635
+ ['xlink:type'] = 'simple' }
636
+
637
+ return xml('graphic', s, attr)
638
+ end
639
+
640
+ -- handle bold and italic
641
+ function Span(s, attr)
642
+ if attr.style == "font-weight:bold" then
643
+ return Strong(s)
644
+ elseif attr.style == "font-style:italic" then
645
+ return Emph(s)
646
+ elseif attr.style == "font-variant: small-caps" then
647
+ return SmallCaps(s)
648
+ else
649
+ return s
650
+ end
651
+ end
652
+
653
+ -- The following code will produce runtime warnings when you haven't defined
654
+ -- all of the functions you need for the custom writer, so it's useful
655
+ -- to include when you're working on a writer.
656
+ local meta = {}
657
+ meta.__index =
658
+ function(_, key)
659
+ io.stderr:write(string.format("WARNING: Undefined function '%s'\n",key))
660
+ return function() return "" end
661
+ end
662
+ setmetatable(_G, meta)