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