csl 1.0.0.pre2 → 1.0.0.pre3
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/.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:
|