csl 1.0.0.pre3 → 1.0.0.pre4

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.
@@ -1,73 +1,169 @@
1
1
  module CSL
2
-
2
+ #
3
+ # {Info} nodes contain a {Style} (or {Locale}) metadata. Their XML structure
4
+ # is based on the Atom Syndication Format. For independent styles an {Info}
5
+ # node typically has the following child elements:
6
+ #
7
+ # * {Author} and {Contributor}: used to respectively acknowledge
8
+ # style authors and contributors, may each be used multiple times.
9
+ # * {Category}: styles may be assigned one or more categories. One
10
+ # {Category} node may be used once to describe how in-text citations
11
+ # are rendered, using its citation-format attribute.
12
+ # * {Id}: Must appear once. The element should contain a URI to establish
13
+ # the identity of the style. A stable, unique and dereferenceable URI
14
+ # is desired for publicly available styles.
15
+ # * {Link}: Multiple links can be added to an {Info} node: self,
16
+ # documentation, and template links all have dedicated accessors.
17
+ # * {Title}: Must appear once. The contents of this node should be the
18
+ # name of the style as shown to users.
19
+ # * {TitleShort}: May appear once. The contents of this node should be a
20
+ # shortened style name (e.g. "APA").
21
+ # * {Summary}: This node gives a description of the style.
22
+ # * {Rights}: This node specifies the license under which the style file
23
+ # is released. The element may carry a license attribute to specify the
24
+ # URI of the license.
25
+ # * {Updated}: Must appear once. This node must contain a timestamp that
26
+ # shows when the style was last updated.
27
+ #
28
+ # In dependent styles, the {Info} node must contain a {Link} with rel set
29
+ # to "independent-parent", with the URI of the independent parent style
30
+ # set on href. This link is also accessible as a string using the
31
+ # {#independent_parent} accessors. In addition, dependent styles should
32
+ # not contain template links.
33
+ #
34
+ # In a {Locale} node the {Info} node typically carries only {Translator},
35
+ # {Rights} and {Updated} nodes.
3
36
  class Info < Node
4
37
 
5
38
  attr_children :title, :'title-short', :id, :issn, :eissn, :issnl,
6
39
  :link, :author, :contributor, :category, :published, :summary,
7
40
  :updated, :rights, :'link-dependent-style'
8
-
41
+
9
42
  alias_child :contributors, :contributor
10
- alias_child :authors, :contributor
43
+ alias_child :authors, :author
11
44
  alias_child :links, :link
12
-
45
+ alias_child :categories, :category
46
+
13
47
  def initialize(attributes = {})
14
48
  super(attributes, &nil)
15
- children[:link] = []
49
+ children[:link], children[:category] = [], []
16
50
 
17
51
  yield self if block_given?
18
52
  end
19
-
53
+
20
54
  # @!attribute self_link
21
55
  # @return [String,nil] the style's URI
22
56
 
23
57
  # @!attribute template_link
24
58
  # @return [String,nil] URI of the style from which the current style is derived
25
-
59
+
26
60
  # @!attribute documentation_link
27
61
  # @return [String,nil] URI of style documentation
28
- [:self, :template, :documentation].each do |type|
29
- method_id = "#{type}_link"
30
-
62
+
63
+ # @!attribute independent_parent_link
64
+ # @return [String,nil] URI of independent-parent
65
+ %w{ self template documentation independent-parent }.each do |type|
66
+ method_id = "#{type.tr('-', '_')}_link"
67
+
31
68
  define_method method_id do
32
- link = links.detect { |l| l.match? :rel => type.to_s }
69
+ link = links.detect { |l| l.match? :rel => type }
33
70
  link.nil? ? nil : link[:href]
34
71
  end
35
-
72
+
36
73
  alias_method "has_#{method_id}?", method_id
37
-
74
+
38
75
  define_method "#{method_id}=" do |value|
39
- link = links.detect { |l| l.match? :rel => type.to_s }
40
-
76
+ link = links.detect { |l| l.match? :rel => type }
77
+
41
78
  if link.nil?
42
- set_child_link :href => value.to_s, :rel => type.to_s
79
+ set_child_link :href => value.to_s, :rel => type
43
80
  else
44
81
  link[:href] = value.to_s
45
82
  link
46
83
  end
47
84
  end
48
85
  end
49
-
86
+
87
+ # Ruby 1.8 still has Object#id methods so the attr_children generator
88
+ # has not created those; since #id is deprecated in 1.8.7 we're
89
+ # forcing the override anyway. Live dangerously!
90
+
91
+ # @return [Id] the id text node
50
92
  def id
51
93
  children[:id]
52
94
  end
53
-
95
+
54
96
  alias id= set_child_id
55
97
 
98
+ # @return [Time,nil] when the info node's parent was last updated
99
+ def updated_at
100
+ return unless has_updated?
101
+ updated.to_time
102
+ end
103
+
104
+ # Sets the updated_at timestamp.
105
+ # @return [self]
106
+ def update!(timestamp = Time.now)
107
+ ts = timestamp.respond_to?(:xmlschema) ? timestamp.xmlschema : timestamp.to_s
108
+
109
+ if has_updated?
110
+ updated = Updated.new { |u| u.text = ts }
111
+ else
112
+ updated.text = ts
113
+ end
114
+
115
+ self
116
+ end
117
+
118
+ # @return [Time,nil] when the info node's parent was published
119
+ def published_at
120
+ return unless has_published?
121
+ published.to_time
122
+ end
123
+
124
+ # Sets the updated_at timestamp.
125
+ # @return [self]
126
+ def publish!(timestamp = Time.now)
127
+ ts = timestamp.respond_to?(:xmlschema) ? timestamp.xmlschema : timestamp.to_s
128
+
129
+ if has_published?
130
+ published = Published.new { |u| u.text = ts }
131
+ else
132
+ published.text = ts
133
+ end
134
+
135
+ self
136
+ end
137
+
138
+ # @return [Symbol] the parent style's citation format
139
+ def citation_format
140
+ return unless has_categories?
141
+
142
+ end
143
+
144
+ def ciation_format=(new_format)
145
+ end
146
+
147
+ #
148
+ # Info Child Nodes
149
+ #
56
150
 
57
151
  class Contributor < Node
58
152
  attr_children :name, :email, :uri
59
153
  end
60
-
154
+
61
155
  class Author < Node
62
156
  attr_children :name, :email, :uri
63
157
  end
64
-
158
+
65
159
  class Translator < Node
66
160
  attr_children :name, :email, :uri
67
161
  end
68
-
162
+
69
163
  class Link < Node
70
164
  attr_struct :href, :rel
165
+
166
+ # TODO xml:lang
71
167
  end
72
168
 
73
169
  class DependentStyle < TextNode
@@ -77,36 +173,55 @@ module CSL
77
173
 
78
174
  class Category < Node
79
175
  attr_struct :field, :'citation-format'
80
- end
176
+ end
81
177
 
82
178
  class Id < TextNode
83
179
  end
84
-
180
+
85
181
  class Name < TextNode
86
182
  end
87
-
183
+
88
184
  class Email < TextNode
89
185
  end
90
186
 
187
+ class URI < TextNode
188
+ end
189
+
91
190
  class Title < TextNode
191
+ # TODO xml:lang
92
192
  end
93
193
 
94
- class ShortTitle < TextNode
194
+ class TitleShort < TextNode
195
+ # TODO xml:lang
95
196
  end
96
197
 
97
198
  class Summary < TextNode
199
+ # TODO xml:lang
98
200
  end
99
-
201
+
100
202
  class Rights < TextNode
203
+ attr_struct :license
204
+ # TODO xml:lang
101
205
  end
102
206
 
103
- class Uri < TextNode
207
+ class Updated < TextNode
208
+
209
+ def to_time
210
+ return if empty?
211
+ Time.parse(to_s)
212
+ end
213
+ alias to_date to_time
104
214
  end
105
215
 
106
- class Updated < TextNode
216
+ class Published < TextNode
217
+ def to_time
218
+ return if empty?
219
+ Time.parse(to_s)
220
+ end
221
+ alias to_date to_time
107
222
  end
108
223
 
109
224
  end
110
-
111
-
225
+
226
+
112
227
  end
@@ -1,16 +1,16 @@
1
1
  module CSL
2
-
2
+
3
3
  class Node
4
-
4
+
5
5
  extend Forwardable
6
-
6
+
7
7
  include Enumerable
8
8
  include Comparable
9
-
9
+
10
10
  include Treelike
11
11
  include PrettyPrinter
12
-
13
-
12
+
13
+
14
14
  class << self
15
15
 
16
16
  def inherited(subclass)
@@ -19,19 +19,19 @@ module CSL
19
19
  klass.types << subclass if klass < Node
20
20
  end
21
21
  end
22
-
22
+
23
23
  def types
24
24
  @types ||= Set.new
25
25
  end
26
-
26
+
27
27
  def default_attributes
28
28
  @default_attributes ||= {}
29
29
  end
30
-
30
+
31
31
  def constantize(name)
32
- pattern = /#{name.to_s.tr('-', '')}$/i
32
+ pattern = /:#{name.to_s.tr('-', '')}$/i
33
33
  klass = types.detect { |t| t.matches?(pattern) }
34
-
34
+
35
35
  case
36
36
  when !klass.nil?
37
37
  klass
@@ -48,16 +48,16 @@ module CSL
48
48
  name_pattern === name
49
49
  end
50
50
  alias matches? match?
51
-
51
+
52
52
  # Returns a new node with the passed in name and attributes.
53
53
  def create(name, attributes = {}, &block)
54
54
  klass = constantize(name)
55
55
 
56
- node = (klass || Node).new(attributes, &block)
56
+ node = (klass || Node).new(attributes, &block)
57
57
  node.nodename = name
58
58
  node
59
59
  end
60
-
60
+
61
61
  def create_attributes(attributes)
62
62
  if const?(:Attributes)
63
63
  const_get(:Attributes).new(default_attributes.merge(attributes))
@@ -101,27 +101,27 @@ module CSL
101
101
  def values
102
102
  super.compact
103
103
  end
104
-
104
+
105
105
  # def to_a
106
106
  # keys.zip(values_at(*keys)).reject { |k,v| v.nil? }
107
107
  # end
108
-
108
+
109
109
  # @return [Boolean] true if all the attribute values are nil;
110
110
  # false otherwise.
111
111
  def empty?
112
112
  values.compact.empty?
113
113
  end
114
-
114
+
115
115
  def fetch(key, default = nil)
116
116
  value = keys.include?(key.to_sym) && send(:'[]', key)
117
-
118
- if block_given?
117
+
118
+ if block_given?
119
119
  value || yield(key)
120
120
  else
121
121
  value || default
122
122
  end
123
123
  end
124
-
124
+
125
125
  # Merges the current with the passed-in attributes.
126
126
  #
127
127
  # @param other [#each_pair] the other attributes
@@ -162,14 +162,14 @@ module CSL
162
162
  attr_reader :attributes
163
163
 
164
164
  def_delegators :attributes, :[], :[]=, :values, :values_at, :length, :size
165
-
165
+
166
166
  def initialize(attributes = {})
167
167
  @attributes = self.class.create_attributes(attributes)
168
168
  @children = self.class.create_children
169
-
169
+
170
170
  yield self if block_given?
171
171
  end
172
-
172
+
173
173
  # Iterates through the Node's attributes
174
174
  def each
175
175
  if block_given?
@@ -186,7 +186,7 @@ module CSL
186
186
  def attribute?(name)
187
187
  attributes.fetch(name, false)
188
188
  end
189
-
189
+
190
190
  # Returns true if the node contains any attributes (ignores nil values);
191
191
  # false otherwise.
192
192
  def has_attributes?
@@ -202,7 +202,7 @@ module CSL
202
202
  File.open(path, 'w:UTF-8') do |f|
203
203
  f << (options[:compact] ? to_xml : pretty_print)
204
204
  end
205
-
205
+
206
206
  self
207
207
  end
208
208
 
@@ -231,7 +231,7 @@ module CSL
231
231
  # @return [Boolean] whether or not the query matches the node
232
232
  def match?(name = nodename, conditions = {})
233
233
  name, conditions = match_conditions_for(name, conditions)
234
-
234
+
235
235
  return false unless name === nodename
236
236
  return true if conditions.empty?
237
237
 
@@ -265,10 +265,10 @@ module CSL
265
265
  # @return [Boolean] whether or not the query matches the node exactly
266
266
  def exact_match?(name = nodename, conditions = {})
267
267
  name, conditions = match_conditions_for(name, conditions)
268
-
268
+
269
269
  return false unless name === nodename
270
270
  return true if conditions.empty?
271
-
271
+
272
272
  conditions.values_at(*attributes.keys).zip(
273
273
  attributes.values_at(*attributes.keys)).all? do |condition, value|
274
274
  condition === value
@@ -281,40 +281,40 @@ module CSL
281
281
  rescue
282
282
  nil
283
283
  end
284
-
284
+
285
285
  # Returns the node' XML tags (including attribute assignments) as an
286
286
  # array of strings.
287
287
  def tags
288
288
  if has_children?
289
289
  tags = []
290
290
  tags << "<#{[nodename, *attribute_assignments].join(' ')}>"
291
-
291
+
292
292
  tags << children.map { |node|
293
293
  node.respond_to?(:tags) ? node.tags : [node.to_s]
294
294
  }.flatten(1)
295
-
295
+
296
296
  tags << "</#{nodename}>"
297
297
  tags
298
298
  else
299
299
  ["<#{[nodename, *attribute_assignments].join(' ')}/>"]
300
300
  end
301
301
  end
302
-
302
+
303
303
  def inspect
304
304
  "#<#{[self.class.name, *attribute_assignments].join(' ')} children=[#{children.count}]>"
305
305
  end
306
-
306
+
307
307
  alias to_s pretty_print
308
308
 
309
-
309
+
310
310
  private
311
-
311
+
312
312
  def attribute_assignments
313
313
  each_pair.map { |name, value|
314
314
  value.nil? ? nil: [name, value.to_s.inspect].join('=')
315
315
  }.compact
316
316
  end
317
-
317
+
318
318
  def match_conditions_for(name, conditions)
319
319
  case name
320
320
  when Hash
@@ -327,15 +327,15 @@ module CSL
327
327
  end
328
328
 
329
329
  end
330
-
331
-
330
+
331
+
332
332
  class TextNode < Node
333
-
333
+
334
334
  has_no_children
335
335
 
336
336
  class << self
337
337
  undef_method :attr_children
338
-
338
+
339
339
  # @override
340
340
  def create(name, attributes = {}, &block)
341
341
  klass = constantize(name)
@@ -347,19 +347,22 @@ module CSL
347
347
  end
348
348
 
349
349
  attr_accessor :text
350
- alias to_s text
350
+
351
+ def to_s
352
+ text.to_s.strip
353
+ end
351
354
 
352
355
  # TextNodes quack like a string.
353
356
  # def_delegators :to_s, *String.instance_methods(false).reject do |m|
354
357
  # m.to_s =~ /^\W|!$|(?:^(?:hash|eql?|to_s|length|size|inspect)$)/
355
358
  # end
356
- #
359
+ #
357
360
  # String.instance_methods(false).select { |m| m.to_s =~ /!$/ }.each do |m|
358
361
  # define_method(m) do
359
362
  # content.send(m) if content.respond_to?(m)
360
363
  # end
361
364
  # end
362
-
365
+
363
366
  def initialize(argument = '')
364
367
  case
365
368
  when argument.is_a?(Hash)
@@ -372,19 +375,23 @@ module CSL
372
375
  raise ArgumentError, "failed to create text node from #{argument.inspect}"
373
376
  end
374
377
  end
375
-
378
+
376
379
  def textnode?
377
380
  true
378
381
  end
379
-
382
+
383
+ def empty?
384
+ text.nil? || text.empty?
385
+ end
386
+
380
387
  def tags
381
388
  ["<#{attribute_assignments.unshift(nodename).join(' ')}>#{text}</#{nodename}>"]
382
389
  end
383
-
390
+
384
391
  def inspect
385
392
  "#<#{[self.class.name, text.inspect, *attribute_assignments].join(' ')}>"
386
393
  end
387
-
394
+
388
395
  end
389
-
396
+
390
397
  end