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.
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