citeproc-ruby 0.0.1
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 +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
|