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
@@ -1,61 +0,0 @@
1
- module CiteProc
2
-
3
- class Selector
4
- include Support::Attributes
5
-
6
- attr_fields :select, :include, :exclude, :quash
7
-
8
-
9
- def initialize(argument = {})
10
- key_filter['all'] = 'select'
11
- key_filter['any'] = 'include'
12
- key_filter['none'] = 'exclude'
13
- key_filter['skip'] = 'quash'
14
-
15
- merge(normalize(argument))
16
- end
17
-
18
- def type
19
- attributes.keys.detect { |k| [:select, :include, :exclude].include?(k.to_sym) }
20
- end
21
-
22
- # @returns one of :all?, :any?, :none?
23
- def matcher
24
- type == 'include' ? :any? : type == 'exclude' ? :none? : :all?
25
- end
26
-
27
- def conditions
28
- attributes[type] || []
29
- end
30
-
31
- def matches?(item)
32
- conditions.send(matcher) { |condition| match(item, condition) }
33
- end
34
-
35
- def skip?(item)
36
- has_quash? && quash.all? { |condition| match(item, condition) }
37
- end
38
-
39
- def to_proc
40
- Proc.new { |item| matches?(item) && !skip?(item) }
41
- end
42
-
43
- protected
44
-
45
- def match(item, condition)
46
- values, expected = [item[condition['field']]].flatten.map(&:to_s), [condition['value']].flatten
47
- expected & values != []
48
- end
49
-
50
- def normalize(argument)
51
- case
52
- when [String, Symbol].include?(argument.class) && !(argument.to_s =~ /^\s*\{/)
53
- { argument.to_s => [] }
54
- else
55
- argument
56
- end
57
- end
58
-
59
- end
60
-
61
- end
@@ -1,82 +0,0 @@
1
- require 'forwardable'
2
-
3
- module CiteProc
4
-
5
- class Variable
6
- extend Forwardable
7
-
8
- include Support::Attributes
9
- include Comparable
10
-
11
- @date_fields = %w{ accessed container event-date issued original-date }
12
-
13
- @name_fields = %w{
14
- author editor translator recipient interviewer publisher composer
15
- original-publisher original-author container-author collection-editor }
16
-
17
- @text_fields = %w{
18
- id abstract annote archive archive-location archive-place authority
19
- call-number chapter-number citation-label citation-number collection-title
20
- container-title DOI edition event event-place first-reference-note-number
21
- genre ISBN issue jurisdiction keyword locator medium note number
22
- number-of-pages number-of-volumes original-publisher original-publisher-place
23
- original-title page page-first publisher publisher-place references
24
- section status title URL version volume year-suffix }
25
-
26
- @filters = Hash.new
27
-
28
- @types = Hash.new(Variable)
29
-
30
- attr_fields :value
31
-
32
- def_delegators :value, :empty?, :to_s, :match
33
-
34
- class << self
35
- attr_reader :date_fields, :name_fields, :text_fields, :filters, :types
36
-
37
- def fields
38
- date_fields + name_fields + text_fields
39
- end
40
-
41
- def filter(id, key)
42
- Variable.filters[id][key]
43
- end
44
-
45
- def parse(values, name=nil)
46
- values.is_a?(Array) ? values.map { |value| Variable.types[name].new(value) } :
47
- Variable.types[name].new(values)
48
- end
49
- end
50
-
51
- def initialize(attributes={}, &block)
52
- parse!(attributes)
53
- yield self if block_given?
54
- end
55
-
56
- def parse!(argument)
57
- argument = argument.to_hash if argument.is_a?(Variable)
58
- argument.is_a?(Hash) ? self.merge!(argument) : self.value = argument.to_s
59
- end
60
-
61
- def numeric?
62
- to_s =~ /\d/
63
- end
64
-
65
- # @returns (first) numeric data contained in the variable's value
66
- def to_i
67
- to_s =~ /(-?\d[\d,\.]*)/ && $1.to_i || 0
68
- end
69
-
70
-
71
- def <=>(other)
72
- strip_markup(to_s) <=> strip_markup(other.to_s)
73
- end
74
-
75
- protected
76
-
77
- def strip_markup(string)
78
- string.gsub(/<[^>]*>/, '')
79
- end
80
- end
81
-
82
- end
@@ -1,3 +0,0 @@
1
- module CiteProc
2
- VERSION = '0.0.6'.freeze
3
- end
data/lib/csl/locale.rb DELETED
@@ -1,223 +0,0 @@
1
- module CSL
2
-
3
- class Locale
4
- include Support::Tree
5
- include Comparable
6
-
7
- # Class Instance Variables
8
- @path = File.expand_path('../../../resource/locale/', __FILE__)
9
- @default = 'en-US'
10
-
11
- # Language and region defaults
12
- @regions = Hash.new { |hash, key| hash[key] = Locale.match_region(key) }
13
- @regions['en'] = 'US'
14
- @regions['de'] = 'DE'
15
- @regions['pt'] = 'PT'
16
-
17
- @languages = Hash.new { |hash, key| hash[key] = Locale.match_language(key) }
18
-
19
- class << self
20
- attr_accessor :path, :default, :regions, :languages
21
-
22
- # @returns first available region for current language.
23
- def match_region(language)
24
- Dir.entries(Locale.path).detect { |l| l.match(%r/^[\w]+-#{language}-([A-Z]{2})\.xml$/) }
25
- $1
26
- end
27
-
28
- # @returns first available language for current region.
29
- def match_language(region)
30
- Dir.entries(Locale.path).detect { |l| l.match(%r/^[\w]+-([a-z]{2})-#{region}\.xml$/) }
31
- $1
32
- end
33
- end
34
-
35
-
36
- attr_reader :language, :region
37
-
38
- alias :style :parent
39
-
40
- # @param argument a language tag; or an XML node
41
- def initialize(argument=nil, style=nil, &block)
42
- case
43
- when argument.is_a?(Nokogiri::XML::Node)
44
- @language, @region = argument['lang'].split(/-/) if argument['lang']
45
- parse!(argument)
46
-
47
- when argument.is_a?(String) && argument.match(/^\s*<locale/)
48
- argument = Nokogiri::XML.parse(argument) { |config| config.strict.noblanks }.root
49
- @language, @region = argument['lang'].split(/-/) if argument['lang']
50
- parse!(argument)
51
-
52
- when argument.is_a?(String) || argument.is_a?(Symbol)
53
- set(argument)
54
- end
55
-
56
- @parent = style
57
-
58
- yield self if block_given?
59
- end
60
-
61
- def language=(language)
62
- @language = language
63
- @region = Locale.regions[language]
64
- end
65
-
66
- def region=(region)
67
- @region = region
68
- @language = Locale.languages[region]
69
- end
70
-
71
- def parse!(node)
72
- raise(ArgumentError, "expected XML node, was: #{ node.inspect }") unless node.is_a?(Nokogiri::XML::Node)
73
-
74
- @terms = Term.build(node)
75
-
76
- @options = Hash[*node.css('style-options').map(&:attributes).map { |a| a.map { |name, a| [name, a.value] } }.flatten]
77
-
78
- @date = Hash.new([])
79
- ['text', 'numeric'].each do |form|
80
- @date[form] = node.css("date[form='#{form}'] > date-part").map do |part|
81
- Nodes::DatePart.new(Hash[*part.attributes.values.map { |a| [a.name, a.value] }.flatten])
82
- end
83
- end
84
-
85
- self
86
- end
87
-
88
- def set(tag)
89
- @language, @region = tag.to_s.split(/-/)
90
- end
91
-
92
- def tag
93
- [@language, @region].compact.join('-')
94
- end
95
-
96
- def terms
97
- @terms ||= Term.build
98
- end
99
-
100
- alias :term :terms
101
-
102
- def has_term?(term)
103
- terms.has_key?(term.to_s)
104
- end
105
-
106
- def [](term)
107
- terms[term.to_s]
108
- end
109
-
110
- # @example
111
- # #options['punctuation-in-quotes'] => 'true'
112
- def options
113
- @options ||= {}
114
- end
115
-
116
- alias :style_options :options
117
-
118
- # @example
119
- # #date['numeric']['month']['suffix'] => '/'
120
- def date
121
- @date ||= Hash.new([])
122
- end
123
-
124
- # Around Alias Chains to call reload whenver locale changes
125
- [:language=, :region=, :set].each do |method|
126
- original = [:original, method].join('_')
127
- alias_method original, method
128
- define_method method do |*args|
129
- self.send(original, *args)
130
- reload!()
131
- end
132
- end
133
-
134
- # Reloads the XML file. Called automatically whenever language or region changes.
135
- def reload!
136
- @region = Locale.regions[@language] if @region.nil?
137
- @language = Locale.languages[@region] if @language.nil?
138
-
139
- parse!(Nokogiri::XML(File.open(document_path)) { |config| config.strict.noblanks }.root)
140
- rescue Exception => e
141
- CiteProc.log.error "Failed to open locale file, falling back to default: #{e.message}"
142
- CiteProc.log.debug e.backtrace[0..10].join("\n")
143
-
144
- unless tag == Locale.default
145
- @language, @region = Locale.default.split(/-/)
146
- retry
147
- end
148
- end
149
-
150
- # Locales are sorted first by language, then by region; sort order is
151
- # alphabetical with the following exceptions: en_US (the default locale)
152
- # is prioritised; in case of a language-match the default region of that
153
- # language will be prioritised (thus, de_DE will comes before de_AT even
154
- # though the alphabetical order would be different).
155
- def <=>(other)
156
- Locale.sort.call(self, other)
157
- end
158
-
159
- def self.sort(language = nil, region = nil)
160
- Proc.new do |a,b|
161
- if a.language != b.language
162
- case
163
- when a.language == language.to_s || b.language.nil? then -1
164
- when b.language == language.to_s || a.language.nil? then 1
165
- when a.language == 'en' then -1
166
- when b.language == 'en' then 1
167
- else
168
- a.language <=> b.language
169
- end
170
- else
171
- case
172
- when a.region == b.region then 0
173
- when a.region == region.to_s || b.region.nil? then -1
174
- when b.region == region.to_s || a.region.nil? then 1
175
- when a.region == Locale.regions[a.language] then -1
176
- when b.region == Locale.regions[a.language] then 1
177
- else
178
- a.region <=> b.region
179
- end
180
- end
181
- end
182
- end
183
-
184
- # Returns an ordinalized number according to the rules specified in the
185
- # given locale. Does not conform to CSL 1.0 in order to work around some
186
- # shortcomings in the schema: this version tries a useful fallback if
187
- # there is no direct hit in the locale (e.g., if 21 is not specified, we
188
- # will try with 21 and then with 1). The fallback of the long-ordinal form
189
- # is ordinal.
190
- #
191
- # TODO: long-ordinals may be influenced by gender in some locales
192
- #
193
- # @param number a Fixnum
194
- # @param options the options hash; should contain a 'form' attribute
195
- # @returns a string, e.g., '1st'
196
- #
197
- def ordinalize(number, options={})
198
- number = number.to_i
199
-
200
- options['form'] ||= 'ordinal'
201
- key = [options['form'], '%02d'].join('-')
202
-
203
- ordinal = self[key % number].to_s(options)
204
- mod = 100
205
-
206
- while ordinal.empty? && mod >= 1
207
- key = 'ordinal-%02d'
208
- ordinal = self[key % (number % mod)].to_s(options)
209
- mod = mod / 10
210
- end
211
-
212
- key.match(/^ordinal/) ? [number, ordinal].join : ordinal
213
- end
214
-
215
-
216
- private
217
-
218
- def document_path
219
- File.expand_path("./locales-#{@language}-#{@region}.xml", Locale.path)
220
- end
221
-
222
- end
223
- end
data/lib/csl/node.rb DELETED
@@ -1,72 +0,0 @@
1
- module CSL
2
-
3
- def Node(*args, &block)
4
- Node.parse(*args, &block)
5
- end
6
-
7
- module_function :Node
8
-
9
- class Node
10
- include Support::Attributes
11
- include Support::Tree
12
-
13
- def self.parse(*args, &block)
14
- return if args.compact.empty?
15
-
16
- node = args.detect { |argument| argument.is_a?(Nokogiri::XML::Node) } ||
17
- raise(ArgumentError, "arguments must contain an XML node; was #{ args.map(&:class).inspect }")
18
-
19
- name = node.name.split(/[\s-]+/).map(&:capitalize).join
20
- klass = CSL.const_defined?(name) ? CSL.const_get(name) : self
21
-
22
- klass.new({ :node => node }, &block)
23
- end
24
-
25
- def initialize(arguments = {})
26
- parse(normalize(arguments[:node])) if arguments.has_key?(:node)
27
-
28
- merge!(arguments[:attributes])
29
-
30
- @node_name = arguments[:name].to_s if arguments.has_key?(:name)
31
-
32
- yield self if block_given?
33
- end
34
-
35
- def name
36
- node_name || self.class.name.split(/::/).last.gsub(/([[:lower:]])([[:upper:]])/) { [$1, $2].join('-') }.downcase
37
- end
38
-
39
- alias :name= :node_name=
40
-
41
- def style!
42
- @style = root!.is_a?(Style) ? nil : root
43
- end
44
-
45
- def style; @style || style!; end
46
-
47
- def parse(node)
48
- @node_name = node.name
49
-
50
- node.attributes.values.each { |a| attributes[a.name] = a.value }
51
- add_children(node.children.map { |child| Node.parse(child) })
52
- end
53
-
54
- def to_xml
55
- end
56
-
57
- protected
58
-
59
- def normalize(node)
60
- case node
61
- when Nokogiri::XML::Node
62
- node
63
- when String
64
- # TODO file path (e.g. locale or style)
65
- Nokogiri::XML.parse(node) { |config| config.strict.noblanks }.root
66
- else
67
- raise(ArgumentError, "failed to parse #{ node.inspect }")
68
- end
69
- end
70
-
71
- end
72
- end
data/lib/csl/nodes.rb DELETED
@@ -1,1418 +0,0 @@
1
- module CSL
2
-
3
- class Nodes
4
-
5
- @formatting_attributes = %w{ text-case font-style font-variant font-weight
6
- text-decoration vertical-align prefix suffix display strip-periods }
7
-
8
- @inheritable_name_attributes = %w{ and delimiter-precedes-last et-al-min
9
- et-al-use-first et-al-subsequent-min et-al-subsequent-use-first
10
- initialize-with name-as-sort-order sort-separator }
11
-
12
- class << self
13
- attr_reader :formatting_attributes, :inheritable_name_attributes
14
-
15
- # Parses the given node an returns a new instance of Node or a suitable
16
- # subclass corresponding to the node's name.
17
- def parse(*args, &block)
18
- node = args.detect { |argument| argument.is_a?(Nokogiri::XML::Node) }
19
- raise(ArgumentError, "arguments must contain an XML node; was #{ args.map(&:class).inspect }") if node.nil?
20
-
21
- name = node.name.split(/[\s-]+/).map(&:capitalize).join
22
- klass = Nodes.const_defined?(name) ? Nodes.const_get(name) : Node
23
-
24
- klass.new(*args, &block)
25
- end
26
- end
27
-
28
-
29
- # == Node
30
- #
31
- # A Node represents a CSL rendering element.
32
- # Rendering elements are used to specify which, and in what order,
33
- # bibliographic data should be included in citations and bibliographies.
34
- # Rendering elements also partly control the formatting of this data.
35
- #
36
- # Each Node is bound to a node in a CSL Style document. Furthermore,
37
- # before any processiong can be done, the Node must be linked with a
38
- # processor, in order to be able to access items, format, or locale
39
- # information.
40
- #
41
- class Node
42
- include Support::Attributes
43
- include Support::Tree
44
-
45
- attr_reader :style
46
-
47
- class << self
48
- # Chains the format method to the given methods
49
- def format_on(*arguments)
50
- arguments.flatten.each do |method_id|
51
-
52
- # Set up Around Alias Chain
53
- original_method = [method_id, 'without_formatting'].join('_')
54
- alias_method original_method, method_id
55
-
56
- define_method method_id do |*args, &block|
57
- begin
58
- string = send(original_method, *args, &block)
59
- processor = args.detect { |argument| argument.is_a?(CiteProc::Processor) }
60
-
61
- processor.nil? ? string : processor.format(string, attributes)
62
- rescue Exception => e
63
- CiteProc.log :error, "failed to format string #{ string.inspect }", e
64
- args[0]
65
- end
66
- end
67
- end
68
- end
69
- end
70
-
71
- def initialize(*args)
72
- @style = args.detect { |argument| argument.is_a?(Style) }
73
- args.delete(@style) unless @style.nil?
74
-
75
- args.each do |argument|
76
- case
77
- when argument.is_a?(Node)
78
- @parent = argument
79
- @style = @parent.style || @style
80
-
81
- when argument.is_a?(String) && argument.match(/^\s*</)
82
- parse(Nokogiri::XML.parse(argument) { |config| config.strict.noblanks }.root)
83
-
84
- when argument.is_a?(Nokogiri::XML::Node)
85
- parse(argument)
86
-
87
- when argument.is_a?(Hash)
88
- merge!(argument)
89
-
90
- else
91
- CiteProc.log.warn "cannot initialize Node from argument #{ argument.inspect }" unless argument.nil?
92
- end
93
- end
94
-
95
- set_defaults
96
-
97
- yield self if block_given?
98
- end
99
-
100
- # Parses the given XML node.
101
- def parse(node)
102
- return if node.nil?
103
-
104
- node.attributes.values.each { |a| attributes[a.name] = a.value }
105
-
106
- @children = node.children.map do |child|
107
- Nodes.parse(self, child)
108
- end
109
-
110
- inherit_attributes(node)
111
- self
112
- end
113
-
114
- # @returns a new Node with the attributes and style of self and other
115
- # merged.
116
- def merge(other)
117
- return self.copy if other.nil?
118
- self.class.new(attributes.merge(other.attributes), other.style || style)
119
- end
120
-
121
- # @returns a new Node that contains the same attributes and style as self.
122
- def copy; self.class.new(attributes, style); end
123
-
124
- # @returns a new Node with the attributes of self and other merged;
125
- # attributes in other take precedence.
126
- def reverse_merge(other)
127
- other.merge(self)
128
- end
129
-
130
- # @returns the localized term with the given key.
131
- def localized_terms(key, processor=nil)
132
- localize(:term, key, processor) do |hash|
133
- return hash[key] if hash.has_key?(key) && !hash[key].empty?
134
- end
135
- end
136
-
137
- # @returns the localized date parts.
138
- def localized_date_parts(key, processor=nil)
139
- localize(:date, key, processor) do |hash|
140
- return hash[key] if hash.has_key?(key) && !hash[key].empty?
141
- end
142
- end
143
-
144
- def localized_options(key, processor=nil)
145
- localize(:options, key, processor) do |hash|
146
- return hash[key] if hash.has_key?(key)
147
- end
148
- end
149
-
150
- # Processes the supplied data. @returns a formatted string.
151
- def process(data, processor)
152
- ''
153
- end
154
-
155
- def evaluate(data, processor)
156
- false
157
- end
158
-
159
- def to_s
160
- attributes.merge('node' => self.class.name).inspect
161
- end
162
-
163
- protected
164
-
165
- # Empty method; nodes may override this method.
166
- def set_defaults
167
- end
168
-
169
- # TODO Refactor: move all processor-dependencies to citeproc
170
-
171
- # Prioritized locale look-up.
172
- def localize(type, key, processor, &block)
173
- unless @style.nil?
174
- style.locales(processor && processor.language || nil).each do |locale|
175
- yield locale.send(type)
176
- end
177
- end
178
-
179
- unless processor.nil?
180
- yield processor.locale.send(type)
181
- yield CSL::Locale.new(processor.language).send(type)
182
- end
183
-
184
- CSL.default_locale.send(type)[key]
185
- end
186
-
187
- # @returns the locale with the highest priority
188
- def locale(processor = nil)
189
- locales(processor).first
190
- end
191
-
192
- def locales(processor = nil)
193
- if processor.nil?
194
- style && style.locales || []
195
- else
196
- (style && style.locales(processor.language, processor.region) || []) + [Locale.new(processor.language)]
197
- end + [Locale.default]
198
- end
199
-
200
-
201
- # Empty method; nodes may override this method.
202
- def inherit_attributes(node)
203
- end
204
-
205
- # Attributes for the cs:names and cs:name elements may also be set on
206
- # cs:style, cs:citation and cs:bibliography. This eliminates the need to
207
- # repeat the same attributes and attribute values for every occurrence
208
- # of the cs:names and cs:name elements.
209
- #
210
- # The available inheritable attributes for cs:name are and,
211
- # delimiter-precedes-last, et-al-min, et-al-use-first,
212
- # et-al-subsequent-min, et-al-subsequent-use-first, initialize-with,
213
- # name-as-sort-order and sort-separator. The attributes name-form and
214
- # name-delimiter accompany the form and delimiter attributes on cs:name.
215
- # Similarly, names-delimiter, the only inheritable attribute available
216
- # for cs:names, accompanies the delimiter attribute on cs:names.
217
- #
218
- # When an inheritable name attribute is set on cs:style, cs:citation or
219
- # cs:bibliography, its value is used for all cs:names elements within
220
- # the element carrying the attribute. When an element lower in the
221
- # hierarchy includes the same attribute with a different value, this
222
- # latter value will override the value(s) specified higher in the
223
- # hierarchy.
224
- #
225
- # This mehtod traverses a nodes ancestor chain and inherits all
226
- # specified attributes from each parent that matches a name in names.
227
- #
228
- def inherit_attributes_from(node, nodes=[], attributes=[], prefix='')
229
- return unless node
230
-
231
- # TODO refactor so that node is not required anymore
232
- parent = node.parent
233
- until parent.name == 'document' do
234
- attributes.each { |attribute| self[attribute] ||= parent[[prefix, attribute].join] } if nodes.include?(parent.name)
235
- parent = parent.parent
236
- end
237
- end
238
-
239
- def handle_processing_error(e, data, processor)
240
- CiteProc.log :error, "failed to process item #{ data.inspect }", e
241
- ''
242
- end
243
- end
244
-
245
-
246
- # All the rendering elements that should appear in the citations and
247
- # bibliography should be nested inside the cs:layout element. Itself a
248
- # rendering element, cs:layout accepts both affixes and formatting
249
- # attributes. When used in the cs:citation element, a delimiter can be set
250
- # to separate multiple bibliographic items in a single citation.
251
- class Layout < Node
252
- attr_fields Nodes.formatting_attributes
253
- attr_fields %w{ delimiter }
254
-
255
- def process(data, processor)
256
- children.map { |child| child.process(data, processor) }.join
257
- rescue Exception => e
258
- handle_processing_error(e, data, processor)
259
- end
260
-
261
- format_on :process
262
- end
263
-
264
- class Macro < Layout
265
- attr_fields :name
266
- end
267
-
268
- # The cs:text element is used to output text, which can originate from
269
- # different sources. The source-type is indicated with an attribute, and
270
- # the attribute value acts as an identifier within the source-type. For
271
- # example,
272
- #
273
- # <text variable="title" form="short" font-style="italic"/>
274
- #
275
- # indicates that the source-type is a variable, and that the variable that
276
- # should be displayed is the italicized short form of "title". The
277
- # different source-types are:
278
- #
279
- # * variable - the text contents of a variable (see Standard Variables). The
280
- # optional form attribute can be set to either "long" (the default) or
281
- # "short" to select the long or short forms of variables, e.g. the full
282
- # and short title.
283
- # * macro - the text generated by a macro. The value of macro should
284
- # correspond to the value of the name attribute of the desired cs:macro
285
- # element.
286
- # * term - the text of a localized term (see Appendix III - Terms and
287
- # Locale). The plural attribute can be set to choose either the singular
288
- # (value "false", the default) or plural variant (value "true") of a term.
289
- # In addition, the form attribute can be set to select the desired term
290
- # form ("long" [default], "short", "verb", "verb-short" or "symbol"). If
291
- # for a given term the desired form does not exist, another form may be
292
- # used: "verb-short" reverts to "verb", "symbol" reverts to "short", and
293
- # "verb" and "short" both revert to "long".
294
- # * value - used to output verbatim text, which is set via the value of
295
- # value (e.g. value="some text")
296
- #
297
- # In all cases the attributes for affixes, display, formatting, quotes,
298
- # strip-periods and text-case may be applied to cs:text.
299
- class Text < Node
300
- attr_fields Nodes.formatting_attributes
301
- attr_fields %w{ variable form macro term plural value quotes }
302
-
303
- def process(data, processor)
304
- case
305
- when has_value?
306
- text = value
307
-
308
- when has_macro?
309
- text = @style.macros[macro].process(data, processor)
310
-
311
- when has_term?
312
- text = localized_terms(term).to_s(attributes)
313
-
314
- when has_variable?
315
- text = (data["short#{variable.capitalize}"] || data[['short', variable].join('-')] || data[variable]).to_s
316
-
317
- if form == 'short'
318
- text = processor.abbreviate(variable, text)
319
- end
320
-
321
- if variable == 'page' && @style.options.has_key?('page-range-format')
322
- text = format_page_range(text, @style.options['page-range-format'])
323
- end
324
-
325
- if variable == 'page-first' && text.empty?
326
- text = data['page'].to_s.scan(/\d+/).first.to_s
327
- end
328
-
329
- else
330
- text = ''
331
- end
332
-
333
- # Add localized quotes
334
- if has_quotes? && !text.empty?
335
- prefix = [self['prefix'], localized_terms('open-quote')].compact.join
336
-
337
- if localized_options('punctuation-in-quote', processor) == 'true'
338
- suffix = self['suffix'].to_s.sub(/^([\.,!?;:]+)/, '')
339
- suffix = [$1, localized_terms('close-quote'), suffix].compact.join
340
- else
341
- text = text.sub(/([\.,!?;:]+)$/, '')
342
- suffix = [localized_terms('close-quote'), $1, self['suffix']].compact.join
343
- end
344
-
345
- self['prefix'] = prefix
346
- self['suffix'] = suffix
347
- end
348
-
349
- text
350
- rescue Exception => e
351
- handle_processing_error(e, data, processor)
352
- end
353
-
354
- format_on :process
355
-
356
- protected
357
-
358
- # The page abbreviation rules for the different values of the
359
- # page-range-format attribute on cs:style are:
360
- #
361
- # * "minimum": All digits repeated in the second number are left out:
362
- # 42-5, 321-8, 2787-816
363
- # * "expanded": Abbreviated page ranges are expanded to their
364
- # non-abbreviated form: 42-45, 321-328, 2787-2816
365
- # * "chicago": Page ranges are abbreviated according to the link Chicago
366
- # Manual of Style-rules
367
- #
368
- def format_page_range(value, format)
369
- return value unless value.match(/([a-z]*)(\d+)\s*\D\s*([a-z]*)(\d+)/i)
370
-
371
- tokens = [$1, $2, "\xe2\x80\x93", $3, $4]
372
- tokens[2].force_encoding("UTF-8") if RUBY_VERSION >= '1.9.0'
373
-
374
- # normalize page range to expanded form
375
- f, t = tokens[1].chars.to_a, tokens[4].chars.to_a
376
- d = t.length - f.length
377
- d > 0 ? d.times { f.unshift('0') } : t = f.take(d.abs) + t
378
-
379
- # TODO handle prefixes correctly
380
- # TODO handle multiple ranges
381
-
382
- case format
383
- when /mini/
384
- tokens[4] = f.zip(t).map { |f, t| f == t ? nil : t }.reject(&:nil?)
385
- tokens[3] = nil if tokens[3] == tokens[0]
386
- when 'expanded'
387
- tokens[4] = t
388
- tokens[3] = tokens[0] if tokens[3].nil? || tokens[3].empty?
389
- when 'chicago'
390
- case
391
- when f.length < 3 || f.join.to_i % 100 == 0
392
- # use all digits
393
- tokens[4] = t
394
- tokens[3] = tokens[0] if tokens[3].nil? || tokens[3].empty?
395
- when f.join.to_i % 100 < 10
396
- # use changed part only
397
- tokens[4] = f.zip(t).map { |f, t| f == t ? nil : t }.reject(&:nil?)
398
- tokens[3] = nil if tokens[3] == tokens[0]
399
- when f.length == 4
400
- # use at least two digits, and all if three or more change
401
- match = f[0..-3].zip(t[0..-3]).map { |f, t| f == t ? nil : t }.reject(&:nil?) + t[-2..-1]
402
- tokens[4] = match.length > 2 ? t : match
403
- tokens[3] = tokens[0] if tokens[3].nil? || tokens[3].empty?
404
- else
405
- # use at least two digits in second number
406
- tokens[4] = f[0..-3].zip(t[0..-3]).map { |f, t| f == t ? nil : t }.reject(&:nil?) + t[-2..-1]
407
- tokens[3] = nil if tokens[3] == tokens[0]
408
- end
409
- else
410
- value
411
- end
412
- tokens.join
413
- end
414
-
415
- end
416
-
417
-
418
- #
419
- # The Date element is used to output dates, in either a localized or a
420
- # non-localized format. The desired date variable (@see CiteProc::Date) is
421
- # selected with the variable attribute.
422
- #
423
- # Localized date formats are selected with the form attribute. This
424
- # attribute can be set to "numeric" (for numeric date formats, e.g.
425
- # "12-15-2005"), or to "text" (for date formats with a non-numeric month,
426
- # e.g. "December 15, 2005"). Localized dates can be customized in two
427
- # ways. First, the date-parts attribute may be used to specify which
428
- # cs:date-part elements are shown. The possible values are:
429
- #
430
- # * "year-month-day" - default, displays year, month and day
431
- # * "year-month" - displays year and month
432
- # * "year" - displays year only
433
- #
434
- # Secondly, cs:date may include one or more cs:date-part elements (see
435
- # Date-part). The attributes set on these elements override those
436
- # originally specified for the localized date formats (e.g. the form
437
- # attribute of the month-cs:date-part element can be set to "short" to get
438
- # abbreviated month names in all locales.). Note that the use of
439
- # cs:date-part elements for localized dates does not affect which, and in
440
- # what order, the cs:date-part elements are included in the rendered date.
441
- # Also, the cs:date-part elements may not carry the attributes for
442
- # affixes, as these are considered to be locale-specific.
443
- #
444
- # Non-localized date formats are self-contained: the date format is
445
- # entirely controlled by cs:date and its cs:date-part children. In
446
- # contrast to localized dates, cs:date is used without the form and
447
- # date-parts attributes. Only the included cs:date-part elements will be
448
- # rendered, in the order in which they are specified. The cs:date-part
449
- # elements may carry attributes for both affixes and formatting, while
450
- # cs:date may carry a delimiter (delimiting the various cs:date-part
451
- # elements).
452
- #
453
- # For both localized and non-localized dates, affixes, display and
454
- # formatting attributes may be specified for the cs:date element.
455
- #
456
- class Date < Node
457
- attr_fields Nodes.formatting_attributes
458
- attr_fields %w{ variable form date-parts delimiter }
459
-
460
- def process(data, processor)
461
- date = data[variable]
462
-
463
- case
464
- when date.nil?
465
- ''
466
- when date.literal?
467
- date.literal
468
- when date.range?
469
- process_range(date, processor)
470
- else
471
- parts(processor).map { |part| part.process(date, processor) }.join(delimiter)
472
- end
473
- rescue Exception => e
474
- handle_processing_error(e, data, processor)
475
- end
476
-
477
- format_on :process
478
-
479
- # By default, date ranges are delimited by an en-dash (e.g. "May-July
480
- # 2008"). The range-delimiter attribute can be used to specify custom
481
- # date range delimiters. The attribute value set on the largest
482
- # date-part ("day", "month" or "year") that differs between the two
483
- # dates of the date range will then be used instead of the en-dash. For
484
- # example,
485
- #
486
- # <style>
487
- # <citation>
488
- # <layout>
489
- # <date variable="issued">
490
- # <date-part name="month" suffix=" "/>
491
- # <date-part name="year" range-delimiter="/"/>
492
- # </date>
493
- # </layout>
494
- # </citation>
495
- # </style>
496
- #
497
- # would result in "May-July 2008" and "May 2008/June 2009".
498
- #
499
- def process_range(date, processor)
500
- order = parts(processor)
501
-
502
- parts = [order, order].zip(date.display_parts).map do |order, parts|
503
- order.map { |part| parts.include?(part['name']) ? part : nil }.compact
504
- end
505
-
506
- result = parts.zip([date, date.to]).map { |parts, date| parts.map { |part| part.process(date, processor) }.join(delimiter) }.compact
507
- result[0].gsub!(/\s+$/, '')
508
- result.join(parts[0].last.range_delimiter)
509
-
510
- # case
511
- # when date.open_range?
512
- # result << parts.map { |part| part.process(date, processor) }.join(delimiter)
513
- # result << parts.last.range_delimiter
514
- #
515
- # when discriminator == 'month'
516
- # month_parts = parts.reject { |part| part['name'] == 'year' }
517
- #
518
- # result << month_parts.map { |part| part.process(date, processor) }.join(delimiter)
519
- # result << month_parts.last.range_delimiter
520
- # result << parts.map { |part| part.process(date.to, processor) }.join(delimiter)
521
- #
522
- # when discriminator == 'day'
523
- # day_parts = parts.select { |part| part['name'] == 'day' }
524
- #
525
- # result << day_parts.map { |part| part.process(date, processor) }.join(delimiter)
526
- # result << day_parts.last.range_delimiter
527
- # result << parts.map { |part| part.process(date.to, processor) }.join(delimiter)
528
- #
529
- # else # year
530
- # year_parts = parts.select { |part| part['name'] == 'year' }
531
- #
532
- # result << parts.map { |part| part.process(date, processor) }.join(delimiter)
533
- # result << year_parts.last.range_delimiter
534
- # result << year_parts.map { |part| part.process(date.to, processor) }.join(delimiter)
535
- #
536
- # end
537
- #
538
- # result.join
539
- end
540
-
541
- def parts(processor)
542
- has_form? ? merge_parts(localized_date_parts(form, processor), children) : children
543
- end
544
-
545
-
546
- # Combines two lists of date-part elements; includes only the parts set
547
- # in the 'date-parts' attribute and retains the order of elements in the
548
- # first list.
549
- def merge_parts(p1, p2)
550
- merged = p1.map do |part|
551
- DatePart.new(part.attributes, style).merge(p2.detect { |p| p['name'] == part['name'] })
552
- end
553
- merged.reject { |part| !date_parts.match(Regexp.new(part['name'])) }
554
- end
555
-
556
- def date_parts
557
- self['date-parts'] || 'year-month-day'
558
- end
559
- end
560
-
561
- class DatePart < Node
562
- attr_fields Nodes.formatting_attributes
563
- attr_fields %w{ name form range-delimiter strip-periods }
564
-
565
- def process(date, processor)
566
- send(['process', self['name']].join('_'), date, processor)
567
- rescue Exception => e
568
- handle_processing_error(e, data, processor)
569
- end
570
-
571
- format_on :process
572
-
573
- def process_year(date, processor)
574
- return '' if date.year.nil?
575
-
576
- year = date.year.abs.to_s
577
- year = year[-2..-1] if form == 'short'
578
- year = [year, localized_terms('ad', processor)].join if date.ad?
579
- year = [year, localized_terms('bc', processor)].join if date.bc?
580
- year
581
- end
582
-
583
- def process_month(date, processor)
584
- return process_season(date, processor) if date.has_season?
585
- return '' if date.month.nil?
586
-
587
- case
588
- when form == 'numeric'
589
- date.month.to_s
590
- when form == 'numeric-leading-zeros'
591
- "%02d" % date.month
592
- else
593
- localized_terms("month-%02d" % date.month, processor).to_s(attributes)
594
- end
595
- end
596
-
597
- def process_season(date, processor)
598
- season = date.season.to_s
599
- season = date.month.to_s if season.match(/true|always|yes/i)
600
- season = localized_terms('season-%02d' % season.to_i, processor).to_s if season.match(/0?[1-4]/)
601
- season
602
- end
603
-
604
- def process_day(date, processor)
605
- return '' if date.day.nil?
606
-
607
- case
608
- when form == 'ordinal'
609
- locale(processor).ordinalize(date.day, 'gender-form' => gender(date, processor))
610
- when form == 'numeric-leading-zeros'
611
- "%02d" % date.day
612
- else # 'numeric'
613
- date.day.to_s
614
- end
615
- end
616
-
617
- def gender(date, processor)
618
- localized_terms("month-%02d" % date.month, processor).gender
619
- end
620
-
621
- protected
622
-
623
- def set_defaults
624
- unless self['range-delimiter']
625
- self['range-delimiter'] = "\xe2\x80\x93"
626
- self['range-delimiter'].force_encoding("UTF-8") if RUBY_VERSION >= '1.9.0'
627
- end
628
- end
629
- end
630
-
631
-
632
- # The cs:number element can be used to output any of the following
633
- # variables (selected with the variable attribute):
634
- #
635
- # "edition"
636
- # "volume"
637
- # "issue"
638
- # "number"
639
- # "number-of-volumes"
640
- #
641
- # Although these variables can also be rendered with cs:text, cs:number
642
- # has the benefit of offering number-specific formatting via the form
643
- # attribute, with values:
644
- #
645
- # * "numeric" (default) - e.g. "1", "2", "3"
646
- # * "ordinal" - e.g. "1st", "2nd", "3rd"
647
- # * "long-ordinal" - e.g. "first", "second", "third"
648
- # * "roman" - e.g. "i", "ii", "iii"
649
- #
650
- # If a variable displayed with cs:number contains a mixture of numeric and
651
- # non-numeric text, only the first number encountered is used for
652
- # rendering (e.g. "12" when the entire string is "12th edition"). If a
653
- # variable only contains non-numeric text (e.g. "special edition"), the
654
- # entire string is rendered, as if cs:text were used instead. Fields can
655
- # be tested for containing numeric content with the is-numeric
656
- # conditional, e.g. "12th edition" would test "true" while "third edition"
657
- # would test "false" (@see Choose).
658
- #
659
- # The cs:number element may carry any of the affixes, display, formatting
660
- # and text-case attributes.
661
- #
662
- class Number < Node
663
- attr_fields Nodes.formatting_attributes
664
- attr_fields %w{ variable form }
665
-
666
- def process(data, processor)
667
- number = data[variable]
668
-
669
- term = localized_terms(variable, processor)
670
- attributes['gender-form'] = term.gender if term.has_gender?
671
-
672
- case
673
- when number.nil? || number.empty? || !number.numeric?
674
- number.to_s
675
- when form == 'roman'
676
- number.to_i.romanize
677
- when form == 'ordinal'
678
- locale(processor).ordinalize(number.to_i, attributes)
679
- when form == 'long-ordinal'
680
- locale(processor).ordinalize(number.to_i, attributes)
681
- else
682
- number.to_i.to_s
683
- end
684
- rescue Exception => e
685
- handle_processing_error(e, data, processor)
686
- end
687
-
688
- format_on :process
689
- end
690
-
691
- # The cs:name element is a required child element of cs:names, and describes
692
- # both how individual names are formatted, and how names within a name
693
- # variable are separated from each other. The attributes that may be used on
694
- # cs:name are:
695
- #
696
- # 'and'
697
- # This attribute specifies the delimiter between the second to last
698
- # and the last name of the names in a name variable. The value of the
699
- # attribute may be either "text", which selects the "and" term, or "symbol",
700
- # which selects the ampersand (&).
701
- #
702
- # 'delimiter'
703
- # Specifies the text string to separate names of a name variable. The
704
- # default value is ", " ("J. Doe, S. Smith").
705
- #
706
- # 'delimiter-precedes-last'
707
- # Determines in which cases the delimiter used to delimit names is also used
708
- # to separate the second to last and the last name in name lists. The
709
- # possible values are:
710
- #
711
- # "contextual" (default): the delimiter is only included for name lists with three or more names
712
- # 2 names: "J. Doe and T. Williams,"
713
- # 3 names: "J. Doe, S. Smith, and T. Williams"
714
- # "always": the delimiter is always included
715
- # 2 names: "J. Doe, and T. Williams"
716
- # 3 names: "J. Doe, S. Smith, and T. Williams"
717
- # "never": the delimiter is never included
718
- # 2 names: "J. Doe and T. Williams,"
719
- # 3 names: "J. Doe, S. Smith and T. Williams"
720
- #
721
- # 'et-al-min / et-al-use-first'
722
- # Together, these attributes control et-al abbreviation. When the number of
723
- # names in a name variable matches or exceeds the number set on et-al-min,
724
- # the rendered name list is truncated at the number of names set on
725
- # et-al-use-first. If truncation occurs, the "et-al" term is appended to the
726
- # names rendered (see also Et-al). With a single name (et-al-use-first="1"),
727
- # the "et-al" term is preceded by a space (e.g. "Doe et al."). With multiple
728
- # names, the "et-al" term is preceded by the name delimiter (e.g. "Doe,
729
- # Smith, et al.").
730
- #
731
- # 'et-al-subsequent-min / et-al-subsequent-use-first'
732
- # The (optional) et-al-min and et-al-use-first attributes take effect for
733
- # all cites and bibliographic entries. With the et-al-subsequent-min and
734
- # et-al-subsequent-use-first attributes divergent et-al abbreviation rules
735
- # can be specified for subsequent cites (cites referencing earlier cited
736
- # items).
737
- #
738
- # The remaining attributes, discussed below, only affect personal names.
739
- # Personal names require a "family" name-part, and may also contain "given",
740
- # "suffix", "non-dropping-particle" and "dropping-particle" name-parts. The
741
- # roles of these name-parts, which are delimited by single spaces in
742
- # rendered names, are:
743
- #
744
- # * "family": the surname minus any particles and suffixes
745
- # * "given": the given names, which may be either full ("John Edward") or
746
- # initialized ("J. E.")
747
- # * "suffix": name suffix, e.g. "Jr." in "John Smith Jr." and "III" in "Bill
748
- # Gates III"
749
- # * "non-dropping-particle": name particles that are not dropped when only the
750
- # last name is shown ("de" in the Dutch surname "de Koning") but which may
751
- # be treated as a separate object from the family name (e.g. for sorting)
752
- # * "dropping-particle": name particles that are dropped when only the
753
- # surname is shown ("van" in "Ludwig van Beethoven", which becomes
754
- # "Beethoven")
755
- #
756
- # 'form'
757
- # Specifies whether all the name-parts of personal names should be displayed
758
- # (value "long"), or only the family name and the non-dropping-particle
759
- # (value "short"). A third value, "count", returns the total number of names
760
- # that would be otherwise displayed by the use of the cs:names element
761
- # (taking into account the effects of et-al abbreviation and
762
- # editor/translator collapsing), and may be used for advanced sorting.
763
- #
764
- # 'initialize-with'
765
- # If this attribute is set, given names are converted to initials. The
766
- # attribute value specifies the suffix that is included after each initial
767
- # ("." results in "J.J. Doe"). Note that the global initialize-with-hyphen
768
- # option controls how compound given names (e.g. "Jean-Luc") are hyphenated
769
- # when initialized (see Hyphenation of Initialized Names).
770
- #
771
- # 'name-as-sort-order'
772
- # Specifies that names should be displayed with the given name following the
773
- # family name (e.g. "John Doe" becomes "Doe, John"). The attribute may have
774
- # one of the two values:
775
- #
776
- # "first": name-as-sort-order applies to the first name in each name variable
777
- # "all": name-as-sort-order applies to all names
778
- #
779
- # Note that the sort order of names may differ from the display order for
780
- # names containing particles and suffixes (see Name-part order). Also, this
781
- # attribute only affects names written in the latin or Cyrillic alphabet.
782
- # Names written in other alphabets (e.g. Asian scripts) are always shown
783
- # with the family name preceding the given name.
784
- #
785
- # 'sort-separator'
786
- # Sets the delimiter for name-parts that have switched positions as a result
787
- # of name-as-sort-order. The default value is ", " ("Doe, John"). As is the
788
- # case for name-as-sort-order, this attribute only affects names written in
789
- # the latin or Cyrillic alphabet.
790
- #
791
- # The cs:name element may also carry any of the attributes for affixes and formatting.
792
- #
793
- class Name < Node
794
- attr_fields Nodes.formatting_attributes
795
- attr_fields Nodes.inheritable_name_attributes
796
- attr_fields %w{ form delimiter delimiter-precedes-et-al }
797
-
798
- def parts
799
- @parts ||= Hash.new { |h, k| k.match(/(non-)?dropping-particle/) ? h['family'] : {} }
800
- end
801
-
802
- def process_names(role, names, processor)
803
-
804
- # set display options
805
- names = names.each { |name| name.merge_options(attributes) }
806
- names.first.options['name-as-sort-order'] = 'true' if name_as_sort_order == 'first'
807
-
808
- # name-part formatting
809
- names.map! do |name|
810
- name.normalize(name.display_order.map do |token|
811
- processor.format(name.send(token.tr('-', '_')), parts[token])
812
- end.compact.join(name.delimiter))
813
- end
814
-
815
- # join names
816
- if names.length > 2
817
- names = [names[0..-2].join(delimiter), names.last]
818
- end
819
-
820
- names.join(ampersand(processor))
821
- rescue Exception => e
822
- CiteProc.log :error, "failed to process names #{ names.inspect }", e
823
- end
824
-
825
- format_on :process_names
826
-
827
- def truncate(names)
828
- # TODO subsequent
829
- et_al_min? && et_al_min.to_i <= names.length ? names[0, et_al_use_first.to_i] : names
830
- end
831
-
832
- protected
833
-
834
- def set_defaults
835
- attributes['delimiter'] ||= ', '
836
- attributes['delimiter-precedes-last'] ||= 'false'
837
- attributes['et-al-use-first'] ||= '1'
838
-
839
- children.each { |child| parts[child['name']] = child.attributes }
840
- end
841
-
842
- # @returns the delimiter to be used between the penultimate and last
843
- # name in the list.
844
- def ampersand(processor)
845
- if self.and?
846
- ampersand = self.and == 'symbol' ? '&' : localized_terms(self.and == 'text' ? 'and' : self.and, processor).to_s(attributes)
847
- delimiter_precedes_last? ? [delimiter, ampersand, ' '].join : ampersand.center(ampersand.length + 2)
848
- else
849
- delimiter
850
- end
851
- end
852
-
853
- def inherit_attributes(node)
854
- inherit_attributes_from(node, ['citation', 'bibliography', 'style'], Nodes.inheritable_name_attributes)
855
- inherit_attributes_from(node, ['citation', 'bibliography', 'style'], ['et-al-use-first', 'delimiter-precedes-et-al'])
856
- inherit_attributes_from(node, ['citation', 'bibliography', 'style'], ['form', 'delimiter'], 'name-')
857
- inherit_attributes_from(node, ['style'], ['demote-non-dropping-particle', 'initialize-with-hyphen'])
858
- end
859
-
860
- end
861
-
862
- # The cs:name element may include one or two cs:name-part child elements.
863
- # These child elements accept the formatting and text-case attributes, which
864
- # allows for separate formatting of the different name parts (e.g. "Jane
865
- # DOE", see example below). The required name attribute on cs:name-part
866
- # specifies which name-parts are affected: when set to "given", the
867
- # formatting only acts on the "given" name-part. When set to "family", the
868
- # formatting acts on the "family", "dropping-particle" and
869
- # "non-dropping-particle" name-parts (the "suffix" name-part is not subject
870
- # to any name-part formatting). The order of the cs:name-part elements does
871
- # not affect which, and in what order, the name-parts are rendered.
872
- #
873
- # <names variable="author">
874
- # <name>
875
- # <name-part name="family" text-case="uppercase">
876
- # </name>
877
- # </names>
878
- #
879
- class NamePart < Node
880
- attr_fields Nodes.formatting_attributes
881
- attr_fields %w{ name }
882
-
883
- end
884
-
885
- # Et-al abbreviation, controlled via the et-al attributes on cs:name (see
886
- # Name), can be further customized with the optional cs:et-al element, which
887
- # should be included directly after the cs:name element. The term attribute
888
- # of this element can be set to either "et-al" (default) or to "and others"
889
- # to use either term (with this different et-al terms can be used for
890
- # citations and the bibliography). In addition, attributes for affixes and
891
- # formatting can be used, for example to italicize the et-al term:
892
- #
893
- # <names variable="author">
894
- # <name/>
895
- # <et-al term="and others" font-style="italic"/>
896
- # </names>
897
- #
898
- class EtAl < Node
899
- attr_fields Nodes.formatting_attributes
900
- attr_fields %w{ term }
901
-
902
- attr_accessor :parent
903
-
904
- def process(data, processor)
905
- localized_terms(term || 'et-al', processor).to_s(attributes)
906
- rescue Exception => e
907
- handle_processing_error(e, data, processor)
908
- end
909
-
910
- format_on :process
911
-
912
- end
913
-
914
-
915
- # The cs:label element, used to output text terms whose pluralization
916
- # depends on the contents of another variable (e.g. "(editors)" in "Doe and
917
- # Smith (editors)"), is discussed in detail in the label section. It should
918
- # be included after the cs:name and cs:et-al elements, but before the
919
- # cs:substitute element. When used within cs:names, the variable attribute
920
- # should be omitted, as the value set on the parent cs:names element is
921
- # used.
922
- #
923
- # The Citation Style Language includes several variables that have
924
- # matching terms. The cs:label element can be used to render one of these
925
- # terms, while matching the term plurality with that of the corresponding
926
- # variable. The variable/term combination is selected with the variable
927
- # attribute, which can be set to either "page" or "locator". When cs:label
928
- # is used as a child element of cs:names, the value of the variable
929
- # attribute is automatically inherited from the parent cs:names element.
930
- # The example below displays the "page" variable, using the singular form
931
- # of the "page" term for a single page ("page 5"), or the plural form for
932
- # a page range ("pages 5-7").
933
- #
934
- class Label < Node
935
- attr_fields Nodes.formatting_attributes
936
- attr_fields %w{ variable plural form }
937
-
938
- def process(data, processor)
939
- localized_terms(data['label'].to_s, processor).to_s(attributes.merge({ 'plural' => plural?(data, 0).to_s }))
940
- rescue Exception => e
941
- handle_processing_error(e, data, processor)
942
- end
943
-
944
- def process_names(role, number, processor)
945
- localized_terms(role, processor).to_s(attributes.merge({ 'plural' => plural?(nil, number).to_s }))
946
- rescue Exception => e
947
- handle_processing_error(e, data, processor)
948
- end
949
-
950
- format_on :process
951
- format_on :process_names
952
-
953
- def plural?(data, number)
954
- case
955
- when plural == 'always'
956
- true
957
- when plural == 'never'
958
- false
959
- when number > 1
960
- true
961
- when ['locator'].include?(variable)
962
- data[variable].to_s.match(/\d+f|\d+\-\d+/)
963
- else
964
- false
965
- end
966
- end
967
- end
968
-
969
-
970
- # The optional cs:substitute element, which should be included as the last
971
- # child element of cs:names, controls substitution in case the name
972
- # variables specified in the parent cs:names element are empty. The
973
- # substitutions are specified as child elements of cs:substitute, and can
974
- # consist of any of the standard rendering elements (with the exception of
975
- # cs:layout). It is also possible to use a shorthand version of cs:names,
976
- # which doesn't allow for any child elements, and uses the attributes values
977
- # set on the cs:name and cs:et-al child elements of the original cs:names
978
- # element. If cs:substitute contains multiple child elements, the first
979
- # element to return a non-empty result is used for substitution. Substituted
980
- # variables are repressed in the rest of the output to prevent duplication.
981
- # An example, where an empty "author" name variable is substituted by the
982
- # "editor" name variable, or, when no editors exist, by the "title" macro:
983
- #
984
- # <macro name="author">
985
- # <names variable="author">
986
- # <name/>
987
- # <substitute>
988
- # <names variable="editor"/>
989
- # <text macro="title"/>
990
- # </substitute>
991
- # </names>
992
- # </macro>
993
- #
994
- class Substitute < Node
995
-
996
- def process(data, processor)
997
- super
998
- start_observing(data)
999
-
1000
- children.each do |child|
1001
- processed = child.process(data, processor)
1002
- return processed unless processed.empty?
1003
- end
1004
- ''
1005
- rescue Exception => e
1006
- handle_processing_error(e, data, processor)
1007
- ensure
1008
- stop_observing(data)
1009
- end
1010
-
1011
- def start_observing(item)
1012
- @item = item
1013
- item.add_observer(self)
1014
- end
1015
-
1016
- def stop_observing(item)
1017
- item.delete_observer(self)
1018
- ensure
1019
- @item = nil
1020
- end
1021
-
1022
- def update(key, value)
1023
- if key.to_s =~ /author|editor|translator/ && !value.nil?
1024
- @item[key] = nil
1025
- end
1026
- end
1027
- end
1028
-
1029
- # The cs:names element can be used to display the contents of one or more
1030
- # name variables, each of which can contain multiple names (e.g. the
1031
- # "author" variable will contain all the cited item's author names). The
1032
- # variables to be displayed are set with the variable attribute. If multiple
1033
- # variables are selected (separated by single spaces, see example below),
1034
- # each variable is independently rendered in the order specified, with one
1035
- # exception: if the value of variable consists of "editor" and "translator"
1036
- # (in either order), and if the contents of the two name variables is
1037
- # identical, then the contents of only one name variable is rendered. In
1038
- # addition, the "editor-translator" term is used if the cs:names element
1039
- # contains a cs:label element, replacing the default "editor" and
1040
- # "translator" terms (e.g., this might result in "Doe (editor &
1041
- # translator)". The delimiter attribute may be set on cs:names to delimit
1042
- # the names of the different name variables (e.g. the semicolon in "Doe
1043
- # (editor); Johnson (translator)").
1044
- #
1045
- # <names variable="editor translator" delimiter="; ">
1046
- # <name/>
1047
- # <label prefix=" (" suffix=")"/>
1048
- # </names>
1049
- #
1050
- # There are four child elements associated with the cs:names element:
1051
- # cs:name, cs:et-al, cs:substitute and cs:label (all discussed below). In
1052
- # addition, the cs:names element may carry the attributes for affixes,
1053
- # display and formatting.
1054
- #
1055
- class Names < Node
1056
- attr_fields Nodes.formatting_attributes
1057
- attr_fields %w{ variable delimiter }
1058
-
1059
- [:name, :et_al, :label, :substitute].each do |method_id|
1060
- klass = CSL::Nodes.const_get(method_id.to_s.split(/_/).map(&:capitalize).join)
1061
- define_method method_id do
1062
- elements = children.empty? && parent.is_a?(Substitute) && klass != Substitute ? parent.parent.children : children
1063
- elements.detect { |child| child.class == klass }
1064
- end
1065
- end
1066
-
1067
- def prefix_label?
1068
- children.map {|c| [Label, Name].include?(c.class) ? c.class : nil }.compact == [Label, Name]
1069
- end
1070
-
1071
- def process(data, processor)
1072
- names = collect_names(data)
1073
-
1074
- count_only = self.name.form == 'count'
1075
-
1076
- unless names.empty? || names.map(&:last).flatten.empty?
1077
-
1078
- # handle the editor-translator special case
1079
- if names.map(&:first).sort.join.match(/editortranslator/)
1080
- editors = names.detect { |name| name.first == 'editor' }
1081
- translators = names.detect { |name| name.first == 'translator' }
1082
-
1083
- if editors.last.sort == translators.last.sort
1084
- editors[0] = 'editortranslator'
1085
- names.delete(translators)
1086
- end
1087
- end
1088
-
1089
- names = names.map do |role, names|
1090
- processed = []
1091
-
1092
- truncated = name.truncate(names)
1093
-
1094
- unless count_only
1095
- processed << name.process_names(role, truncated, processor)
1096
-
1097
- if names.length > truncated.length
1098
- # use delimiter before et al. if there is more than a single name; squeeze whitespace
1099
- others = (et_al.nil? ? localized_terms('et-al', processor).to_s : et_al.process(data, processor))
1100
- link = (name.et_al_use_first.to_i > 1 || name.delimiter_precedes_et_al? ? name.delimiter : ' ')
1101
-
1102
- processed << [link, others].join.squeeze(' ')
1103
- end
1104
-
1105
- processed.send(prefix_label? ? :unshift : :push, label.process_names(role, names.length, processor)) unless label.nil?
1106
- else
1107
- processed << truncated.length
1108
- end
1109
-
1110
- processed.join
1111
- end
1112
-
1113
- count_only ? names.inject(0) { |a, b| a.to_i + b.to_i }.to_s : names.join(delimiter)
1114
- else
1115
- count_only ? '0' : substitute.nil? ? '' : substitute.process(data, processor)
1116
- end
1117
- rescue Exception => e
1118
- handle_processing_error(e, data, processor)
1119
- end
1120
-
1121
- format_on :process
1122
-
1123
- protected
1124
-
1125
- # @returns a list of all name variables covered by this node; each list
1126
- # is wrapped in a list containing the lists role (e.g., 'editor')
1127
- # followed by the list proper.
1128
- def collect_names(item)
1129
- return [] unless self.variable?
1130
- self.variable.split(/\s+/).map { |variable| [variable, (item[variable] || []).map(&:clone)] }.reject { |(_, n)| n.empty? }
1131
- end
1132
-
1133
- def inherit_attributes(node)
1134
- inherit_attributes_from(node, ['citation', 'bibliography', 'style'], ['delimiter'], 'names-')
1135
- end
1136
-
1137
- end
1138
-
1139
-
1140
- # The cs:group element may contain one or more rendering elements (not
1141
- # cs:layout). cs:group itself may carry the delimiter attribute (to
1142
- # delimit the enclosed elements) and the attributes for affixes (applied
1143
- # to the group output as a whole), display and formatting (formatting
1144
- # settings are transmitted to the enclosed elements). Note that cs:group
1145
- # implicitly acts as a conditional: cs:group and its child elements are
1146
- # suppressed if a) at least one rendering element in cs:group calls a
1147
- # variable (either directly or via a macro), and b) all variables that are
1148
- # called are empty. This behavior exists to accommodate descriptive
1149
- # cs:text elements. For example
1150
- #
1151
- # <layout>
1152
- # <group prefix="(" suffix=")">
1153
- # <text value="Published by: "/>
1154
- # <text variable="publisher"/>
1155
- # </group>
1156
- # </layout>
1157
- #
1158
- # results in "(Published by: Company A)" when the "publisher" variable is
1159
- # set to "Company A", but doesn't generate output when the "publisher"
1160
- # variable is empty.
1161
- #
1162
- class Group < Node
1163
- attr_fields Nodes.formatting_attributes
1164
- attr_fields %w{ delimiter }
1165
-
1166
- def process(data, processor)
1167
- start_observing(data)
1168
-
1169
- if delimiter.nil? || children.length < 2
1170
- processed = children.map { |c| c.process(data, processor) }.reject(&:empty?).join('')
1171
- else
1172
- processed = children.reduce('') do |ps, child|
1173
- p = child.process(data, processor)
1174
-
1175
- unless p.empty?
1176
- if delimiter && !ps.empty?
1177
- ps.chop! if ps[-1] =~ /[\s\.,:!\?]/ && ps[-1] == delimiter[0]
1178
-
1179
- ps << delimiter
1180
-
1181
- ps.chop! if ps[-1] =~ /[\s\.,:!\?]/ && ps[-1] == p[0]
1182
- end
1183
-
1184
- ps << p
1185
- end
1186
-
1187
- ps
1188
- end
1189
- end
1190
-
1191
- stop_observing(data)
1192
-
1193
- # if any variable returned nil, skip the entire group
1194
- skip? ? '' : processor.format(processed, attributes)
1195
- rescue Exception => e
1196
- handle_processing_error(e, data, processor)
1197
- end
1198
-
1199
- def start_observing(item)
1200
- @variables = []
1201
- item.add_observer(self)
1202
- end
1203
-
1204
- def stop_observing(item)
1205
- item.delete_observer(self)
1206
- end
1207
-
1208
- def update(key, value)
1209
- @variables << [key, value]
1210
- end
1211
-
1212
- def skip?
1213
- @variables && @variables.map(&:last).all?(&:nil?)
1214
- end
1215
-
1216
-
1217
- protected
1218
-
1219
- def set_defaults
1220
- formatting_attributes = collect_formatting_attributes(%w{ delimiter suffix prefix })
1221
-
1222
- children.each do |child|
1223
- formatting_attributes.each_pair do |key, value|
1224
- child[key] ||= value
1225
- end
1226
- end
1227
- end
1228
-
1229
- def collect_formatting_attributes(exceptions=[])
1230
- formatting_attributes = {}
1231
- (Nodes.formatting_attributes - exceptions).each do |key|
1232
- formatting_attributes[key] = self[key]
1233
- self.attributes.delete(key)
1234
- end
1235
- formatting_attributes
1236
- end
1237
-
1238
- end
1239
-
1240
- # Similarly to the conditional statements encountered in programming
1241
- # languages, the cs:choose element allows for the conditional rendering of
1242
- # rendering elements. An example is shown below:
1243
- #
1244
- # <choose>
1245
- # <if type="book thesis" match="any">
1246
- # <text variable="title" font-style="italic">
1247
- # </if>
1248
- # <else-if type="chapter">
1249
- # <text variable="title" quotes="true">
1250
- # </else-if>
1251
- # <else>
1252
- # <text variable="title">
1253
- # </else>
1254
- # </choose>
1255
- #
1256
- # cs:choose requires a cs:if child element, which may be followed by one or
1257
- # more cs:else-if child elements, and an optional closing cs:else child
1258
- # element. The cs:if and cs:else-if elements may contain any number of
1259
- # rendering elements (except for cs:layout). As an empty cs:else element
1260
- # would be superfluous, cs:else must contain at least one rendering element.
1261
- # cs:if and cs:else-if elements must each hold at least one condition, which
1262
- # are expressed as attributes. The different types of conditions available
1263
- # are:
1264
- #
1265
- # disambiguate
1266
- # The contents of an <if disambiguate="true"> block is only rendered if it
1267
- # disambiguates two otherwise identical citations. This attempt at
1268
- # disambiguation will only be made when all other disambiguation methods
1269
- # have failed to uniquely identify the target source.
1270
- #
1271
- # is-numeric
1272
- # Tests whether the given variables (Appendix I - Variables) contain numeric
1273
- # data.
1274
- #
1275
- # is-uncertain-date
1276
- # Tests whether the given date variables contain uncertain dates.
1277
- #
1278
- # locator
1279
- # Tests whether the locator matches the given locator variable subtype (see
1280
- # Locators).
1281
- #
1282
- # position
1283
- # Tests whether the position of the item cite matches the given positions
1284
- # (when called within cs:bibliography, this condition will always test
1285
- # "false"). The different positions are (note on terminology: a citation
1286
- # refers to a citation group, which contains one or more cites to individual
1287
- # items):
1288
- #
1289
- # * "first": the position of a cite that is the first to reference an item
1290
- # * "ibid"/"ibid-with-locator"/"subsequent": a cite that references an
1291
- # earlier cited item always has the "subsequent" position. In special
1292
- # cases cites may have the "ibid" or "ibid-with-locator" position. These
1293
- # positions are only assigned when:
1294
- #
1295
- # 1. the current cite immediately follows on another cite, within the same
1296
- # citation, that references the same item
1297
- #
1298
- # or
1299
- #
1300
- # 2. the current cite is the first cite in the citation, and the previous
1301
- # citation includes a single cite that references the same item
1302
- #
1303
- #
1304
- # If either requirement is met, the presence of locators determines which
1305
- # position is assigned:
1306
- #
1307
- # 1. Preceding cite does not have a locator: if the current cite has a
1308
- # locator, the position of the current cite is "ibid-with-locator".
1309
- # Otherwise the position is "ibid".
1310
- #
1311
- # 2. Preceding cite does have a locator: if the current cite has the same
1312
- # locator, the position of the current cite is "ibid". If the locator
1313
- # differs the position is "ibid-with-locator". If the current cite
1314
- # lacks a locator the position is "subsequent".
1315
- #
1316
- # * "near-note": the position of a cite following another cite that
1317
- # references the same item. Both cites have to be located in foot or
1318
- # endnotes, and the distance between both cites may not exceed the maximum
1319
- # distance (measured in number of foot or endnotes) set with the
1320
- # near-note-distance option (see Note Distance).
1321
- #
1322
- # Note that each cite can have multiple position values. Whenever
1323
- # position="ibid-with-locator" is true, position="ibid" is also true.
1324
- # And whenever position="ibid" or position="near-note" is true,
1325
- # position="subsequent" is also true.
1326
- #
1327
- # type
1328
- # Tests whether the item matches the given types (Appendix II - Types).
1329
- #
1330
- # variable
1331
- # Tests whether the given variables (Appendix I - Variables) contain
1332
- # non-empty values.
1333
- #
1334
- # With the exception of disambiguate, all conditions allow for multiple test
1335
- # values (separated with spaces, e.g. "book thesis").
1336
- #
1337
- # The cs:if and cs:else-if elements may include the match attribute to
1338
- # control the testing logic, with possible values:
1339
- #
1340
- # * "all" (default): the element only tests "true" when all conditions test "true" for all given test values
1341
- # * "any": the element tests "true" when any condition tests "true" for any given test value
1342
- # * "none": the element only tests "true" when none of the conditions test "true" for any given test value
1343
- #
1344
- class Choose < Node
1345
-
1346
- def process(data, processor)
1347
- children.each do |child|
1348
- return child.process(data, processor) if child.evaluate(data, processor)
1349
- end
1350
- ''
1351
- rescue Exception => e
1352
- handle_processing_error(e, data, processor)
1353
- end
1354
-
1355
- end
1356
-
1357
- class ConditionalBlock < Node
1358
- attr_fields %w{ disambiguate is-numeric is-uncertain-date locator
1359
- position type variable match }
1360
-
1361
- def process(data, processor)
1362
- children.map { |child| child.process(data, processor) }.join
1363
- rescue Exception => e
1364
- handle_processing_error(e, data, processor)
1365
- end
1366
-
1367
- def evaluate(data, processor)
1368
- case
1369
- when disambiguate?
1370
- # CiteProc.log.warn "Choose disambiguate not implemented yet"
1371
- false
1372
-
1373
- when is_numeric?
1374
- data[is_numeric] && data[is_numeric].numeric?
1375
-
1376
- when is_uncertain_date?
1377
- data[is_uncertain_date] && data[is_uncertain_date].uncertain?
1378
-
1379
- when has_locator?
1380
- locator == data['locator'].to_s
1381
-
1382
- when has_position?
1383
- # CiteProc.log.warn "Choose position not implemented yet"
1384
- false
1385
-
1386
- when has_type?
1387
- matches?(type.split(/\s+/)) { |type| type == data['type'].to_s }
1388
-
1389
- when has_variable?
1390
- matches?(variable.split(/\s+/)) { |variable| !data[variable].nil? }
1391
-
1392
- when self.is_a?(Else)
1393
- true
1394
-
1395
- else
1396
- CiteProc.log :warn, "conditional block #{ inspect } could not be evaluated"
1397
- false
1398
-
1399
- end
1400
- rescue Exception => e
1401
- CiteProc.log.error "failed to evaluate item #{data.inspect}: #{ e.message }; returning false."
1402
- false
1403
- end
1404
-
1405
- # @returns true if &condition is true for any/all/none elements in the list
1406
- def matches?(list, &condition)
1407
- list.send([self['match'] || 'all', '?'].join, &condition)
1408
- end
1409
-
1410
- end
1411
-
1412
- end
1413
-
1414
- %w{ If ElseIf Else }.each do |node_name|
1415
- CSL::Nodes.const_set(node_name.to_sym, Class.new(CSL::Nodes::ConditionalBlock))
1416
- end
1417
-
1418
- end