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 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: