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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +340 -0
- data/LICENSE.txt +21 -0
- data/README.md +44 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/pandoc-jats-ruby/default.jats +193 -0
- data/lib/pandoc-jats-ruby/jats.lua +662 -0
- data/lib/pandoc-jats-ruby/version.rb +3 -0
- data/pandoc-jats-ruby.gemspec +34 -0
- metadata +116 -0
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -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
|
data/bin/setup
ADDED
|
@@ -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 = { ['<'] = '<',
|
|
57
|
+
['>'] = '>',
|
|
58
|
+
['&'] = '&',
|
|
59
|
+
['"'] = '"',
|
|
60
|
+
['\'']= ''' }
|
|
61
|
+
return s:gsub("[<>&\"']", function(x) return map[x] end)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
function unescape(s)
|
|
65
|
+
local map = { ['<'] = '<',
|
|
66
|
+
['>'] = '>',
|
|
67
|
+
['&'] = '&',
|
|
68
|
+
['"'] = '"',
|
|
69
|
+
[''']= '\'' }
|
|
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)
|