csl 1.0.0.pre1 → 1.0.0.pre2

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.
@@ -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