citeproc-ruby 0.0.6 → 1.0.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 +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
|