citeproc-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +78 -0
- data/lib/citeproc.rb +100 -0
- data/lib/citeproc/bibliography.rb +57 -0
- data/lib/citeproc/data.rb +149 -0
- data/lib/citeproc/date.rb +133 -0
- data/lib/citeproc/formatter.rb +38 -0
- data/lib/citeproc/item.rb +53 -0
- data/lib/citeproc/name.rb +284 -0
- data/lib/citeproc/processor.rb +166 -0
- data/lib/citeproc/selector.rb +61 -0
- data/lib/citeproc/variable.rb +82 -0
- data/lib/citeproc/version.rb +3 -0
- data/lib/csl/locale.rb +223 -0
- data/lib/csl/node.rb +72 -0
- data/lib/csl/nodes.rb +1364 -0
- data/lib/csl/renderer.rb +88 -0
- data/lib/csl/sort.rb +53 -0
- data/lib/csl/style.rb +110 -0
- data/lib/csl/term.rb +124 -0
- data/lib/extensions/core.rb +43 -0
- data/lib/plugins/filters/bibtex.rb +12 -0
- data/lib/plugins/formats/default.rb +134 -0
- data/lib/plugins/formats/html.rb +67 -0
- data/lib/support/attributes.rb +99 -0
- data/lib/support/tree.rb +80 -0
- data/resource/locale/locales-af-ZA.xml +304 -0
- data/resource/locale/locales-ar-AR.xml +304 -0
- data/resource/locale/locales-bg-BG.xml +304 -0
- data/resource/locale/locales-ca-AD.xml +304 -0
- data/resource/locale/locales-cs-CZ.xml +304 -0
- data/resource/locale/locales-da-DK.xml +304 -0
- data/resource/locale/locales-de-AT.xml +304 -0
- data/resource/locale/locales-de-CH.xml +304 -0
- data/resource/locale/locales-de-DE.xml +332 -0
- data/resource/locale/locales-el-GR.xml +303 -0
- data/resource/locale/locales-en-US.xml +313 -0
- data/resource/locale/locales-es-ES.xml +304 -0
- data/resource/locale/locales-et-EE.xml +304 -0
- data/resource/locale/locales-fr-FR.xml +304 -0
- data/resource/locale/locales-he-IL.xml +304 -0
- data/resource/locale/locales-hu-HU.xml +304 -0
- data/resource/locale/locales-is-IS.xml +304 -0
- data/resource/locale/locales-it-IT.xml +304 -0
- data/resource/locale/locales-ja-JP.xml +304 -0
- data/resource/locale/locales-kh-KH.xml +303 -0
- data/resource/locale/locales-ko-KR.xml +304 -0
- data/resource/locale/locales-mn-MN.xml +304 -0
- data/resource/locale/locales-nb-NO.xml +304 -0
- data/resource/locale/locales-nl-NL.xml +304 -0
- data/resource/locale/locales-nn-NO.xml +304 -0
- data/resource/locale/locales-pl-PL.xml +304 -0
- data/resource/locale/locales-pt-BR.xml +304 -0
- data/resource/locale/locales-pt-PT.xml +304 -0
- data/resource/locale/locales-ro-RO.xml +304 -0
- data/resource/locale/locales-ru-RU.xml +304 -0
- data/resource/locale/locales-sk-SK.xml +304 -0
- data/resource/locale/locales-sl-SI.xml +304 -0
- data/resource/locale/locales-sr-RS.xml +304 -0
- data/resource/locale/locales-sv-SE.xml +304 -0
- data/resource/locale/locales-th-TH.xml +304 -0
- data/resource/locale/locales-tr-TR.xml +304 -0
- data/resource/locale/locales-uk-UA.xml +304 -0
- data/resource/locale/locales-vi-VN.xml +304 -0
- data/resource/locale/locales-zh-CN.xml +304 -0
- data/resource/locale/locales-zh-TW.xml +304 -0
- data/resource/schema/csl-categories.rnc +39 -0
- data/resource/schema/csl-data.rnc +98 -0
- data/resource/schema/csl-terms.rnc +106 -0
- data/resource/schema/csl-types.rnc +39 -0
- data/resource/schema/csl-variables.rnc +182 -0
- data/resource/schema/csl.rnc +941 -0
- data/resource/style/acta-materialia-x.csl +128 -0
- data/resource/style/advanced-engineering-materials-x.csl +121 -0
- data/resource/style/ama.csl +185 -0
- data/resource/style/ama2-x.csl +179 -0
- data/resource/style/apa-x.csl +324 -0
- data/resource/style/apa.csl +254 -0
- data/resource/style/apsa-x.csl +163 -0
- data/resource/style/apsa.csl +176 -0
- data/resource/style/asa-x.csl +203 -0
- data/resource/style/asa.csl +216 -0
- data/resource/style/asm-journals-x.csl +131 -0
- data/resource/style/bibtex-x2.csl +175 -0
- data/resource/style/bluebook-demo-x.csl +392 -0
- data/resource/style/bluebook-demo.csl +942 -0
- data/resource/style/chicago-author-date-listing.csl +434 -0
- data/resource/style/chicago-author-date.csl +369 -0
- data/resource/style/chicago-fullnote-bibliography-bb.csl +928 -0
- data/resource/style/chicago-fullnote-bibliography.csl +695 -0
- data/resource/style/chicago-note-bibliography.csl +446 -0
- data/resource/style/chicago-note.csl +388 -0
- data/resource/style/greek-chicago-x.csl +1182 -0
- data/resource/style/harvard1-institution-italic.csl +190 -0
- data/resource/style/harvard1.csl +181 -0
- data/resource/style/ieee.csl +129 -0
- data/resource/style/mhra-x.csl +312 -0
- data/resource/style/mhra.csl +390 -0
- data/resource/style/mhra_note_without_bibliography-x.csl +330 -0
- data/resource/style/mhra_note_without_bibliography.csl +338 -0
- data/resource/style/mla-x.csl +178 -0
- data/resource/style/mla.csl +189 -0
- data/resource/style/nature-x.csl +81 -0
- data/resource/style/nature.csl +88 -0
- data/resource/style/nlm.csl +117 -0
- data/spec/citeproc/bibliography_spec.rb +45 -0
- data/spec/citeproc/citeproc_spec.rb +76 -0
- data/spec/citeproc/date_spec.rb +85 -0
- data/spec/citeproc/formatter_spec.rb +101 -0
- data/spec/citeproc/item_spec.rb +71 -0
- data/spec/citeproc/name_spec.rb +30 -0
- data/spec/citeproc/processor_spec.rb +61 -0
- data/spec/citeproc/selector_spec.rb +82 -0
- data/spec/citeproc/variable_spec.rb +69 -0
- data/spec/csl/locale_spec.rb +208 -0
- data/spec/csl/node_spec.rb +25 -0
- data/spec/csl/nodes_spec.rb +140 -0
- data/spec/csl/style_spec.rb +62 -0
- data/spec/csl/term_spec.rb +56 -0
- data/spec/fixtures/dates.yaml +80 -0
- data/spec/fixtures/names.yaml +115 -0
- data/spec/fixtures/nodes.yaml +245 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/attributes_spec.rb +39 -0
- data/spec/support/tree_spec.rb +163 -0
- metadata +264 -0
data/lib/csl/renderer.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
module CSL
|
2
|
+
|
3
|
+
# Represents a cs:citation or cs:bibliography element.
|
4
|
+
class Renderer < Node
|
5
|
+
|
6
|
+
attr_fields Nodes.inheritable_name_attributes
|
7
|
+
attr_fields %w{ delimiter-precedes-et-al }
|
8
|
+
|
9
|
+
attr_reader :layout, :style
|
10
|
+
|
11
|
+
def initialize(*args, &block)
|
12
|
+
@style = args.detect { |argument| argument.is_a?(Style) }
|
13
|
+
args.delete(@style) unless @style.nil?
|
14
|
+
@parent = @style
|
15
|
+
|
16
|
+
args.each do |argument|
|
17
|
+
case
|
18
|
+
when argument.is_a?(String) && argument.match(/^\s*</)
|
19
|
+
parse(Nokogiri::XML.parse(argument) { |config| config.strict.noblanks }.root)
|
20
|
+
|
21
|
+
when argument.is_a?(Nokogiri::XML::Node)
|
22
|
+
parse(argument)
|
23
|
+
|
24
|
+
when argument.is_a?(Hash)
|
25
|
+
merge!(argument)
|
26
|
+
|
27
|
+
else
|
28
|
+
CiteProc.log.warn "failed to initialize Renderer from argument #{ argument.inspect }" unless argument.nil?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
set_defaults
|
33
|
+
|
34
|
+
yield self if block_given?
|
35
|
+
end
|
36
|
+
|
37
|
+
def sort(data, processor)
|
38
|
+
sort = find_children_by_name('sort').first
|
39
|
+
sort.nil? ? data : sort.apply(data, processor)
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse(node)
|
43
|
+
@layout = Nodes.parse(node.at_css('layout'), style)
|
44
|
+
add_children(Node.parse(node.at_css('sort')))
|
45
|
+
end
|
46
|
+
|
47
|
+
def render(data, processor=nil)
|
48
|
+
# TODO add support for one-off processor instance
|
49
|
+
processor.format(process(data, processor).join(delimiter), attributes)
|
50
|
+
rescue Exception => e
|
51
|
+
CiteProc.log :error, "failed to render data #{ data.inspect }", e
|
52
|
+
end
|
53
|
+
|
54
|
+
def process(data, processor)
|
55
|
+
sort(data, processor).map do |item|
|
56
|
+
[item['prefix'], @layout.process(item, processor), item['suffix']].compact.join(' ')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def set_defaults
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
class Bibliography < Renderer
|
68
|
+
attr_fields %w{ hanging-indent second-field-align line-spacing
|
69
|
+
entry-spacing subsequent-author-substitute }
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
class Citation < Renderer
|
74
|
+
attr_fields %w{ collapse year-suffix-delimiter after-collapse-delimiter
|
75
|
+
near-note-distance disambiguate-add-names disambiguate-add-given-name
|
76
|
+
given-name-disambiguation-rule disambiguate-add-year-suffix }
|
77
|
+
|
78
|
+
attr_fields %w{ delimiter suffix prefix }
|
79
|
+
|
80
|
+
def initialize(*arguments, &block)
|
81
|
+
super
|
82
|
+
%w{ delimiter suffix prefix }.each do |attribute|
|
83
|
+
self[attribute] = @layout.attributes.delete(attribute)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
data/lib/csl/sort.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module CSL
|
2
|
+
|
3
|
+
class Sort < Node
|
4
|
+
attr_children 'key'
|
5
|
+
|
6
|
+
alias :keys :key
|
7
|
+
|
8
|
+
def sort(items, processor)
|
9
|
+
items.sort do |a,b|
|
10
|
+
comparison = 0
|
11
|
+
keys.each do |key|
|
12
|
+
this, that = key.convert(a, processor), key.convert(b, processor)
|
13
|
+
|
14
|
+
comparison = this <=> that
|
15
|
+
comparison = comparison * -1 if comparison && key.descending?
|
16
|
+
|
17
|
+
comparison = comparison ? comparison : that.nil? ? -1 : 1
|
18
|
+
|
19
|
+
break unless comparison.zero?
|
20
|
+
end
|
21
|
+
|
22
|
+
comparison
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
alias :apply :sort
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class Key < Node
|
31
|
+
attr_fields %w{ variable macro sort names-min names-use-first names-use-last }
|
32
|
+
|
33
|
+
def convert(item, processor)
|
34
|
+
case
|
35
|
+
when has_variable?
|
36
|
+
item[variable]
|
37
|
+
when has_macro?
|
38
|
+
processor.style.macros[macro].process(item, processor)
|
39
|
+
else
|
40
|
+
CiteProc.log.warn "sort key #{ inspect } contains no variable or macro definition."
|
41
|
+
item
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def ascending?; !descending?; end
|
46
|
+
|
47
|
+
def descending?
|
48
|
+
has_sort? && sort == 'descending'
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/csl/style.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module CSL
|
2
|
+
|
3
|
+
# class StyleOptions < Node
|
4
|
+
# attr_fields %w{ punctuation-in-quote }
|
5
|
+
# end
|
6
|
+
#
|
7
|
+
# class Info < Node
|
8
|
+
# end
|
9
|
+
|
10
|
+
|
11
|
+
class Style < Node
|
12
|
+
|
13
|
+
@schema = File.expand_path('../../resource/schema/csl.rnc', __FILE__)
|
14
|
+
@path = File.expand_path('../../../resource/style', __FILE__)
|
15
|
+
@default = 'apa'
|
16
|
+
|
17
|
+
class << self; attr_accessor :path, :schema, :default; end
|
18
|
+
|
19
|
+
def initialize(style=nil)
|
20
|
+
open(style || Style.default)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param style A CSL stream, a file, the name of Style in the local repository, or an URI
|
24
|
+
def open(style)
|
25
|
+
@attributes = {}
|
26
|
+
doc = Nokogiri::XML(locate(style)) { |config| config.strict.noblanks }
|
27
|
+
|
28
|
+
[:citation, :bibliography].each do |element|
|
29
|
+
@attributes[element] ||= CSL.const_get(element.to_s.capitalize).new(doc.at_css("style > #{element}"), self)
|
30
|
+
end
|
31
|
+
|
32
|
+
@attributes[:locales] = doc.css('style > locale').map { |locale| Locale.new(locale) }
|
33
|
+
|
34
|
+
@attributes[:info] = Hash[*doc.at_css('style > info').children.map { |node| [node.name.downcase, node.content] }.flatten]
|
35
|
+
@attributes[:options] = Hash[doc.root.attributes.values.map { |a| [a.name, a.value] }]
|
36
|
+
@attributes[:macros] = Hash[doc.css('style > macro').map { |m| [m[:name], Nodes::Macro.new(m, self)] } ]
|
37
|
+
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the CSL Relax NG schema defintion.
|
42
|
+
def schema
|
43
|
+
@attributes[:schema] ||= Nokogiri::XML::RelaxNG(File.open(Style.schema))
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Validates the current style's source document against the CSL defintion.
|
48
|
+
def validate
|
49
|
+
[] # schema.validate(@doc)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns true if the current style's source document conforms to the CSL definition.
|
53
|
+
def valid?
|
54
|
+
validate.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
# Updates the current style using the URI returned by #link.
|
58
|
+
def update!
|
59
|
+
open(link)
|
60
|
+
end
|
61
|
+
|
62
|
+
def options
|
63
|
+
@attributes[:options] ||= {}
|
64
|
+
end
|
65
|
+
|
66
|
+
def [](key)
|
67
|
+
options[key.to_s]
|
68
|
+
end
|
69
|
+
|
70
|
+
def info
|
71
|
+
@attributes[:info]
|
72
|
+
end
|
73
|
+
|
74
|
+
[:title, :id].each do |method_id|
|
75
|
+
define_method method_id do
|
76
|
+
@attributes[method_id] ||= info[method_id.to_s]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
[:info, :macros, :citation, :bibliography].each do |method_id|
|
81
|
+
define_method method_id do; @attributes[method_id]; end
|
82
|
+
end
|
83
|
+
|
84
|
+
alias :macro macros
|
85
|
+
|
86
|
+
# @returns the style's locales.
|
87
|
+
def locales(language = nil, region = nil)
|
88
|
+
@attributes[:locales].select { |lc| lc.language.nil? || language.nil? || lc.language == language }.sort(&Locale.sort(language, region))
|
89
|
+
end
|
90
|
+
|
91
|
+
def link
|
92
|
+
@attributes[:link] ||= info.at_css('link')['href']
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def locate(resource)
|
99
|
+
resource = resource.to_s
|
100
|
+
return resource if resource.match(/^\s*<(\?xml|style)/)
|
101
|
+
return File.read(resource) if File.exists?(resource)
|
102
|
+
|
103
|
+
local = File.join(Style.path, "#{resource}.csl")
|
104
|
+
return File.read(local) if File.exists?(local)
|
105
|
+
|
106
|
+
Kernel.open(resource)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
data/lib/csl/term.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
module CSL
|
2
|
+
|
3
|
+
# == Term
|
4
|
+
#
|
5
|
+
# Terms are localized strings. For example, if a style specifies that the
|
6
|
+
# term "and" should be used, the string that appears in the style output
|
7
|
+
# depends on the locale: "and" for English, "und" for German. Terms are
|
8
|
+
# defined using cs:term elements, child elements of cs:terms, itself a child
|
9
|
+
# element of cs:locale. Terms are identified by the value of the name
|
10
|
+
# attribute of cs:term. Two types of terms exist: simple terms, where the
|
11
|
+
# content of the cs:term is the localized string, and compound terms, where
|
12
|
+
# cs:term includes the two child elements cs:single and cs:multiple, which
|
13
|
+
# respectively contain the singular and plural variant of the term (e.g.
|
14
|
+
# "page" and "pages"). Some terms are defined for multiple forms. In these
|
15
|
+
# cases, multiple cs:term element share the same value of name, but differ
|
16
|
+
# in the value of the optional form attribute. The different forms are:
|
17
|
+
#
|
18
|
+
# * "long" - the default, e.g. "editor" and "editors" for the term "editor"
|
19
|
+
# * "short" - e.g. "ed" and "eds" for the term "editor"
|
20
|
+
# * "verb" - e.g. "edited by" for the term "editor"
|
21
|
+
# * "verb-short" - e.g. "ed" for the term "editor"
|
22
|
+
# * "symbol" - e.g. "§" for the term "section"
|
23
|
+
#
|
24
|
+
# The plural attribute can be set to choose either the singular (value
|
25
|
+
# "false", the default) or plural variant (value "true") of a term. In
|
26
|
+
# addition, the form attribute can be set to select the desired term form
|
27
|
+
# ("long" [default], "short", "verb", "verb-short" or "symbol"). If for a
|
28
|
+
# given term the desired form does not exist, another form may be used:
|
29
|
+
# "verb-short" reverts to "verb", "symbol" reverts to "short", and "verb"
|
30
|
+
# and "short" both revert to "long".
|
31
|
+
#
|
32
|
+
class Term
|
33
|
+
include Support::Attributes
|
34
|
+
|
35
|
+
attr_fields %w{ name long short verb verb-short symbol gender feminine
|
36
|
+
masculine neutral }
|
37
|
+
|
38
|
+
def initialize(argument=nil, &block)
|
39
|
+
case
|
40
|
+
when argument.nil?
|
41
|
+
|
42
|
+
when argument.is_a?(Hash)
|
43
|
+
merge!(argument)
|
44
|
+
|
45
|
+
when argument.is_a?(Nokogiri::XML::Node)
|
46
|
+
parse!(argument)
|
47
|
+
|
48
|
+
when argument.is_a?(String) && argument.match(/^<term/)
|
49
|
+
parse!(Nokogiri::XML.parse(argument).root)
|
50
|
+
|
51
|
+
when argument.is_a?(String) || argument.is_a?(Symbol)
|
52
|
+
attributes['name'] = argument.to_s
|
53
|
+
|
54
|
+
else
|
55
|
+
CiteProc.log.warn "failed to create new Term from #{ argument.inspect }"
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
yield self if block_given?
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# @returns a hash containing all the terms in the given document
|
64
|
+
def self.build(doc=nil)
|
65
|
+
terms = Hash.new { |h,k| h[k] = Term.new(k) }
|
66
|
+
doc.css('terms term').each { |term| terms[term['name']].parse!(term) } unless doc.nil?
|
67
|
+
|
68
|
+
terms
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse!(node)
|
72
|
+
raise(ArgumentError, "failed to parse node; expected <term>, was: #{ node.inspect }") unless node.name == 'term'
|
73
|
+
|
74
|
+
self['name'] = node['name']
|
75
|
+
self['gender'] = node['gender']
|
76
|
+
self[node['form'] || node['gender-form'] || 'long'] = Hash[%w{ singular plural }.zip(node.children.map(&:content))]
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
def singularize(options={})
|
81
|
+
options['plural'] = 'false'
|
82
|
+
to_s(options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def pluralize(options={})
|
86
|
+
options['plural'] = 'true'
|
87
|
+
to_s(options)
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s(options={})
|
91
|
+
plural = ['', 'false', '1', 'never'].include?(options['plural'].to_s) ? false : true
|
92
|
+
|
93
|
+
term = case options['form']
|
94
|
+
when 'verb-short' then verb_short || verb || long
|
95
|
+
when 'symbol' then symbol || short || long
|
96
|
+
when 'verb' then verb || long
|
97
|
+
when 'short' then short || long
|
98
|
+
else
|
99
|
+
self[options['form']] || self[options['gender-form']] || long || masculine || feminine || neutral
|
100
|
+
end || {}
|
101
|
+
|
102
|
+
plural && !term['plural'].nil? ? term['plural'].to_s : term['singular'].to_s
|
103
|
+
rescue Exception => e
|
104
|
+
CiteProc.log.error "failed to convert Term to String: #{ e.message }"
|
105
|
+
''
|
106
|
+
end
|
107
|
+
|
108
|
+
def empty?
|
109
|
+
long.nil? && short.nil? && verb.nil? && verb_short.nil? && symbol.nil?
|
110
|
+
end
|
111
|
+
|
112
|
+
def has_gender?
|
113
|
+
!gender.nil?
|
114
|
+
end
|
115
|
+
|
116
|
+
%w{ masculine feminine neutral }.each do |gender|
|
117
|
+
define_method "#{gender}?" do
|
118
|
+
self['gender'] == gender
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
# ---------- Open Class ----------
|
3
|
+
|
4
|
+
module Kernel
|
5
|
+
alias :is_an? :is_a? unless defined?(is_an?)
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
# ---------- Extensions ----------
|
10
|
+
|
11
|
+
module Extensions
|
12
|
+
module Core
|
13
|
+
|
14
|
+
module Numbers
|
15
|
+
MAX_ROMAN = 4999
|
16
|
+
FACTORS = [["M", 1000], ["CM", 900], ["D", 500], ["CD", 400],
|
17
|
+
["C", 100], ["XC", 90], ["L", 50], ["XL", 40],
|
18
|
+
["X", 10], ["IX", 9], ["V", 5], ["IV", 4],
|
19
|
+
["I", 1]]
|
20
|
+
|
21
|
+
# Returns roman equivalent of the integer
|
22
|
+
# This function is featured in the pickaxe book
|
23
|
+
def romanize
|
24
|
+
num = self.to_i
|
25
|
+
roman = ""
|
26
|
+
unless num < 1 || num > MAX_ROMAN
|
27
|
+
for code, factor in FACTORS
|
28
|
+
count, num = num.divmod(factor)
|
29
|
+
roman << (code * count)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
roman.downcase
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# ---------- Include extensions ----------
|
40
|
+
|
41
|
+
class Fixnum
|
42
|
+
include Extensions::Core::Numbers
|
43
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
CiteProc::Variable.filters[:bibtex] = (Hash.new { |h, k| k }).merge(Hash[*%w{
|
2
|
+
date issued
|
3
|
+
isbn ISBN
|
4
|
+
booktitle container-title
|
5
|
+
journal container-title
|
6
|
+
series collection-title
|
7
|
+
address publisher-place
|
8
|
+
pages page
|
9
|
+
number issue
|
10
|
+
url URL
|
11
|
+
doi DOI
|
12
|
+
}])
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module CiteProc
|
2
|
+
|
3
|
+
module Format
|
4
|
+
|
5
|
+
class Default
|
6
|
+
|
7
|
+
attr_reader :input
|
8
|
+
|
9
|
+
Token = Struct.new :content, :annotations, :styles
|
10
|
+
AffixFilter = /([\.,\s!?()])/
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
reset
|
14
|
+
end
|
15
|
+
|
16
|
+
def name; "CiteProc default style (plain text)"; end
|
17
|
+
|
18
|
+
def reset
|
19
|
+
@styles = {}
|
20
|
+
@tokens = []
|
21
|
+
@affixes = [nil, nil]
|
22
|
+
end
|
23
|
+
|
24
|
+
def input=(input)
|
25
|
+
reset
|
26
|
+
@tokens = input.split(/(<span[^>]*>[^<]*<\/span>)/).map do |t|
|
27
|
+
token = Token.new
|
28
|
+
|
29
|
+
if t.match(/^<span(?:\s+class=['"]([\w\s]*)["'])?>([^<]*)<\/span>$/)
|
30
|
+
token.content = $2 || ''
|
31
|
+
token.annotations = $1.split(/\s+/)
|
32
|
+
else
|
33
|
+
token = Token.new
|
34
|
+
token.content = t
|
35
|
+
token.annotations = []
|
36
|
+
end
|
37
|
+
|
38
|
+
token
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def finalize
|
43
|
+
[prefix, @tokens.map(&:content).join, suffix].compact.join
|
44
|
+
end
|
45
|
+
|
46
|
+
def prefix
|
47
|
+
return nil if @affixes[0].nil?
|
48
|
+
@affixes[0].match(/([\.;:!?\s])$/) && @tokens.first.content.start_with?($1) ? @affixes[0].sub(/\.$/, '') : @affixes[0]
|
49
|
+
end
|
50
|
+
|
51
|
+
def suffix
|
52
|
+
return nil if @affixes[1].nil?
|
53
|
+
@affixes[1].match(/^([\.;:!?\s])/) && @tokens.last.content.end_with?($1) ? @affixes[1].sub(/^\./, '') : @affixes[1]
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_prefix(prefix)
|
57
|
+
@affixes[0] = prefix
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_suffix(suffix)
|
61
|
+
@affixes[1] = suffix
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param display 'block', 'left-margin', 'right-inline', 'inline'
|
65
|
+
def set_display(display)
|
66
|
+
@styles['display'] = display || 'inline'
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_strip_periods(strip)
|
70
|
+
@tokens.each { |token| token.content = token.content.gsub(/\.+/, ' ').squeeze(' ').gsub(/^\s+|\s+$/, '') } if strip == 'true'
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param style 'normal', 'italic', 'oblique'
|
74
|
+
def set_font_style(style)
|
75
|
+
@styles['font-style'] = style || 'normal'
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param variant 'normal', 'small-caps'
|
79
|
+
def set_font_variant(variant)
|
80
|
+
@styles['font-variant'] = variant || 'normal'
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param weight 'normal', 'bold', 'light'
|
84
|
+
def set_font_weight(weight)
|
85
|
+
@styles['font-weight'] = weight || 'normal'
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param decoration 'none', 'underline'
|
89
|
+
def set_text_decoration(decoration)
|
90
|
+
@styles['text-decoration'] = decoration || 'none'
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param align 'baseline', 'sub', 'sup'
|
94
|
+
def set_vertical_align(align)
|
95
|
+
@styles['vertical-align'] = align || 'baseline'
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param case 'lowercase', 'uppercase', 'capitalize-first', 'capitalize-all', 'title', 'sentence'
|
99
|
+
def set_text_case(text_case)
|
100
|
+
|
101
|
+
# note: the nocase annotations does not override lowercase and uppercase
|
102
|
+
|
103
|
+
case text_case
|
104
|
+
when 'lowercase'
|
105
|
+
@tokens.each { |token| token.content = UnicodeUtils ? UnicodeUtils.downcase(token.content) : token.content.downcase }
|
106
|
+
|
107
|
+
when 'uppercase'
|
108
|
+
@tokens.each { |token| token.content = UnicodeUtils ? UnicodeUtils.upcase(token.content) : token.content.upcase }
|
109
|
+
|
110
|
+
when 'capitalize-first'
|
111
|
+
token = @tokens.detect { |token| !token.annotations.include?('nocase') }
|
112
|
+
token.content.sub!(/^./) { UnicodeUtils ? UnicodeUtils.upcase($&) : $&.upcase }
|
113
|
+
|
114
|
+
when 'capitalize-all'
|
115
|
+
# @tokens.each { |token| token.content.gsub!(/\b\w/) { $&.upcase } unless token.annotations.include?('nocase') }
|
116
|
+
@tokens.each { |token| token.content = token.content.split(/(\s+)/).map(&:capitalize).join unless token.annotations.include?('nocase') }
|
117
|
+
|
118
|
+
# TODO exact specification?
|
119
|
+
when 'title'
|
120
|
+
@tokens.each { |token| token.content = token.content.split(/(\s+)/).map { |w| w.match(/^(and|of|a|an|the)$/i) ? w : w.gsub(/\b\w/) { UnicodeUtils ? UnicodeUtils.upcase($&) : $&.upcase } }.join.sub(/^(\w)/) {$&.upcase} unless token.annotations.include?('nocase') }
|
121
|
+
|
122
|
+
# TODO exact specification?
|
123
|
+
when 'sentence'
|
124
|
+
@tokens.each { |token| token.content.capitalize! unless token.annotations.include?('nocase') }
|
125
|
+
|
126
|
+
else
|
127
|
+
# nothing
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|