csl 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.gitignore +1 -0
- data/.simplecov +3 -1
- data/.travis.yml +4 -10
- data/AGPL +1 -1
- data/BSDL +2 -2
- data/Gemfile +22 -12
- data/README.md +20 -11
- data/Rakefile +6 -1
- data/csl.gemspec +4 -2
- data/features/parser/choose.feature +16 -0
- data/features/step_definitions/parser_steps.rb +10 -0
- data/features/support/env.rb +16 -2
- data/lib/csl.rb +7 -4
- data/lib/csl/compatibility.rb +10 -16
- data/lib/csl/info.rb +3 -2
- data/lib/csl/locale.rb +92 -19
- data/lib/csl/locale/term.rb +41 -2
- data/lib/csl/name_options.rb +44 -0
- data/lib/csl/node.rb +54 -6
- data/lib/csl/parser.rb +17 -17
- data/lib/csl/schema.rb +37 -32
- data/lib/csl/style.rb +32 -8
- data/lib/csl/style/bibliography.rb +46 -9
- data/lib/csl/style/choose.rb +4 -2
- data/lib/csl/style/citation.rb +10 -11
- data/lib/csl/style/group.rb +13 -6
- data/lib/csl/style/label.rb +1 -1
- data/lib/csl/style/layout.rb +4 -4
- data/lib/csl/style/names.rb +161 -22
- data/lib/csl/style/sort.rb +74 -18
- data/lib/csl/style/text.rb +6 -4
- data/lib/csl/treelike.rb +16 -5
- data/lib/csl/version.rb +2 -2
- data/spec/csl/locale/term_spec.rb +70 -70
- data/spec/csl/locale_spec.rb +140 -99
- data/spec/csl/parser_spec.rb +26 -26
- data/spec/csl/schema_spec.rb +15 -15
- data/spec/csl/style/layout_spec.rb +2 -2
- data/spec/csl/style/sort_spec.rb +11 -0
- data/spec/spec_helper.rb +13 -4
- metadata +20 -14
data/lib/csl/locale/term.rb
CHANGED
@@ -21,6 +21,28 @@ module CSL
|
|
21
21
|
|
22
22
|
alias each each_child
|
23
23
|
|
24
|
+
# @example
|
25
|
+
# terms.store(term)
|
26
|
+
# terms.store('book', ['book', 'books'])
|
27
|
+
# terms.store('book', 'bk', :form => 'short')
|
28
|
+
#
|
29
|
+
# Shorthand method to stores a new term translations.
|
30
|
+
#
|
31
|
+
# @param [Term, String] the term; or the the term's name
|
32
|
+
# @param [String] the term's translation
|
33
|
+
# @param [Hash] additional term attributes
|
34
|
+
#
|
35
|
+
# @return [self]
|
36
|
+
def store(term, translation = nil, options = nil)
|
37
|
+
unless term.is_a?(Term)
|
38
|
+
term = Term.new(:name => term)
|
39
|
+
term.attributes.merge(options) unless options.nil?
|
40
|
+
term.set(*translation)
|
41
|
+
end
|
42
|
+
|
43
|
+
self << term
|
44
|
+
end
|
45
|
+
|
24
46
|
# If a style uses a term in a form that is undefined, there is a
|
25
47
|
# fallback to other forms: "verb-short" first falls back to "verb",
|
26
48
|
# "symbol" first falls back to "short", and "verb" and "short" both
|
@@ -151,7 +173,7 @@ module CSL
|
|
151
173
|
delete_children tmp
|
152
174
|
end
|
153
175
|
|
154
|
-
|
176
|
+
protected
|
155
177
|
|
156
178
|
# @!attribute [r] registry
|
157
179
|
# @return [Hash] a private registry to map term names to the respective
|
@@ -163,6 +185,8 @@ module CSL
|
|
163
185
|
# term objects for quick ordinal look-up
|
164
186
|
attr_reader :ordinals
|
165
187
|
|
188
|
+
private
|
189
|
+
|
166
190
|
def added_child(term)
|
167
191
|
raise ValidationError, "failed to register term #{term.inspect}: name attribute missing" unless
|
168
192
|
term.attribute?(:name)
|
@@ -214,7 +238,7 @@ module CSL
|
|
214
238
|
|
215
239
|
def specialize(options)
|
216
240
|
specialized = {}
|
217
|
-
|
241
|
+
|
218
242
|
options.each do |key, value|
|
219
243
|
key = key.to_sym
|
220
244
|
|
@@ -330,6 +354,21 @@ module CSL
|
|
330
354
|
|
331
355
|
alias plural pluralize
|
332
356
|
|
357
|
+
alias singular= single=
|
358
|
+
alias plural= multiple=
|
359
|
+
|
360
|
+
def set(singular, plural = nil)
|
361
|
+
if plural.nil?
|
362
|
+
self.text = singular
|
363
|
+
else
|
364
|
+
self.single = singular
|
365
|
+
self.multiple = plural
|
366
|
+
end
|
367
|
+
|
368
|
+
self
|
369
|
+
end
|
370
|
+
|
371
|
+
|
333
372
|
# @!method masculine?
|
334
373
|
# @return [Boolean] whether or not the term is masculine
|
335
374
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CSL
|
2
|
+
module InheritableNameOptions
|
3
|
+
def inheritable_name_options
|
4
|
+
options = attributes_for(*Schema.attr(:name))
|
5
|
+
|
6
|
+
if attribute?(:'name-delimiter')
|
7
|
+
options[:delimiter] = attributes[:'name-delimiter']
|
8
|
+
end
|
9
|
+
|
10
|
+
if attribute?(:'name-form')
|
11
|
+
options[:form] = attributes[:'name-form']
|
12
|
+
end
|
13
|
+
|
14
|
+
options
|
15
|
+
end
|
16
|
+
|
17
|
+
def inheritable_names_options
|
18
|
+
return {} unless attribute? :'names-delimiter'
|
19
|
+
{ :delimiter => attributes[:'names-delimiter'] }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module InheritsNameOptions
|
24
|
+
def inherits(name)
|
25
|
+
inheritable_options = "inheritable_#{name}".to_sym
|
26
|
+
|
27
|
+
define_method("inherited_#{name}") do |node, style|
|
28
|
+
options = {}
|
29
|
+
|
30
|
+
if node.respond_to?(inheritable_options)
|
31
|
+
options = node.send(inheritable_options).merge(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
style ||= root
|
35
|
+
|
36
|
+
if !root? && style.respond_to?(inheritable_options)
|
37
|
+
options = style.send(inheritable_options).merge(options)
|
38
|
+
end
|
39
|
+
|
40
|
+
options
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/csl/node.rb
CHANGED
@@ -137,8 +137,10 @@ module CSL
|
|
137
137
|
attr_reader :keys
|
138
138
|
end
|
139
139
|
|
140
|
-
|
141
|
-
|
140
|
+
CSL.silence_warnings do
|
141
|
+
def initialize(attrs = {})
|
142
|
+
super(*attrs.symbolize_keys.values_at(*keys))
|
143
|
+
end
|
142
144
|
end
|
143
145
|
|
144
146
|
# @return [<Symbol>] a list of symbols representing the names/keys
|
@@ -190,6 +192,7 @@ module CSL
|
|
190
192
|
|
191
193
|
self
|
192
194
|
end
|
195
|
+
alias merge! merge
|
193
196
|
|
194
197
|
# @overload values_at(selector, ... )
|
195
198
|
# Returns an array containing the attributes in self according
|
@@ -231,9 +234,7 @@ module CSL
|
|
231
234
|
|
232
235
|
def initialize_copy(other)
|
233
236
|
super
|
234
|
-
|
235
|
-
@children = self.class.create_children
|
236
|
-
@parent, @ancestors, @descendants, @siblings, @root, @depth = nil
|
237
|
+
initialize(other.attributes)
|
237
238
|
end
|
238
239
|
|
239
240
|
def deep_copy
|
@@ -246,6 +247,19 @@ module CSL
|
|
246
247
|
copy
|
247
248
|
end
|
248
249
|
|
250
|
+
def merge!(options)
|
251
|
+
attributes.merge!(options)
|
252
|
+
self
|
253
|
+
end
|
254
|
+
|
255
|
+
def reverse_merge!(options)
|
256
|
+
options.each_pair do |key, value|
|
257
|
+
attributes[key] = value unless attribute? key
|
258
|
+
end
|
259
|
+
|
260
|
+
self
|
261
|
+
end
|
262
|
+
|
249
263
|
# @return [Boolean] whether or not the node has default attributes
|
250
264
|
def has_default_attributes?
|
251
265
|
!default_attributes.empty?
|
@@ -417,9 +431,41 @@ module CSL
|
|
417
431
|
}.compact]
|
418
432
|
end
|
419
433
|
|
434
|
+
|
420
435
|
# @return [Hash] the node's formatting options
|
421
436
|
def formatting_options
|
422
|
-
attributes_for Schema.attr(:formatting)
|
437
|
+
options = attributes_for Schema.attr(:formatting)
|
438
|
+
|
439
|
+
if !root? && parent.respond_to?(:inheritable_formatting_options)
|
440
|
+
parent.inheritable_formatting_options.merge(options)
|
441
|
+
else
|
442
|
+
options
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
# Whether or not page ranges should be formatted when
|
447
|
+
# rendering this node.
|
448
|
+
#
|
449
|
+
# Page ranges must be formatted if the node is part of
|
450
|
+
# a {Style} with a page-range-format value.
|
451
|
+
#
|
452
|
+
# @return [Boolean] whether or not page ranges should
|
453
|
+
# be formatted
|
454
|
+
def format_page_ranges?
|
455
|
+
root.respond_to?(:has_page_range_format?) && root.has_page_range_format?
|
456
|
+
end
|
457
|
+
|
458
|
+
def page_range_format
|
459
|
+
return unless format_page_ranges?
|
460
|
+
root.page_range_format
|
461
|
+
end
|
462
|
+
|
463
|
+
def strip_periods?
|
464
|
+
attribute?(:'strip-periods') && !!(attributes[:'strip-periods'].to_s =~ /^true$/i)
|
465
|
+
end
|
466
|
+
|
467
|
+
def quotes?
|
468
|
+
attribute?(:'quotes') && !!(attributes[:'quotes'].to_s =~ /^true$/i)
|
423
469
|
end
|
424
470
|
|
425
471
|
def <=>(other)
|
@@ -536,6 +582,8 @@ module CSL
|
|
536
582
|
true
|
537
583
|
end
|
538
584
|
|
585
|
+
remove_method :empty?
|
586
|
+
|
539
587
|
def empty?
|
540
588
|
text.nil? || text.empty?
|
541
589
|
end
|
data/lib/csl/parser.rb
CHANGED
@@ -7,21 +7,21 @@ module CSL
|
|
7
7
|
include Singleton
|
8
8
|
|
9
9
|
attr_accessor :parser
|
10
|
-
|
10
|
+
|
11
11
|
@engines = {
|
12
12
|
:nokogiri => lambda { |source|
|
13
13
|
Nokogiri::XML::Document.parse(source, nil, nil,
|
14
|
-
Nokogiri::XML::ParseOptions::DEFAULT_XML | Nokogiri::XML::ParseOptions::NOBLANKS)
|
14
|
+
Nokogiri::XML::ParseOptions::DEFAULT_XML | Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::NOENT)
|
15
15
|
},
|
16
16
|
:default => lambda { |source|
|
17
17
|
REXML::Document.new(source, :compress_whitespace => :all, :ignore_whitespace_nodes => :all)
|
18
18
|
}
|
19
19
|
}
|
20
|
-
|
20
|
+
|
21
21
|
class << self
|
22
22
|
attr_reader :engines
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def initialize
|
26
26
|
require 'nokogiri'
|
27
27
|
@parser = Parser.engines[:nokogiri]
|
@@ -29,23 +29,23 @@ module CSL
|
|
29
29
|
require 'rexml/document'
|
30
30
|
@parser = Parser.engines[:default]
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
def parse(*arguments)
|
34
34
|
parse!(*arguments)
|
35
35
|
rescue
|
36
36
|
nil
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def parse!(source, scope = Node)
|
40
40
|
root = parser[source].children.detect { |child| !skip?(child) }
|
41
41
|
parse_tree root, scope
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
45
|
-
|
45
|
+
|
46
46
|
def parse_node(node, scope = Node)
|
47
47
|
attributes, text = parse_attributes(node), parse_text(node)
|
48
|
-
|
48
|
+
|
49
49
|
if text
|
50
50
|
n = TextNode.create node.name, attributes
|
51
51
|
n.text = text
|
@@ -54,26 +54,26 @@ module CSL
|
|
54
54
|
scope.create node.name, attributes
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
def parse_attributes(node)
|
59
59
|
Hash[*node.attributes.map { |n, a|
|
60
60
|
[n.to_sym, a.respond_to?(:value) ? a.value : a.to_s]
|
61
61
|
}.flatten]
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
def parse_tree(node, scope = Node)
|
65
65
|
return nil if node.nil?
|
66
|
-
|
66
|
+
|
67
67
|
root = parse_node node, scope
|
68
68
|
scope = specialize_scope(root, scope)
|
69
|
-
|
69
|
+
|
70
70
|
node.children.each do |child|
|
71
71
|
root << parse_tree(child, scope) unless comment?(child)
|
72
72
|
end unless root.textnode?
|
73
|
-
|
73
|
+
|
74
74
|
root
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
def parse_text(node)
|
78
78
|
if node.respond_to?(:has_text?)
|
79
79
|
node.has_text? && node.text
|
@@ -82,7 +82,7 @@ module CSL
|
|
82
82
|
child && child.respond_to?(:text?) && child.text? && child.text
|
83
83
|
end
|
84
84
|
end
|
85
|
-
|
85
|
+
|
86
86
|
def comment?(node)
|
87
87
|
node.respond_to?(:comment?) && node.comment? ||
|
88
88
|
node.respond_to?(:node_type) &&
|
@@ -103,5 +103,5 @@ module CSL
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
end
|
106
|
-
|
107
|
-
end
|
106
|
+
|
107
|
+
end
|
data/lib/csl/schema.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
module CSL
|
2
|
-
|
2
|
+
|
3
3
|
class Schema
|
4
|
-
|
4
|
+
|
5
5
|
@version = '1.0.1'.freeze
|
6
6
|
@major_version = '1.0'.freeze
|
7
7
|
|
8
8
|
@namespace = 'http://purl.org/net/xbiblio/csl'.freeze
|
9
9
|
@preamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".freeze
|
10
|
-
|
10
|
+
|
11
11
|
@default_license = 'http://creativecommons.org/licenses/by-sa/3.0/'
|
12
12
|
@default_rights_string =
|
13
13
|
'This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License'
|
14
|
-
|
14
|
+
|
15
15
|
@types = %w{ article article-journal article-magazine article-newspaper
|
16
16
|
bill book broadcast chapter entry entry-dictionary entry-encyclopedia
|
17
17
|
figure graphic interview legal_case legislation manuscript map
|
@@ -31,7 +31,7 @@ module CSL
|
|
31
31
|
|
32
32
|
:number => %w{
|
33
33
|
chapter-number collection-number edition issue number number-of-pages
|
34
|
-
number-of-volumes volume
|
34
|
+
number-of-volumes volume
|
35
35
|
},
|
36
36
|
|
37
37
|
:text => %w{
|
@@ -60,7 +60,7 @@ module CSL
|
|
60
60
|
[:date,:names,:text,:number].reduce([]) { |s,a| s.concat(@variables[a]) }.sort
|
61
61
|
|
62
62
|
@variables.freeze
|
63
|
-
|
63
|
+
|
64
64
|
@attributes = Hash.new { |h,k| h.fetch(k.to_sym, nil) }.merge({
|
65
65
|
:affixes => %w{
|
66
66
|
prefix suffix
|
@@ -72,13 +72,22 @@ module CSL
|
|
72
72
|
font-style font-variant font-weight text-decoration vertical-align
|
73
73
|
},
|
74
74
|
:name => %w{
|
75
|
-
|
75
|
+
and delimiter-precedes-et-al initialize-with
|
76
76
|
delimiter-precedes-last et-al-min et-al-use-first et-al-subsequent-min
|
77
77
|
et-al-subsequent-use-first et-al-use-last name-as-sort-order
|
78
78
|
sort-separator initialize
|
79
79
|
},
|
80
80
|
:names => %w{
|
81
|
-
names-delimiter
|
81
|
+
names-delimiter name-delimiter name-form
|
82
|
+
},
|
83
|
+
:bibliography => %w{
|
84
|
+
hanging-indent second-field-align line-spacing entry-spacing
|
85
|
+
},
|
86
|
+
:citation => %w{
|
87
|
+
disambiguate-add-names disambiguate-add-given-name
|
88
|
+
givenname-disambiguation-rule disambiguate-add-year-suffix
|
89
|
+
cite-group-delimiter collapse year-suffix-delimiter
|
90
|
+
after-collapse-delimiter near-note-distance
|
82
91
|
},
|
83
92
|
:conditionals => %w{
|
84
93
|
disambiguate position
|
@@ -90,7 +99,7 @@ module CSL
|
|
90
99
|
variable variable-any variable-all variable-none
|
91
100
|
}
|
92
101
|
})
|
93
|
-
|
102
|
+
|
94
103
|
@attributes.each_value { |v| v.map!(&:to_sym).freeze }
|
95
104
|
|
96
105
|
@attributes[:formatting] = [:'text-case', :display].concat(
|
@@ -103,11 +112,11 @@ module CSL
|
|
103
112
|
:form => %w{ numeric numeric-leading-zeros ordinal long short }
|
104
113
|
}
|
105
114
|
})
|
106
|
-
|
115
|
+
|
107
116
|
@values.freeze
|
108
117
|
|
109
118
|
@file = File.expand_path('../../../vendor/schema/csl.rng', __FILE__)
|
110
|
-
|
119
|
+
|
111
120
|
@validators = {
|
112
121
|
:nokogiri => lambda { |schema, style|
|
113
122
|
begin
|
@@ -117,12 +126,12 @@ module CSL
|
|
117
126
|
[[0, $!.message]]
|
118
127
|
end
|
119
128
|
},
|
120
|
-
|
129
|
+
|
121
130
|
:default => lambda { |schema, style|
|
122
131
|
raise ValidationError, "please `gem install nokogiri' for validation support"
|
123
132
|
}
|
124
133
|
}
|
125
|
-
|
134
|
+
|
126
135
|
begin
|
127
136
|
# TODO enable java validator when nokogiri issue is fixed
|
128
137
|
if RUBY_PLATFORM =~ /java/i
|
@@ -135,19 +144,19 @@ module CSL
|
|
135
144
|
rescue LoadError
|
136
145
|
@validator = @validators[:default]
|
137
146
|
end
|
138
|
-
|
147
|
+
|
139
148
|
class << self
|
140
|
-
|
149
|
+
|
141
150
|
attr_accessor :version, :major_version, :namespace, :types,
|
142
151
|
:variables, :categories, :attributes, :preamble, :values,
|
143
152
|
:default_rights_string, :default_license
|
144
|
-
|
153
|
+
|
145
154
|
private :new
|
146
|
-
|
155
|
+
|
147
156
|
def attr(*arguments)
|
148
157
|
attributes.values_at(*arguments).flatten(1)
|
149
158
|
end
|
150
|
-
|
159
|
+
|
151
160
|
# Validates the passed-in style or list of styles. The style argument(s)
|
152
161
|
# can either be a {Style} object, a style's file handle, XML content
|
153
162
|
# or a valid location (wildcards are supported). The method returns
|
@@ -172,9 +181,9 @@ module CSL
|
|
172
181
|
def validate(node)
|
173
182
|
case
|
174
183
|
when node.is_a?(Node)
|
175
|
-
validator[schema, node.to_xml]
|
184
|
+
@validator[@schema, node.to_xml]
|
176
185
|
when node.respond_to?(:read)
|
177
|
-
validator[schema, node.read]
|
186
|
+
@validator[@schema, node.read]
|
178
187
|
when node.is_a?(Enumerable) && !node.is_a?(String)
|
179
188
|
node.map { |n| validate(n) }.flatten(1)
|
180
189
|
when node.respond_to?(:to_s)
|
@@ -182,23 +191,23 @@ module CSL
|
|
182
191
|
|
183
192
|
case
|
184
193
|
when node =~ /^\s*</
|
185
|
-
validator[schema, node]
|
194
|
+
@validator[@schema, node]
|
186
195
|
when File.exists?(node)
|
187
|
-
validator[schema, File.open(node, 'r:UTF-8')]
|
196
|
+
@validator[@schema, File.open(node, 'r:UTF-8')]
|
188
197
|
else
|
189
198
|
glob = Dir.glob(node)
|
190
|
-
|
199
|
+
|
191
200
|
if glob.empty?
|
192
|
-
validator[schema, Kernel.open(node)]
|
201
|
+
@validator[@schema, Kernel.open(node)]
|
193
202
|
else
|
194
|
-
glob.map { |n| validator[schema, File.open(n, 'r:UTF-8')] }.flatten(1)
|
203
|
+
glob.map { |n| @validator[@schema, File.open(n, 'r:UTF-8')] }.flatten(1)
|
195
204
|
end
|
196
205
|
end
|
197
206
|
else
|
198
207
|
raise ArgumentError, "failed to validate #{node.inspect}: not a CSL node"
|
199
208
|
end
|
200
209
|
end
|
201
|
-
|
210
|
+
|
202
211
|
# Whether or not the passed-in style (or list of styles) is valid.
|
203
212
|
#
|
204
213
|
# @see validate
|
@@ -215,11 +224,7 @@ module CSL
|
|
215
224
|
def valid?(style)
|
216
225
|
validate(style).empty?
|
217
226
|
end
|
218
|
-
|
219
|
-
private
|
220
|
-
|
221
|
-
attr_reader :validators, :validator, :schema
|
222
227
|
end
|
223
|
-
|
224
|
-
end
|
228
|
+
|
229
|
+
end
|
225
230
|
end
|