csl 1.0.0.pre2 → 1.0.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -3
- data/features/locales/ordinalize.feature +25 -76
- data/lib/csl.rb +1 -0
- data/lib/csl/info.rb +46 -1
- data/lib/csl/locale.rb +7 -8
- data/lib/csl/locale/term.rb +7 -31
- data/lib/csl/node.rb +95 -8
- data/lib/csl/schema.rb +9 -4
- data/lib/csl/style.rb +34 -7
- data/lib/csl/treelike.rb +67 -31
- data/lib/csl/version.rb +1 -1
- data/spec/csl/info_spec.rb +33 -6
- data/spec/csl/locale/term_spec.rb +10 -4
- data/spec/csl/schema_spec.rb +2 -1
- data/spec/csl/style_spec.rb +36 -1
- metadata +8 -8
data/.travis.yml
CHANGED
@@ -860,52 +860,14 @@ Feature: Converting numbers to ordinals using CSL locales
|
|
860
860
|
# | 1001:e |
|
861
861
|
|
862
862
|
@v1.0.1 @locale @ordinals @i18n @gender @lang:pl
|
863
|
-
Scenario: Gendered
|
863
|
+
Scenario: Gendered Polish CSL 1.0.1 locales
|
864
864
|
Given the locale:
|
865
865
|
"""
|
866
866
|
<?xml version="1.0" encoding="utf-8"?>
|
867
|
-
<locale xmlns="http://purl.org/net/xbiblio/csl" version="1.0.1" xml:lang="
|
867
|
+
<locale xmlns="http://purl.org/net/xbiblio/csl" version="1.0.1" xml:lang="pl">
|
868
868
|
<terms>
|
869
869
|
<term name="ordinal-00">:e</term>
|
870
870
|
|
871
|
-
<term name="ordinal-01">:a</term>
|
872
|
-
<term name="ordinal-01" gender-form="masculine">:e</term>
|
873
|
-
<term name="ordinal-01" gender-form="feminine">:a</term>
|
874
|
-
|
875
|
-
<term name="ordinal-02">:a</term>
|
876
|
-
<term name="ordinal-02" gender-form="masculine">:e</term>
|
877
|
-
<term name="ordinal-02" gender-form="feminine">:a</term>
|
878
|
-
|
879
|
-
<term name="ordinal-11">:e</term>
|
880
|
-
<term name="ordinal-11" gender-form="feminine">:e</term>
|
881
|
-
<term name="ordinal-12">:e</term>
|
882
|
-
<term name="ordinal-12" gender-form="feminine">:e</term>
|
883
|
-
|
884
|
-
<term name="ordinal-21">:e</term>
|
885
|
-
<term name="ordinal-21" gender-form="feminine">:e</term>
|
886
|
-
<term name="ordinal-22">:e</term>
|
887
|
-
<term name="ordinal-22" gender-form="feminine">:e</term>
|
888
|
-
|
889
|
-
<term name="ordinal-31">:e</term>
|
890
|
-
<term name="ordinal-32">:e</term>
|
891
|
-
|
892
|
-
<term name="ordinal-41">:e</term>
|
893
|
-
<term name="ordinal-42">:e</term>
|
894
|
-
|
895
|
-
<term name="ordinal-51">:e</term>
|
896
|
-
<term name="ordinal-52">:e</term>
|
897
|
-
|
898
|
-
<term name="ordinal-61">:e</term>
|
899
|
-
<term name="ordinal-62">:e</term>
|
900
|
-
|
901
|
-
<term name="ordinal-71">:e</term>
|
902
|
-
<term name="ordinal-72">:e</term>
|
903
|
-
|
904
|
-
<term name="ordinal-81">:e</term>
|
905
|
-
<term name="ordinal-82">:e</term>
|
906
|
-
|
907
|
-
<term name="ordinal-91">:e</term>
|
908
|
-
<term name="ordinal-92">:e</term>
|
909
871
|
</terms>
|
910
872
|
</locale>
|
911
873
|
"""
|
@@ -932,39 +894,26 @@ Feature: Converting numbers to ordinals using CSL locales
|
|
932
894
|
| 11 | | feminine | |
|
933
895
|
| 11 | | masculine | |
|
934
896
|
| 21 | | | |
|
935
|
-
#
|
936
|
-
#
|
937
|
-
#
|
938
|
-
|
939
|
-
|
940
|
-
#
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
| 23:e |
|
959
|
-
| 999:e |
|
960
|
-
| 11:e |
|
961
|
-
| 11:e |
|
962
|
-
| 11:e |
|
963
|
-
| 21:e |
|
964
|
-
# | 101:e |
|
965
|
-
# | 1001:e |
|
966
|
-
# | 301:e |
|
967
|
-
| 21:e |
|
968
|
-
| 21:e |
|
969
|
-
# | 1001:e |
|
970
|
-
|
897
|
+
# Then the ordinals should be:
|
898
|
+
# | ordinal |
|
899
|
+
# | 0:e |
|
900
|
+
# | 1:a |
|
901
|
+
# | 2:a |
|
902
|
+
# | 3:e |
|
903
|
+
# | 4:e |
|
904
|
+
# | 5:e |
|
905
|
+
# | 6:e |
|
906
|
+
# | 7:e |
|
907
|
+
# | 8:e |
|
908
|
+
# | 9:e |
|
909
|
+
# | 10:e |
|
910
|
+
# | 1:a |
|
911
|
+
# | 1:e |
|
912
|
+
# | 2:a |
|
913
|
+
# | 2:e |
|
914
|
+
# | 23:e |
|
915
|
+
# | 999:e |
|
916
|
+
# | 11:e |
|
917
|
+
# | 11:e |
|
918
|
+
# | 11:e |
|
919
|
+
# | 21:e |
|
data/lib/csl.rb
CHANGED
data/lib/csl/info.rb
CHANGED
@@ -6,9 +6,54 @@ module CSL
|
|
6
6
|
:link, :author, :contributor, :category, :published, :summary,
|
7
7
|
:updated, :rights, :'link-dependent-style'
|
8
8
|
|
9
|
-
|
9
|
+
alias_child :contributors, :contributor
|
10
|
+
alias_child :authors, :contributor
|
11
|
+
alias_child :links, :link
|
10
12
|
|
13
|
+
def initialize(attributes = {})
|
14
|
+
super(attributes, &nil)
|
15
|
+
children[:link] = []
|
16
|
+
|
17
|
+
yield self if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!attribute self_link
|
21
|
+
# @return [String,nil] the style's URI
|
22
|
+
|
23
|
+
# @!attribute template_link
|
24
|
+
# @return [String,nil] URI of the style from which the current style is derived
|
25
|
+
|
26
|
+
# @!attribute documentation_link
|
27
|
+
# @return [String,nil] URI of style documentation
|
28
|
+
[:self, :template, :documentation].each do |type|
|
29
|
+
method_id = "#{type}_link"
|
30
|
+
|
31
|
+
define_method method_id do
|
32
|
+
link = links.detect { |l| l.match? :rel => type.to_s }
|
33
|
+
link.nil? ? nil : link[:href]
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method "has_#{method_id}?", method_id
|
37
|
+
|
38
|
+
define_method "#{method_id}=" do |value|
|
39
|
+
link = links.detect { |l| l.match? :rel => type.to_s }
|
40
|
+
|
41
|
+
if link.nil?
|
42
|
+
set_child_link :href => value.to_s, :rel => type.to_s
|
43
|
+
else
|
44
|
+
link[:href] = value.to_s
|
45
|
+
link
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def id
|
51
|
+
children[:id]
|
52
|
+
end
|
11
53
|
|
54
|
+
alias id= set_child_id
|
55
|
+
|
56
|
+
|
12
57
|
class Contributor < Node
|
13
58
|
attr_children :name, :email, :uri
|
14
59
|
end
|
data/lib/csl/locale.rb
CHANGED
@@ -4,6 +4,7 @@ module CSL
|
|
4
4
|
# translations, and a number ordinalizer.
|
5
5
|
#
|
6
6
|
class Locale < Node
|
7
|
+
types << CSL::Info
|
7
8
|
|
8
9
|
include Comparable
|
9
10
|
|
@@ -30,7 +31,6 @@ module CSL
|
|
30
31
|
|
31
32
|
|
32
33
|
class << self
|
33
|
-
|
34
34
|
include Loader
|
35
35
|
|
36
36
|
attr_accessor :default
|
@@ -43,8 +43,7 @@ module CSL
|
|
43
43
|
node.is_a?(self)
|
44
44
|
|
45
45
|
node
|
46
|
-
end
|
47
|
-
|
46
|
+
end
|
48
47
|
end
|
49
48
|
|
50
49
|
attr_defaults :version => Schema.version, :xmlns => Schema.namespace
|
@@ -54,9 +53,9 @@ module CSL
|
|
54
53
|
|
55
54
|
attr_accessor :language, :region
|
56
55
|
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
alias_child :metadata, :info
|
57
|
+
alias_child :dates, :date
|
58
|
+
alias_child :options, :style_options
|
60
59
|
|
61
60
|
private :attributes
|
62
61
|
undef_method :[]=
|
@@ -93,7 +92,7 @@ module CSL
|
|
93
92
|
raise ArgumentError, "wrong number of arguments (#{arguments.length} for 0..2)"
|
94
93
|
end
|
95
94
|
|
96
|
-
super(attributes)
|
95
|
+
super(attributes, &nil)
|
97
96
|
|
98
97
|
set(locale) unless locale.nil?
|
99
98
|
|
@@ -360,7 +359,7 @@ module CSL
|
|
360
359
|
|
361
360
|
# @return [String] a string representation of the Locale
|
362
361
|
def inspect
|
363
|
-
"#<#{self.class.name} #{to_s}
|
362
|
+
"#<#{self.class.name} #{to_s}>"
|
364
363
|
end
|
365
364
|
|
366
365
|
private
|
data/lib/csl/locale/term.rb
CHANGED
@@ -19,13 +19,15 @@ module CSL
|
|
19
19
|
alias each each_child
|
20
20
|
|
21
21
|
def lookup(query)
|
22
|
-
|
23
|
-
|
22
|
+
query = { :name => query } unless query.is_a?(Hash)
|
23
|
+
|
24
|
+
terms = if query[:name].is_a?(Regexp)
|
25
|
+
registry.select { |name, _| name =~ query[:name] }.flatten(1)
|
24
26
|
else
|
25
|
-
registry[
|
27
|
+
registry[query[:name].to_s]
|
26
28
|
end
|
27
29
|
|
28
|
-
terms.detect { |t| t.
|
30
|
+
terms.detect { |t| t.exact_match?(query) }
|
29
31
|
end
|
30
32
|
|
31
33
|
alias [] lookup
|
@@ -82,33 +84,7 @@ module CSL
|
|
82
84
|
children.multiple.to_s
|
83
85
|
end
|
84
86
|
|
85
|
-
alias plural pluralize
|
86
|
-
|
87
|
-
# Tests whether or not the Term matches the passed-in query. Tests
|
88
|
-
# vary slightly depending on the the query's type: if a String or
|
89
|
-
# Regexp is passed-in, the return value will be true if the Term's
|
90
|
-
# name matches the query (disregarding all other attributes); if the
|
91
|
-
# query is a Hash, however, the return value will only be true if
|
92
|
-
# all passed-in attributes
|
93
|
-
#
|
94
|
-
# @param query [Hash,Regexp,#to_s] the query
|
95
|
-
# @raise [ArgumentError] if the term cannot be matched using query
|
96
|
-
#
|
97
|
-
# @return [Boolean] whether or not the query matches the term
|
98
|
-
def match?(query)
|
99
|
-
case
|
100
|
-
when query.is_a?(Hash)
|
101
|
-
query.symbolize_keys.values_at(*attributes.keys) == attributes.values_at(*attributes.keys)
|
102
|
-
when query.is_a?(Regexp)
|
103
|
-
query =~ name
|
104
|
-
when query.respond_to?(:to_s)
|
105
|
-
query.to_s == name
|
106
|
-
else
|
107
|
-
raise ArgumentError, "cannot match term to query: #{query.inspect}"
|
108
|
-
end
|
109
|
-
end
|
110
|
-
alias matches? match?
|
111
|
-
|
87
|
+
alias plural pluralize
|
112
88
|
|
113
89
|
# @!method masculine?
|
114
90
|
# @return [Boolean] whether or not the term is masculine
|
data/lib/csl/node.rb
CHANGED
@@ -29,19 +29,25 @@ module CSL
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def constantize(name)
|
32
|
-
|
32
|
+
pattern = /#{name.to_s.tr('-', '')}$/i
|
33
|
+
klass = types.detect { |t| t.matches?(pattern) }
|
33
34
|
|
34
|
-
|
35
|
+
case
|
36
|
+
when !klass.nil?
|
35
37
|
klass
|
38
|
+
when nesting[-2].respond_to?(:constantize)
|
39
|
+
nesting[-2].constantize(name)
|
36
40
|
else
|
37
|
-
|
41
|
+
nil
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
41
|
-
# @return [Boolean] whether or not the node's name matches the
|
42
|
-
|
43
|
-
|
45
|
+
# @return [Boolean] whether or not the node's name matches the
|
46
|
+
# passed-in name pattern
|
47
|
+
def match?(name_pattern)
|
48
|
+
name_pattern === name
|
44
49
|
end
|
50
|
+
alias matches? match?
|
45
51
|
|
46
52
|
# Returns a new node with the passed in name and attributes.
|
47
53
|
def create(name, attributes = {}, &block)
|
@@ -199,7 +205,77 @@ module CSL
|
|
199
205
|
|
200
206
|
self
|
201
207
|
end
|
202
|
-
|
208
|
+
|
209
|
+
# Tests whether or not the Name matches the passed-in node name and
|
210
|
+
# attribute conditions; if a Hash is passed as a single argument,
|
211
|
+
# it is taken as the conditions parameter (the name parameter is
|
212
|
+
# automatically matches in this case).
|
213
|
+
#
|
214
|
+
# Whether or not the arguments match the node is determined as
|
215
|
+
# follows:
|
216
|
+
#
|
217
|
+
# 1. The name must match {#nodename}
|
218
|
+
# 2. All attribute name/value pairs passed as conditions must match
|
219
|
+
# the corresponding attributes of the node
|
220
|
+
#
|
221
|
+
# Note that only attributes present in the passed-in conditions
|
222
|
+
# influence the match – if you want to match only nodes that contain
|
223
|
+
# no other attributes than specified by the conditions, {#exact_match?}
|
224
|
+
# should be used instead.
|
225
|
+
#
|
226
|
+
# @see #exact_match?
|
227
|
+
#
|
228
|
+
# @param name [String,Regexp] must match the nodename
|
229
|
+
# @param conditions [Hash] the conditions
|
230
|
+
#
|
231
|
+
# @return [Boolean] whether or not the query matches the node
|
232
|
+
def match?(name = nodename, conditions = {})
|
233
|
+
name, conditions = match_conditions_for(name, conditions)
|
234
|
+
|
235
|
+
return false unless name === nodename
|
236
|
+
return true if conditions.empty?
|
237
|
+
|
238
|
+
conditions.values.zip(
|
239
|
+
attributes.values_at(*conditions.keys)).all? do |condition, value|
|
240
|
+
condition === value
|
241
|
+
end
|
242
|
+
end
|
243
|
+
alias matches? match?
|
244
|
+
|
245
|
+
# Tests whether or not the Name matches the passed-in node name and
|
246
|
+
# attribute conditions exactly; if a Hash is passed as a single argument,
|
247
|
+
# it is taken as the conditions parameter (the name parameter is
|
248
|
+
# automatically matches in this case).
|
249
|
+
#
|
250
|
+
# Whether or not the arguments match the node is determined as
|
251
|
+
# follows:
|
252
|
+
#
|
253
|
+
# 1. The name must match {#nodename}
|
254
|
+
# 2. All attribute name/value pairs of the node must match the
|
255
|
+
# corresponding pairs in the passed-in Hash
|
256
|
+
#
|
257
|
+
# Note that all node attributes are used by this method – if you want
|
258
|
+
# to match only a subset of attributes {#match?} should be used instead.
|
259
|
+
#
|
260
|
+
# @see #match?
|
261
|
+
#
|
262
|
+
# @param name [String,Regexp] must match the nodename
|
263
|
+
# @param conditions [Hash] the conditions
|
264
|
+
#
|
265
|
+
# @return [Boolean] whether or not the query matches the node exactly
|
266
|
+
def exact_match?(name = nodename, conditions = {})
|
267
|
+
name, conditions = match_conditions_for(name, conditions)
|
268
|
+
|
269
|
+
return false unless name === nodename
|
270
|
+
return true if conditions.empty?
|
271
|
+
|
272
|
+
conditions.values_at(*attributes.keys).zip(
|
273
|
+
attributes.values_at(*attributes.keys)).all? do |condition, value|
|
274
|
+
condition === value
|
275
|
+
end
|
276
|
+
end
|
277
|
+
alias matches_exactly? exact_match?
|
278
|
+
|
203
279
|
def <=>(other)
|
204
280
|
[nodename, attributes, children] <=> [other.nodename, other.attributes, other.children]
|
205
281
|
rescue
|
@@ -225,7 +301,7 @@ module CSL
|
|
225
301
|
end
|
226
302
|
|
227
303
|
def inspect
|
228
|
-
"#<#{[self.class.name, *attribute_assignments].join(' ')} children=[#{children.
|
304
|
+
"#<#{[self.class.name, *attribute_assignments].join(' ')} children=[#{children.count}]>"
|
229
305
|
end
|
230
306
|
|
231
307
|
alias to_s pretty_print
|
@@ -239,6 +315,17 @@ module CSL
|
|
239
315
|
}.compact
|
240
316
|
end
|
241
317
|
|
318
|
+
def match_conditions_for(name, conditions)
|
319
|
+
case name
|
320
|
+
when Hash
|
321
|
+
conditions, name = name, nodename
|
322
|
+
when Symbol
|
323
|
+
name = name.to_s
|
324
|
+
end
|
325
|
+
|
326
|
+
[name, conditions.symbolize_keys]
|
327
|
+
end
|
328
|
+
|
242
329
|
end
|
243
330
|
|
244
331
|
|
data/lib/csl/schema.rb
CHANGED
@@ -101,9 +101,14 @@ module CSL
|
|
101
101
|
}
|
102
102
|
|
103
103
|
begin
|
104
|
-
|
105
|
-
|
106
|
-
|
104
|
+
# TODO enable java validator when nokogiri issue is fixed
|
105
|
+
if RUBY_PLATFORM =~ /java/i
|
106
|
+
@validator = @validators[:default]
|
107
|
+
else
|
108
|
+
require 'nokogiri'
|
109
|
+
@validator = @validators[:nokogiri]
|
110
|
+
@schema = Nokogiri::XML::RelaxNG(File.open(@file, 'r:UTF-8'))
|
111
|
+
end
|
107
112
|
rescue LoadError
|
108
113
|
@validator = @validators[:default]
|
109
114
|
end
|
@@ -130,7 +135,7 @@ module CSL
|
|
130
135
|
#
|
131
136
|
# CSL::Schema.validate('my-styles/style.csl')
|
132
137
|
# CSL::Schema.validate('my-styles/*.csl')
|
133
|
-
# CSL::Schema.validate('http://www.
|
138
|
+
# CSL::Schema.validate('http://www.zotero.org/styles/vancouver')
|
134
139
|
#
|
135
140
|
# @param style [Node,String,IO,Array] the style (or a list of styles)
|
136
141
|
# to validate.
|
data/lib/csl/style.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module CSL
|
2
2
|
|
3
3
|
class Style < Node
|
4
|
+
types << CSL::Info << CSL::Locale
|
4
5
|
|
5
6
|
@default = :apa
|
6
7
|
|
@@ -21,7 +22,7 @@ module CSL
|
|
21
22
|
node.is_a?(self)
|
22
23
|
|
23
24
|
node
|
24
|
-
end
|
25
|
+
end
|
25
26
|
end
|
26
27
|
|
27
28
|
attr_defaults :version => Schema.version, :xmlns => Schema.namespace
|
@@ -33,15 +34,17 @@ module CSL
|
|
33
34
|
attr_children :'style-options', :info, :locale, :macro,
|
34
35
|
:citation, :bibliography
|
35
36
|
|
36
|
-
alias metadata info
|
37
37
|
alias options style_options
|
38
38
|
alias locales locale
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
def_delegators :info, :self_link, :self_link=, :has_self_link?,
|
41
|
+
:template_link, :template_link=, :has_template_link?,
|
42
|
+
:documentation_link, :documentation_link=, :has_documentation_link?,
|
43
|
+
:title=, :id=
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
+
def initialize(attributes = {})
|
46
|
+
super(attributes, &nil)
|
47
|
+
children[:locale], children[:macro] = [], []
|
45
48
|
|
46
49
|
yield self if block_given?
|
47
50
|
end
|
@@ -54,11 +57,35 @@ module CSL
|
|
54
57
|
validate.empty?
|
55
58
|
end
|
56
59
|
|
60
|
+
def info
|
61
|
+
children[:info] ||= Info.new
|
62
|
+
end
|
63
|
+
|
64
|
+
alias_child :metadata, :info
|
65
|
+
|
66
|
+
# @return [String] the style's id
|
67
|
+
def id
|
68
|
+
return nil unless info.has_id?
|
69
|
+
info.id.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [String] the style's title
|
73
|
+
def title
|
74
|
+
return nil unless info.has_title?
|
75
|
+
info.title.to_s
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Time] timestamp for the time set in info.updated
|
79
|
+
def updated_at
|
80
|
+
return nil unless info.has_updated?
|
81
|
+
Time.parse(info.updated)
|
82
|
+
end
|
83
|
+
|
57
84
|
private
|
58
85
|
|
59
86
|
def preamble
|
60
87
|
Schema.preamble.dup
|
61
88
|
end
|
62
89
|
end
|
63
|
-
|
90
|
+
|
64
91
|
end
|
data/lib/csl/treelike.rb
CHANGED
@@ -12,7 +12,7 @@ module CSL
|
|
12
12
|
base.extend(ClassMethods)
|
13
13
|
end
|
14
14
|
|
15
|
-
# @
|
15
|
+
# @return [String] the node's name.
|
16
16
|
def nodename
|
17
17
|
@nodename ||= self.class.name.split(/::/)[-1].gsub(/([[:lower:]])([[:upper:]])/, '\1-\2').downcase
|
18
18
|
end
|
@@ -63,6 +63,7 @@ module CSL
|
|
63
63
|
nodes.each do |node|
|
64
64
|
add_child node
|
65
65
|
end
|
66
|
+
self
|
66
67
|
end
|
67
68
|
|
68
69
|
def add_child(node)
|
@@ -86,28 +87,40 @@ module CSL
|
|
86
87
|
end
|
87
88
|
|
88
89
|
# Returns the first immediate child node whose nodename matches the
|
89
|
-
# passed-in name
|
90
|
-
|
90
|
+
# passed-in name/pattern and attribute conditions.
|
91
|
+
#
|
92
|
+
# @param name [String,Regexp] the node name to match
|
93
|
+
# @param conditions [Hash] the attributes to match
|
94
|
+
#
|
95
|
+
# @return [Node,nil] the first matching child node
|
96
|
+
def find_child(name, conditions = {})
|
91
97
|
children.detect do |child|
|
92
|
-
name
|
98
|
+
child.match?(name, conditions)
|
93
99
|
end
|
94
100
|
end
|
95
|
-
alias >
|
101
|
+
alias > find_child
|
96
102
|
|
97
103
|
# Returns all immediate child nodes whose nodename matches the passed-in
|
98
|
-
# name
|
99
|
-
|
104
|
+
# name/pattern and attribute conditions; returns an empty array if there
|
105
|
+
# is no match.
|
106
|
+
#
|
107
|
+
# @param name [String,Regexp] the node name to match
|
108
|
+
# @param conditions [Hash] the attributes to match
|
109
|
+
#
|
110
|
+
# @return [Array<Node>] all matching child nodes
|
111
|
+
def find_children(name, conditions = {})
|
100
112
|
children.select do |child|
|
101
|
-
name
|
113
|
+
child.match?(name, conditions)
|
102
114
|
end
|
103
115
|
end
|
104
|
-
alias >>
|
116
|
+
alias >> find_children
|
105
117
|
|
106
|
-
#
|
118
|
+
# @return [Boolean] true if this node has child nodes; false otherwise.
|
107
119
|
def has_children?
|
108
120
|
!empty?
|
109
121
|
end
|
110
122
|
|
123
|
+
# @return [Boolean] true if this node has no child nodes; false otherwise.
|
111
124
|
def empty?
|
112
125
|
children.empty?
|
113
126
|
end
|
@@ -143,6 +156,7 @@ module CSL
|
|
143
156
|
@siblings = each_sibling.to_a
|
144
157
|
end
|
145
158
|
|
159
|
+
# Traverses the node's sub-tree in depth-first order.
|
146
160
|
def each_descendant
|
147
161
|
if block_given?
|
148
162
|
each_child do |child|
|
@@ -156,8 +170,8 @@ module CSL
|
|
156
170
|
end
|
157
171
|
end
|
158
172
|
|
159
|
-
# Returns all descendants of the node. See #
|
160
|
-
# version.
|
173
|
+
# Returns all descendants of the node. See {#descendants!}
|
174
|
+
# for a memoized version.
|
161
175
|
def descendants
|
162
176
|
@descendants = each_descendant.to_a
|
163
177
|
end
|
@@ -177,22 +191,22 @@ module CSL
|
|
177
191
|
end
|
178
192
|
end
|
179
193
|
|
180
|
-
#
|
194
|
+
# @returns this node's ancestors as an array
|
181
195
|
def ancestors
|
182
196
|
@ancestors = each_ancestor.to_a
|
183
197
|
end
|
184
198
|
|
185
|
-
#
|
199
|
+
# @return [Fixnum] the node's current depth in the tree
|
186
200
|
def depth
|
187
201
|
@depth = ancestors.length
|
188
202
|
end
|
189
203
|
|
190
|
-
#
|
204
|
+
# @return [Node] the root node
|
191
205
|
def root
|
192
206
|
@root = root? ? self : parent.root!
|
193
207
|
end
|
194
208
|
|
195
|
-
#
|
209
|
+
# @returns [Boolean] whether or not the node is the tree's root node
|
196
210
|
def root?
|
197
211
|
parent.nil?
|
198
212
|
end
|
@@ -249,13 +263,12 @@ module CSL
|
|
249
263
|
end
|
250
264
|
|
251
265
|
def constantize_nodename(name)
|
266
|
+
return constantize(name) if respond_to?(:constantize)
|
267
|
+
|
252
268
|
klass = name.to_s.capitalize.gsub(/(\w)-(\w)/) { [$1, $2.upcase].join }
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
constantize(klass)
|
257
|
-
when const_defined?(klass)
|
258
|
-
const_get(klass)
|
269
|
+
|
270
|
+
if const_defined?(klass)
|
271
|
+
const_get(klass)
|
259
272
|
else
|
260
273
|
nil
|
261
274
|
end
|
@@ -264,6 +277,11 @@ module CSL
|
|
264
277
|
|
265
278
|
private
|
266
279
|
|
280
|
+
def attr_child_names_for(name)
|
281
|
+
reader = name.to_s.downcase.tr('-', '_')
|
282
|
+
[name.to_sym, reader, "set_child_#{reader}", "has_#{reader}?"]
|
283
|
+
end
|
284
|
+
|
267
285
|
# Creates a Struct for the passed-in child node names that will be
|
268
286
|
# used internally by the Node to manage its children. The Struct
|
269
287
|
# will be automatically initialized and is used similarly to the
|
@@ -273,19 +291,23 @@ module CSL
|
|
273
291
|
# a node defining it's children that way can only contain nodes of the
|
274
292
|
# given types.
|
275
293
|
#
|
276
|
-
# This method also generates accessors for each child.
|
294
|
+
# This method also generates accessors for each child. The writer
|
295
|
+
# method will try to coerce the passed-in value into the correct
|
296
|
+
# node type automatically.
|
277
297
|
def attr_children(*names)
|
278
298
|
|
279
299
|
names.each do |name|
|
280
|
-
name
|
281
|
-
reader = name.to_s.downcase.tr('-', '_')
|
282
|
-
writer = "set_child_#{reader}"
|
283
|
-
|
300
|
+
name, reader, writer, predicate = attr_child_names_for(name)
|
284
301
|
|
285
302
|
define_method(reader) do
|
286
303
|
children[name]
|
287
304
|
end unless method_defined?(reader)
|
288
305
|
|
306
|
+
define_method(predicate) do
|
307
|
+
c = children[name]
|
308
|
+
!(c.nil? || c.is_a?(Array) && c.empty?)
|
309
|
+
end unless method_defined?(predicate)
|
310
|
+
|
289
311
|
unless method_defined?(writer)
|
290
312
|
define_method(writer) do |value|
|
291
313
|
begin
|
@@ -304,10 +326,10 @@ module CSL
|
|
304
326
|
end unless value.respond_to?(:nodename)
|
305
327
|
|
306
328
|
children << value
|
329
|
+
value
|
307
330
|
end
|
308
331
|
|
309
|
-
alias_method :"#{reader}=", writer
|
310
|
-
|
332
|
+
alias_method :"#{reader}=", writer unless method_defined?(:"#{reader}=")
|
311
333
|
end
|
312
334
|
end
|
313
335
|
|
@@ -327,11 +349,15 @@ module CSL
|
|
327
349
|
# @return [<Symbol>] a list of symbols representing the names/keys
|
328
350
|
# of the attribute variables.
|
329
351
|
def keys
|
330
|
-
|
352
|
+
__class__.keys
|
331
353
|
end
|
332
354
|
|
333
355
|
alias original_each each
|
334
356
|
|
357
|
+
def count
|
358
|
+
values.reject { |c| c.nil? || c.empty? }.length
|
359
|
+
end
|
360
|
+
|
335
361
|
# Iterates through all children. Nil values are skipped and Arrays
|
336
362
|
# expanded.
|
337
363
|
def each
|
@@ -417,7 +443,13 @@ module CSL
|
|
417
443
|
})
|
418
444
|
end
|
419
445
|
|
420
|
-
|
446
|
+
def alias_child(new_name, old_name)
|
447
|
+
attr_child_names_for(new_name).zip(attr_child_names_for(old_name)).each do |nn, on|
|
448
|
+
alias_method nn, on if method_defined?(on)
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# Turns the node into a leaf-node.
|
421
453
|
def has_no_children
|
422
454
|
undef_method :add_child
|
423
455
|
undef_method :added_child
|
@@ -434,6 +466,10 @@ module CSL
|
|
434
466
|
false
|
435
467
|
end
|
436
468
|
|
469
|
+
define_method(:empty?) do
|
470
|
+
true
|
471
|
+
end
|
472
|
+
|
437
473
|
end
|
438
474
|
|
439
475
|
end
|
data/lib/csl/version.rb
CHANGED
data/spec/csl/info_spec.rb
CHANGED
@@ -7,6 +7,8 @@ module CSL
|
|
7
7
|
it { should_not be_nil }
|
8
8
|
it { should_not have_children }
|
9
9
|
|
10
|
+
let(:info) { Info.new }
|
11
|
+
|
10
12
|
describe '#nodename' do
|
11
13
|
it 'returns "info"' do
|
12
14
|
subject.nodename.should == 'info'
|
@@ -18,10 +20,6 @@ module CSL
|
|
18
20
|
Info.new.children.should be_a(Info::Children)
|
19
21
|
end
|
20
22
|
|
21
|
-
it 'allows to set the id by writer method' do
|
22
|
-
lambda { Info.new.children.id = 'foo' }.should_not raise_error
|
23
|
-
end
|
24
|
-
|
25
23
|
it 'allows to set the id by array accessor' do
|
26
24
|
lambda { Info.new.children[:id] = 'foo' }.should_not raise_error
|
27
25
|
end
|
@@ -29,8 +27,37 @@ module CSL
|
|
29
27
|
|
30
28
|
describe '#category' do
|
31
29
|
it 'returns nil by default' do
|
32
|
-
Info.new.category.should
|
30
|
+
Info.new.category.should be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'link accessors' do
|
35
|
+
it { should_not have_self_link }
|
36
|
+
it { should_not have_documentation_link }
|
37
|
+
it { should_not have_template_link }
|
38
|
+
|
39
|
+
it 'self_link is nil by default' do
|
40
|
+
Info.new.self_link.should be_nil
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns nil if no suitable link is set' do
|
44
|
+
Info.new {|i| i.link = {:href => 'foo', :rel => 'documentation'} }.self_link.should be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns the href value of the link if it is set' do
|
48
|
+
Info.new {|i| i.link = {:href => 'foo', :rel => 'self'} }.self_link.should == 'foo'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'setter changes the value of existing link' do
|
52
|
+
info = Info.new {|i| i.link = {:href => 'foo', :rel => 'self'} }
|
53
|
+
expect { info.self_link = 'bar' }.to change { info.self_link }.from('foo').to('bar')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'setter creates new link node if link did not exist' do
|
57
|
+
expect { info.self_link = 'bar' }.to change { info.has_self_link? }
|
58
|
+
info.links[0].should be_a(Info::Link)
|
33
59
|
end
|
60
|
+
|
34
61
|
end
|
35
62
|
|
36
63
|
describe '#to_xml' do
|
@@ -43,7 +70,7 @@ module CSL
|
|
43
70
|
end
|
44
71
|
|
45
72
|
it 'prints the category if present' do
|
46
|
-
Info.new { |i| i.category = 'author' }.to_xml.should == '<info><category
|
73
|
+
Info.new { |i| i.category = {:'citation-format' => 'author'} }.to_xml.should == '<info><category citation-format="author"/></info>'
|
47
74
|
end
|
48
75
|
end
|
49
76
|
|
@@ -57,15 +57,15 @@ module CSL
|
|
57
57
|
|
58
58
|
describe '#match?' do
|
59
59
|
it 'matches the name when passed a string' do
|
60
|
-
m.
|
60
|
+
m.should be_match(:name => 'month-05')
|
61
61
|
end
|
62
62
|
|
63
63
|
it 'matches the name when passed a pattern' do
|
64
|
-
m.
|
64
|
+
m.should be_match(:name => /month-\d\d/)
|
65
65
|
end
|
66
66
|
|
67
|
-
it '
|
68
|
-
f.
|
67
|
+
it 'matches when passed a matching hash without gender' do
|
68
|
+
f.should be_match(:name => 'edition')
|
69
69
|
end
|
70
70
|
|
71
71
|
it 'does not match when passed a matching hash with wrong gender' do
|
@@ -76,6 +76,12 @@ module CSL
|
|
76
76
|
f.matches?(:name => 'edition', :gender => 'feminine').should be_true
|
77
77
|
end
|
78
78
|
end
|
79
|
+
|
80
|
+
describe '#exact_match?' do
|
81
|
+
it 'does not match when passed a matching hash without gender' do
|
82
|
+
f.should_not be_exact_match(:name => 'edition')
|
83
|
+
end
|
84
|
+
end
|
79
85
|
|
80
86
|
describe 'attributes#to_a' do
|
81
87
|
it 'returns an array of all attribute values of underlying struct' do
|
data/spec/csl/schema_spec.rb
CHANGED
data/spec/csl/style_spec.rb
CHANGED
@@ -2,6 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module CSL
|
4
4
|
describe Style do
|
5
|
+
let(:style) { Style.new }
|
5
6
|
|
6
7
|
it 'has a 1.x version by default' do
|
7
8
|
Style.new[:version].should match(/1\.\d+(\.\d+)?/)
|
@@ -16,6 +17,40 @@ module CSL
|
|
16
17
|
Style.parse(Style.load(:apa).to_xml).should be_a(Style)
|
17
18
|
end
|
18
19
|
end
|
19
|
-
|
20
|
+
|
21
|
+
describe '#children' do
|
22
|
+
|
23
|
+
it { should_not have_info }
|
24
|
+
it { should_not have_locale }
|
25
|
+
it { should_not have_macro }
|
26
|
+
it { should_not have_citation }
|
27
|
+
it { should_not have_bibliography }
|
28
|
+
|
29
|
+
describe 'when it has a title' do
|
30
|
+
before(:all) { style.title = 'foo' }
|
31
|
+
|
32
|
+
it { style.should have_info }
|
33
|
+
|
34
|
+
it 'info.title is a text node' do
|
35
|
+
style.info.title.should be_a(TextNode)
|
36
|
+
end
|
37
|
+
|
38
|
+
it '#title returns the title as a string' do
|
39
|
+
style.title.should be_a(String)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#id accessor' do
|
45
|
+
|
46
|
+
it 'returns nil by default' do
|
47
|
+
Style.new.id.should be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'writer sets the id to the passed-in string' do
|
51
|
+
expect { style.id = 'foobar' }.to change { style.id }.from(nil).to('foobar')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
20
55
|
end
|
21
56
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.pre3
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
12
|
+
date: 2012-07-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cucumber
|
16
|
-
requirement: &
|
16
|
+
requirement: &70232058166620 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '1.1'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70232058166620
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70232058165200 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '2.7'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70232058165200
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &70232058177680 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0.9'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70232058177680
|
47
47
|
description: ! "\n\t\tA Ruby parser and library for the Citation Style Language (CSL),
|
48
48
|
an open\n\t\tXML-based language to describe the formatting of citations and\n\t\tbibliographies.\n\t\t"
|
49
49
|
email:
|