citeproc-ruby 0.0.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.document +4 -0
  4. data/.gitignore +10 -0
  5. data/.rspec +3 -0
  6. data/.simplecov +4 -0
  7. data/.travis.yml +17 -0
  8. data/.yardopts +2 -0
  9. data/AGPL +662 -0
  10. data/BSDL +29 -0
  11. data/Gemfile +42 -0
  12. data/Guardfile +14 -0
  13. data/README.md +32 -76
  14. data/Rakefile +60 -0
  15. data/citeproc-ruby.gemspec +46 -0
  16. data/cucumber.yml +1 -0
  17. data/features/bibliography.feature +25 -0
  18. data/features/name_options.feature +37 -0
  19. data/features/names.feature +192 -0
  20. data/features/renderer.feature +74 -0
  21. data/features/sort.feature +50 -0
  22. data/features/step_definitions/renderer.rb +80 -0
  23. data/features/support/env.rb +33 -0
  24. data/features/support/hooks.rb +10 -0
  25. data/lib/citeproc/ruby.rb +32 -0
  26. data/lib/citeproc/ruby/engine.rb +122 -0
  27. data/lib/citeproc/ruby/format.rb +303 -0
  28. data/lib/citeproc/ruby/formats/default.rb +25 -0
  29. data/lib/citeproc/ruby/formats/html.rb +221 -0
  30. data/lib/citeproc/ruby/renderer.rb +140 -0
  31. data/lib/citeproc/ruby/renderer/choose.rb +106 -0
  32. data/lib/citeproc/ruby/renderer/date.rb +90 -0
  33. data/lib/citeproc/ruby/renderer/format.rb +129 -0
  34. data/lib/citeproc/ruby/renderer/group.rb +34 -0
  35. data/lib/citeproc/ruby/renderer/history.rb +40 -0
  36. data/lib/citeproc/ruby/renderer/label.rb +66 -0
  37. data/lib/citeproc/ruby/renderer/layout.rb +20 -0
  38. data/lib/citeproc/ruby/renderer/locale.rb +26 -0
  39. data/lib/citeproc/ruby/renderer/macro.rb +20 -0
  40. data/lib/citeproc/ruby/renderer/names.rb +401 -0
  41. data/lib/citeproc/ruby/renderer/number.rb +41 -0
  42. data/lib/citeproc/ruby/renderer/observer.rb +44 -0
  43. data/lib/citeproc/ruby/renderer/state.rb +96 -0
  44. data/lib/citeproc/ruby/renderer/text.rb +62 -0
  45. data/lib/citeproc/ruby/sort.rb +82 -0
  46. data/lib/citeproc/ruby/version.rb +5 -0
  47. data/spec/citeproc/ruby/engine_spec.rb +94 -0
  48. data/spec/citeproc/ruby/formats/default_spec.rb +159 -0
  49. data/spec/citeproc/ruby/formats/html_spec.rb +162 -0
  50. data/spec/citeproc/ruby/renderer/choose_spec.rb +293 -0
  51. data/spec/citeproc/ruby/renderer/date_spec.rb +173 -0
  52. data/spec/citeproc/ruby/renderer/group_spec.rb +88 -0
  53. data/spec/citeproc/ruby/renderer/history_spec.rb +38 -0
  54. data/spec/citeproc/ruby/renderer/label_spec.rb +225 -0
  55. data/spec/citeproc/ruby/renderer/layout_spec.rb +41 -0
  56. data/spec/citeproc/ruby/renderer/macro_spec.rb +31 -0
  57. data/spec/citeproc/ruby/renderer/names_spec.rb +396 -0
  58. data/spec/citeproc/ruby/renderer/number_spec.rb +120 -0
  59. data/spec/citeproc/ruby/renderer/text_spec.rb +120 -0
  60. data/spec/citeproc/ruby/renderer_spec.rb +65 -0
  61. data/spec/fixtures/items.rb +80 -0
  62. data/{resource/locale → spec/fixtures/locales}/locales-en-US.xml +2 -11
  63. data/{resource/locale → spec/fixtures/locales}/locales-fr-FR.xml +77 -66
  64. data/{resource/style → spec/fixtures/styles}/apa.csl +5 -5
  65. data/spec/spec_helper.rb +67 -14
  66. metadata +121 -211
  67. data/lib/citeproc.rb +0 -100
  68. data/lib/citeproc/bibliography.rb +0 -57
  69. data/lib/citeproc/data.rb +0 -149
  70. data/lib/citeproc/date.rb +0 -133
  71. data/lib/citeproc/formatter.rb +0 -38
  72. data/lib/citeproc/item.rb +0 -53
  73. data/lib/citeproc/name.rb +0 -284
  74. data/lib/citeproc/processor.rb +0 -166
  75. data/lib/citeproc/selector.rb +0 -61
  76. data/lib/citeproc/variable.rb +0 -82
  77. data/lib/citeproc/version.rb +0 -3
  78. data/lib/csl/locale.rb +0 -223
  79. data/lib/csl/node.rb +0 -72
  80. data/lib/csl/nodes.rb +0 -1418
  81. data/lib/csl/renderer.rb +0 -88
  82. data/lib/csl/sort.rb +0 -61
  83. data/lib/csl/style.rb +0 -110
  84. data/lib/csl/term.rb +0 -124
  85. data/lib/extensions/core.rb +0 -43
  86. data/lib/plugins/filters/bibtex.rb +0 -12
  87. data/lib/plugins/formats/default.rb +0 -134
  88. data/lib/plugins/formats/html.rb +0 -67
  89. data/lib/support/attributes.rb +0 -99
  90. data/lib/support/compatibility.rb +0 -83
  91. data/lib/support/tree.rb +0 -80
  92. data/resource/locale/locales-af-ZA.xml +0 -305
  93. data/resource/locale/locales-ar-AR.xml +0 -306
  94. data/resource/locale/locales-bg-BG.xml +0 -305
  95. data/resource/locale/locales-ca-AD.xml +0 -305
  96. data/resource/locale/locales-cs-CZ.xml +0 -305
  97. data/resource/locale/locales-da-DK.xml +0 -305
  98. data/resource/locale/locales-de-AT.xml +0 -304
  99. data/resource/locale/locales-de-CH.xml +0 -304
  100. data/resource/locale/locales-de-DE.xml +0 -332
  101. data/resource/locale/locales-el-GR.xml +0 -305
  102. data/resource/locale/locales-en-GB.xml +0 -304
  103. data/resource/locale/locales-es-ES.xml +0 -305
  104. data/resource/locale/locales-et-EE.xml +0 -304
  105. data/resource/locale/locales-eu.xml +0 -305
  106. data/resource/locale/locales-fa-IR.xml +0 -304
  107. data/resource/locale/locales-fi-FI.xml +0 -304
  108. data/resource/locale/locales-fr-CA.xml +0 -306
  109. data/resource/locale/locales-he-IL.xml +0 -304
  110. data/resource/locale/locales-hu-HU.xml +0 -305
  111. data/resource/locale/locales-is-IS.xml +0 -304
  112. data/resource/locale/locales-it-IT.xml +0 -305
  113. data/resource/locale/locales-ja-JP.xml +0 -305
  114. data/resource/locale/locales-kh-KH.xml +0 -303
  115. data/resource/locale/locales-km-KH.xml +0 -304
  116. data/resource/locale/locales-ko-KR.xml +0 -305
  117. data/resource/locale/locales-mn-MN.xml +0 -306
  118. data/resource/locale/locales-nb-NO.xml +0 -304
  119. data/resource/locale/locales-nl-NL.xml +0 -304
  120. data/resource/locale/locales-nn-NO.xml +0 -304
  121. data/resource/locale/locales-pl-PL.xml +0 -305
  122. data/resource/locale/locales-pt-BR.xml +0 -304
  123. data/resource/locale/locales-pt-PT.xml +0 -305
  124. data/resource/locale/locales-ro-RO.xml +0 -305
  125. data/resource/locale/locales-ru-RU.xml +0 -306
  126. data/resource/locale/locales-sk-SK.xml +0 -304
  127. data/resource/locale/locales-sl-SI.xml +0 -305
  128. data/resource/locale/locales-sr-RS.xml +0 -305
  129. data/resource/locale/locales-sv-SE.xml +0 -305
  130. data/resource/locale/locales-th-TH.xml +0 -304
  131. data/resource/locale/locales-tr-TR.xml +0 -305
  132. data/resource/locale/locales-uk-UA.xml +0 -306
  133. data/resource/locale/locales-vi-VN.xml +0 -305
  134. data/resource/locale/locales-zh-CN.xml +0 -304
  135. data/resource/locale/locales-zh-TW.xml +0 -305
  136. data/resource/schema/csl-categories.rnc +0 -39
  137. data/resource/schema/csl-data.rnc +0 -98
  138. data/resource/schema/csl-terms.rnc +0 -106
  139. data/resource/schema/csl-types.rnc +0 -39
  140. data/resource/schema/csl-variables.rnc +0 -182
  141. data/resource/schema/csl.rnc +0 -941
  142. data/resource/style/bibtex.csl +0 -177
  143. data/resource/style/chicago-annotated-bibliography.csl +0 -513
  144. data/resource/style/chicago-author-date-basque.csl +0 -707
  145. data/resource/style/chicago-author-date-de.csl +0 -394
  146. data/resource/style/chicago-author-date-listing.csl +0 -434
  147. data/resource/style/chicago-author-date.csl +0 -425
  148. data/resource/style/chicago-dated-note-biblio-no-ibid.csl +0 -472
  149. data/resource/style/chicago-fullnote-bibliography-bb.csl +0 -928
  150. data/resource/style/chicago-fullnote-bibliography-delimiter-fixes.csl +0 -972
  151. data/resource/style/chicago-fullnote-bibliography-no-ibid-delimiter-fixes.csl +0 -963
  152. data/resource/style/chicago-fullnote-bibliography-no-ibid.csl +0 -785
  153. data/resource/style/chicago-fullnote-bibliography.csl +0 -803
  154. data/resource/style/chicago-library-list.csl +0 -511
  155. data/resource/style/chicago-note-biblio-no-ibid.csl +0 -514
  156. data/resource/style/chicago-note-bibliography.csl +0 -530
  157. data/resource/style/chicago-note.csl +0 -388
  158. data/resource/style/chicago-quick-copy.csl +0 -685
  159. data/resource/style/ieee.csl +0 -299
  160. data/resource/style/mla-notes.csl +0 -796
  161. data/resource/style/mla-underline.csl +0 -175
  162. data/resource/style/mla-url.csl +0 -214
  163. data/resource/style/mla.csl +0 -394
  164. data/resource/style/vancouver-brackets.csl +0 -256
  165. data/resource/style/vancouver-superscript-bracket-only-year.csl +0 -165
  166. data/resource/style/vancouver-superscript.csl +0 -256
  167. data/resource/style/vancouver.csl +0 -256
  168. data/spec/citeproc/bibliography_spec.rb +0 -45
  169. data/spec/citeproc/citeproc_spec.rb +0 -80
  170. data/spec/citeproc/date_spec.rb +0 -89
  171. data/spec/citeproc/formatter_spec.rb +0 -101
  172. data/spec/citeproc/item_spec.rb +0 -71
  173. data/spec/citeproc/name_spec.rb +0 -30
  174. data/spec/citeproc/processor_spec.rb +0 -61
  175. data/spec/citeproc/selector_spec.rb +0 -82
  176. data/spec/citeproc/variable_spec.rb +0 -69
  177. data/spec/csl/locale_spec.rb +0 -208
  178. data/spec/csl/node_spec.rb +0 -25
  179. data/spec/csl/nodes_spec.rb +0 -145
  180. data/spec/csl/style_spec.rb +0 -62
  181. data/spec/csl/term_spec.rb +0 -56
  182. data/spec/fixtures/dates.yaml +0 -80
  183. data/spec/fixtures/names.yaml +0 -115
  184. data/spec/fixtures/nodes.yaml +0 -245
  185. data/spec/support/attributes_spec.rb +0 -39
  186. data/spec/support/tree_spec.rb +0 -163
@@ -0,0 +1,74 @@
1
+ Feature: Rendering CSL nodes
2
+ As a hacker of cite processors
3
+ I want to render citation items
4
+ With selected CSL nodes
5
+
6
+ Scenario: Simple Date Rendering
7
+ Given the following style node:
8
+ """
9
+ <date variable="issued">
10
+ <date-part name="year"/>
11
+ </date>
12
+ """
13
+ When I render the following citation items as "text":
14
+ | issued |
15
+ | November 7, 2006 |
16
+ | 2014-01-01 |
17
+ | 1999 |
18
+ Then the results should be:
19
+ | 2006 |
20
+ | 2014 |
21
+ | 1999 |
22
+
23
+ Scenario: Date Group Rendering
24
+ Given the following style node:
25
+ """
26
+ <group prefix="(" suffix=").">
27
+ <date variable="issued">
28
+ <date-part name="year"/>
29
+ </date>
30
+ <text variable="year-suffix"/>
31
+ </group>
32
+ """
33
+ When I render the following citation items as "text":
34
+ | issued | year-suffix |
35
+ | November 7, 2006 | |
36
+ | 2014-01-01 | |
37
+ | 1999 | a |
38
+ Then the results should be:
39
+ | (2006). |
40
+ | (2014). |
41
+ | (1999a). |
42
+
43
+ @html @formatting
44
+ Scenario: Formatted Groups
45
+ Given the following style node:
46
+ """
47
+ <group>
48
+ <group suffix=" " font-weight="bold">
49
+ <!--group formatting is pushed down-->
50
+ <text variable="volume" suffix=","/>
51
+ </group>
52
+ <text variable="page" suffix="," font-weight="bold"/>
53
+ </group>
54
+ """
55
+ When I render the following citation item as "html":
56
+ | volume | 5 |
57
+ | page | 23 |
58
+ Then the result should be: <b>5</b>, <b>23</b>,
59
+
60
+ Scenario: Page labels
61
+ Given the following style node:
62
+ """
63
+ <label variable="page"/>
64
+ """
65
+ When I render the following citation items as "text":
66
+ | page |
67
+ | 23 |
68
+ | |
69
+ | 23, 34 |
70
+ Then the results should be:
71
+ | page |
72
+ | |
73
+ | pages |
74
+
@@ -0,0 +1,50 @@
1
+ Feature: Sorting
2
+ As a hacker of cite processors
3
+ I want to sort citation items
4
+ According to the rules of a CSL style
5
+
6
+ Scenario: Name sorting
7
+ Given the following sort keys:
8
+ """
9
+ <sort>
10
+ <key variable="author"/>
11
+ </sort>
12
+ """
13
+ When I sort the following items:
14
+ | author |
15
+ | Edelweis |
16
+ | ANZ-Group |
17
+ | Aardvaark |
18
+ Then the order should be:
19
+ | ID-2 |
20
+ | ID-1 |
21
+ | ID-0 |
22
+
23
+ Scenario: Name sorting macros
24
+ Given the following sort keys:
25
+ """
26
+ <sort>
27
+ <key macro="author"/>
28
+ </sort>
29
+ """
30
+ And the following macro:
31
+ """
32
+ <macro name="author">
33
+ <names variable="author">
34
+ <name name-as-sort-order="all" and="symbol" sort-separator=", "
35
+ initialize-with="." delimiter-precedes-last="never" delimiter=", "/>
36
+ <label form="short" prefix=" (" suffix=".)" text-case="capitalize-first"/>
37
+ <substitute>
38
+ <names variable="editor"/>
39
+ <text value="Anon"/>
40
+ </substitute>
41
+ </names>
42
+ </macro>
43
+ """
44
+ When I sort the following items:
45
+ | author |
46
+ | ABC |
47
+ | Aaa |
48
+ Then the order should be:
49
+ | ID-1 |
50
+ | ID-0 |
@@ -0,0 +1,80 @@
1
+
2
+ Given(/^the "(.*?)" style's (bibliography|citation) node$/) do |style, mode|
3
+ @node = CSL::Style.load(style).send(mode).layout
4
+ end
5
+
6
+ Given(/^the following style node:$/) do |string|
7
+ @node = CSL.parse!(string, CSL::Style)
8
+ end
9
+
10
+ Given(/^the following sort keys:$/) do |string|
11
+ @sort = CSL.parse!(string, CSL::Style)
12
+ end
13
+
14
+ Given(/^the following macro:$/) do |string|
15
+ @macro = CSL.parse!(string, CSL::Style)
16
+ end
17
+
18
+ When(/^I render the following citation items as "(.*?)":$/) do |format, items|
19
+ r = CiteProc::Ruby::Renderer.new(:format => format)
20
+
21
+ @results = items.hashes.map.with_index do |data, idx|
22
+ i = CiteProc::CitationItem.new(:id => "ID-#{idx}")
23
+
24
+ data[:id] = "ID-#{idx}"
25
+ i.data = CiteProc::Item.new(data)
26
+
27
+ r.render i, @node
28
+ end
29
+ end
30
+
31
+ When(/^I render the following citation item as "(.*?)":$/) do |format, item|
32
+ r = CiteProc::Ruby::Renderer.new(:format => format)
33
+
34
+ i = CiteProc::CitationItem.new(:id => 'ID-1')
35
+ i.data = CiteProc::Item.new(item.rows_hash.merge(:id => 'ID-1'))
36
+
37
+ @result = r.render i, @node
38
+ end
39
+
40
+ When(/^I sort the following items:$/) do |items|
41
+ engine = CiteProc::Ruby::Engine.new
42
+
43
+ @order = items.hashes.map.with_index do |data, idx|
44
+ data[:id] = "ID-#{idx}"
45
+ CiteProc::Item.new(data)
46
+ end
47
+
48
+ unless @macro.nil?
49
+ @sort.each_child do |key|
50
+ key.stub(:macro).and_return(@macro)
51
+ key.stub(:macro?).and_return(true)
52
+ end
53
+ end
54
+
55
+ engine.sort! @order, @sort.children
56
+ end
57
+
58
+ Then(/^the results should be:$/) do |expected|
59
+ expected = expected.raw.map(&:first)
60
+
61
+ @results.length.should == expected.length
62
+
63
+ @results.zip(expected).each do |result, expectation|
64
+ result.should == expectation
65
+ end
66
+ end
67
+
68
+ Then(/^the result should be: (.*)$/) do |expected|
69
+ @result.should == expected
70
+ end
71
+
72
+ Then(/^the order should be:$/) do |expected|
73
+ expected = expected.raw.map(&:first)
74
+
75
+ @order.length.should == expected.length
76
+
77
+ @order.zip(expected).each do |order, expectation|
78
+ order['id'].should == expectation
79
+ end
80
+ end
@@ -0,0 +1,33 @@
1
+ begin
2
+ require 'simplecov'
3
+ require 'coveralls' if ENV['CI']
4
+ rescue LoadError
5
+ # ignore
6
+ end
7
+
8
+ begin
9
+ case
10
+ when RUBY_PLATFORM < 'java'
11
+ require 'debug'
12
+ Debugger.start
13
+ when defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
14
+ require 'rubinius/debugger'
15
+ else
16
+ require 'debugger'
17
+ end
18
+ rescue LoadError
19
+ # ignore
20
+ end
21
+
22
+ require 'cucumber/rspec/doubles'
23
+ require 'citeproc/ruby'
24
+
25
+ module Fixtures
26
+ PATH = File.expand_path('../../../spec/fixtures', __FILE__)
27
+
28
+ Dir[File.join(PATH, '*.rb')].each do |fixture|
29
+ require fixture
30
+ end
31
+ end
32
+
33
+ World(Fixtures)
@@ -0,0 +1,10 @@
1
+ Before do
2
+ @style_root, @locale_root = CSL::Style.root, CSL::Locale.root
3
+
4
+ CSL::Style.root = File.join(Fixtures::PATH, 'styles')
5
+ CSL::Locale.root = File.join(Fixtures::PATH, 'locales')
6
+ end
7
+
8
+ After do
9
+ CSL::Style.root, CSL::Locale.root = @style_root, @locale_root
10
+ end
@@ -0,0 +1,32 @@
1
+ require 'observer'
2
+
3
+ require 'csl'
4
+ require 'citeproc'
5
+
6
+ require 'citeproc/ruby/version'
7
+
8
+ require 'citeproc/ruby/format'
9
+
10
+ require 'citeproc/ruby/formats/default'
11
+ require 'citeproc/ruby/formats/html'
12
+
13
+ require 'citeproc/ruby/renderer'
14
+
15
+ require 'citeproc/ruby/renderer/locale'
16
+ require 'citeproc/ruby/renderer/format'
17
+ require 'citeproc/ruby/renderer/observer'
18
+ require 'citeproc/ruby/renderer/state'
19
+ require 'citeproc/ruby/renderer/history'
20
+
21
+ require 'citeproc/ruby/renderer/text'
22
+ require 'citeproc/ruby/renderer/number'
23
+ require 'citeproc/ruby/renderer/label'
24
+ require 'citeproc/ruby/renderer/date'
25
+ require 'citeproc/ruby/renderer/names'
26
+ require 'citeproc/ruby/renderer/layout'
27
+ require 'citeproc/ruby/renderer/macro'
28
+ require 'citeproc/ruby/renderer/group'
29
+ require 'citeproc/ruby/renderer/choose'
30
+
31
+ require 'citeproc/ruby/sort.rb'
32
+ require 'citeproc/ruby/engine.rb'
@@ -0,0 +1,122 @@
1
+ module CiteProc
2
+ module Ruby
3
+
4
+ class Engine < CiteProc::Engine
5
+
6
+ include SortItems
7
+
8
+ @name = 'citeproc-ruby'.freeze
9
+ @type = 'CSL'.freeze
10
+ @version = CSL::Schema.version
11
+ @priority = 1
12
+
13
+ attr_reader :renderer, :style
14
+
15
+ def_delegators :renderer,
16
+ :format, :format=, :locale, :locale=
17
+
18
+ def initialize(*arguments)
19
+ super(*arguments)
20
+ @renderer = Renderer.new(self)
21
+
22
+ update! unless processor.nil?
23
+ end
24
+
25
+ def style=(new_style)
26
+ @style = CSL::Style.load new_style
27
+ end
28
+
29
+ def process(data)
30
+ node = style.citation
31
+
32
+ return unless node
33
+ return '' if data.empty?
34
+
35
+ # populate item data
36
+ data.each do |item|
37
+ item.data = processor[item.id].dup
38
+ end
39
+
40
+ # TODO implement sort in citation data
41
+ sort!(data, node.sort_keys) unless !node.sort?
42
+
43
+ # TODO citation number (after sorting?)
44
+
45
+ renderer.render_citation data, node
46
+ end
47
+
48
+ def append
49
+ raise NotImplementedByEngine
50
+ end
51
+
52
+ def bibliography(selector)
53
+ node = style.bibliography
54
+ return unless node
55
+
56
+ selection = processor.data.select do |item|
57
+ selector.matches?(item) && !selector.skip?(item)
58
+ end
59
+
60
+ sort!(selection, node.sort_keys) unless selection.empty? || !node.sort?
61
+
62
+ Bibliography.new(node.bibliography_options) do |bib|
63
+ format.bibliography(bib)
64
+
65
+ idx = 1
66
+
67
+ selection.each do |item|
68
+ begin
69
+ bib.push item.id, renderer.render_bibliography(item.cite(idx), node)
70
+ rescue => error
71
+ bib.errors << [item.id.to_s, error]
72
+ ensure
73
+ idx += 1 unless error
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def update_items
80
+ raise NotImplementedByEngine
81
+ end
82
+
83
+ def update_uncited_items
84
+ raise NotImplementedByEngine
85
+ end
86
+
87
+ # @return [String, Array<String>]
88
+ def render(mode, data)
89
+ case mode
90
+ when :bibliography
91
+ node = style.bibliography
92
+
93
+ data.map do |item|
94
+ item.data = processor[item.id].dup
95
+ renderer.render item, node
96
+ end
97
+
98
+ when :citation
99
+ node = style.citation
100
+
101
+ data.each do |item|
102
+ item.data = processor[item.id].dup
103
+ end
104
+
105
+ renderer.render_citation data, node
106
+
107
+ else
108
+ raise ArgumentError, "cannot render unknown mode: #{mode.inspect}"
109
+ end
110
+ end
111
+
112
+
113
+ def update!
114
+ renderer.format = processor.options[:format]
115
+ renderer.locale = processor.options[:locale]
116
+
117
+ @style = CSL::Style.load processor.options[:style]
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,303 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module CiteProc
4
+ module Ruby
5
+
6
+ class Format
7
+
8
+ @available = []
9
+
10
+ @squeezable = /^[\s\.,:;!?\)\(\[\]]+$/
11
+
12
+ @stopwords = {
13
+ :en => [
14
+ 'about', 'above', 'across', 'afore', 'after', 'against', 'along',
15
+ 'alongside', 'amid', 'amidst', 'among', 'amongst', 'anenst', 'apropos',
16
+ 'apud', 'around', 'as', 'aside', 'astride', 'at', 'athwart', 'atop',
17
+ 'barring', 'before', 'behind', 'below', 'beneath', 'beside', 'besides',
18
+ 'between', 'beyond', 'but', 'by', 'circa', 'despite', 'd', 'down', 'during',
19
+ 'except', 'for', 'forenenst', 'from', 'given', 'in', 'inside', 'into',
20
+ 'lest', 'like', 'modulo' 'near', 'next', 'notwithstanding', 'of', 'off',
21
+ 'on', 'onto', 'out', 'over', 'per', 'plus', 'pro', 'qua', 'sans', 'since',
22
+ 'than', 'through', 'thru', 'throughout', 'thruout', 'till', 'to', 'toward',
23
+ 'towards', 'under', 'underneath', 'until', 'unto', 'up', 'upon', 'versus',
24
+ 'vs', 'v', 'via', 'vis-à-vis', 'with', 'within', 'without'
25
+ ]
26
+ }
27
+
28
+ class << self
29
+ attr_reader :available, :stopwords
30
+
31
+ def inherited(base)
32
+ Format.available << base
33
+ end
34
+
35
+ def load(name = nil)
36
+ return new unless name
37
+ return name if name.is_a?(Format)
38
+
39
+ name = name.to_s.downcase
40
+
41
+ klass = available.detect do |format|
42
+ format.name.split('::')[-1].downcase == name
43
+ end
44
+
45
+ raise(Error, "unknown format: #{name}") unless klass
46
+
47
+ klass.new
48
+ end
49
+
50
+ def stopword?(word, locale = :en)
51
+ return unless stopwords.key?(locale)
52
+ stopwords[locale].include?(word.downcase)
53
+ end
54
+
55
+ def squeezable?(string)
56
+ squeezable === string
57
+ end
58
+
59
+ def squeezable
60
+ @squeezable ||= Format.squeezable
61
+ end
62
+ end
63
+
64
+ attr_reader :locale
65
+
66
+ def keys
67
+ @keys ||= (CSL::Schema.attr(:formatting) - [:prefix, :suffix, :display])
68
+ end
69
+
70
+ def squeezable?(string)
71
+ self.class.squeezable?(string)
72
+ end
73
+
74
+ def squeeze_suffix(string, suffix)
75
+ raise ArgumentError unless string.is_a?(::String)
76
+ raise ArgumentError unless suffix.is_a?(::String)
77
+
78
+ return string.dup if suffix.empty?
79
+ return suffix.dup if string.empty?
80
+
81
+ string, stripped = strip(string)
82
+ string, quotes = split_closing_quotes(string)
83
+
84
+ suffix = decode_entities(suffix)
85
+
86
+ suffix = suffix.each_char.drop_while.with_index { |c, i|
87
+ squeezable?(c) && string.end_with?(suffix[0, i + 1])
88
+ }.join('')
89
+
90
+ # Handle special cases like ?. or ;.
91
+ if suffix.start_with?('.') && string.end_with?(';', ',', '!', '?', ':')
92
+ suffix = suffix[1..-1]
93
+ end
94
+
95
+ # Handle special cases ;, and :,
96
+ if suffix.start_with?(',') && string.end_with?(';', ':')
97
+ suffix = suffix[1..-1]
98
+ end
99
+
100
+ # Handle special cases ,; and :;
101
+ if suffix.start_with?(';') && string.end_with?(',', ':')
102
+ suffix = suffix[1..-1]
103
+ end
104
+
105
+ # Handle punctiation-in-quote
106
+ if !quotes.nil? && punctuation_in_quotes?
107
+ if suffix.sub!(/^([\.,])/, '')
108
+ punctuation = ($1).to_s
109
+ end
110
+ end
111
+
112
+ "#{string}#{punctuation}#{quotes}#{stripped}#{suffix}"
113
+ end
114
+
115
+ def squeeze_prefix(string, prefix)
116
+ raise ArgumentError unless string.is_a?(::String)
117
+ raise ArgumentError unless prefix.is_a?(::String)
118
+
119
+ prefix = prefix.reverse.each_char.drop_while.with_index { |c, i|
120
+ squeezable?(c) && string.start_with?(prefix[-(i + 1) .. -1])
121
+ }.join('').reverse
122
+
123
+ "#{prefix}#{string}"
124
+ end
125
+
126
+ alias concat squeeze_suffix
127
+
128
+ def join(list, delimiter = nil)
129
+ raise ArgumentError unless list.is_a?(Enumerable)
130
+ return '' if list.length.zero?
131
+ return list[0] if list.length == 1
132
+
133
+ if delimiter.nil? || delimiter.empty?
134
+ list.inject do |m, n|
135
+ concat(m, n)
136
+ end
137
+ else
138
+ list.inject do |m, n|
139
+ concat(concat(m, delimiter), n)
140
+ end
141
+ end
142
+ end
143
+
144
+ def bibliography(bibliography, locale = nil)
145
+ bibliography.connector = "\n" * bibliography.entry_spacing
146
+ bibliography
147
+ end
148
+
149
+ def apply(input, node, locale = nil)
150
+ return '' if input.nil?
151
+ return input if input.empty? || node.nil?
152
+
153
+ return ArgumentError unless node.respond_to?(:formatting_options)
154
+
155
+
156
+ @input, @output, @node, @locale = input, input.dup, node, locale
157
+
158
+ setup!
159
+
160
+ # NB: Layout nodes apply formatting to
161
+ # affixes; all other nodes do not!
162
+ if node.is_a? CSL::Style::Layout
163
+ apply_prefix if options.key?(:prefix)
164
+ apply_suffix if options.key?(:suffix)
165
+ end
166
+
167
+ keys.each do |format|
168
+ if options.key?(format)
169
+ method = "apply_#{format}".tr('-', '_')
170
+ send method if respond_to?(method)
171
+ end
172
+ end unless options.empty?
173
+
174
+ output.gsub!(/\.+/, '') if node.strip_periods?
175
+
176
+ apply_quotes if node.quotes? && !locale.nil?
177
+
178
+ finalize_content!
179
+
180
+ unless node.is_a? CSL::Style::Layout
181
+ apply_prefix if options.key?(:prefix)
182
+ apply_suffix if options.key?(:suffix)
183
+ end
184
+
185
+ apply_display if options.key?(:display)
186
+
187
+ finalize!
188
+
189
+ output
190
+ ensure
191
+ cleanup!
192
+ end
193
+
194
+ def escape_quotes?
195
+ false
196
+ end
197
+
198
+ def close_quote
199
+ locale && locale.t('close-quote') || '"'
200
+ end
201
+
202
+ def close_inner_quote
203
+ locale && locale.t('close-inner-quote') || "'"
204
+ end
205
+
206
+ def split_closing_quotes(string)
207
+ string.split(/([#{close_inner_quote}#{close_quote}]+)$/, 2)
208
+ end
209
+
210
+ def apply_quotes
211
+ output.replace locale.quote(output, escape_quotes?)
212
+ end
213
+
214
+ def apply_text_case
215
+ case options[:'text-case']
216
+ when 'lowercase'
217
+ output.replace CiteProc.downcase output
218
+
219
+ when 'uppercase'
220
+ output.replace CiteProc.upcase output
221
+
222
+ when 'capitalize-first'
223
+ output.sub!(/^([^\p{L}]*)(\p{Ll})/) { "#{$1}#{CiteProc.upcase($2)}" }
224
+
225
+ when 'capitalize-all'
226
+ output.gsub!(/\b(\p{Ll})/) { CiteProc.upcase($1) }
227
+
228
+ when 'sentence'
229
+ output.sub!(/^([^\p{L}]*)(\p{Ll})/) { "#{$1}#{CiteProc.upcase($2)}" }
230
+ output.gsub!(/\b(\p{Lu})(\p{Lu}+)\b/) { "#{$1}#{CiteProc.downcase($2)}" }
231
+
232
+ when 'title'
233
+ return if locale && locale.language != :en
234
+
235
+ # TODO add support for stop words consisting of multiple words
236
+ #output.gsub!(/\b(\p{Lu})(\p{Lu}+)\b/) { "#{$1}#{CiteProc.downcase($2)}" }
237
+
238
+ # TODO exceptions: first, last word; followed by colon
239
+ output.gsub!(/\b(\p{Ll})(\p{L}+)\b/) do |word|
240
+ if Format.stopword?(word)
241
+ word
242
+ else
243
+ "#{CiteProc.upcase($1)}#{$2}"
244
+ end
245
+ end
246
+
247
+ end
248
+ end
249
+
250
+ def punctuation_in_quotes?
251
+ !locale.nil? && locale.punctuation_in_quotes?
252
+ end
253
+
254
+ def apply_prefix
255
+ output.replace(squeeze_prefix(output, prefix))
256
+ end
257
+
258
+ def apply_suffix
259
+ output.replace(squeeze_suffix(output, suffix))
260
+ end
261
+
262
+ def prefix
263
+ options[:prefix].to_s
264
+ end
265
+
266
+ def suffix
267
+ options[:suffix].to_s
268
+ end
269
+
270
+ def strip(string)
271
+ string
272
+ end
273
+
274
+ protected
275
+
276
+ attr_reader :input, :output, :node
277
+
278
+ def options
279
+ @options ||= @node.formatting_options
280
+ end
281
+
282
+ def finalize!
283
+ end
284
+
285
+ def finalize_content!
286
+ end
287
+
288
+ def setup!
289
+ end
290
+
291
+ def decode_entities(string)
292
+ string.gsub(/&#x([0-9a-f]);/i) do
293
+ [Integer("0x#{$1}")].pack('U')
294
+ end
295
+ end
296
+
297
+ def cleanup!
298
+ @input, @output, @node, @options = nil
299
+ end
300
+ end
301
+
302
+ end
303
+ end