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.
Files changed (186) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.document +4 -0
  4. data/.gitignore +10 -0
  5. data/.rspec +3 -0
  6. data/.simplecov +4 -0
  7. data/.travis.yml +17 -0
  8. data/.yardopts +2 -0
  9. data/AGPL +662 -0
  10. data/BSDL +29 -0
  11. data/Gemfile +42 -0
  12. data/Guardfile +14 -0
  13. data/README.md +32 -76
  14. data/Rakefile +60 -0
  15. data/citeproc-ruby.gemspec +46 -0
  16. data/cucumber.yml +1 -0
  17. data/features/bibliography.feature +25 -0
  18. data/features/name_options.feature +37 -0
  19. data/features/names.feature +192 -0
  20. data/features/renderer.feature +74 -0
  21. data/features/sort.feature +50 -0
  22. data/features/step_definitions/renderer.rb +80 -0
  23. data/features/support/env.rb +33 -0
  24. data/features/support/hooks.rb +10 -0
  25. data/lib/citeproc/ruby.rb +32 -0
  26. data/lib/citeproc/ruby/engine.rb +122 -0
  27. data/lib/citeproc/ruby/format.rb +303 -0
  28. data/lib/citeproc/ruby/formats/default.rb +25 -0
  29. data/lib/citeproc/ruby/formats/html.rb +221 -0
  30. data/lib/citeproc/ruby/renderer.rb +140 -0
  31. data/lib/citeproc/ruby/renderer/choose.rb +106 -0
  32. data/lib/citeproc/ruby/renderer/date.rb +90 -0
  33. data/lib/citeproc/ruby/renderer/format.rb +129 -0
  34. data/lib/citeproc/ruby/renderer/group.rb +34 -0
  35. data/lib/citeproc/ruby/renderer/history.rb +40 -0
  36. data/lib/citeproc/ruby/renderer/label.rb +66 -0
  37. data/lib/citeproc/ruby/renderer/layout.rb +20 -0
  38. data/lib/citeproc/ruby/renderer/locale.rb +26 -0
  39. data/lib/citeproc/ruby/renderer/macro.rb +20 -0
  40. data/lib/citeproc/ruby/renderer/names.rb +401 -0
  41. data/lib/citeproc/ruby/renderer/number.rb +41 -0
  42. data/lib/citeproc/ruby/renderer/observer.rb +44 -0
  43. data/lib/citeproc/ruby/renderer/state.rb +96 -0
  44. data/lib/citeproc/ruby/renderer/text.rb +62 -0
  45. data/lib/citeproc/ruby/sort.rb +82 -0
  46. data/lib/citeproc/ruby/version.rb +5 -0
  47. data/spec/citeproc/ruby/engine_spec.rb +94 -0
  48. data/spec/citeproc/ruby/formats/default_spec.rb +159 -0
  49. data/spec/citeproc/ruby/formats/html_spec.rb +162 -0
  50. data/spec/citeproc/ruby/renderer/choose_spec.rb +293 -0
  51. data/spec/citeproc/ruby/renderer/date_spec.rb +173 -0
  52. data/spec/citeproc/ruby/renderer/group_spec.rb +88 -0
  53. data/spec/citeproc/ruby/renderer/history_spec.rb +38 -0
  54. data/spec/citeproc/ruby/renderer/label_spec.rb +225 -0
  55. data/spec/citeproc/ruby/renderer/layout_spec.rb +41 -0
  56. data/spec/citeproc/ruby/renderer/macro_spec.rb +31 -0
  57. data/spec/citeproc/ruby/renderer/names_spec.rb +396 -0
  58. data/spec/citeproc/ruby/renderer/number_spec.rb +120 -0
  59. data/spec/citeproc/ruby/renderer/text_spec.rb +120 -0
  60. data/spec/citeproc/ruby/renderer_spec.rb +65 -0
  61. data/spec/fixtures/items.rb +80 -0
  62. data/{resource/locale → spec/fixtures/locales}/locales-en-US.xml +2 -11
  63. data/{resource/locale → spec/fixtures/locales}/locales-fr-FR.xml +77 -66
  64. data/{resource/style → spec/fixtures/styles}/apa.csl +5 -5
  65. data/spec/spec_helper.rb +67 -14
  66. metadata +121 -211
  67. data/lib/citeproc.rb +0 -100
  68. data/lib/citeproc/bibliography.rb +0 -57
  69. data/lib/citeproc/data.rb +0 -149
  70. data/lib/citeproc/date.rb +0 -133
  71. data/lib/citeproc/formatter.rb +0 -38
  72. data/lib/citeproc/item.rb +0 -53
  73. data/lib/citeproc/name.rb +0 -284
  74. data/lib/citeproc/processor.rb +0 -166
  75. data/lib/citeproc/selector.rb +0 -61
  76. data/lib/citeproc/variable.rb +0 -82
  77. data/lib/citeproc/version.rb +0 -3
  78. data/lib/csl/locale.rb +0 -223
  79. data/lib/csl/node.rb +0 -72
  80. data/lib/csl/nodes.rb +0 -1418
  81. data/lib/csl/renderer.rb +0 -88
  82. data/lib/csl/sort.rb +0 -61
  83. data/lib/csl/style.rb +0 -110
  84. data/lib/csl/term.rb +0 -124
  85. data/lib/extensions/core.rb +0 -43
  86. data/lib/plugins/filters/bibtex.rb +0 -12
  87. data/lib/plugins/formats/default.rb +0 -134
  88. data/lib/plugins/formats/html.rb +0 -67
  89. data/lib/support/attributes.rb +0 -99
  90. data/lib/support/compatibility.rb +0 -83
  91. data/lib/support/tree.rb +0 -80
  92. data/resource/locale/locales-af-ZA.xml +0 -305
  93. data/resource/locale/locales-ar-AR.xml +0 -306
  94. data/resource/locale/locales-bg-BG.xml +0 -305
  95. data/resource/locale/locales-ca-AD.xml +0 -305
  96. data/resource/locale/locales-cs-CZ.xml +0 -305
  97. data/resource/locale/locales-da-DK.xml +0 -305
  98. data/resource/locale/locales-de-AT.xml +0 -304
  99. data/resource/locale/locales-de-CH.xml +0 -304
  100. data/resource/locale/locales-de-DE.xml +0 -332
  101. data/resource/locale/locales-el-GR.xml +0 -305
  102. data/resource/locale/locales-en-GB.xml +0 -304
  103. data/resource/locale/locales-es-ES.xml +0 -305
  104. data/resource/locale/locales-et-EE.xml +0 -304
  105. data/resource/locale/locales-eu.xml +0 -305
  106. data/resource/locale/locales-fa-IR.xml +0 -304
  107. data/resource/locale/locales-fi-FI.xml +0 -304
  108. data/resource/locale/locales-fr-CA.xml +0 -306
  109. data/resource/locale/locales-he-IL.xml +0 -304
  110. data/resource/locale/locales-hu-HU.xml +0 -305
  111. data/resource/locale/locales-is-IS.xml +0 -304
  112. data/resource/locale/locales-it-IT.xml +0 -305
  113. data/resource/locale/locales-ja-JP.xml +0 -305
  114. data/resource/locale/locales-kh-KH.xml +0 -303
  115. data/resource/locale/locales-km-KH.xml +0 -304
  116. data/resource/locale/locales-ko-KR.xml +0 -305
  117. data/resource/locale/locales-mn-MN.xml +0 -306
  118. data/resource/locale/locales-nb-NO.xml +0 -304
  119. data/resource/locale/locales-nl-NL.xml +0 -304
  120. data/resource/locale/locales-nn-NO.xml +0 -304
  121. data/resource/locale/locales-pl-PL.xml +0 -305
  122. data/resource/locale/locales-pt-BR.xml +0 -304
  123. data/resource/locale/locales-pt-PT.xml +0 -305
  124. data/resource/locale/locales-ro-RO.xml +0 -305
  125. data/resource/locale/locales-ru-RU.xml +0 -306
  126. data/resource/locale/locales-sk-SK.xml +0 -304
  127. data/resource/locale/locales-sl-SI.xml +0 -305
  128. data/resource/locale/locales-sr-RS.xml +0 -305
  129. data/resource/locale/locales-sv-SE.xml +0 -305
  130. data/resource/locale/locales-th-TH.xml +0 -304
  131. data/resource/locale/locales-tr-TR.xml +0 -305
  132. data/resource/locale/locales-uk-UA.xml +0 -306
  133. data/resource/locale/locales-vi-VN.xml +0 -305
  134. data/resource/locale/locales-zh-CN.xml +0 -304
  135. data/resource/locale/locales-zh-TW.xml +0 -305
  136. data/resource/schema/csl-categories.rnc +0 -39
  137. data/resource/schema/csl-data.rnc +0 -98
  138. data/resource/schema/csl-terms.rnc +0 -106
  139. data/resource/schema/csl-types.rnc +0 -39
  140. data/resource/schema/csl-variables.rnc +0 -182
  141. data/resource/schema/csl.rnc +0 -941
  142. data/resource/style/bibtex.csl +0 -177
  143. data/resource/style/chicago-annotated-bibliography.csl +0 -513
  144. data/resource/style/chicago-author-date-basque.csl +0 -707
  145. data/resource/style/chicago-author-date-de.csl +0 -394
  146. data/resource/style/chicago-author-date-listing.csl +0 -434
  147. data/resource/style/chicago-author-date.csl +0 -425
  148. data/resource/style/chicago-dated-note-biblio-no-ibid.csl +0 -472
  149. data/resource/style/chicago-fullnote-bibliography-bb.csl +0 -928
  150. data/resource/style/chicago-fullnote-bibliography-delimiter-fixes.csl +0 -972
  151. data/resource/style/chicago-fullnote-bibliography-no-ibid-delimiter-fixes.csl +0 -963
  152. data/resource/style/chicago-fullnote-bibliography-no-ibid.csl +0 -785
  153. data/resource/style/chicago-fullnote-bibliography.csl +0 -803
  154. data/resource/style/chicago-library-list.csl +0 -511
  155. data/resource/style/chicago-note-biblio-no-ibid.csl +0 -514
  156. data/resource/style/chicago-note-bibliography.csl +0 -530
  157. data/resource/style/chicago-note.csl +0 -388
  158. data/resource/style/chicago-quick-copy.csl +0 -685
  159. data/resource/style/ieee.csl +0 -299
  160. data/resource/style/mla-notes.csl +0 -796
  161. data/resource/style/mla-underline.csl +0 -175
  162. data/resource/style/mla-url.csl +0 -214
  163. data/resource/style/mla.csl +0 -394
  164. data/resource/style/vancouver-brackets.csl +0 -256
  165. data/resource/style/vancouver-superscript-bracket-only-year.csl +0 -165
  166. data/resource/style/vancouver-superscript.csl +0 -256
  167. data/resource/style/vancouver.csl +0 -256
  168. data/spec/citeproc/bibliography_spec.rb +0 -45
  169. data/spec/citeproc/citeproc_spec.rb +0 -80
  170. data/spec/citeproc/date_spec.rb +0 -89
  171. data/spec/citeproc/formatter_spec.rb +0 -101
  172. data/spec/citeproc/item_spec.rb +0 -71
  173. data/spec/citeproc/name_spec.rb +0 -30
  174. data/spec/citeproc/processor_spec.rb +0 -61
  175. data/spec/citeproc/selector_spec.rb +0 -82
  176. data/spec/citeproc/variable_spec.rb +0 -69
  177. data/spec/csl/locale_spec.rb +0 -208
  178. data/spec/csl/node_spec.rb +0 -25
  179. data/spec/csl/nodes_spec.rb +0 -145
  180. data/spec/csl/style_spec.rb +0 -62
  181. data/spec/csl/term_spec.rb +0 -56
  182. data/spec/fixtures/dates.yaml +0 -80
  183. data/spec/fixtures/names.yaml +0 -115
  184. data/spec/fixtures/nodes.yaml +0 -245
  185. data/spec/support/attributes_spec.rb +0 -39
  186. 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