citeproc 0.0.2 → 0.0.3
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/.rspec +1 -0
- data/README.md +2 -0
- data/auto.watchr +2 -0
- data/lib/citeproc.rb +16 -0
- data/lib/citeproc/assets.rb +66 -0
- data/lib/citeproc/attributes.rb +92 -16
- data/lib/citeproc/bibliography.rb +180 -0
- data/lib/citeproc/citation_data.rb +208 -0
- data/lib/citeproc/compatibility.rb +5 -1
- data/lib/citeproc/date.rb +225 -162
- data/lib/citeproc/engine.rb +6 -11
- data/lib/citeproc/errors.rb +5 -4
- data/lib/citeproc/extensions.rb +71 -3
- data/lib/citeproc/item.rb +113 -0
- data/lib/citeproc/names.rb +556 -0
- data/lib/citeproc/processor.rb +30 -4
- data/lib/citeproc/selector.rb +128 -0
- data/lib/citeproc/variable.rb +85 -45
- data/lib/citeproc/version.rb +1 -1
- data/spec/citeproc/assets_spec.rb +60 -0
- data/spec/citeproc/attributes_spec.rb +23 -10
- data/spec/citeproc/bibliography_spec.rb +72 -0
- data/spec/citeproc/citation_data_spec.rb +94 -0
- data/spec/citeproc/date_spec.rb +65 -9
- data/spec/citeproc/extensions_spec.rb +33 -0
- data/spec/citeproc/item_spec.rb +52 -0
- data/spec/citeproc/names_spec.rb +492 -0
- data/spec/citeproc/processor_spec.rb +39 -0
- data/spec/citeproc/selector_spec.rb +81 -0
- data/spec/citeproc/variable_spec.rb +9 -3
- metadata +133 -121
data/.rspec
CHANGED
data/README.md
CHANGED
@@ -1,2 +1,4 @@
|
|
1
1
|
CiteProc
|
2
2
|
========
|
3
|
+
|
4
|
+
The CiteProc gem is a Ruby abstraction layer for the JSON API used by citeproc-js. To process citations you also require a dedicate processor engine available as a separate gem: these will include citeproc-js, citeproc-ruby and eventually citeproc-hs. As work on these packages is still in progress, please be aware of the following caveats: the 0.0.2 release of citeproc-ruby is still a standalone package and will conflict with this release of citeproc; support will be added in future versions. The citeproc-js gem currently works only in JRuby (using an embedded version of the Rhino JavaScript interpreter) but is going to support other versions of Ruby using different JavaScript runtimes.
|
data/auto.watchr
CHANGED
data/lib/citeproc.rb
CHANGED
@@ -12,8 +12,24 @@ require 'citeproc/errors'
|
|
12
12
|
|
13
13
|
require 'citeproc/abbreviate'
|
14
14
|
require 'citeproc/attributes'
|
15
|
+
|
15
16
|
require 'citeproc/variable'
|
16
17
|
require 'citeproc/date'
|
18
|
+
require 'citeproc/names'
|
19
|
+
|
20
|
+
CiteProc::Variable.class_eval do
|
21
|
+
@factories = Hash.new { |h,k| h.fetch(k.to_sym, CiteProc::Variable) }.merge(
|
22
|
+
Hash[*@types.map { |n,k|
|
23
|
+
[n, CiteProc.const_get(k.to_s.capitalize)]
|
24
|
+
}.flatten]
|
25
|
+
).freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'citeproc/item'
|
29
|
+
require 'citeproc/citation_data'
|
30
|
+
require 'citeproc/selector'
|
31
|
+
require 'citeproc/bibliography'
|
32
|
+
require 'citeproc/assets'
|
17
33
|
|
18
34
|
require 'citeproc/engine'
|
19
35
|
require 'citeproc/processor'
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module CiteProc
|
4
|
+
module Asset
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :asset
|
11
|
+
|
12
|
+
alias to_s asset
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
to_s.inspect
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
attr_accessor :root, :extension, :prefix
|
21
|
+
|
22
|
+
def load(asset)
|
23
|
+
instance = new
|
24
|
+
case
|
25
|
+
when File.exists?(asset)
|
26
|
+
instance.asset = read(asset)
|
27
|
+
when File.exists?(File.join(root.to_s, extend_name(asset)))
|
28
|
+
instance.asset = read(File.join(root.to_s, extend_name(asset)))
|
29
|
+
else
|
30
|
+
instance.asset = asset
|
31
|
+
end
|
32
|
+
instance
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def read(name)
|
38
|
+
io = open(name, 'r:UTF-8')
|
39
|
+
io.read
|
40
|
+
ensure
|
41
|
+
io.close
|
42
|
+
end
|
43
|
+
|
44
|
+
def extend_name(file)
|
45
|
+
file = File.extname(file).empty? ? [file, extension].compact.join : file
|
46
|
+
file = file.start_with?(prefix.to_s) ? file : [prefix,file].join
|
47
|
+
file
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
class Style
|
54
|
+
include Asset
|
55
|
+
@root = '/usr/local/share/citation-style-language/styles'.freeze
|
56
|
+
@extension = '.csl'.freeze
|
57
|
+
end
|
58
|
+
|
59
|
+
class Locale
|
60
|
+
include Asset
|
61
|
+
@root = '/usr/local/share/citation-style-language/locales'.freeze
|
62
|
+
@extension = '.xml'.freeze
|
63
|
+
@prefix = 'locales-'
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/citeproc/attributes.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
|
2
2
|
module CiteProc
|
3
3
|
|
4
|
+
# TODO refactor using a Struct instead of a hash. This will have to convert
|
5
|
+
# the CiteProc/CSL names which are no proper method names.
|
6
|
+
|
7
|
+
|
4
8
|
module Attributes
|
5
9
|
extend Forwardable
|
6
10
|
|
11
|
+
|
12
|
+
FALSE_PATTERN = (/^(false|no|never)$/i).freeze
|
13
|
+
|
7
14
|
def self.included(base)
|
8
15
|
base.extend(ClassMethods)
|
9
16
|
end
|
@@ -12,37 +19,105 @@ module CiteProc
|
|
12
19
|
@attributes ||= {}
|
13
20
|
end
|
14
21
|
|
22
|
+
def_delegators :attributes, :length, :empty?
|
23
|
+
|
24
|
+
def [](key)
|
25
|
+
attributes[filter_key(key)]
|
26
|
+
end
|
27
|
+
|
28
|
+
def []=(key, value)
|
29
|
+
attributes[filter_key(key)] = filter_value(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def filter_key(key)
|
33
|
+
key.to_sym
|
34
|
+
end
|
35
|
+
|
36
|
+
def filter_value(value, key = nil)
|
37
|
+
value.respond_to?(:deep_copy) ? value.deep_copy : value.dup
|
38
|
+
rescue
|
39
|
+
value
|
40
|
+
end
|
41
|
+
|
15
42
|
def merge(other)
|
16
43
|
return self if other.nil?
|
17
44
|
|
18
45
|
case other
|
19
46
|
when String, /^\s*\{/
|
20
|
-
other = MulitJson.
|
47
|
+
other = MulitJson.decode(other, :symbolize_keys => true)
|
21
48
|
when Hash
|
22
|
-
|
23
|
-
|
49
|
+
# do nothing
|
50
|
+
when Attributes
|
24
51
|
other = other.to_hash
|
52
|
+
else
|
53
|
+
raise ParseError, "failed to merge attributes and #{other.inspect}"
|
25
54
|
end
|
26
55
|
|
27
|
-
other.each_pair
|
56
|
+
other.each_pair do |key, value|
|
57
|
+
attributes[filter_key(key)] = filter_value(value, key)
|
58
|
+
end
|
28
59
|
|
29
60
|
self
|
30
61
|
end
|
31
62
|
|
32
63
|
alias update merge
|
33
64
|
|
34
|
-
|
35
|
-
|
36
|
-
|
65
|
+
def reverse_merge(other)
|
66
|
+
fail "not implemented yet"
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_hash
|
70
|
+
attributes.deep_copy
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_citeproc
|
74
|
+
Hash[*attributes.map { |k,v|
|
75
|
+
[k.to_s, v.respond_to?(:to_citeproc) ? v.to_citeproc : v.to_s]
|
76
|
+
}.flatten(1)]
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_json
|
80
|
+
MultiJson.encode(to_citeproc)
|
81
|
+
end
|
37
82
|
|
38
|
-
|
83
|
+
# Don't expose internals to public API
|
84
|
+
private :filter_key, :filter_value
|
85
|
+
|
86
|
+
# initialize_copy should be able to access attributes
|
87
|
+
protected :attributes
|
88
|
+
|
39
89
|
|
90
|
+
|
91
|
+
# def eql?(other)
|
92
|
+
# case
|
93
|
+
# when equal?(other)
|
94
|
+
# true
|
95
|
+
# when self.class != other.class, length != other.length
|
96
|
+
# false
|
97
|
+
# else
|
98
|
+
# other.attributes.each_pair do |key, value|
|
99
|
+
# return false unless attributes[key].eql?(value)
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# true
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# def hash
|
107
|
+
# end
|
108
|
+
|
40
109
|
module ClassMethods
|
41
110
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
111
|
+
def create(parameters)
|
112
|
+
create!(parameters)
|
113
|
+
rescue
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def create!(parameters)
|
118
|
+
new.merge(parameters)
|
119
|
+
end
|
120
|
+
|
46
121
|
def attr_predicates(*arguments)
|
47
122
|
arguments.flatten.each do |field|
|
48
123
|
field, default = *(field.is_a?(Hash) ? field.to_a.flatten : [field]).map(&:to_s)
|
@@ -50,14 +125,14 @@ module CiteProc
|
|
50
125
|
end
|
51
126
|
end
|
52
127
|
|
53
|
-
def attr_fields
|
128
|
+
def attr_fields(*arguments)
|
54
129
|
arguments.flatten.each do |field|
|
55
130
|
attr_field(*(field.is_a?(Hash) ? field.to_a.flatten : [field]).map(&:to_s))
|
56
131
|
end
|
57
132
|
end
|
58
133
|
|
59
134
|
def attr_field(field, default = nil, predicate = false)
|
60
|
-
method_id = field.downcase.gsub(/[-\s]+/, '_')
|
135
|
+
method_id = field.to_s.downcase.gsub(/[-\s]+/, '_')
|
61
136
|
|
62
137
|
unless instance_methods.include?(method_id)
|
63
138
|
if default
|
@@ -77,11 +152,12 @@ module CiteProc
|
|
77
152
|
attributes[field.to_sym] = value
|
78
153
|
end
|
79
154
|
end
|
80
|
-
|
155
|
+
|
81
156
|
predicate_id = [method_id, '?'].join
|
82
157
|
if predicate && !instance_methods.include?(predicate_id)
|
83
158
|
define_method(predicate_id) do
|
84
|
-
|
159
|
+
v = attributes[field.to_sym]
|
160
|
+
!(v.nil? || (v.respond_to?(:empty?) && v.empty?) || v =~ FALSE_PATTERN)
|
85
161
|
end
|
86
162
|
|
87
163
|
has_predicate = ['has_', predicate_id].join
|
@@ -0,0 +1,180 @@
|
|
1
|
+
|
2
|
+
module CiteProc
|
3
|
+
|
4
|
+
# = Bibliography
|
5
|
+
#
|
6
|
+
# Typically a Bibliography is returned by the Processor. It contains a list
|
7
|
+
# of references and formatting options.
|
8
|
+
#
|
9
|
+
# A Bibliography can be created from (and exported to) CiteProc JSON
|
10
|
+
# bibliographies; these consist of a two-element list, composed of a
|
11
|
+
# JavaScript array containing certain formatting parameters, and a list of
|
12
|
+
# strings representing bibliography entries.
|
13
|
+
#
|
14
|
+
# == Bibliography Formatting Options
|
15
|
+
#
|
16
|
+
# :offset => 0
|
17
|
+
# Some citation styles apply a label (either a number or an alphanumeric
|
18
|
+
# code) to each bibliography entry, and use this label to cite
|
19
|
+
# bibliography items in the main text. In the bibliography, the labels
|
20
|
+
# may either be hung in the margin, or they may be set flush to the
|
21
|
+
# margin, with the citations indented by a uniform amount to the right.
|
22
|
+
# In the latter case, the amount of indentation needed depends on the
|
23
|
+
# maximum width of any label. The :offset option gives the maximum
|
24
|
+
# number of characters that appear in any label used in the
|
25
|
+
# bibliography. The client that controls the final rendering of the
|
26
|
+
# bibliography string should use this value to calculate and apply a
|
27
|
+
# suitable indentation length.
|
28
|
+
#
|
29
|
+
# :entry_spacing => 0
|
30
|
+
# An integer representing the spacing between entries in the
|
31
|
+
# bibliography.
|
32
|
+
#
|
33
|
+
# :line_spacing => 0
|
34
|
+
# An integer representing the spacing between the lines within each
|
35
|
+
# bibliography entry.
|
36
|
+
#
|
37
|
+
# :indent => 0
|
38
|
+
# The number of em-spaces to apply in hanging indents within the
|
39
|
+
# bibliography.
|
40
|
+
#
|
41
|
+
# :align => false
|
42
|
+
# When the second-field-align CSL option is set, this returns either
|
43
|
+
# "flush" or "margin". The calling application should align text in
|
44
|
+
# bibliography output as described in the CSL specification. Where
|
45
|
+
# second-field-align is not set, this return value is set to false.
|
46
|
+
#
|
47
|
+
class Bibliography
|
48
|
+
|
49
|
+
@defaults = {
|
50
|
+
:offset => 0,
|
51
|
+
:entry_spacing => 0,
|
52
|
+
:line_spacing => 0,
|
53
|
+
:indent => 0,
|
54
|
+
:align => false
|
55
|
+
}.freeze
|
56
|
+
|
57
|
+
|
58
|
+
# citeproc-js and csl attributes are often inconsistent or difficult
|
59
|
+
# to use as symbols/method names, so we're using different names at the
|
60
|
+
# cost of making conversion to and from the json format more difficult
|
61
|
+
|
62
|
+
@cp2rb = {
|
63
|
+
'maxoffset' => :offset,
|
64
|
+
'entryspacing' => :entry_spacing,
|
65
|
+
'linespacing' => :line_spacing,
|
66
|
+
'hangingindent' => :indent,
|
67
|
+
'second-field-align' => :align
|
68
|
+
}
|
69
|
+
|
70
|
+
@rb2cp = @cp2rb.invert.freeze
|
71
|
+
@cp2rb.freeze
|
72
|
+
|
73
|
+
class << self
|
74
|
+
|
75
|
+
attr_reader :defaults, :cp2rb, :rb2cp
|
76
|
+
|
77
|
+
# Create a new Bibliography from the passed-in string or array.
|
78
|
+
def parse(input)
|
79
|
+
case
|
80
|
+
when input.is_a?(String)
|
81
|
+
parse(MultiJson.decode(input))
|
82
|
+
|
83
|
+
when input.is_a?(Array) && input.length == 2
|
84
|
+
options, references = input
|
85
|
+
|
86
|
+
new do |b|
|
87
|
+
b.concat(references)
|
88
|
+
b.errors.concat(options.fetch('bibliography_errors', []))
|
89
|
+
|
90
|
+
b.preamble, b.postamble = options['bibstart'], options['bibend']
|
91
|
+
|
92
|
+
(options.keys & cp2rb.keys).each do |k|
|
93
|
+
b.options[cp2rb[k]] = options[k]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
else
|
98
|
+
raise ParseError, "failed to create Bibliography from #{input.inspect}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
include Comparable
|
105
|
+
include Enumerable
|
106
|
+
|
107
|
+
extend Forwardable
|
108
|
+
|
109
|
+
attr_reader :options, :errors, :references
|
110
|
+
|
111
|
+
attr_accessor :preamble, :postamble
|
112
|
+
|
113
|
+
alias before preamble
|
114
|
+
alias after postamble
|
115
|
+
|
116
|
+
def_delegators :@references, :[], :[]=, :<<, :push, :unshift, :pop,
|
117
|
+
:concat, :include?, :index, :length, :empty?
|
118
|
+
|
119
|
+
def initialize(options = {})
|
120
|
+
@options = Bibliography.defaults.merge(options)
|
121
|
+
@errors, @references = [], []
|
122
|
+
|
123
|
+
yield self if block_given?
|
124
|
+
end
|
125
|
+
|
126
|
+
def initialize_copy(other)
|
127
|
+
@options = other.options.dup
|
128
|
+
@errors, @references = other.errors.dup, other.references.dup
|
129
|
+
end
|
130
|
+
|
131
|
+
def has_errors?
|
132
|
+
!errors.empty?
|
133
|
+
end
|
134
|
+
|
135
|
+
alias errors? has_errors?
|
136
|
+
|
137
|
+
def each
|
138
|
+
if block_given?
|
139
|
+
references.each(&Proc.new)
|
140
|
+
self
|
141
|
+
else
|
142
|
+
to_enum
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def <=>(other)
|
147
|
+
return nil unless other.respond_to?(:references)
|
148
|
+
references <=> other.references
|
149
|
+
end
|
150
|
+
|
151
|
+
def citeproc_options
|
152
|
+
Hash[*options.map { |k,v|
|
153
|
+
[Bibliography.rb2cp[k] || k.to_s, v]
|
154
|
+
}.flatten]
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_citeproc
|
158
|
+
[
|
159
|
+
citeproc_options.merge({
|
160
|
+
'bibstart' => preamble,
|
161
|
+
'bibend' => postamble,
|
162
|
+
'bibliography_errors' => errors
|
163
|
+
}),
|
164
|
+
|
165
|
+
references
|
166
|
+
|
167
|
+
]
|
168
|
+
end
|
169
|
+
|
170
|
+
def to_json
|
171
|
+
MultiJson.encode(to_citeproc)
|
172
|
+
end
|
173
|
+
|
174
|
+
def inspect
|
175
|
+
"#<CiteProc::Bibliography @references=[#{references.length}], @errors=[#{errors.length}]>"
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|