asciidoctor-csl 0.1.0 → 0.3.0
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 +4 -4
- data/README.adoc +8 -0
- data/lib/asciidoctor-csl/bibitem_macro.rb +7 -4
- data/lib/asciidoctor-csl/bibliography_macro.rb +2 -1
- data/lib/asciidoctor-csl/citation_macro.rb +12 -2
- data/lib/asciidoctor-csl/citation_processor.rb +6 -2
- data/lib/asciidoctor-csl/citeproc_patches.rb +54 -1
- data/lib/asciidoctor-csl/csl_renderer.rb +60 -0
- data/lib/asciidoctor-csl/processor.rb +26 -8
- data/lib/asciidoctor-csl/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91d11025bf31c30262f9edf298ebf53b5039c458d3d8415e782ee69778a0b2e5
|
|
4
|
+
data.tar.gz: 3f06bc987a676662545763a41483833e86a2eaade05cb4eb8aa7dff773171208
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75b1473dd6047b43cab042abc3d5cc1ea1cb3f0ec515342afa4db5f17a3d922b79a6a14b8abb22413b4d6ca0680c9bf580bb3ccd4819cddf6a02a9602010d1a3
|
|
7
|
+
data.tar.gz: ae2b790ba2d632736f6b1f9d61335b69ea86610f431623f5896f0e2b308d9fdc0da8023579cc0ca5ae716cd48bf9045bca8546c43f4924f2707da1938e5e03ce
|
data/README.adoc
CHANGED
|
@@ -46,6 +46,11 @@ Defaults to `ieee`.
|
|
|
46
46
|
The language used for terms such as "p." or "and".
|
|
47
47
|
Defaults to the `lang` attribute of the document, or `en` if that is not set.
|
|
48
48
|
|
|
49
|
+
`csl-link-title`::
|
|
50
|
+
If set, the title of each entry links to the source's DOI or URL.
|
|
51
|
+
Citations are not affected.
|
|
52
|
+
Override it per macro with the `link-title` attribute of `bibliography::[]` and `bibitem:key[]`.
|
|
53
|
+
|
|
49
54
|
=== Citations
|
|
50
55
|
|
|
51
56
|
`cite:key[]` cites a source.
|
|
@@ -87,12 +92,15 @@ Citations in section titles are not supported.
|
|
|
87
92
|
By default, it only renders the cited sources.
|
|
88
93
|
Use `bibliography::[select=all]` to include everything in the bibliography.
|
|
89
94
|
|
|
95
|
+
Use `bibliography::[link-title=true]` or `bibliography::[link-title=false]` to override the `csl-link-title` setting for this bibliography.
|
|
96
|
+
|
|
90
97
|
Each entry gets an anchor with the source's key as ID, like `<<smith2020>>`.
|
|
91
98
|
|
|
92
99
|
=== Single entries
|
|
93
100
|
|
|
94
101
|
`bibitem:key[]` renders the entry of a single source inline.
|
|
95
102
|
It does not count as a citation: the source is not added to the bibliography and does not affect the numbering.
|
|
103
|
+
Like the bibliography, it accepts `link-title=true` or `link-title=false` to override the `csl-link-title` setting.
|
|
96
104
|
|
|
97
105
|
== Citation Style Language support
|
|
98
106
|
|
|
@@ -7,7 +7,7 @@ require 'citeproc'
|
|
|
7
7
|
module AsciidoctorCsl
|
|
8
8
|
# BibitemMacro
|
|
9
9
|
#
|
|
10
|
-
# The `bibitem:key1[]` macro.
|
|
10
|
+
# The `bibitem:key1[link-title=false]` macro.
|
|
11
11
|
class BibitemMacro < ::Asciidoctor::Extensions::InlineMacroProcessor
|
|
12
12
|
include ::Asciidoctor::Logging
|
|
13
13
|
|
|
@@ -17,14 +17,17 @@ module AsciidoctorCsl
|
|
|
17
17
|
|
|
18
18
|
attr_accessor :processor
|
|
19
19
|
|
|
20
|
-
def process(parent, target,
|
|
21
|
-
|
|
20
|
+
def process(parent, target, attributes)
|
|
21
|
+
options = attributes.key?('link-title') ? { link_titles: attributes['link-title'] != 'false' } : {}
|
|
22
|
+
entry = processor&.render_entry(target, **options)
|
|
22
23
|
if entry.nil?
|
|
23
24
|
logger.warn "bibitem: unknown reference: #{target}"
|
|
24
25
|
return create_inline parent, :quoted, "[#{target}]"
|
|
25
26
|
end
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
# Asciidoctor scans the output of inline macros for URLs again, linking those already linked.
|
|
29
|
+
# Encoding the colon of `://` hides them from that scan; it renders the same.
|
|
30
|
+
create_inline parent, :quoted, parent.apply_subs(entry).gsub('://', '://')
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
end
|
|
@@ -6,7 +6,7 @@ require 'asciidoctor/extensions'
|
|
|
6
6
|
module AsciidoctorCsl
|
|
7
7
|
# BibliographyBlockMacro
|
|
8
8
|
#
|
|
9
|
-
# Provides the bibliography block macro. (bibliography::[select=cited])
|
|
9
|
+
# Provides the bibliography block macro. (bibliography::[select=cited, link-title=false])
|
|
10
10
|
class BibliographyBlockMacro < ::Asciidoctor::Extensions::BlockMacroProcessor
|
|
11
11
|
include ::Asciidoctor::Logging
|
|
12
12
|
|
|
@@ -30,6 +30,7 @@ module AsciidoctorCsl
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
block = create_block parent, :open, nil, { 'role' => 'bibliography' }
|
|
33
|
+
block.set_attr 'link-title', attrs['link-title'] if attrs.key?('link-title')
|
|
33
34
|
blocks << block
|
|
34
35
|
block
|
|
35
36
|
end
|
|
@@ -50,10 +50,20 @@ module AsciidoctorCsl
|
|
|
50
50
|
|
|
51
51
|
csl.engine.format.link_citations = @bibliography && attributes['link'] != 'false'
|
|
52
52
|
text = csl.render(:citation, items)
|
|
53
|
-
|
|
54
|
-
text = "footnote:[#{text.gsub(']', '\\]')}]" if csl.engine.style.info.citation_format == :note
|
|
53
|
+
return create_footnote parent, text if csl.engine.style.info.citation_format == :note
|
|
55
54
|
|
|
56
55
|
create_inline parent, :quoted, text, attributes: { 'subs' => :normal }
|
|
57
56
|
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Create the footnote like the footnote macro does, so the citation is not escaped to fit in `footnote:[]`.
|
|
61
|
+
def create_footnote(parent, text)
|
|
62
|
+
document = parent.document
|
|
63
|
+
content = parent.apply_subs text
|
|
64
|
+
index = document.counter 'footnote-number'
|
|
65
|
+
document.register :footnotes, ::Asciidoctor::Document::Footnote.new(index, nil, content)
|
|
66
|
+
create_inline parent, :footnote, content, attributes: { 'index' => index }
|
|
67
|
+
end
|
|
58
68
|
end
|
|
59
69
|
end
|
|
@@ -11,6 +11,7 @@ module AsciidoctorCsl
|
|
|
11
11
|
csl_file = (document.attr 'csl-file').to_s
|
|
12
12
|
csl_style = ((document.attr 'csl-style') || 'ieee').to_s
|
|
13
13
|
csl_lang = (document.attr('csl-lang') || document.attr('lang') || 'en').to_s
|
|
14
|
+
csl_link_title = document.attr? 'csl-link-title'
|
|
14
15
|
|
|
15
16
|
return if csl_file.empty?
|
|
16
17
|
|
|
@@ -19,7 +20,7 @@ module AsciidoctorCsl
|
|
|
19
20
|
bibitem = document.extensions.inline_macros.map(&:instance).find { |m| m.name == :bibitem }
|
|
20
21
|
|
|
21
22
|
path_to_csl = document.normalize_system_path csl_file, document.base_dir
|
|
22
|
-
processor = Processor.new path_to_csl, csl_style, csl_lang
|
|
23
|
+
processor = Processor.new path_to_csl, csl_style, csl_lang, link_titles: csl_link_title
|
|
23
24
|
|
|
24
25
|
# Collect all cited keys, in appearance order.
|
|
25
26
|
document.find_by(traverse_documents: true) { |b| prose?(b) }.each do |block|
|
|
@@ -39,7 +40,10 @@ module AsciidoctorCsl
|
|
|
39
40
|
cite.bibliography = !bibliography.blocks.empty?
|
|
40
41
|
bibitem.processor = processor
|
|
41
42
|
|
|
42
|
-
bibliography.blocks.each
|
|
43
|
+
bibliography.blocks.each do |block|
|
|
44
|
+
link_titles = block.attr?('link-title') ? block.attr('link-title') != 'false' : csl_link_title
|
|
45
|
+
parse_content block, processor.build_bibliography_list(link_titles:)
|
|
46
|
+
end
|
|
43
47
|
|
|
44
48
|
nil
|
|
45
49
|
end
|
|
@@ -21,13 +21,66 @@ module AsciidoctorCsl
|
|
|
21
21
|
|
|
22
22
|
item.suppress! 'author' if item.suppress_author?
|
|
23
23
|
|
|
24
|
-
text = render_layout(item, node)
|
|
24
|
+
text = in_xref { render_layout(item, node) }
|
|
25
25
|
text = "<<#{item.id},#{text}>>" unless text.empty?
|
|
26
26
|
join [item.prefix, text, item.suffix].compact
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def in_xref
|
|
32
|
+
format.in_xref = true
|
|
33
|
+
yield
|
|
34
|
+
ensure
|
|
35
|
+
format.in_xref = false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Support for linking URLs in the title.
|
|
40
|
+
module LinkTitles
|
|
41
|
+
def render_bibliography(item, node)
|
|
42
|
+
return super unless format.respond_to?(:link_titles) && format.link_titles
|
|
43
|
+
|
|
44
|
+
target = link_target(item.data)
|
|
45
|
+
return super unless target
|
|
46
|
+
|
|
47
|
+
doi = item.data[:DOI]
|
|
48
|
+
url = item.data[:URL]
|
|
49
|
+
suppressed = item.data.suppressed.dup
|
|
50
|
+
|
|
51
|
+
format.title_link = target
|
|
52
|
+
# Blank DOI/URL so the style renders the entry as if it had neither:
|
|
53
|
+
# groups around them collapse as for any missing variable.
|
|
54
|
+
item.data[:DOI] = nil
|
|
55
|
+
item.data[:URL] = nil
|
|
56
|
+
|
|
57
|
+
result = super
|
|
58
|
+
return result unless format.title_link
|
|
59
|
+
|
|
60
|
+
# The style rendered no title: render the entry as usual.
|
|
61
|
+
format.title_link = nil
|
|
62
|
+
item.data[:DOI] = doi
|
|
63
|
+
item.data[:URL] = url
|
|
64
|
+
# Rendering suppresses variables used as substitutes; undo that too.
|
|
65
|
+
item.data.suppressed.replace suppressed
|
|
66
|
+
super
|
|
67
|
+
ensure
|
|
68
|
+
format.title_link = nil if format.respond_to?(:title_link=)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def link_target(data)
|
|
74
|
+
doi = data[:DOI].to_s.strip
|
|
75
|
+
return doi.match?(%r{\Ahttps?://}i) ? doi : "https://doi.org/#{doi}" unless doi.empty?
|
|
76
|
+
|
|
77
|
+
url = data[:URL].to_s.strip
|
|
78
|
+
url unless url.empty?
|
|
79
|
+
end
|
|
28
80
|
end
|
|
29
81
|
end
|
|
30
82
|
end
|
|
31
83
|
|
|
32
84
|
CiteProc::Item.prepend AsciidoctorCsl::CiteprocPatches::SeparateSuppression
|
|
33
85
|
CiteProc::Ruby::Renderer.prepend AsciidoctorCsl::CiteprocPatches::LinkCitations
|
|
86
|
+
CiteProc::Ruby::Renderer.prepend AsciidoctorCsl::CiteprocPatches::LinkTitles
|
|
@@ -7,10 +7,20 @@ module AsciidoctorCsl
|
|
|
7
7
|
|
|
8
8
|
PROTECTED_RX = /<<[^,>]+,|\{[\w-]+}|\b[a-z][\w-]*:\S*?\[[^\]]*\]|\[[^\]]*\](?=#)|\+\+\+.*?\+\+\+|\+[^+]+\+/
|
|
9
9
|
PLACEHOLDER_RX = /\u0000(\d+)\u0000/
|
|
10
|
+
URL_VARIABLES = %w[URL DOI].freeze
|
|
10
11
|
|
|
11
12
|
# Whether rendered citations link to their bibliography entry.
|
|
12
13
|
attr_accessor :link_citations
|
|
13
14
|
|
|
15
|
+
# Whether bibliography titles link.
|
|
16
|
+
attr_accessor :link_titles
|
|
17
|
+
|
|
18
|
+
# The target for the next rendered title, set per entry by the renderer.
|
|
19
|
+
attr_accessor :title_link
|
|
20
|
+
|
|
21
|
+
# Whether we are rendering inside a citation's cross-reference, where links cannot nest.
|
|
22
|
+
attr_accessor :in_xref
|
|
23
|
+
|
|
14
24
|
def bibliography(bibliography, _locale = nil)
|
|
15
25
|
bibliography.connector = "\n\n"
|
|
16
26
|
bibliography
|
|
@@ -77,5 +87,55 @@ module AsciidoctorCsl
|
|
|
77
87
|
ensure
|
|
78
88
|
output.gsub!(PLACEHOLDER_RX) { protected[Regexp.last_match(1).to_i] } if protected
|
|
79
89
|
end
|
|
90
|
+
|
|
91
|
+
# An affix that became part of a link is not added again.
|
|
92
|
+
def prefix
|
|
93
|
+
@prefix_in_link ? '' : super
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
protected
|
|
97
|
+
|
|
98
|
+
# Escape URLs and DOIs before any formatting, so AsciiDoc does not parse them.
|
|
99
|
+
def setup!
|
|
100
|
+
super
|
|
101
|
+
escape_url if node.is_a?(CSL::Style::Text) && URL_VARIABLES.include?(node.variable)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def cleanup!
|
|
105
|
+
super
|
|
106
|
+
@prefix_in_link = nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def finalize_content!
|
|
110
|
+
super
|
|
111
|
+
|
|
112
|
+
return unless title_link && node.is_a?(CSL::Style::Text) && node.variable == 'title'
|
|
113
|
+
|
|
114
|
+
output.replace "link:#{passthrough title_link}[#{output.gsub(']', '\]')}]"
|
|
115
|
+
self.title_link = nil # link only the first rendered title
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
# Link the value if it is a URL, or becomes one with its prefix (as in prefix="https://doi.org/");
|
|
121
|
+
# a value that already is a URL ignores such a prefix. Anything else is passed through as text.
|
|
122
|
+
def escape_url
|
|
123
|
+
url = if url?(output) then output
|
|
124
|
+
elsif url?(prefix + output) then prefix + output
|
|
125
|
+
end
|
|
126
|
+
@prefix_in_link = url && url?(prefix)
|
|
127
|
+
|
|
128
|
+
text = passthrough(url || output)
|
|
129
|
+
output.replace(url && !in_xref ? "link:#{text}[]" : text)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Not `++text++`, which cannot contain `++`.
|
|
133
|
+
def passthrough(text)
|
|
134
|
+
"pass:c[#{text.gsub(']', '\]')}]"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def url?(text)
|
|
138
|
+
text.match?(%r{\Ahttps?://}i)
|
|
139
|
+
end
|
|
80
140
|
end
|
|
81
141
|
end
|
|
@@ -35,12 +35,14 @@ module AsciidoctorCsl
|
|
|
35
35
|
def initialize(
|
|
36
36
|
bibliography_file,
|
|
37
37
|
style = 'ieee',
|
|
38
|
-
locale = 'en'
|
|
38
|
+
locale = 'en',
|
|
39
|
+
link_titles: false
|
|
39
40
|
)
|
|
40
41
|
raise "File '#{bibliography_file}' is not found" unless FileTest.file? bibliography_file
|
|
41
42
|
|
|
42
43
|
@style = style
|
|
43
44
|
@locale = locale
|
|
45
|
+
@link_titles = link_titles
|
|
44
46
|
|
|
45
47
|
# Citations in the order they appear in the document
|
|
46
48
|
@citations = []
|
|
@@ -54,10 +56,11 @@ module AsciidoctorCsl
|
|
|
54
56
|
@citations.concat keys.map(&:strip)
|
|
55
57
|
end
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
# Render a single entry; link_titles overrides the document default.
|
|
60
|
+
def render_entry(key, link_titles: @link_titles)
|
|
58
61
|
return nil unless @bibliography.key?(key)
|
|
59
62
|
|
|
60
|
-
entries.render(:bibliography, id: key).first
|
|
63
|
+
render_bibliography_with(entries, link_titles) { entries.render(:bibliography, id: key).first }
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
# Finalize citation macro processing and build internal citation list.
|
|
@@ -70,16 +73,16 @@ module AsciidoctorCsl
|
|
|
70
73
|
keys = render_all ? @citations | @bibliography.keys : @citations
|
|
71
74
|
@citeproc.import(keys.filter_map { |key| @bibliography[key] })
|
|
72
75
|
|
|
73
|
-
@
|
|
74
|
-
|
|
75
|
-
@rendered.ids.each_with_index do |id, i|
|
|
76
|
+
@citeproc.bibliography.ids.each_with_index do |id, i|
|
|
76
77
|
@citeproc[id][:'citation-number'] = i + 1
|
|
77
78
|
end
|
|
78
79
|
nil
|
|
79
80
|
end
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
# Render the bibliography; link_titles overrides the document default.
|
|
83
|
+
def build_bibliography_list(link_titles: @link_titles)
|
|
84
|
+
rendered = render_bibliography_with(@citeproc, link_titles) { @citeproc.bibliography }
|
|
85
|
+
rendered.ids.zip(rendered.references).flat_map do |id, reference|
|
|
83
86
|
["[[#{id}]]#{reference}", '']
|
|
84
87
|
end
|
|
85
88
|
end
|
|
@@ -101,10 +104,25 @@ module AsciidoctorCsl
|
|
|
101
104
|
|
|
102
105
|
def new_citeproc
|
|
103
106
|
citeproc = CiteProc::Processor.new style: @style, format: :asciidoc, locale: @locale
|
|
107
|
+
citeproc.engine.format.link_titles = @link_titles
|
|
104
108
|
use_asciidoc_quotes citeproc
|
|
105
109
|
citeproc
|
|
106
110
|
end
|
|
107
111
|
|
|
112
|
+
# Render bibliography entries with the given title link setting.
|
|
113
|
+
#
|
|
114
|
+
# Also forget earlier bibliography renders: subsequent-author-substitute
|
|
115
|
+
# compares against the last rendered entry, even from a previous render.
|
|
116
|
+
def render_bibliography_with(citeproc, link_titles)
|
|
117
|
+
format = citeproc.engine.format
|
|
118
|
+
previous = format.link_titles
|
|
119
|
+
format.link_titles = link_titles
|
|
120
|
+
citeproc.engine.renderer.state.history.memory.delete('bibliography')
|
|
121
|
+
yield
|
|
122
|
+
ensure
|
|
123
|
+
format.link_titles = previous
|
|
124
|
+
end
|
|
125
|
+
|
|
108
126
|
# Override the quote handling by inserting dynamic AsciiDoc quotes.
|
|
109
127
|
# That way, AsciiDoc handles the quotes, and it is consistent with the rest of the document.
|
|
110
128
|
def use_asciidoc_quotes(processor)
|