citeproc-ruby 0.0.6 → 1.0.0

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.
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