csl 1.0.0.pre10 → 1.0.0.pre11
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/README.md +21 -8
- data/lib/csl/loader.rb +22 -15
- data/lib/csl/locale.rb +92 -36
- data/lib/csl/locale/date.rb +16 -16
- data/lib/csl/locale/ordinalize.rb +14 -7
- data/lib/csl/locale/style_options.rb +4 -3
- data/lib/csl/locale/term.rb +92 -22
- data/lib/csl/node.rb +96 -42
- data/lib/csl/parser.rb +21 -19
- data/lib/csl/style.rb +1 -10
- data/lib/csl/version.rb +1 -1
- data/spec/csl/locale/style_options_spec.rb +1 -1
- data/spec/csl/locale/term_spec.rb +46 -19
- data/spec/csl/locale_spec.rb +78 -40
- metadata +32 -13
- data/.gitmodules +0 -6
data/README.md
CHANGED
@@ -4,26 +4,39 @@ CSL-Ruby is a Ruby parser and library for the Citation Style Language (CSL),
|
|
4
4
|
an XML-based format to describe the formatting of citations, notes and
|
5
5
|
bibliographies.
|
6
6
|
|
7
|
-
[](http://travis-ci.org/inukshuk/csl-ruby)
|
8
|
+
|
9
|
+
Styles and Locales
|
10
|
+
------------------
|
11
|
+
You can load CSL styles and locales by passing a respective XML string, file
|
12
|
+
name, or URL. You can also load styles and locales by name if the
|
13
|
+
corresponding files are installed in your local styles and locale directories.
|
14
|
+
By default, CSL-Ruby looks for CSL styles and locale files in
|
15
|
+
|
16
|
+
/usr/local/share/csl/styles
|
17
|
+
/usr/local/share/csl/locales
|
18
|
+
|
19
|
+
You can change these locations by changing the value of `CSL::Style.root` and
|
20
|
+
`CSL::Locale.root` respectively.
|
21
|
+
|
22
|
+
Alternatively, you can `gem install csl-styles` to install all official CSL
|
23
|
+
styles and locales.
|
8
24
|
|
9
25
|
Development
|
10
26
|
-----------
|
11
|
-
The CSL-Ruby source code is [hosted on GitHub](https://github.com/
|
27
|
+
The CSL-Ruby source code is [hosted on GitHub](https://github.com/inukshuk/csl-ruby).
|
12
28
|
You can check out a copy of the latest code using Git:
|
13
29
|
|
14
|
-
$ git clone https://github.com/
|
30
|
+
$ git clone https://github.com/inukshuk/csl-ruby.git
|
15
31
|
|
16
|
-
To get started, install the development dependencies
|
17
|
-
styles and locales, and run all tests:
|
32
|
+
To get started, install the development dependencies and run all tests:
|
18
33
|
|
19
34
|
$ cd csl-ruby
|
20
|
-
$ git submodule init
|
21
|
-
$ git submodule update
|
22
35
|
$ bundle install
|
23
36
|
$ rake
|
24
37
|
|
25
38
|
If you've found a bug or have a question, please open an issue on the
|
26
|
-
[issue tracker](https://github.com/
|
39
|
+
[issue tracker](https://github.com/inukshuk/csl-ruby/issues).
|
27
40
|
Or, for extra credit, clone the CSL-Ruby repository, write a failing
|
28
41
|
example, fix the bug and submit a pull request.
|
29
42
|
|
data/lib/csl/loader.rb
CHANGED
@@ -5,11 +5,13 @@ module CSL
|
|
5
5
|
# appropriate root, prefix and extension values and a parse method that
|
6
6
|
# will be passed the contents of the asset data.
|
7
7
|
#
|
8
|
+
# @note
|
9
|
+
# Base classes are exepcted to define a #parse method.
|
8
10
|
module Loader
|
9
|
-
|
11
|
+
|
10
12
|
attr_accessor :root, :prefix, :extension
|
11
|
-
|
12
|
-
#
|
13
|
+
|
14
|
+
# @example
|
13
15
|
# Style.load(:apa) -> style
|
14
16
|
# Style.load('chicago-author.csl') -> style
|
15
17
|
# Locale.load('en') -> locale
|
@@ -18,6 +20,9 @@ module CSL
|
|
18
20
|
# Resolves the passed-in path/name or string and loads the asset data.
|
19
21
|
# The data will be passed on to the #parse method of the base class.
|
20
22
|
# Typically, this will return a new instance of the class.
|
23
|
+
#
|
24
|
+
# @note
|
25
|
+
# The base class is exepcted to define a #parse method.
|
21
26
|
def load(input)
|
22
27
|
case
|
23
28
|
when input.respond_to?(:read)
|
@@ -25,7 +30,7 @@ module CSL
|
|
25
30
|
when input.to_s =~ /^\s*</
|
26
31
|
data = input
|
27
32
|
else
|
28
|
-
|
33
|
+
|
29
34
|
case
|
30
35
|
when File.exists?(input.to_s)
|
31
36
|
location = input
|
@@ -43,16 +48,23 @@ module CSL
|
|
43
48
|
end
|
44
49
|
|
45
50
|
parse(data)
|
46
|
-
|
51
|
+
|
47
52
|
rescue => e
|
48
53
|
raise ParseError, "failed to load #{input.inspect}: #{e.message}"
|
49
54
|
end
|
50
|
-
|
55
|
+
|
56
|
+
def list
|
57
|
+
Dir["#{root}/#{prefix}*#{extension}"].map do |path|
|
58
|
+
File.basename(path, extension).sub(/^#{prefix}/, '')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
alias ls list
|
62
|
+
|
51
63
|
# Extends the passed-in string to a full path.
|
52
64
|
def extend_path(string)
|
53
65
|
File.join(root.to_s, extend_name(string))
|
54
66
|
end
|
55
|
-
|
67
|
+
|
56
68
|
# Extends the passed-in string to a style/locale name, by prefixing and
|
57
69
|
# appending the default name prefix and extension.
|
58
70
|
def extend_name(string)
|
@@ -61,18 +73,13 @@ module CSL
|
|
61
73
|
else
|
62
74
|
name = string.to_s.dup
|
63
75
|
end
|
64
|
-
|
76
|
+
|
65
77
|
unless name.start_with?(prefix.to_s)
|
66
78
|
name = [prefix, name].join
|
67
79
|
end
|
68
|
-
|
69
|
-
name
|
70
|
-
end
|
71
80
|
|
72
|
-
|
73
|
-
def parse(data)
|
74
|
-
raise 'Not Implemented'
|
81
|
+
name
|
75
82
|
end
|
76
83
|
end
|
77
|
-
|
84
|
+
|
78
85
|
end
|
data/lib/csl/locale.rb
CHANGED
@@ -7,14 +7,15 @@ module CSL
|
|
7
7
|
types << CSL::Info
|
8
8
|
|
9
9
|
include Comparable
|
10
|
-
|
10
|
+
|
11
11
|
@default = 'en-US'.freeze
|
12
12
|
|
13
|
-
@root =
|
13
|
+
@root = '/usr/local/share/csl/locales'.freeze
|
14
14
|
|
15
15
|
@extension = '.xml'.freeze
|
16
16
|
@prefix = 'locales-'.freeze
|
17
17
|
|
18
|
+
@tag_pattern = /^[a-z]{2}(-[A-Z]{2})?|-[A-Z]{2}$/
|
18
19
|
|
19
20
|
# Default languages/regions.
|
20
21
|
# Auto-detection is based on these lists.
|
@@ -22,11 +23,11 @@ module CSL
|
|
22
23
|
af ZA ar AR bg BG ca AD cs CZ da DK de DE el GR en US es ES et EE fa IR
|
23
24
|
fr FR he IL hu HU is IS it IT ja JP km KH ko KR mn MN nb NO nl NL nn NO
|
24
25
|
pl PL pt PT ro RO ru RU sk SK sl SI sr RS sv SE th TH tr TR uk UA vi VN
|
25
|
-
zh CN
|
26
|
+
zh CN
|
26
27
|
}.map(&:to_sym)].freeze
|
27
28
|
|
28
29
|
@languages = @regions.invert.merge(Hash[*%w{
|
29
|
-
AT de BR pt CA en CH de GB en
|
30
|
+
AT de BR pt CA en CH de GB en TW zh
|
30
31
|
}.map(&:to_sym)]).freeze
|
31
32
|
|
32
33
|
|
@@ -36,18 +37,39 @@ module CSL
|
|
36
37
|
attr_accessor :default
|
37
38
|
attr_reader :languages, :regions
|
38
39
|
|
39
|
-
def parse(data)
|
40
|
-
node = CSL.parse!(data, self)
|
41
|
-
|
42
|
-
raise ParseError, "root node is not a locale: #{node.inspect}" unless
|
43
|
-
node.is_a?(self)
|
44
|
-
|
45
|
-
node
|
46
|
-
end
|
47
|
-
|
48
40
|
def load(input = Locale.default)
|
41
|
+
input = normalize input if input.to_s =~ tag_pattern
|
49
42
|
super
|
50
43
|
end
|
44
|
+
|
45
|
+
# Normalizes an IETF tag; adds a language's default region or a
|
46
|
+
# region's default language.
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# Locale.normalize("en") #-> "en-US"
|
50
|
+
# Locale.normalize("-BR") #-> "pt-BR"
|
51
|
+
#
|
52
|
+
# @raise [ArgumentError] if the passed-in string is no IETF tag
|
53
|
+
#
|
54
|
+
# @param tag [String] an IETF tag to be normalized
|
55
|
+
# @return [String] the normalized IETF tag
|
56
|
+
def normalize(tag)
|
57
|
+
tag = tag.to_s.strip
|
58
|
+
|
59
|
+
raise ArgumentError, "not a valid IETF tag: #{tag.inspect}" unless
|
60
|
+
tag =~ tag_pattern
|
61
|
+
|
62
|
+
language, region = tag.split(/-/)
|
63
|
+
|
64
|
+
return [language, regions[language.to_sym]].compact.join('-') if region.nil?
|
65
|
+
return [languages[region.to_sym], region].join('-') if language.empty?
|
66
|
+
|
67
|
+
tag
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
attr_reader :tag_pattern
|
51
73
|
end
|
52
74
|
|
53
75
|
attr_defaults :version => Schema.version, :xmlns => Schema.namespace
|
@@ -56,20 +78,20 @@ module CSL
|
|
56
78
|
attr_children :'style-options', :info, :date, :terms
|
57
79
|
|
58
80
|
has_language
|
59
|
-
|
81
|
+
|
60
82
|
attr_accessor :region
|
61
83
|
|
62
84
|
alias_child :metadata, :info
|
63
85
|
alias_child :dates, :date
|
64
86
|
alias_child :options, :style_options
|
65
87
|
|
66
|
-
|
88
|
+
protected :attributes
|
67
89
|
undef_method :[]=
|
68
90
|
|
69
91
|
# @example
|
70
92
|
# Locale.new #-> default
|
71
93
|
# Locale.new('en') #-> American English
|
72
|
-
# Locale.new('en', :'punctuation-in-quote' =>
|
94
|
+
# Locale.new('en', :'punctuation-in-quote' => false) #-> with style-options
|
73
95
|
# Locale.new(:lang => 'en-GB', :version => '1.0') #-> British English
|
74
96
|
#
|
75
97
|
# Returns a new locale. In the first form, the language/regions is set
|
@@ -90,7 +112,7 @@ module CSL
|
|
90
112
|
|
91
113
|
attributes, options = arguments
|
92
114
|
else
|
93
|
-
attributes, locale, options = {}, arguments
|
115
|
+
attributes, locale, options = {}, *arguments
|
94
116
|
end
|
95
117
|
when 2
|
96
118
|
attributes, locale, options = {}, *arguments
|
@@ -109,10 +131,10 @@ module CSL
|
|
109
131
|
yield self if block_given?
|
110
132
|
end
|
111
133
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
134
|
+
def initialize_copy(other)
|
135
|
+
super
|
136
|
+
@language, @region = other.language, other.region
|
137
|
+
end
|
116
138
|
|
117
139
|
|
118
140
|
def added_to(node)
|
@@ -162,21 +184,7 @@ module CSL
|
|
162
184
|
#
|
163
185
|
# @return [self]
|
164
186
|
def set(locale)
|
165
|
-
language, region =
|
166
|
-
tag.respond_to?(:to_sym) ? tag.to_sym : nil
|
167
|
-
end
|
168
|
-
|
169
|
-
case
|
170
|
-
when language && region
|
171
|
-
@language, @region = language, region
|
172
|
-
when language
|
173
|
-
@language, @region = language, Locale.regions[language]
|
174
|
-
when region
|
175
|
-
@language, @region = Locale.languages[region], region
|
176
|
-
else
|
177
|
-
raise ArgumentError, "not a valid locale string: #{locale.inspect}"
|
178
|
-
end
|
179
|
-
|
187
|
+
@language, @region = Locale.normalize(locale).split(/-/).map(&:to_sym)
|
180
188
|
self
|
181
189
|
end
|
182
190
|
|
@@ -246,6 +254,22 @@ module CSL
|
|
246
254
|
validate.empty?
|
247
255
|
end
|
248
256
|
|
257
|
+
# @return [Locale]
|
258
|
+
def merge(*others)
|
259
|
+
deep_copy.merge!(*others)
|
260
|
+
end
|
261
|
+
|
262
|
+
# @return [self]
|
263
|
+
def merge!(*others)
|
264
|
+
others.each do |other|
|
265
|
+
merge_options other
|
266
|
+
merge_dates other
|
267
|
+
end
|
268
|
+
|
269
|
+
self
|
270
|
+
end
|
271
|
+
|
272
|
+
|
249
273
|
# Locales are sorted first by language, then by region; sort order is
|
250
274
|
# alphabetical with the following exceptions: the default locale is
|
251
275
|
# prioritised; in case of a language match the default region of that
|
@@ -302,6 +326,38 @@ module CSL
|
|
302
326
|
Schema.preamble.dup
|
303
327
|
end
|
304
328
|
|
329
|
+
# @param other [Locale] an other locale whose options should be merged
|
330
|
+
# @return [self]
|
331
|
+
def merge_options(other)
|
332
|
+
return self unless other.has_options?
|
333
|
+
|
334
|
+
if has_options?
|
335
|
+
options.attributes.merge! other.options.attributes
|
336
|
+
else
|
337
|
+
add_child other.options.dup
|
338
|
+
end
|
339
|
+
|
340
|
+
self
|
341
|
+
end
|
342
|
+
|
343
|
+
# @param other [Locale] an other locale whose date nodes should be merged
|
344
|
+
# @return [self]
|
345
|
+
def merge_dates(other)
|
346
|
+
return self unless other.has_dates?
|
347
|
+
|
348
|
+
if has_dates?
|
349
|
+
other.each_date do |date|
|
350
|
+
delete_children each_date.select { |d| d[:form] == date[:form] }
|
351
|
+
add_child date.deep_copy
|
352
|
+
end
|
353
|
+
else
|
354
|
+
other.each_date do |date|
|
355
|
+
add_child date.deep_copy
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
self
|
360
|
+
end
|
305
361
|
end
|
306
362
|
|
307
363
|
end
|
data/lib/csl/locale/date.rb
CHANGED
@@ -1,39 +1,39 @@
|
|
1
|
-
module CSL
|
1
|
+
module CSL
|
2
2
|
class Locale
|
3
|
-
|
3
|
+
|
4
4
|
# A localized Date comprises a set of formatting rules for dates.
|
5
5
|
class Date < Node
|
6
|
-
|
7
|
-
attr_struct :form,
|
6
|
+
|
7
|
+
attr_struct :form, *Schema.attr(:formatting, :delimiter)
|
8
8
|
attr_children :'date-part'
|
9
|
-
|
9
|
+
|
10
10
|
alias parts date_part
|
11
11
|
alias locale parent
|
12
|
-
|
12
|
+
|
13
13
|
def initialize(attributes = {})
|
14
14
|
super(attributes)
|
15
15
|
children[:'date-part'] = []
|
16
|
-
|
16
|
+
|
17
17
|
yield self if block_given?
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def added_to(node)
|
21
21
|
raise ValidationError, "parent must be locale node: was #{node.inspect}" unless node.is_a?(Locale)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
%w{ text numeric }.each do |type|
|
25
25
|
define_method("#{type}?") { attributes.form == type }
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
end
|
29
29
|
|
30
30
|
# DatePart represent the localized formatting options for an individual
|
31
31
|
# date part (day, month, or year).
|
32
32
|
class DatePart < Node
|
33
|
-
has_no_children
|
34
|
-
|
35
|
-
attr_struct :name, :form, :'range-delimiter',
|
36
|
-
*Schema.attr(:
|
33
|
+
has_no_children
|
34
|
+
|
35
|
+
attr_struct :name, :form, :'range-delimiter',
|
36
|
+
*Schema.attr(:formatting, :periods)
|
37
37
|
|
38
38
|
%w{ day month year }.each do |part|
|
39
39
|
define_method("#{part}?") do
|
@@ -42,7 +42,7 @@ module CSL
|
|
42
42
|
end
|
43
43
|
|
44
44
|
end
|
45
|
-
|
46
|
-
|
45
|
+
|
46
|
+
|
47
47
|
end
|
48
48
|
end
|
@@ -37,7 +37,9 @@ module CSL
|
|
37
37
|
raise ArgumentError, "unable to ordinalize #{number}; integer expected" unless
|
38
38
|
number.respond_to?(:to_i)
|
39
39
|
|
40
|
-
number
|
40
|
+
number = number.to_i
|
41
|
+
|
42
|
+
|
41
43
|
|
42
44
|
key = query[:name]
|
43
45
|
|
@@ -97,8 +99,17 @@ module CSL
|
|
97
99
|
|
98
100
|
private
|
99
101
|
|
100
|
-
|
101
|
-
|
102
|
+
def normalize_gender_options(options)
|
103
|
+
gender = (options[:'gender-form'] || options[:gender]).to_s
|
104
|
+
unless gender.empty? || gender =~ /^n/i
|
105
|
+
q[:'gender-form'] = (gender =~ /^m/i) ? 'masculine' : 'feminine'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def normalize_plural_options(options)
|
110
|
+
end
|
111
|
+
|
112
|
+
def ordinalize_options_for(options)
|
102
113
|
q = { :name => 'ordinal-%02d' }
|
103
114
|
|
104
115
|
unless options.nil?
|
@@ -106,10 +117,6 @@ module CSL
|
|
106
117
|
q[:name] = 'long-ordinal-%02d'
|
107
118
|
end
|
108
119
|
|
109
|
-
gender = (options[:'gender-form'] || options[:gender]).to_s
|
110
|
-
unless gender.empty? || gender =~ /^n/i
|
111
|
-
q[:'gender-form'] = (gender =~ /^m/i) ? 'masculine' : 'feminine'
|
112
|
-
end
|
113
120
|
end
|
114
121
|
|
115
122
|
q
|