csl 1.0.0.pre3 → 1.0.0.pre4

This diff has not been reviewed by any users.
Sign up to get free protection for your applications and to get access to all the features.
@@ -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