csl 1.0.0.pre1 → 1.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
@@ -65,7 +65,8 @@ module CSL
65
65
  return nil if node.nil?
66
66
 
67
67
  root = parse_node node, scope
68
-
68
+ scope = specialize_scope(root, scope)
69
+
69
70
  node.children.each do |child|
70
71
  root << parse_tree(child, scope) unless comment?(child)
71
72
  end unless root.textnode?
@@ -86,7 +87,19 @@ module CSL
86
87
  node.respond_to?(:comment?) && node.comment? ||
87
88
  node.respond_to?(:node_type) && [:comment, :xmldecl].include?(node.node_type)
88
89
  end
89
-
90
+
91
+ def specialize_scope(root, scope = Node)
92
+ case root
93
+ when Style
94
+ Style
95
+ when Locale
96
+ Locale
97
+ when Info
98
+ Info
99
+ else
100
+ scope
101
+ end
102
+ end
90
103
  end
91
104
 
92
105
  end
@@ -8,9 +8,9 @@ module CSL
8
8
  def to_xml
9
9
  tags.flatten.join
10
10
  end
11
-
11
+
12
12
  def pretty_print
13
- pp(tags).join("\n")
13
+ preamble << tags.map { |t| pp t }.join("\n")
14
14
  end
15
15
 
16
16
  private
@@ -19,13 +19,15 @@ module CSL
19
19
  2
20
20
  end
21
21
 
22
- def pp(tags, level = 0)
23
- tags.map do |tag|
24
- if tag.respond_to?(:map)
25
- pp tag, level + 1
26
- else
27
- ' ' * (level * tabwidth) + tag.to_s
28
- end
22
+ def preamble
23
+ ''
24
+ end
25
+
26
+ def pp(tag, level = 0)
27
+ if tag.is_a?(Array)
28
+ tag.map { |t| pp t, level + 1 }.join("\n")
29
+ else
30
+ (' ' * (level * tabwidth)) << tag.to_s
29
31
  end
30
32
  end
31
33
 
@@ -4,7 +4,7 @@ module CSL
4
4
 
5
5
  @version = '1.0.1'.freeze
6
6
  @namespace = 'http://purl.org/net/xbiblio/csl'.freeze
7
- @preamble = '<?xml version="1.0" encoding="utf-8"?>'.freeze
7
+ @preamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".freeze
8
8
 
9
9
  @types = %w{ article article-journal article-magazine article-newspaper
10
10
  bill book broadcast chapter entry entry-dictionary entry-encyclopedia
@@ -74,13 +74,11 @@ module CSL
74
74
  :periods => %w{
75
75
  strip-periods
76
76
  },
77
- :textcase => %w{
78
- lowercase uppercase capitalize-first capitalize-all title sentence
79
- },
80
77
  :name => %w{
81
78
  name-form name-delimiter and delimiter-precedes-et-al initialize-with
82
- delimiter-precedes-last et-al-min etal-use-first et-al-subsequent-min
83
- et-al-subsequent-use-first et-al-use-last
79
+ delimiter-precedes-last et-al-min et-al-use-first et-al-subsequent-min
80
+ et-al-subsequent-use-first et-al-use-last name-as-sort-order
81
+ sort-separator initialize
84
82
  },
85
83
  :names => %w{
86
84
  names-delimiter
@@ -90,6 +88,25 @@ module CSL
90
88
  @attributes.each_value { |v| v.map!(&:to_sym).freeze }
91
89
  @attributes.freeze
92
90
 
91
+ @file = File.expand_path('../../../vendor/schema/csl.rng', __FILE__)
92
+
93
+ @validators = {
94
+ :nokogiri => lambda { |schema, style|
95
+ schema.validate(Nokogiri::XML(style)).map { |e| [e.line, e.message] }
96
+ },
97
+
98
+ :default => lambda { |schema, style|
99
+ raise ValidationError, "please `gem install nokogiri' for validation support"
100
+ }
101
+ }
102
+
103
+ begin
104
+ require 'nokogiri'
105
+ @validator = @validators[:nokogiri]
106
+ @schema = Nokogiri::XML::RelaxNG(File.open(@file))
107
+ rescue LoadError
108
+ @validator = @validators[:default]
109
+ end
93
110
 
94
111
  class << self
95
112
 
@@ -102,8 +119,78 @@ module CSL
102
119
  attributes.values_at(*arguments).flatten(1)
103
120
  end
104
121
 
122
+ # Validates the passed-in style or list of styles. The style argument(s)
123
+ # can either be a {Style} object, a style's file handle, XML content
124
+ # or a valid location (wildcards are supported). The method returns
125
+ # a list of validation errors; the passed-in style is valid if the
126
+ # method returns an empty list.
127
+ #
128
+ # @example
129
+ # CSL::Schema.validate(CSL::Style.load(:apa))
130
+ #
131
+ # CSL::Schema.validate('my-styles/style.csl')
132
+ # CSL::Schema.validate('my-styles/*.csl')
133
+ # CSL::Schema.validate('http://www.example.org/style.csl')
134
+ #
135
+ # @param style [Node,String,IO,Array] the style (or a list of styles)
136
+ # to validate.
137
+ #
138
+ # @raise [ArgumentError] if the passed-in argument is not a Style or
139
+ # a valid style location.
140
+ # @raise [ValidationError] if the validation process fails
141
+ #
142
+ # @return [<<Fixnum,String>>] a list of validation errors
143
+ def validate(node)
144
+ case
145
+ when node.is_a?(Node)
146
+ validator[schema, node.to_xml]
147
+ when node.respond_to?(:read)
148
+ validator[schema, node.read]
149
+ when node.is_a?(Enumerable) && !node.is_a?(String)
150
+ node.map { |n| validate(n) }.flatten(1)
151
+ when node.respond_to?(:to_s)
152
+ node = node.to_s
153
+
154
+ case
155
+ when node =~ /^\s*</
156
+ validator[schema, node]
157
+ when File.exists?(node)
158
+ validator[schema, File.open(node, 'r:UTF-8')]
159
+ else
160
+ glob = Dir.glob(node)
161
+
162
+ if glob.empty?
163
+ validator[schema, Kernel.open(node)]
164
+ else
165
+ glob.map { |n| validator[schema, File.open(n, 'r:UTF-8')] }.flatten(1)
166
+ end
167
+ end
168
+ else
169
+ raise ArgumentError, "failed to validate #{node.inspect}: not a CSL node"
170
+ end
171
+ end
172
+
173
+ # Whether or not the passed-in style (or list of styles) is valid.
174
+ #
175
+ # @see validate
176
+ #
177
+ # @param style [Style,String,IO,Array] the style (or a list of styles)
178
+ # to validate.
179
+ #
180
+ # @raise [ArgumentError] if the passed-in argument is not a Style or
181
+ # a valid style location.
182
+ # @raise [ValidationError] if the validation process fails
183
+ #
184
+ # @return [Boolean] whether or not the passed-in style (or styles)
185
+ # is valid.
186
+ def valid?(style)
187
+ validate(style).empty?
188
+ end
189
+
190
+ private
191
+
192
+ attr_reader :validators, :validator, :schema
105
193
  end
106
194
 
107
- end
108
-
195
+ end
109
196
  end
@@ -15,24 +15,23 @@ module CSL
15
15
  attr_accessor :default
16
16
 
17
17
  def parse(data)
18
- node = CSL.parse!(data)
18
+ node = CSL.parse!(data, self)
19
19
 
20
20
  raise ParseError, "root node is not a style: #{node.inspect}" unless
21
21
  node.is_a?(self)
22
22
 
23
23
  node
24
24
  end
25
-
26
25
  end
27
26
 
28
27
  attr_defaults :version => Schema.version, :xmlns => Schema.namespace
29
28
 
30
- attr_struct :xmlns, :version, :'style-class', :'default-locale',
29
+ attr_struct :xmlns, :version, :class, :'default-locale',
31
30
  :'initialize-with-hyphen', :'page-range-format',
32
31
  :'demote-non-dropping-particle', *Schema.attr(:name, :names)
33
32
 
34
- attr_children :'style-options', :info, :locale, :macro, :citation,
35
- :bibliography
33
+ attr_children :'style-options', :info, :locale, :macro,
34
+ :citation, :bibliography
36
35
 
37
36
  alias metadata info
38
37
  alias options style_options
@@ -46,8 +45,20 @@ module CSL
46
45
 
47
46
  yield self if block_given?
48
47
  end
48
+
49
+ def validate
50
+ Schema.validate self
51
+ end
52
+
53
+ def valid?
54
+ validate.empty?
55
+ end
49
56
 
57
+ private
50
58
 
59
+ def preamble
60
+ Schema.preamble.dup
61
+ end
51
62
  end
52
63
 
53
64
  end
@@ -0,0 +1,16 @@
1
+ module CSL
2
+ class Style
3
+
4
+ class Choose < Node
5
+
6
+ class Block < Node
7
+
8
+ def self.matches?(nodename)
9
+ nodename.to_s =~ /^if(-else)?|else$/
10
+ end
11
+
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -2,13 +2,13 @@ module CSL
2
2
  class Style
3
3
 
4
4
  class Date < Node
5
- attr_struct :name, :form, :'range-delimiter', :'date-parts',
6
- *Schema.attr(:affixes, :display, :font, :textcase)
5
+ attr_struct :name, :form, :'range-delimiter', :'date-parts', :variable,
6
+ :'text-case', *Schema.attr(:affixes, :display, :font, :delimiter)
7
7
  end
8
8
 
9
9
  class DatePart < Node
10
- attr_struct :name, :form, :'range-delimiter',
11
- *Schema.attr(:affixes, :textcase, :font, :periods)
10
+ attr_struct :name, :form, :'range-delimiter', :'text-case',
11
+ *Schema.attr(:affixes, :font, :periods)
12
12
  end
13
13
 
14
14
 
@@ -5,8 +5,8 @@ module CSL
5
5
 
6
6
  has_no_children
7
7
 
8
- attr_struct :variable, :form, :plural,
9
- *Schema.attr(:affixes, :font, :textcase, :periods)
8
+ attr_struct :variable, :form, :plural, :'text-case',
9
+ *Schema.attr(:affixes, :font, :periods)
10
10
 
11
11
  end
12
12
 
@@ -3,7 +3,7 @@ module CSL
3
3
 
4
4
  class Names < Node
5
5
 
6
- attr_struct :variable, *Schema.attr(:names)
6
+ attr_struct :variable, *Schema.attr(:names, :delimiter, :affixes, :display, :font)
7
7
 
8
8
  attr_children :name, :'et-al', :label, :substitute
9
9
 
@@ -30,7 +30,7 @@ module CSL
30
30
  def initialize(attributes = {})
31
31
  super(attributes)
32
32
  children[:'name-part'] = []
33
-
33
+
34
34
  yield self if block_given?
35
35
  end
36
36
 
@@ -38,7 +38,7 @@ module CSL
38
38
 
39
39
  class NamePart < Node
40
40
  has_no_children
41
- attr_struct :name, *Schema.attr(:textcase, :affixes, :font)
41
+ attr_struct :name, :'text-case', *Schema.attr(:affixes, :font)
42
42
  end
43
43
 
44
44
  class EtAl < Node
@@ -2,7 +2,8 @@ module CSL
2
2
  class Style
3
3
 
4
4
  class Number < Node
5
- attr_struct :form, *Schema.attr(:affixes, :display, :font, :textcase)
5
+ attr_struct :variable, :form, :'text-case',
6
+ *Schema.attr(:affixes, :display, :font)
6
7
 
7
8
 
8
9
  # @return [Boolean] whether or not the number's format is set to
@@ -2,8 +2,8 @@ module CSL
2
2
  class Style
3
3
 
4
4
  class Text < Node
5
- attr_struct :macro, :term, :form, :plural, :value,
6
- *Schema.attr(:affixes, :display, :font, :quotes, :periods, :textcase)
5
+ attr_struct :variable, :macro, :term, :form, :plural, :'text-case',
6
+ :value, *Schema.attr(:affixes, :display, :font, :quotes, :periods)
7
7
  end
8
8
 
9
9
  end
@@ -1,3 +1,3 @@
1
1
  module CSL
2
- VERSION = '1.0.0.pre1'.freeze
2
+ VERSION = '1.0.0.pre2'.freeze
3
3
  end
@@ -45,9 +45,28 @@ module CSL
45
45
  it 'prints the category if present' do
46
46
  Info.new { |i| i.category = 'author' }.to_xml.should == '<info><category>author</category></info>'
47
47
  end
48
-
49
48
  end
50
49
 
50
+ describe '#pretty_print' do
51
+ it 'returns an empty info element by default' do
52
+ subject.pretty_print.should == '<info/>'
53
+ end
54
+
55
+ it 'prints the id indented if present' do
56
+ Info.new { |i| i.set_child_id 'apa' }.pretty_print.should == "<info>\n <id>apa</id>\n</info>"
57
+ end
58
+ end
59
+
60
+ describe '#tags' do
61
+ it 'returns a list with an empty info element by default' do
62
+ subject.tags.should == ['<info/>']
63
+ end
64
+
65
+ it 'returns a nested list if id is present' do
66
+ Info.new { |i| i.set_child_id 'apa' }.tags.should == ['<info>', ['<id>apa</id>'], '</info>']
67
+ end
68
+
69
+ end
51
70
  end
52
71
 
53
72
  describe Info::Author do
@@ -76,6 +76,51 @@ module CSL
76
76
  f.matches?(:name => 'edition', :gender => 'feminine').should be_true
77
77
  end
78
78
  end
79
+
80
+ describe 'attributes#to_a' do
81
+ it 'returns an array of all attribute values of underlying struct' do
82
+ f.attributes.to_a.should == ['edition', nil, 'feminine', nil]
83
+ end
84
+ end
85
+ end
86
+
87
+ describe '#to_s' do
88
+ it 'returns an empty string by default' do
89
+ Locale::Term.new.to_s.should == ''
90
+ end
91
+
92
+ describe 'given a simple term' do
93
+ let(:node) { Locale::Term.new { |t| t.text = 'foo' } }
94
+
95
+ it "returns the term's text" do
96
+ node.to_s.should == node.text
97
+ end
98
+ end
99
+
100
+ describe 'given a compound term' do
101
+ let(:node) { Locale::Term.new { |t| t.single = 'shoe'; t.multiple = 'shoes' } }
102
+
103
+ it "returns the term's singular form by default" do
104
+ node.to_s.should == node.singularize
105
+ end
106
+
107
+ it "returns the term's plural form when passed :number => :plural" do
108
+ node.to_s(:number => :plural).should == node.pluralize
109
+ end
110
+
111
+ it "returns the term's plural form when passed :number => 2" do
112
+ node.to_s(:number => 2).should == node.pluralize
113
+ end
114
+
115
+ it "returns the term's singular form when passed :number => 1" do
116
+ node.to_s(:number => 1).should == node.singularize
117
+ end
118
+
119
+ it "returns the term's plural form when passed :plural => true" do
120
+ node.to_s(:plural => true).should == node.pluralize
121
+ end
122
+
123
+ end
79
124
  end
80
125
 
81
126
  describe '#to_xml' do
@@ -92,9 +92,11 @@ module CSL
92
92
  it 'accepts hash and yields itself to the optional block' do
93
93
  TextNode.new(:foo => 'bar') { |n| n.text = 'foo' }.to_xml.should == '<text-node foo="bar">foo</text-node>'
94
94
  end
95
-
96
95
  end
97
96
 
97
+ describe '#pretty_print' do
98
+ TextNode.new(:foo => 'bar') { |n| n.text = 'foo' }.pretty_print.should == '<text-node foo="bar">foo</text-node>'
99
+ end
98
100
  end
99
101
 
100
102
  end