citeproc-ruby 0.0.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.document +4 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.simplecov +4 -0
- data/.travis.yml +17 -0
- data/.yardopts +2 -0
- data/AGPL +662 -0
- data/BSDL +29 -0
- data/Gemfile +42 -0
- data/Guardfile +14 -0
- data/README.md +32 -76
- data/Rakefile +60 -0
- data/citeproc-ruby.gemspec +46 -0
- data/cucumber.yml +1 -0
- data/features/bibliography.feature +25 -0
- data/features/name_options.feature +37 -0
- data/features/names.feature +192 -0
- data/features/renderer.feature +74 -0
- data/features/sort.feature +50 -0
- data/features/step_definitions/renderer.rb +80 -0
- data/features/support/env.rb +33 -0
- data/features/support/hooks.rb +10 -0
- data/lib/citeproc/ruby.rb +32 -0
- data/lib/citeproc/ruby/engine.rb +122 -0
- data/lib/citeproc/ruby/format.rb +303 -0
- data/lib/citeproc/ruby/formats/default.rb +25 -0
- data/lib/citeproc/ruby/formats/html.rb +221 -0
- data/lib/citeproc/ruby/renderer.rb +140 -0
- data/lib/citeproc/ruby/renderer/choose.rb +106 -0
- data/lib/citeproc/ruby/renderer/date.rb +90 -0
- data/lib/citeproc/ruby/renderer/format.rb +129 -0
- data/lib/citeproc/ruby/renderer/group.rb +34 -0
- data/lib/citeproc/ruby/renderer/history.rb +40 -0
- data/lib/citeproc/ruby/renderer/label.rb +66 -0
- data/lib/citeproc/ruby/renderer/layout.rb +20 -0
- data/lib/citeproc/ruby/renderer/locale.rb +26 -0
- data/lib/citeproc/ruby/renderer/macro.rb +20 -0
- data/lib/citeproc/ruby/renderer/names.rb +401 -0
- data/lib/citeproc/ruby/renderer/number.rb +41 -0
- data/lib/citeproc/ruby/renderer/observer.rb +44 -0
- data/lib/citeproc/ruby/renderer/state.rb +96 -0
- data/lib/citeproc/ruby/renderer/text.rb +62 -0
- data/lib/citeproc/ruby/sort.rb +82 -0
- data/lib/citeproc/ruby/version.rb +5 -0
- data/spec/citeproc/ruby/engine_spec.rb +94 -0
- data/spec/citeproc/ruby/formats/default_spec.rb +159 -0
- data/spec/citeproc/ruby/formats/html_spec.rb +162 -0
- data/spec/citeproc/ruby/renderer/choose_spec.rb +293 -0
- data/spec/citeproc/ruby/renderer/date_spec.rb +173 -0
- data/spec/citeproc/ruby/renderer/group_spec.rb +88 -0
- data/spec/citeproc/ruby/renderer/history_spec.rb +38 -0
- data/spec/citeproc/ruby/renderer/label_spec.rb +225 -0
- data/spec/citeproc/ruby/renderer/layout_spec.rb +41 -0
- data/spec/citeproc/ruby/renderer/macro_spec.rb +31 -0
- data/spec/citeproc/ruby/renderer/names_spec.rb +396 -0
- data/spec/citeproc/ruby/renderer/number_spec.rb +120 -0
- data/spec/citeproc/ruby/renderer/text_spec.rb +120 -0
- data/spec/citeproc/ruby/renderer_spec.rb +65 -0
- data/spec/fixtures/items.rb +80 -0
- data/{resource/locale → spec/fixtures/locales}/locales-en-US.xml +2 -11
- data/{resource/locale → spec/fixtures/locales}/locales-fr-FR.xml +77 -66
- data/{resource/style → spec/fixtures/styles}/apa.csl +5 -5
- data/spec/spec_helper.rb +67 -14
- metadata +121 -211
- data/lib/citeproc.rb +0 -100
- data/lib/citeproc/bibliography.rb +0 -57
- data/lib/citeproc/data.rb +0 -149
- data/lib/citeproc/date.rb +0 -133
- data/lib/citeproc/formatter.rb +0 -38
- data/lib/citeproc/item.rb +0 -53
- data/lib/citeproc/name.rb +0 -284
- data/lib/citeproc/processor.rb +0 -166
- data/lib/citeproc/selector.rb +0 -61
- data/lib/citeproc/variable.rb +0 -82
- data/lib/citeproc/version.rb +0 -3
- data/lib/csl/locale.rb +0 -223
- data/lib/csl/node.rb +0 -72
- data/lib/csl/nodes.rb +0 -1418
- data/lib/csl/renderer.rb +0 -88
- data/lib/csl/sort.rb +0 -61
- data/lib/csl/style.rb +0 -110
- data/lib/csl/term.rb +0 -124
- data/lib/extensions/core.rb +0 -43
- data/lib/plugins/filters/bibtex.rb +0 -12
- data/lib/plugins/formats/default.rb +0 -134
- data/lib/plugins/formats/html.rb +0 -67
- data/lib/support/attributes.rb +0 -99
- data/lib/support/compatibility.rb +0 -83
- data/lib/support/tree.rb +0 -80
- data/resource/locale/locales-af-ZA.xml +0 -305
- data/resource/locale/locales-ar-AR.xml +0 -306
- data/resource/locale/locales-bg-BG.xml +0 -305
- data/resource/locale/locales-ca-AD.xml +0 -305
- data/resource/locale/locales-cs-CZ.xml +0 -305
- data/resource/locale/locales-da-DK.xml +0 -305
- data/resource/locale/locales-de-AT.xml +0 -304
- data/resource/locale/locales-de-CH.xml +0 -304
- data/resource/locale/locales-de-DE.xml +0 -332
- data/resource/locale/locales-el-GR.xml +0 -305
- data/resource/locale/locales-en-GB.xml +0 -304
- data/resource/locale/locales-es-ES.xml +0 -305
- data/resource/locale/locales-et-EE.xml +0 -304
- data/resource/locale/locales-eu.xml +0 -305
- data/resource/locale/locales-fa-IR.xml +0 -304
- data/resource/locale/locales-fi-FI.xml +0 -304
- data/resource/locale/locales-fr-CA.xml +0 -306
- data/resource/locale/locales-he-IL.xml +0 -304
- data/resource/locale/locales-hu-HU.xml +0 -305
- data/resource/locale/locales-is-IS.xml +0 -304
- data/resource/locale/locales-it-IT.xml +0 -305
- data/resource/locale/locales-ja-JP.xml +0 -305
- data/resource/locale/locales-kh-KH.xml +0 -303
- data/resource/locale/locales-km-KH.xml +0 -304
- data/resource/locale/locales-ko-KR.xml +0 -305
- data/resource/locale/locales-mn-MN.xml +0 -306
- data/resource/locale/locales-nb-NO.xml +0 -304
- data/resource/locale/locales-nl-NL.xml +0 -304
- data/resource/locale/locales-nn-NO.xml +0 -304
- data/resource/locale/locales-pl-PL.xml +0 -305
- data/resource/locale/locales-pt-BR.xml +0 -304
- data/resource/locale/locales-pt-PT.xml +0 -305
- data/resource/locale/locales-ro-RO.xml +0 -305
- data/resource/locale/locales-ru-RU.xml +0 -306
- data/resource/locale/locales-sk-SK.xml +0 -304
- data/resource/locale/locales-sl-SI.xml +0 -305
- data/resource/locale/locales-sr-RS.xml +0 -305
- data/resource/locale/locales-sv-SE.xml +0 -305
- data/resource/locale/locales-th-TH.xml +0 -304
- data/resource/locale/locales-tr-TR.xml +0 -305
- data/resource/locale/locales-uk-UA.xml +0 -306
- data/resource/locale/locales-vi-VN.xml +0 -305
- data/resource/locale/locales-zh-CN.xml +0 -304
- data/resource/locale/locales-zh-TW.xml +0 -305
- data/resource/schema/csl-categories.rnc +0 -39
- data/resource/schema/csl-data.rnc +0 -98
- data/resource/schema/csl-terms.rnc +0 -106
- data/resource/schema/csl-types.rnc +0 -39
- data/resource/schema/csl-variables.rnc +0 -182
- data/resource/schema/csl.rnc +0 -941
- data/resource/style/bibtex.csl +0 -177
- data/resource/style/chicago-annotated-bibliography.csl +0 -513
- data/resource/style/chicago-author-date-basque.csl +0 -707
- data/resource/style/chicago-author-date-de.csl +0 -394
- data/resource/style/chicago-author-date-listing.csl +0 -434
- data/resource/style/chicago-author-date.csl +0 -425
- data/resource/style/chicago-dated-note-biblio-no-ibid.csl +0 -472
- data/resource/style/chicago-fullnote-bibliography-bb.csl +0 -928
- data/resource/style/chicago-fullnote-bibliography-delimiter-fixes.csl +0 -972
- data/resource/style/chicago-fullnote-bibliography-no-ibid-delimiter-fixes.csl +0 -963
- data/resource/style/chicago-fullnote-bibliography-no-ibid.csl +0 -785
- data/resource/style/chicago-fullnote-bibliography.csl +0 -803
- data/resource/style/chicago-library-list.csl +0 -511
- data/resource/style/chicago-note-biblio-no-ibid.csl +0 -514
- data/resource/style/chicago-note-bibliography.csl +0 -530
- data/resource/style/chicago-note.csl +0 -388
- data/resource/style/chicago-quick-copy.csl +0 -685
- data/resource/style/ieee.csl +0 -299
- data/resource/style/mla-notes.csl +0 -796
- data/resource/style/mla-underline.csl +0 -175
- data/resource/style/mla-url.csl +0 -214
- data/resource/style/mla.csl +0 -394
- data/resource/style/vancouver-brackets.csl +0 -256
- data/resource/style/vancouver-superscript-bracket-only-year.csl +0 -165
- data/resource/style/vancouver-superscript.csl +0 -256
- data/resource/style/vancouver.csl +0 -256
- data/spec/citeproc/bibliography_spec.rb +0 -45
- data/spec/citeproc/citeproc_spec.rb +0 -80
- data/spec/citeproc/date_spec.rb +0 -89
- data/spec/citeproc/formatter_spec.rb +0 -101
- data/spec/citeproc/item_spec.rb +0 -71
- data/spec/citeproc/name_spec.rb +0 -30
- data/spec/citeproc/processor_spec.rb +0 -61
- data/spec/citeproc/selector_spec.rb +0 -82
- data/spec/citeproc/variable_spec.rb +0 -69
- data/spec/csl/locale_spec.rb +0 -208
- data/spec/csl/node_spec.rb +0 -25
- data/spec/csl/nodes_spec.rb +0 -145
- data/spec/csl/style_spec.rb +0 -62
- data/spec/csl/term_spec.rb +0 -56
- data/spec/fixtures/dates.yaml +0 -80
- data/spec/fixtures/names.yaml +0 -115
- data/spec/fixtures/nodes.yaml +0 -245
- data/spec/support/attributes_spec.rb +0 -39
- data/spec/support/tree_spec.rb +0 -163
@@ -0,0 +1,90 @@
|
|
1
|
+
module CiteProc
|
2
|
+
module Ruby
|
3
|
+
|
4
|
+
class Renderer
|
5
|
+
|
6
|
+
# @param item [CiteProc::CitationItem]
|
7
|
+
# @param node [CSL::Node]
|
8
|
+
# @raise RenderingError
|
9
|
+
# @return [String]
|
10
|
+
def render_date(item, node)
|
11
|
+
return '' unless node.has_variable?
|
12
|
+
|
13
|
+
date = item.data[node.variable]
|
14
|
+
return '' if date.nil? || date.empty?
|
15
|
+
|
16
|
+
# TODO date-ranges
|
17
|
+
|
18
|
+
if node.localized?
|
19
|
+
localized_node = locale.date.detect { |d| d.form == node.form } or
|
20
|
+
raise RenderingError, "no localized date for form #{node.form} found"
|
21
|
+
|
22
|
+
delimiter, filter = node.delimiter, node.parts_filter
|
23
|
+
|
24
|
+
parts = localized_node.parts.select do |part|
|
25
|
+
filter.include? part.name
|
26
|
+
end
|
27
|
+
else
|
28
|
+
parts, delimiter = node.parts, node.delimiter
|
29
|
+
end
|
30
|
+
|
31
|
+
parts.map { |part|
|
32
|
+
render date, part
|
33
|
+
}.reject(&:empty?).join(delimiter)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param date [CiteProc::Date]
|
37
|
+
# @param node [CSL::Style::DatePart, CSL::Locale::DatePart]
|
38
|
+
# @return [String]
|
39
|
+
def render_date_part(date, node)
|
40
|
+
case
|
41
|
+
when node.day?
|
42
|
+
case
|
43
|
+
when date.day.nil?
|
44
|
+
''
|
45
|
+
when node.form == 'ordinal'
|
46
|
+
if date.day > 1 && locale.limit_day_ordinals?
|
47
|
+
date.day.to_s
|
48
|
+
else
|
49
|
+
ordinalize date.day
|
50
|
+
end
|
51
|
+
when node.form == 'numeric-leading-zeros'
|
52
|
+
'%02d' % date.day
|
53
|
+
else
|
54
|
+
date.day.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
when node.month?
|
58
|
+
case
|
59
|
+
when date.season?
|
60
|
+
translate(('season-%02d' % date.season), node.attributes_for(:form))
|
61
|
+
when date.month.nil?
|
62
|
+
''
|
63
|
+
when node.numeric?
|
64
|
+
date.month.to_s
|
65
|
+
when node.numeric_leading_zeros?
|
66
|
+
'%02d' % date.month
|
67
|
+
else
|
68
|
+
translate(('month-%02d' % date.month), node.attributes_for(:form))
|
69
|
+
end
|
70
|
+
|
71
|
+
when node.year?
|
72
|
+
year = date.year
|
73
|
+
year = year % 100 if node.short?
|
74
|
+
|
75
|
+
year = year.to_s
|
76
|
+
|
77
|
+
year << translate(:ad) if date.ad?
|
78
|
+
year << translate(:ad) if date.ad?
|
79
|
+
|
80
|
+
year
|
81
|
+
|
82
|
+
else
|
83
|
+
''
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module CiteProc
|
4
|
+
module Ruby
|
5
|
+
|
6
|
+
class Renderer
|
7
|
+
|
8
|
+
def format
|
9
|
+
@format ||= Format.load
|
10
|
+
end
|
11
|
+
|
12
|
+
def format=(format)
|
13
|
+
@format = Format.load(format)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Applies the current format on the string using the
|
17
|
+
# node's formatting options.
|
18
|
+
def format!(string, node)
|
19
|
+
format.apply(string, node, locale)
|
20
|
+
end
|
21
|
+
|
22
|
+
def join(list, delimiter = nil)
|
23
|
+
format.join(list, delimiter)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Concatenates two strings, making sure that squeezable
|
27
|
+
# characters are not duplicated between string and suffix.
|
28
|
+
#
|
29
|
+
# @param [String] string
|
30
|
+
# @param [String] suffix
|
31
|
+
#
|
32
|
+
# @return [String] new string consisting of string
|
33
|
+
# and suffix
|
34
|
+
def concat(string, suffix)
|
35
|
+
format.concat(string, suffix)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# @return [String] the roman numeral of number
|
40
|
+
def romanize(number)
|
41
|
+
CiteProc::Number.romanize(number)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Formats pages accoring to format. Valid formats are:
|
46
|
+
#
|
47
|
+
# * "chicago": page ranges are abbreviated according to
|
48
|
+
# the Chicago Manual of Style rules.
|
49
|
+
# * "expanded": Abbreviated page ranges are expanded to
|
50
|
+
# their non-abbreviated form: 42-45, 321-328, 2787-2816.
|
51
|
+
# * "minimal": All digits repeated in the second number
|
52
|
+
# are left out: 42-45, 321-8, 2787-816.
|
53
|
+
#
|
54
|
+
# @param [String] pages to be formatted
|
55
|
+
# @param [String] format to use for formatting
|
56
|
+
def format_page_range(pages, format)
|
57
|
+
return if pages.nil?
|
58
|
+
format_page_range!(pages.dup, format)
|
59
|
+
end
|
60
|
+
|
61
|
+
def format_page_range!(pages, format)
|
62
|
+
return if pages.nil?
|
63
|
+
|
64
|
+
dash = translate('page-range-delimiter') || '–' # en-dash
|
65
|
+
|
66
|
+
pages.gsub! PAGE_RANGE_PATTERN do
|
67
|
+
affixes, f, t = [$1, $3, $4, $6], $2, $5
|
68
|
+
|
69
|
+
# When there are affixes or no format was
|
70
|
+
# specified we skip this part. As a result,
|
71
|
+
# only the delimiter will be replaced!
|
72
|
+
|
73
|
+
if affixes.all?(&:empty?) && !format.nil?
|
74
|
+
|
75
|
+
dim = f.length
|
76
|
+
delta = dim - t.length
|
77
|
+
|
78
|
+
if delta >= 0
|
79
|
+
t.prepend f[0, delta] unless delta.zero?
|
80
|
+
|
81
|
+
if format == 'chicago'
|
82
|
+
changes = dim - f.chars.zip(t.chars).
|
83
|
+
take_while { |a,b| a == b }.length if dim == 4
|
84
|
+
|
85
|
+
format = case
|
86
|
+
when dim < 3
|
87
|
+
'expanded'
|
88
|
+
when dim == 4 && changes > 2
|
89
|
+
'expanded'
|
90
|
+
when f[-2, 2] == '00'
|
91
|
+
'expanded'
|
92
|
+
when f[-2] == '0'
|
93
|
+
'minimal'
|
94
|
+
else
|
95
|
+
'minimal-two'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
case format
|
100
|
+
when 'expanded'
|
101
|
+
# nothing to do
|
102
|
+
when 'minimal'
|
103
|
+
t = t.each_char.drop_while.with_index { |c, i| c == f[i] }.join('')
|
104
|
+
when 'minimal-two'
|
105
|
+
if dim > 2
|
106
|
+
t = t.each_char.drop_while.with_index { |c, i|
|
107
|
+
c == f[i] && dim - i > 2
|
108
|
+
}.join('')
|
109
|
+
end
|
110
|
+
else
|
111
|
+
raise ArgumentError, "unknown page range format: #{format}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
affixes.zip([f, dash, t]).flatten.compact.join('')
|
117
|
+
end
|
118
|
+
|
119
|
+
pages
|
120
|
+
end
|
121
|
+
|
122
|
+
PAGE_RANGE_PATTERN =
|
123
|
+
# ------------ -2- ------------ ------------ -5- ------------
|
124
|
+
/\b([[:alpha:]]*)(\d+)([[:alpha:]]*)\s*[–-]+\s*([[:alpha:]]*)(\d+)([[:alpha:]]*)\b/
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CiteProc
|
2
|
+
module Ruby
|
3
|
+
|
4
|
+
class Renderer
|
5
|
+
|
6
|
+
# @param item [CiteProc::CitationItem]
|
7
|
+
# @param node [CSL::Style::Group]
|
8
|
+
# @return [String]
|
9
|
+
def render_group(item, node)
|
10
|
+
return '' unless node.has_children?
|
11
|
+
|
12
|
+
observer = ItemObserver.new(item.data)
|
13
|
+
observer.start
|
14
|
+
|
15
|
+
begin
|
16
|
+
rendition = node.each_child.map { |child|
|
17
|
+
render item, child
|
18
|
+
}.reject(&:empty?)
|
19
|
+
|
20
|
+
rendition = join(rendition, node.delimiter)
|
21
|
+
|
22
|
+
ensure
|
23
|
+
observer.stop
|
24
|
+
end
|
25
|
+
|
26
|
+
return '' if observer.skip?
|
27
|
+
|
28
|
+
rendition
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module CiteProc
|
2
|
+
module Ruby
|
3
|
+
class Renderer
|
4
|
+
|
5
|
+
class History
|
6
|
+
attr_reader :maxsize, :memory
|
7
|
+
|
8
|
+
def initialize(state, maxsize = 10)
|
9
|
+
@state, @maxsize, = state, maxsize
|
10
|
+
@state.add_observer(self)
|
11
|
+
|
12
|
+
@memory = Hash.new do |hash, key|
|
13
|
+
hash[key] = []
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def update(action, mode, memories = {})
|
18
|
+
history = memory[mode]
|
19
|
+
history << memories
|
20
|
+
|
21
|
+
ensure
|
22
|
+
history.shift if history.length > maxsize
|
23
|
+
end
|
24
|
+
|
25
|
+
def recall(mode)
|
26
|
+
memory[mode][-1]
|
27
|
+
end
|
28
|
+
|
29
|
+
def citation
|
30
|
+
memory['citation']
|
31
|
+
end
|
32
|
+
|
33
|
+
def bibliography
|
34
|
+
memory['bibliography']
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module CiteProc
|
2
|
+
module Ruby
|
3
|
+
|
4
|
+
class Renderer
|
5
|
+
|
6
|
+
# @param item [CiteProc::CitationItem]
|
7
|
+
# @param node [CSL::Style::Label]
|
8
|
+
# @param variable [String]
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
def render_label(item, node, variable = node.variable)
|
12
|
+
return '' if variable.nil? || variable.empty?
|
13
|
+
|
14
|
+
case
|
15
|
+
when node.page?
|
16
|
+
value, name = item.read_attribute(:page) || item.data[:page].to_s, :page
|
17
|
+
|
18
|
+
format_page_range!(value, node.page_range_format)
|
19
|
+
|
20
|
+
when node.locator?
|
21
|
+
|
22
|
+
# Subtle: when there is no locator we also look
|
23
|
+
# in item.data; there should be no locator there
|
24
|
+
# either but the read access will be noticed by
|
25
|
+
# observers (if any).
|
26
|
+
value, name = item.locator || item.data.locator, item.label || 'page'
|
27
|
+
|
28
|
+
when node.names_label?
|
29
|
+
|
30
|
+
# We handle the editortranslator special case
|
31
|
+
# by fetching editors since we can assume
|
32
|
+
# that both are present and identical!
|
33
|
+
if variable == :editortranslator
|
34
|
+
value, name = item.data[:editor], variable.to_s
|
35
|
+
else
|
36
|
+
value, name = item.data[variable], variable.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
else
|
40
|
+
value, name = item.data[variable], node.term
|
41
|
+
end
|
42
|
+
|
43
|
+
return '' if value.nil? || value.respond_to?(:empty?) && value.empty?
|
44
|
+
|
45
|
+
options = node.attributes_for :form
|
46
|
+
|
47
|
+
options[:plural] = case
|
48
|
+
when node.always_pluralize?
|
49
|
+
true
|
50
|
+
when node.never_pluralize?
|
51
|
+
false
|
52
|
+
when node.number_of_pages?, node.number_of_volumes?
|
53
|
+
value.to_i > 1
|
54
|
+
when value.respond_to?(:plural?)
|
55
|
+
value.plural?
|
56
|
+
else
|
57
|
+
CiteProc::Number.pluralize?(value.to_s)
|
58
|
+
end
|
59
|
+
|
60
|
+
translate name, options
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CiteProc
|
2
|
+
module Ruby
|
3
|
+
|
4
|
+
class Renderer
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
# @param item [CiteProc::CitationItem]
|
9
|
+
# @param node [CSL::Style::Layout]
|
10
|
+
# @return [String]
|
11
|
+
def render_layout(item, node)
|
12
|
+
join node.each_child.map { |child|
|
13
|
+
render item, child
|
14
|
+
}.reject(&:empty?), node.delimiter
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CiteProc
|
2
|
+
module Ruby
|
3
|
+
|
4
|
+
class Renderer
|
5
|
+
|
6
|
+
def locale
|
7
|
+
@locale ||= CSL::Locale.load
|
8
|
+
end
|
9
|
+
|
10
|
+
def locale=(locale)
|
11
|
+
@locale = CSL::Locale.load(locale)
|
12
|
+
end
|
13
|
+
|
14
|
+
def translate(name, options = {})
|
15
|
+
locale.translate(name, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [String] number as an ordinal
|
19
|
+
def ordinalize(number, options = {})
|
20
|
+
locale.ordinalize(number, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CiteProc
|
2
|
+
module Ruby
|
3
|
+
|
4
|
+
class Renderer
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
# @param item [CiteProc::CitationItem]
|
9
|
+
# @param node [CSL::Style::Layout]
|
10
|
+
# @return [String]
|
11
|
+
def render_macro(item, node)
|
12
|
+
node.each_child.map { |child|
|
13
|
+
render item, child
|
14
|
+
}.join('')
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,401 @@
|
|
1
|
+
module CiteProc
|
2
|
+
module Ruby
|
3
|
+
|
4
|
+
class Renderer
|
5
|
+
|
6
|
+
# @param item [CiteProc::CitationItem]
|
7
|
+
# @param node [CSL::Style::Names]
|
8
|
+
# @return [String]
|
9
|
+
def render_names(item, node)
|
10
|
+
return '' unless node.has_variable?
|
11
|
+
|
12
|
+
names = node.variable.split(/\s+/).map do |role|
|
13
|
+
[role.to_sym, item.data[role]]
|
14
|
+
end
|
15
|
+
|
16
|
+
names.reject! { |n| n[1].nil? || n[1].empty? }
|
17
|
+
|
18
|
+
# Filter out suppressed names only now, because
|
19
|
+
# we are not interested in suppressed variables
|
20
|
+
# which are empty anyway!
|
21
|
+
suppressed = names.reject! { |n| item.suppressed? n }
|
22
|
+
|
23
|
+
if names.empty?
|
24
|
+
# We also return when the list is empty because
|
25
|
+
# of a suppression, because we do not want to
|
26
|
+
# substitute suppressed items!
|
27
|
+
return '' unless suppressed.nil? && node.has_substitute?
|
28
|
+
|
29
|
+
rendered_names = render_substitute item, node.substitute
|
30
|
+
|
31
|
+
if substitute_subsequent_authors_completely? &&
|
32
|
+
completely_substitute?(rendered_names)
|
33
|
+
|
34
|
+
rendered_names = state.node.subsequent_author_substitute
|
35
|
+
end
|
36
|
+
|
37
|
+
rendered_names
|
38
|
+
|
39
|
+
else
|
40
|
+
|
41
|
+
resolve_editor_translator_exception! names
|
42
|
+
|
43
|
+
# Pick the names node that will be used for
|
44
|
+
# formatting; if we are currently in substiution
|
45
|
+
# mode, the node that is being substituted for
|
46
|
+
# will take precedence if the current node is
|
47
|
+
# a descendant of it.
|
48
|
+
#
|
49
|
+
# This makes sure that nodes in macros do not
|
50
|
+
# use the original names node.
|
51
|
+
#
|
52
|
+
# When the current node has children the names
|
53
|
+
# will not be substituted either.
|
54
|
+
if substitution_mode? && !node.has_children? &&
|
55
|
+
node.ancestors.include?(state.substitute)
|
56
|
+
|
57
|
+
names_node = state.substitute
|
58
|
+
|
59
|
+
else
|
60
|
+
names_node = node
|
61
|
+
end
|
62
|
+
|
63
|
+
name = name_node_for(names_node)
|
64
|
+
|
65
|
+
return count_names(names, name).to_s if name.count?
|
66
|
+
|
67
|
+
names.map! do |role, ns|
|
68
|
+
if names_node.has_label?
|
69
|
+
label = render_label item, names_node.label, role
|
70
|
+
label = format! label, names_node.label
|
71
|
+
|
72
|
+
rendered_names = render_name(ns, name)
|
73
|
+
|
74
|
+
if rendered_names.empty?
|
75
|
+
rendered_names
|
76
|
+
else
|
77
|
+
if names_node.prefix_label?
|
78
|
+
concat label, rendered_names
|
79
|
+
else
|
80
|
+
concat rendered_names, label
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
else
|
85
|
+
render_name ns, name
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
join names, names_node.delimiter(state.node)
|
90
|
+
end
|
91
|
+
|
92
|
+
ensure
|
93
|
+
state.rendered_names!
|
94
|
+
end
|
95
|
+
|
96
|
+
def count_names(names, node)
|
97
|
+
names.reduce(0) do |count, (_, ns)|
|
98
|
+
if node.truncate?(ns)
|
99
|
+
count + node.truncate(ns).length
|
100
|
+
else
|
101
|
+
count + ns.length
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def substitute_subsequent_authors?
|
107
|
+
bibliography_mode? && state.node.substitute_subsequent_authors?
|
108
|
+
end
|
109
|
+
|
110
|
+
def substitute_subsequent_authors_completely?
|
111
|
+
substitute_subsequent_authors? &&
|
112
|
+
state.node.substitute_subsequent_authors_completely?
|
113
|
+
end
|
114
|
+
|
115
|
+
def substitute_subsequent_authors_individually?
|
116
|
+
substitute_subsequent_authors? &&
|
117
|
+
state.node.substitute_subsequent_authors_individually?
|
118
|
+
end
|
119
|
+
|
120
|
+
def completely_substitute?(names)
|
121
|
+
# Substitution applies only to the first names
|
122
|
+
# node being rendered!
|
123
|
+
return false if state.rendered_names?
|
124
|
+
|
125
|
+
state.store_authors! names
|
126
|
+
previous_names = state.previous_authors
|
127
|
+
|
128
|
+
return false unless previous_names
|
129
|
+
|
130
|
+
names == previous_names[0]
|
131
|
+
end
|
132
|
+
|
133
|
+
def individually_substitute!(names)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Formats one or more names according to the
|
137
|
+
# configuration of the passed-in node.
|
138
|
+
# Returns the formatted name(s) as a string.
|
139
|
+
#
|
140
|
+
# @param names [CiteProc::Names]
|
141
|
+
# @param node [CSL::Style::Name]
|
142
|
+
# @return [String]
|
143
|
+
def render_name(names, node)
|
144
|
+
|
145
|
+
# TODO handle subsequent citation rules
|
146
|
+
|
147
|
+
delimiter = node.delimiter
|
148
|
+
|
149
|
+
connector = node.connector
|
150
|
+
connector = translate('and') if connector == 'text'
|
151
|
+
|
152
|
+
# Add spaces around connector
|
153
|
+
connector = " #{connector} " unless connector.nil?
|
154
|
+
|
155
|
+
rendered_names = case
|
156
|
+
when node.truncate?(names)
|
157
|
+
truncated = node.truncate(names)
|
158
|
+
|
159
|
+
return '' if truncated.empty?
|
160
|
+
|
161
|
+
if node.delimiter_precedes_last?(truncated)
|
162
|
+
connector = join [delimiter, connector].compact
|
163
|
+
end
|
164
|
+
|
165
|
+
if node.ellipsis? && names.length - truncated.length > 1
|
166
|
+
join [
|
167
|
+
join(truncated.map.with_index { |name, idx|
|
168
|
+
render_individual_name name, node, idx + 1
|
169
|
+
}, delimiter),
|
170
|
+
|
171
|
+
render_individual_name(names[-1], node, truncated.length + 1)
|
172
|
+
|
173
|
+
], node.ellipsis
|
174
|
+
|
175
|
+
else
|
176
|
+
others = node.et_al ?
|
177
|
+
format!(translate(node.et_al[:term]), node.et_al) :
|
178
|
+
translate('et-al')
|
179
|
+
|
180
|
+
connector = node.delimiter_precedes_et_al?(truncated) ?
|
181
|
+
delimiter : ' '
|
182
|
+
|
183
|
+
join [
|
184
|
+
join(truncated.map.with_index { |name, idx|
|
185
|
+
render_individual_name name, node, idx + 1
|
186
|
+
}, delimiter),
|
187
|
+
|
188
|
+
others
|
189
|
+
|
190
|
+
], connector
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
when names.length < 3
|
195
|
+
if node.delimiter_precedes_last?(names)
|
196
|
+
connector = [delimiter, connector].compact.join('').squeeze(' ')
|
197
|
+
end
|
198
|
+
|
199
|
+
join names.map.with_index { |name, idx|
|
200
|
+
render_individual_name name, node, idx + 1
|
201
|
+
}, connector || delimiter
|
202
|
+
|
203
|
+
else
|
204
|
+
if node.delimiter_precedes_last?(names)
|
205
|
+
connector = [delimiter, connector].compact.join('').squeeze(' ')
|
206
|
+
end
|
207
|
+
|
208
|
+
join [
|
209
|
+
join(names[0...-1].map.with_index { |name, idx|
|
210
|
+
render_individual_name name, node, idx + 1
|
211
|
+
}, delimiter),
|
212
|
+
|
213
|
+
render_individual_name(names[-1], node, names.length)
|
214
|
+
|
215
|
+
], connector || delimiter
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
if substitute_subsequent_authors_completely? &&
|
220
|
+
completely_substitute?(rendered_names)
|
221
|
+
|
222
|
+
rendered_names = state.node.subsequent_author_substitute
|
223
|
+
end
|
224
|
+
|
225
|
+
format! rendered_names, node
|
226
|
+
end
|
227
|
+
|
228
|
+
# @param names [CiteProc::Name]
|
229
|
+
# @param node [CSL::Style::Name]
|
230
|
+
# @param position [Fixnum]
|
231
|
+
# @return [String]
|
232
|
+
def render_individual_name(name, node, position = 1)
|
233
|
+
if name.personal?
|
234
|
+
name = name.dup
|
235
|
+
|
236
|
+
# TODO move parts of the formatting logic here
|
237
|
+
# because name parts may include particles etc.
|
238
|
+
|
239
|
+
|
240
|
+
# Strip away some unusual characters to normalize
|
241
|
+
# sort order for names.
|
242
|
+
if sort_mode?
|
243
|
+
name.family = name.family.to_s.gsub(/[\[\]]|^\W+/, '')
|
244
|
+
end
|
245
|
+
|
246
|
+
name.options.merge! node.name_options
|
247
|
+
name.sort_order! node.name_as_sort_order_at?(position)
|
248
|
+
|
249
|
+
name.initialize_without_hyphen! if node.initialize_without_hyphen?
|
250
|
+
|
251
|
+
if style && style.demote_particle?
|
252
|
+
name.options[:'demote-non-dropping-particle'] = style.demote_particle
|
253
|
+
end
|
254
|
+
|
255
|
+
# Strip away (hyphenated) particles in sort mode!
|
256
|
+
if sort_mode? && name.demote_particle?
|
257
|
+
name.family = name.family.to_s.sub(/^[[:lower:]]+[\s-]/, '')
|
258
|
+
end
|
259
|
+
|
260
|
+
node.name_part.each do |part|
|
261
|
+
case part[:name]
|
262
|
+
when 'family'
|
263
|
+
if !name.particle? || name.demote_particle?
|
264
|
+
name.family = format!(name.family, part)
|
265
|
+
else
|
266
|
+
name.family = format!("#{name.particle} #{name.family}", part)
|
267
|
+
name.particle = nil
|
268
|
+
end
|
269
|
+
|
270
|
+
# Name suffix must be enclosed by family-part
|
271
|
+
# suffix in display order!
|
272
|
+
if name.has_suffix? && !name.sort_order? && part.attribute?(:suffix)
|
273
|
+
comma = name.comma_suffix? ? name.comma : ' '
|
274
|
+
suffix = part[:suffix]
|
275
|
+
|
276
|
+
name.family.chomp! suffix
|
277
|
+
name.family.concat "#{comma}#{name.suffix}#{suffix}"
|
278
|
+
name.suffix = nil
|
279
|
+
end
|
280
|
+
|
281
|
+
when 'given'
|
282
|
+
if name.dropping_particle?
|
283
|
+
name.given = format!("#{name.initials} #{name.dropping_particle}", part)
|
284
|
+
name.dropping_particle = nil
|
285
|
+
else
|
286
|
+
name.given = format!(name.initials, part)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Demoted particles must be enclosed by
|
290
|
+
# given-part affixes in sort order!
|
291
|
+
if name.particle? && name.demote_particle? &&
|
292
|
+
name.sort_order? && part.attribute?(:suffix)
|
293
|
+
|
294
|
+
suffix = part[:suffix]
|
295
|
+
|
296
|
+
name.given.chomp! suffix
|
297
|
+
name.given.concat " #{name.particle}#{suffix}"
|
298
|
+
|
299
|
+
name.particle = nil
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
format! name.format, node
|
308
|
+
end
|
309
|
+
|
310
|
+
# @param item [CiteProc::CitationItem]
|
311
|
+
# @param node [CSL::Style::Substitute]
|
312
|
+
# @return [String]
|
313
|
+
def render_substitute(item, node)
|
314
|
+
return '' unless node.has_children?
|
315
|
+
|
316
|
+
if substitution_mode?
|
317
|
+
saved_substitute = state.substitute
|
318
|
+
end
|
319
|
+
|
320
|
+
state.substitute! node.parent
|
321
|
+
observer = ItemObserver.new(item.data)
|
322
|
+
|
323
|
+
node.each_child do |child|
|
324
|
+
observer.start
|
325
|
+
|
326
|
+
begin
|
327
|
+
string = render(item, child)
|
328
|
+
|
329
|
+
unless string.empty?
|
330
|
+
# Variables rendered as substitutes
|
331
|
+
# must be suppressed during the remainder
|
332
|
+
# of the rendering process!
|
333
|
+
item.suppress!(*observer.accessed)
|
334
|
+
|
335
|
+
return string # break out of each loop!
|
336
|
+
end
|
337
|
+
|
338
|
+
ensure
|
339
|
+
observer.stop
|
340
|
+
observer.clear!
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
|
345
|
+
'' # no substitute was rendered
|
346
|
+
ensure
|
347
|
+
state.clear_substitute! saved_substitute
|
348
|
+
end
|
349
|
+
|
350
|
+
|
351
|
+
private
|
352
|
+
|
353
|
+
def name_node_for(names_node)
|
354
|
+
# Make a copy of the name node and inherit
|
355
|
+
# options from root and citation/bibliography
|
356
|
+
# depending on current rendering mode.
|
357
|
+
if names_node.has_name?
|
358
|
+
name = names_node.name.deep_copy
|
359
|
+
else
|
360
|
+
name = CSL::Style::Name.new
|
361
|
+
end
|
362
|
+
|
363
|
+
# Inherit name options from style and the
|
364
|
+
# current rendering node. We pass both in,
|
365
|
+
# because this is now an unlinked node!
|
366
|
+
options = name.inherited_name_options(state.node, names_node.root)
|
367
|
+
|
368
|
+
name.reverse_merge! options
|
369
|
+
name.et_al = names_node.et_al if names_node.has_et_al?
|
370
|
+
|
371
|
+
# Override options if we are rendering a sort key!
|
372
|
+
if sort_mode?
|
373
|
+
name.merge! state.node.name_options
|
374
|
+
name.all_names_as_sort_order!
|
375
|
+
end
|
376
|
+
|
377
|
+
name
|
378
|
+
end
|
379
|
+
|
380
|
+
def resolve_editor_translator_exception!(names)
|
381
|
+
|
382
|
+
i = names.index { |role, _| role == :translator }
|
383
|
+
return names if i.nil?
|
384
|
+
|
385
|
+
j = names.index { |role, _| role == :editor }
|
386
|
+
return names if j.nil?
|
387
|
+
|
388
|
+
return names unless names[i][1] == names[j][1]
|
389
|
+
|
390
|
+
# rename the first instance and drop the second one
|
391
|
+
i, j = j, i if j < i
|
392
|
+
|
393
|
+
names[i][0] = :editortranslator
|
394
|
+
names.slice!(j)
|
395
|
+
|
396
|
+
names
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|
401
|
+
end
|