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 CHANGED
@@ -17,6 +17,5 @@ notifications:
17
17
  on_failure: always
18
18
  matrix:
19
19
  allow_failures:
20
- rvm:
21
- - rbx-18mode
22
- - rbx-19mode
20
+ - rvm: rbx-18mode
21
+ - rvm: rbx-19mode
@@ -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 Swedish CSL 1.0.1 locales
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="sv">
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
- # | 101 | | | |
936
- # | 1001 | | feminine | |
937
- # | 301 | | | |
938
- | 21 | | masculine | singular |
939
- | 21 | | masculine | plural |
940
- # | 1001 | | masculine | |
941
- Then the ordinals should be:
942
- | ordinal |
943
- | 0:e |
944
- | 1:a |
945
- | 2:a |
946
- | 3:e |
947
- | 4:e |
948
- | 5:e |
949
- | 6:e |
950
- | 7:e |
951
- | 8:e |
952
- | 9:e |
953
- | 10:e |
954
- | 1:a |
955
- | 1:e |
956
- | 2:a |
957
- | 2:e |
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
@@ -4,6 +4,7 @@ require 'forwardable'
4
4
  require 'open-uri'
5
5
  require 'singleton'
6
6
  require 'set'
7
+ require 'time'
7
8
 
8
9
  require 'csl/version'
9
10
 
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
- alias contributors contributor
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
- alias metadata info
58
- alias dates date
59
- alias options style_options
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}: dates=[#{dates.length}] terms=[#{terms.length}]>"
362
+ "#<#{self.class.name} #{to_s}>"
364
363
  end
365
364
 
366
365
  private
@@ -19,13 +19,15 @@ module CSL
19
19
  alias each each_child
20
20
 
21
21
  def lookup(query)
22
- terms = if Regexp === query
23
- registry.keys.select { |t| t =~ query }.flatten(1)
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[(Hash === query) ? query[:name] : query.to_s]
27
+ registry[query[:name].to_s]
26
28
  end
27
29
 
28
- terms.detect { |t| t.matches?(query) }
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
- klass = types.detect { |t| t.matches?(name) }
32
+ pattern = /#{name.to_s.tr('-', '')}$/i
33
+ klass = types.detect { |t| t.matches?(pattern) }
33
34
 
34
- if klass || !superclass.respond_to?(:constantize)
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
- superclass.constantize(name)
41
+ nil
38
42
  end
39
43
  end
40
44
 
41
- # @return [Boolean] whether or not the node's name matches the passed-in name
42
- def matches?(nodename)
43
- name.split(/::/)[-1].gsub(/([[:lower:]])([[:upper:]])/, '\1-\2').downcase == nodename
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.length}]>"
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
- require 'nokogiri'
105
- @validator = @validators[:nokogiri]
106
- @schema = Nokogiri::XML::RelaxNG(File.open(@file))
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.example.org/style.csl')
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
- def initialize(attributes = {})
41
- super(attributes)
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
- children[:locale] = []
44
- children[:macro] = []
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
- # @eturn [String] the node's name.
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 or pattern; returns nil there is no match.
90
- def find_child_by_name(name)
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 === child.nodename
98
+ child.match?(name, conditions)
93
99
  end
94
100
  end
95
- alias > find_child_by_name
101
+ alias > find_child
96
102
 
97
103
  # Returns all immediate child nodes whose nodename matches the passed-in
98
- # name or pattern; returns an empty array if there is no match.
99
- def find_children_by_name(name)
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 === child.nodename
113
+ child.match?(name, conditions)
102
114
  end
103
115
  end
104
- alias >> find_children_by_name
116
+ alias >> find_children
105
117
 
106
- # Returns true if the node has child nodes; false otherwise.
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 #descendants1 for a memoized
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
- # Returns this node's ancestors as an array.
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
- # Returns the node's current depth in the tree.
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
- # Returns the root node.
204
+ # @return [Node] the root node
191
205
  def root
192
206
  @root = root? ? self : parent.root!
193
207
  end
194
208
 
195
- # Returns true if the node is a root node, or false.
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
- case
255
- when respond_to?(:constantize)
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 = name.to_sym
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
- self.class.keys
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
- # Turns the Node into a leaf-node.
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
@@ -1,3 +1,3 @@
1
1
  module CSL
2
- VERSION = '1.0.0.pre2'.freeze
2
+ VERSION = '1.0.0.pre3'.freeze
3
3
  end
@@ -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 be nil
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>author</category></info>'
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.matches?('month-05').should be_true
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.matches?(/month-\d\d/).should be_true
64
+ m.should be_match(:name => /month-\d\d/)
65
65
  end
66
66
 
67
- it 'does not match when passed a matching hash without gender' do
68
- f.matches?(:name => 'edition').should_not be_true
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
@@ -97,7 +97,8 @@ module CSL
97
97
  Schema.validate(Style.load(:apa)).should == []
98
98
  end
99
99
 
100
- end
100
+ # TODO fix nokogiri/jing validation
101
+ end unless RUBY_PLATFORM =~ /java/i
101
102
 
102
103
  end
103
104
  end
@@ -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.pre2
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-11 00:00:00.000000000 Z
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: &70209999248220 !ruby/object:Gem::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: *70209999248220
24
+ version_requirements: *70232058166620
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70209999246540 !ruby/object:Gem::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: *70209999246540
35
+ version_requirements: *70232058165200
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &70209999245240 !ruby/object:Gem::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: *70209999245240
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: