csl 1.0.0.pre10 → 1.0.0.pre11
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.
- data/README.md +21 -8
- data/lib/csl/loader.rb +22 -15
- data/lib/csl/locale.rb +92 -36
- data/lib/csl/locale/date.rb +16 -16
- data/lib/csl/locale/ordinalize.rb +14 -7
- data/lib/csl/locale/style_options.rb +4 -3
- data/lib/csl/locale/term.rb +92 -22
- data/lib/csl/node.rb +96 -42
- data/lib/csl/parser.rb +21 -19
- data/lib/csl/style.rb +1 -10
- data/lib/csl/version.rb +1 -1
- data/spec/csl/locale/style_options_spec.rb +1 -1
- data/spec/csl/locale/term_spec.rb +46 -19
- data/spec/csl/locale_spec.rb +78 -40
- metadata +32 -13
- data/.gitmodules +0 -6
data/lib/csl/locale/term.rb
CHANGED
@@ -11,32 +11,72 @@ module CSL
|
|
11
11
|
|
12
12
|
def initialize(attributes = {})
|
13
13
|
super(attributes)
|
14
|
-
|
14
|
+
children[:term] = []
|
15
|
+
|
16
|
+
@registry = Term::Registry.new
|
17
|
+
@ordinals = Term::Registry.new
|
15
18
|
|
16
19
|
yield self if block_given?
|
17
20
|
end
|
18
21
|
|
19
22
|
alias each each_child
|
20
23
|
|
21
|
-
def lookup(
|
22
|
-
|
24
|
+
def lookup(name, options = {})
|
25
|
+
options[:name] = name = name.to_s
|
26
|
+
|
27
|
+
term = registry[name].detect { |t| t.match?(options) }
|
28
|
+
return term unless term.nil? && options.delete(:'gender-form')
|
29
|
+
|
30
|
+
registry[name].detect { |t| t.match?(options) }
|
31
|
+
end
|
32
|
+
alias [] lookup
|
33
|
+
|
34
|
+
def ordinalize_modulo(number, divisor, options = {})
|
35
|
+
ordinal = ordinalize(number, options)
|
36
|
+
return unless ordinal && ordinal.match_modulo?(divisor)
|
37
|
+
ordinal
|
38
|
+
end
|
23
39
|
|
24
|
-
|
25
|
-
|
40
|
+
def ordinalize(number, options = {})
|
41
|
+
return unless has_ordinals?
|
42
|
+
|
43
|
+
if number == :default
|
44
|
+
options[:name] = 'ordinal'
|
26
45
|
else
|
27
|
-
|
46
|
+
options[:name] = 'ordinal-%02d' % number
|
28
47
|
end
|
48
|
+
|
49
|
+
if options[:form].to_s =~ /^long/i
|
50
|
+
options.delete :form
|
51
|
+
options[:name][0,0] = 'long-'
|
52
|
+
end
|
53
|
+
|
54
|
+
ordinal = ordinals[number].detect { |t| t.match?(options) }
|
55
|
+
return ordinal unless ordinal.nil? && options.delete(:'gender-form')
|
56
|
+
|
57
|
+
ordinals[number].detect { |t| t.match?(options) }
|
58
|
+
end
|
29
59
|
|
30
|
-
|
60
|
+
# @return [Boolean] whether or not regular terms are registered at this node
|
61
|
+
def has_terms?
|
62
|
+
not registry.empty?
|
31
63
|
end
|
32
|
-
alias [] lookup
|
33
64
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
65
|
+
# @return [Boolean] whether or not ordinal terms are registered at this node
|
66
|
+
def has_ordinals?
|
67
|
+
not ordinals.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
def has_legacy_ordinals?
|
71
|
+
has_ordinals? && !ordinals.key?(:default)
|
38
72
|
end
|
39
73
|
|
74
|
+
def drop_ordinals
|
75
|
+
tmp = ordinals.values.flatten(1)
|
76
|
+
ordinals.clear
|
77
|
+
delete_children tmp
|
78
|
+
end
|
79
|
+
|
40
80
|
private
|
41
81
|
|
42
82
|
# @!attribute [r] registry
|
@@ -44,17 +84,36 @@ module CSL
|
|
44
84
|
# term objects for quick term look-up
|
45
85
|
attr_reader :registry
|
46
86
|
|
87
|
+
# @!attribute [r] ordinals
|
88
|
+
# @return [Hash] a private registry to map ordinals to the respective
|
89
|
+
# term objects for quick ordinal look-up
|
90
|
+
attr_reader :ordinals
|
91
|
+
|
47
92
|
def added_child(term)
|
48
93
|
raise ValidationError, "failed to register term #{term.inspect}: name attribute missing" unless
|
49
94
|
term.attribute?(:name)
|
50
95
|
|
51
|
-
|
96
|
+
if term.ordinal?
|
97
|
+
store = ordinals[term.ordinal]
|
98
|
+
else
|
99
|
+
store = registry[term[:name]]
|
100
|
+
end
|
101
|
+
|
102
|
+
delete_children store.select { |value| term.exact_match? value }
|
103
|
+
|
104
|
+
store.push(term)
|
105
|
+
|
52
106
|
term
|
53
107
|
end
|
54
108
|
|
55
109
|
def deleted_child(term)
|
56
|
-
|
110
|
+
if term.ordinal?
|
111
|
+
ordinals[term.ordinal].delete(term)
|
112
|
+
else
|
113
|
+
registry[term[:name]].delete(term)
|
114
|
+
end
|
57
115
|
end
|
116
|
+
|
58
117
|
end
|
59
118
|
|
60
119
|
class Term < Node
|
@@ -76,7 +135,7 @@ module CSL
|
|
76
135
|
# passed-in divisor.
|
77
136
|
def match_modulo?(divisor)
|
78
137
|
return false unless ordinal?
|
79
|
-
|
138
|
+
|
80
139
|
case attributes.match
|
81
140
|
when '2-digits'
|
82
141
|
divisor.to_i == 100
|
@@ -88,24 +147,30 @@ module CSL
|
|
88
147
|
true
|
89
148
|
end
|
90
149
|
end
|
91
|
-
|
92
150
|
alias matches_modulo? match_modulo?
|
93
|
-
|
151
|
+
|
94
152
|
# @return [Boolean] whether or not this term is an ordinal term
|
95
153
|
def ordinal?
|
96
|
-
/^ordinal(-\d\d+)?$/ === attributes.name
|
154
|
+
/^(long-)?ordinal(-\d\d+)?$/ === attributes.name
|
97
155
|
end
|
98
|
-
|
156
|
+
|
157
|
+
# @return [Fixnum, :default, nil]
|
158
|
+
def ordinal
|
159
|
+
return unless ordinal?
|
160
|
+
return :default if attributes.name == 'ordinal'
|
161
|
+
attributes.name[/\d+/].to_i
|
162
|
+
end
|
163
|
+
|
99
164
|
def gendered?
|
100
|
-
|
165
|
+
not attributes.gender.blank?
|
101
166
|
end
|
102
167
|
|
103
168
|
def neutral?
|
104
|
-
|
169
|
+
not gendered?
|
105
170
|
end
|
106
171
|
|
107
172
|
def textnode?
|
108
|
-
|
173
|
+
not text.blank?
|
109
174
|
end
|
110
175
|
|
111
176
|
def singularize
|
@@ -200,6 +265,11 @@ module CSL
|
|
200
265
|
end
|
201
266
|
end
|
202
267
|
|
268
|
+
class Registry < ::Hash
|
269
|
+
def initialize
|
270
|
+
super { |h,k| h[k] = [] }
|
271
|
+
end
|
272
|
+
end
|
203
273
|
end
|
204
274
|
|
205
275
|
TextNode.types << Term
|
data/lib/csl/node.rb
CHANGED
@@ -66,29 +66,44 @@ module CSL
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
def parse(data)
|
70
|
+
parse!(data)
|
71
|
+
rescue
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse!(data)
|
76
|
+
node = CSL.parse!(data, self)
|
77
|
+
|
78
|
+
raise ParseError, "root node not #{self.name}: #{node.inspect}" unless
|
79
|
+
node.class == self || Node.equal?(self)
|
80
|
+
|
81
|
+
node
|
82
|
+
end
|
83
|
+
|
69
84
|
private
|
70
85
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
86
|
+
def has_language
|
87
|
+
attr_accessor :language
|
88
|
+
|
89
|
+
define_method :has_language? do
|
90
|
+
!language.nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
public :language, :language=, :has_language?
|
94
|
+
|
95
|
+
alias_method :original_attribute_assignments, :attribute_assignments
|
96
|
+
|
97
|
+
define_method :attribute_assignments do
|
98
|
+
if has_language?
|
99
|
+
original_attribute_assignments.unshift('xml:lang="%s"' % language)
|
100
|
+
else
|
101
|
+
original_attribute_assignments
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private :original_attribute_assignments, :attribute_assignments
|
106
|
+
end
|
92
107
|
|
93
108
|
def attr_defaults(attributes)
|
94
109
|
@default_attributes = attributes
|
@@ -120,13 +135,17 @@ module CSL
|
|
120
135
|
__class__.keys
|
121
136
|
end
|
122
137
|
|
138
|
+
def symbolize_keys
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
123
142
|
def values
|
124
143
|
super.compact
|
125
144
|
end
|
126
145
|
|
127
|
-
|
128
|
-
|
129
|
-
|
146
|
+
def to_hash
|
147
|
+
Hash[keys.zip(values_at(*keys)).reject { |_, v| v.nil? }]
|
148
|
+
end
|
130
149
|
|
131
150
|
# @return [Boolean] true if all the attribute values are nil;
|
132
151
|
# false otherwise.
|
@@ -192,6 +211,23 @@ module CSL
|
|
192
211
|
yield self if block_given?
|
193
212
|
end
|
194
213
|
|
214
|
+
def initialize_copy(other)
|
215
|
+
super
|
216
|
+
@attributes = self.class.create_attributes(other.attributes)
|
217
|
+
@children = self.class.create_children
|
218
|
+
@parent, @ancestors, @descendants, @siblings, @root, @depth = nil
|
219
|
+
end
|
220
|
+
|
221
|
+
def deep_copy
|
222
|
+
copy = dup
|
223
|
+
|
224
|
+
each_child do |child|
|
225
|
+
copy.add_child child.deep_copy
|
226
|
+
end
|
227
|
+
|
228
|
+
copy
|
229
|
+
end
|
230
|
+
|
195
231
|
# Iterates through the Node's attributes
|
196
232
|
def each
|
197
233
|
if block_given?
|
@@ -215,9 +251,9 @@ module CSL
|
|
215
251
|
!attributes.empty?
|
216
252
|
end
|
217
253
|
|
218
|
-
|
219
|
-
|
220
|
-
|
254
|
+
def has_language?
|
255
|
+
false
|
256
|
+
end
|
221
257
|
|
222
258
|
def textnode?
|
223
259
|
false
|
@@ -251,8 +287,13 @@ module CSL
|
|
251
287
|
#
|
252
288
|
# @see #exact_match?
|
253
289
|
#
|
254
|
-
#
|
255
|
-
#
|
290
|
+
# @example
|
291
|
+
# node.match?(name, conditions)
|
292
|
+
# node.match?(conditions)
|
293
|
+
# node.match?(other_node)
|
294
|
+
#
|
295
|
+
# @param name [String,Regexp,Node] must match the nodename; alternatively
|
296
|
+
# you can pass a node
|
256
297
|
# @param conditions [Hash] the conditions
|
257
298
|
#
|
258
299
|
# @return [Boolean] whether or not the query matches the node
|
@@ -286,7 +327,13 @@ module CSL
|
|
286
327
|
#
|
287
328
|
# @see #match?
|
288
329
|
#
|
289
|
-
# @
|
330
|
+
# @example
|
331
|
+
# node.exact_match?(name, conditions)
|
332
|
+
# node.exact_match?(conditions)
|
333
|
+
# node.exact_match?(other_node)
|
334
|
+
#
|
335
|
+
# @param name [String,Regexp,Node] must match the nodename; alternatively
|
336
|
+
# you can pass a node
|
290
337
|
# @param conditions [Hash] the conditions
|
291
338
|
#
|
292
339
|
# @return [Boolean] whether or not the query matches the node exactly
|
@@ -303,20 +350,20 @@ module CSL
|
|
303
350
|
end
|
304
351
|
alias matches_exactly? exact_match?
|
305
352
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
353
|
+
# @option filter [Array] a list of attribute names
|
354
|
+
# @return [Hash] the node's attributes matching the filter
|
355
|
+
def attributes_for(*filter)
|
356
|
+
filter.flatten!
|
310
357
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
358
|
+
Hash[map { |name, value|
|
359
|
+
!value.nil? && filter.include?(name) ? [name, value.to_s] : nil
|
360
|
+
}.compact]
|
361
|
+
end
|
315
362
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
363
|
+
# @return [Hash] the node's formatting options
|
364
|
+
def formatting_options
|
365
|
+
attributes_for Schema.attr(:formatting)
|
366
|
+
end
|
320
367
|
|
321
368
|
def <=>(other)
|
322
369
|
[nodename, attributes, children] <=> [other.nodename, other.attributes, other.children]
|
@@ -349,6 +396,11 @@ module CSL
|
|
349
396
|
alias to_s pretty_print
|
350
397
|
|
351
398
|
|
399
|
+
protected
|
400
|
+
|
401
|
+
def match_conditions
|
402
|
+
end
|
403
|
+
|
352
404
|
private
|
353
405
|
|
354
406
|
def attribute_assignments
|
@@ -359,6 +411,8 @@ module CSL
|
|
359
411
|
|
360
412
|
def match_conditions_for(name, conditions)
|
361
413
|
case name
|
414
|
+
when Node
|
415
|
+
[name.nodename, name.attributes.to_hash]
|
362
416
|
when Hash
|
363
417
|
conditions, name = name, nodename
|
364
418
|
when Symbol
|
data/lib/csl/parser.rb
CHANGED
@@ -37,7 +37,7 @@ module CSL
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def parse!(source, scope = Node)
|
40
|
-
root = parser[source].children.detect { |child| !
|
40
|
+
root = parser[source].children.detect { |child| !skip?(child) }
|
41
41
|
parse_tree root, scope
|
42
42
|
end
|
43
43
|
|
@@ -62,8 +62,8 @@ module CSL
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def parse_tree(node, scope = Node)
|
65
|
-
|
66
|
-
|
65
|
+
return nil if node.nil?
|
66
|
+
|
67
67
|
root = parse_node node, scope
|
68
68
|
scope = specialize_scope(root, scope)
|
69
69
|
|
@@ -83,23 +83,25 @@ module CSL
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
86
|
+
def comment?(node)
|
87
|
+
node.respond_to?(:comment?) && node.comment? ||
|
88
|
+
node.respond_to?(:node_type) &&
|
89
|
+
[:comment, :xmldecl, :processing_instruction, 7].include?(node.node_type)
|
90
|
+
end
|
91
|
+
alias skip? comment?
|
90
92
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
93
|
+
def specialize_scope(root, scope = Node)
|
94
|
+
case root
|
95
|
+
when Style
|
96
|
+
Style
|
97
|
+
when Locale
|
98
|
+
Locale
|
99
|
+
when Info
|
100
|
+
Info
|
101
|
+
else
|
102
|
+
scope
|
103
|
+
end
|
104
|
+
end
|
103
105
|
end
|
104
106
|
|
105
107
|
end
|
data/lib/csl/style.rb
CHANGED
@@ -5,7 +5,7 @@ module CSL
|
|
5
5
|
|
6
6
|
@default = :apa
|
7
7
|
|
8
|
-
@root =
|
8
|
+
@root = '/usr/local/share/csl/styles'.freeze
|
9
9
|
|
10
10
|
@extension = '.csl'.freeze
|
11
11
|
@prefix = ''
|
@@ -14,15 +14,6 @@ module CSL
|
|
14
14
|
include Loader
|
15
15
|
|
16
16
|
attr_accessor :default
|
17
|
-
|
18
|
-
def parse(data)
|
19
|
-
node = CSL.parse!(data, self)
|
20
|
-
|
21
|
-
raise ParseError, "root node is not a style: #{node.inspect}" unless
|
22
|
-
node.is_a?(self)
|
23
|
-
|
24
|
-
node
|
25
|
-
end
|
26
17
|
|
27
18
|
def load(input = Style.default)
|
28
19
|
super
|