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.
Files changed (125) hide show
  1. data/README.md +78 -0
  2. data/lib/citeproc.rb +100 -0
  3. data/lib/citeproc/bibliography.rb +57 -0
  4. data/lib/citeproc/data.rb +149 -0
  5. data/lib/citeproc/date.rb +133 -0
  6. data/lib/citeproc/formatter.rb +38 -0
  7. data/lib/citeproc/item.rb +53 -0
  8. data/lib/citeproc/name.rb +284 -0
  9. data/lib/citeproc/processor.rb +166 -0
  10. data/lib/citeproc/selector.rb +61 -0
  11. data/lib/citeproc/variable.rb +82 -0
  12. data/lib/citeproc/version.rb +3 -0
  13. data/lib/csl/locale.rb +223 -0
  14. data/lib/csl/node.rb +72 -0
  15. data/lib/csl/nodes.rb +1364 -0
  16. data/lib/csl/renderer.rb +88 -0
  17. data/lib/csl/sort.rb +53 -0
  18. data/lib/csl/style.rb +110 -0
  19. data/lib/csl/term.rb +124 -0
  20. data/lib/extensions/core.rb +43 -0
  21. data/lib/plugins/filters/bibtex.rb +12 -0
  22. data/lib/plugins/formats/default.rb +134 -0
  23. data/lib/plugins/formats/html.rb +67 -0
  24. data/lib/support/attributes.rb +99 -0
  25. data/lib/support/tree.rb +80 -0
  26. data/resource/locale/locales-af-ZA.xml +304 -0
  27. data/resource/locale/locales-ar-AR.xml +304 -0
  28. data/resource/locale/locales-bg-BG.xml +304 -0
  29. data/resource/locale/locales-ca-AD.xml +304 -0
  30. data/resource/locale/locales-cs-CZ.xml +304 -0
  31. data/resource/locale/locales-da-DK.xml +304 -0
  32. data/resource/locale/locales-de-AT.xml +304 -0
  33. data/resource/locale/locales-de-CH.xml +304 -0
  34. data/resource/locale/locales-de-DE.xml +332 -0
  35. data/resource/locale/locales-el-GR.xml +303 -0
  36. data/resource/locale/locales-en-US.xml +313 -0
  37. data/resource/locale/locales-es-ES.xml +304 -0
  38. data/resource/locale/locales-et-EE.xml +304 -0
  39. data/resource/locale/locales-fr-FR.xml +304 -0
  40. data/resource/locale/locales-he-IL.xml +304 -0
  41. data/resource/locale/locales-hu-HU.xml +304 -0
  42. data/resource/locale/locales-is-IS.xml +304 -0
  43. data/resource/locale/locales-it-IT.xml +304 -0
  44. data/resource/locale/locales-ja-JP.xml +304 -0
  45. data/resource/locale/locales-kh-KH.xml +303 -0
  46. data/resource/locale/locales-ko-KR.xml +304 -0
  47. data/resource/locale/locales-mn-MN.xml +304 -0
  48. data/resource/locale/locales-nb-NO.xml +304 -0
  49. data/resource/locale/locales-nl-NL.xml +304 -0
  50. data/resource/locale/locales-nn-NO.xml +304 -0
  51. data/resource/locale/locales-pl-PL.xml +304 -0
  52. data/resource/locale/locales-pt-BR.xml +304 -0
  53. data/resource/locale/locales-pt-PT.xml +304 -0
  54. data/resource/locale/locales-ro-RO.xml +304 -0
  55. data/resource/locale/locales-ru-RU.xml +304 -0
  56. data/resource/locale/locales-sk-SK.xml +304 -0
  57. data/resource/locale/locales-sl-SI.xml +304 -0
  58. data/resource/locale/locales-sr-RS.xml +304 -0
  59. data/resource/locale/locales-sv-SE.xml +304 -0
  60. data/resource/locale/locales-th-TH.xml +304 -0
  61. data/resource/locale/locales-tr-TR.xml +304 -0
  62. data/resource/locale/locales-uk-UA.xml +304 -0
  63. data/resource/locale/locales-vi-VN.xml +304 -0
  64. data/resource/locale/locales-zh-CN.xml +304 -0
  65. data/resource/locale/locales-zh-TW.xml +304 -0
  66. data/resource/schema/csl-categories.rnc +39 -0
  67. data/resource/schema/csl-data.rnc +98 -0
  68. data/resource/schema/csl-terms.rnc +106 -0
  69. data/resource/schema/csl-types.rnc +39 -0
  70. data/resource/schema/csl-variables.rnc +182 -0
  71. data/resource/schema/csl.rnc +941 -0
  72. data/resource/style/acta-materialia-x.csl +128 -0
  73. data/resource/style/advanced-engineering-materials-x.csl +121 -0
  74. data/resource/style/ama.csl +185 -0
  75. data/resource/style/ama2-x.csl +179 -0
  76. data/resource/style/apa-x.csl +324 -0
  77. data/resource/style/apa.csl +254 -0
  78. data/resource/style/apsa-x.csl +163 -0
  79. data/resource/style/apsa.csl +176 -0
  80. data/resource/style/asa-x.csl +203 -0
  81. data/resource/style/asa.csl +216 -0
  82. data/resource/style/asm-journals-x.csl +131 -0
  83. data/resource/style/bibtex-x2.csl +175 -0
  84. data/resource/style/bluebook-demo-x.csl +392 -0
  85. data/resource/style/bluebook-demo.csl +942 -0
  86. data/resource/style/chicago-author-date-listing.csl +434 -0
  87. data/resource/style/chicago-author-date.csl +369 -0
  88. data/resource/style/chicago-fullnote-bibliography-bb.csl +928 -0
  89. data/resource/style/chicago-fullnote-bibliography.csl +695 -0
  90. data/resource/style/chicago-note-bibliography.csl +446 -0
  91. data/resource/style/chicago-note.csl +388 -0
  92. data/resource/style/greek-chicago-x.csl +1182 -0
  93. data/resource/style/harvard1-institution-italic.csl +190 -0
  94. data/resource/style/harvard1.csl +181 -0
  95. data/resource/style/ieee.csl +129 -0
  96. data/resource/style/mhra-x.csl +312 -0
  97. data/resource/style/mhra.csl +390 -0
  98. data/resource/style/mhra_note_without_bibliography-x.csl +330 -0
  99. data/resource/style/mhra_note_without_bibliography.csl +338 -0
  100. data/resource/style/mla-x.csl +178 -0
  101. data/resource/style/mla.csl +189 -0
  102. data/resource/style/nature-x.csl +81 -0
  103. data/resource/style/nature.csl +88 -0
  104. data/resource/style/nlm.csl +117 -0
  105. data/spec/citeproc/bibliography_spec.rb +45 -0
  106. data/spec/citeproc/citeproc_spec.rb +76 -0
  107. data/spec/citeproc/date_spec.rb +85 -0
  108. data/spec/citeproc/formatter_spec.rb +101 -0
  109. data/spec/citeproc/item_spec.rb +71 -0
  110. data/spec/citeproc/name_spec.rb +30 -0
  111. data/spec/citeproc/processor_spec.rb +61 -0
  112. data/spec/citeproc/selector_spec.rb +82 -0
  113. data/spec/citeproc/variable_spec.rb +69 -0
  114. data/spec/csl/locale_spec.rb +208 -0
  115. data/spec/csl/node_spec.rb +25 -0
  116. data/spec/csl/nodes_spec.rb +140 -0
  117. data/spec/csl/style_spec.rb +62 -0
  118. data/spec/csl/term_spec.rb +56 -0
  119. data/spec/fixtures/dates.yaml +80 -0
  120. data/spec/fixtures/names.yaml +115 -0
  121. data/spec/fixtures/nodes.yaml +245 -0
  122. data/spec/spec_helper.rb +18 -0
  123. data/spec/support/attributes_spec.rb +39 -0
  124. data/spec/support/tree_spec.rb +163 -0
  125. metadata +264 -0
@@ -0,0 +1,78 @@
1
+ CiteProc-Ruby
2
+ =============
3
+
4
+ CiteProc-Ruby is a CSL 1.0 ([Citation Style Language](http://citationstyles.org/))
5
+ Processor written in Ruby.
6
+
7
+ A word of caution: this release of CiteProc-Ruby is purely experimental; the API
8
+ is not complete and liable to change frequently. This release is expected to
9
+ work in Ruby version 1.9.2.
10
+
11
+
12
+ Quickstart
13
+ ----------
14
+
15
+ $ [sudo] gem install citeproc-ruby
16
+ $ irb
17
+ >> require 'citeproc'
18
+ >> book = {
19
+ 'author' => [{ 'given' => 'Edgar Allen', 'family' => 'Poe' }],
20
+ 'title' => 'Poetry, Tales, and Selected Essays',
21
+ 'type' => 'book',
22
+ 'issued' => { 'date-parts' => [[1996]] },
23
+ 'editor' => [{ 'family' => 'Quinn', 'given' => 'Patrick F.'}, { 'family' => 'Thompson', 'given' => 'G.R.' }],
24
+ 'publisher' => 'Library of America',
25
+ 'publisher-place' => 'New York'
26
+ }
27
+ >> CiteProc.process(book)
28
+ => "Poe, E. A. (1996). Poetry, Tales, and Selected Essays. (P. F. Quinn & G. R. Thompson, Eds.). New York: Library of America."
29
+ >> CiteProc.process(book, :format => :html)
30
+ => "Poe, E. A. (1996). <i>Poetry, Tales, and Selected Essays</i>. (P. F. Quinn &#38; G. R. Thompson, Eds.). New York: Library of America."
31
+ >> CiteProc.process(book, :mode => :citation)
32
+ => ["(Poe, 1996)"]
33
+ >> CiteProc.process(book, :style => "https://github.com/citation-style-language/styles/raw/master/chicago-author-date.csl")
34
+ => "Poe, Edgar Allen. 1996. Poetry, Tales, and Selected Essays. Ed. Patrick F. Quinn and G.R. Thompson. New York: Library of America."
35
+
36
+
37
+ The RSpec examples are a valuable resource of usage examples.
38
+
39
+
40
+ Credits
41
+ -------
42
+
43
+ CiteProc-Ruby was written by [Sylvester Keil](http://sylvester.keil.or.at);
44
+ thanks to the excellent documentation and specifications of the
45
+ [CSL](http://citationstyles.org), [citeproc-js](http://bitbucket.org/fbennett/citeproc-js/wiki/Home),
46
+ the [citeproc-test suite](https://bitbucket.org/bdarcus/citeproc-test), and the
47
+ kind feedback and support at the [xbiblio mailing list](http://sourceforge.net/mail/?group_id=117435).
48
+
49
+
50
+ License
51
+ -------
52
+
53
+ Copyright 2009-2011 Sylvester Keil. All rights reserved.
54
+
55
+ Redistribution and use in source and binary forms, with or without
56
+ modification, are permitted provided that the following conditions are met:
57
+
58
+ 1. Redistributions of source code must retain the above copyright notice,
59
+ this list of conditions and the following disclaimer.
60
+
61
+ 2. Redistributions in binary form must reproduce the above copyright notice,
62
+ this list of conditions and the following disclaimer in the documentation
63
+ and/or other materials provided with the distribution.
64
+
65
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
66
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
67
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
68
+ EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
69
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
70
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
71
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
72
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
73
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
74
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
75
+
76
+ The views and conclusions contained in the software and documentation are
77
+ those of the authors and should not be interpreted as representing official
78
+ policies, either expressed or implied, of the copyright holder.
@@ -0,0 +1,100 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'open-uri'
5
+
6
+ require 'logging'
7
+ require 'nokogiri'
8
+ require 'json'
9
+
10
+ #require 'activesupport'
11
+
12
+ require 'unicode_utils/upcase'
13
+ require 'unicode_utils/downcase'
14
+
15
+ module CiteProc
16
+
17
+ @log = Logging.logger[self.name]
18
+ @log.add_appenders(Logging.appenders.stderr)
19
+
20
+ @log.level = ENV.has_key?('DEBUG') ? :debug : :info
21
+
22
+ class << self
23
+ def log(*args)
24
+ return @log if args.empty?
25
+
26
+ level, message, exception = args
27
+
28
+ @log.send(level, [message, exception && exception.message || nil].compact.join(': '))
29
+ @log.debug exception.backtrace[0,10].join("\n\t") unless exception.nil?
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ # Load debugger
36
+ require 'ruby-debug'
37
+ Debugger.start
38
+
39
+ require 'extensions/core'
40
+ require 'support/attributes'
41
+ require 'support/tree'
42
+
43
+ require 'csl/node'
44
+ require 'csl/term'
45
+ require 'csl/locale'
46
+ require 'csl/nodes'
47
+ require 'csl/sort'
48
+ require 'csl/renderer'
49
+ require 'csl/style'
50
+
51
+ require 'citeproc/version'
52
+ require 'citeproc/variable'
53
+ require 'citeproc/name'
54
+ require 'citeproc/date'
55
+ require 'citeproc/data'
56
+ require 'citeproc/selector'
57
+ require 'citeproc/item'
58
+ require 'citeproc/bibliography'
59
+ require 'citeproc/formatter'
60
+ require 'citeproc/processor'
61
+
62
+ # Load filter and format plugins
63
+ Dir.glob("#{File.expand_path('..', __FILE__)}/plugins/formats/*.rb").each do |format|
64
+ require format
65
+ end
66
+
67
+ require 'plugins/formats/default'
68
+
69
+ Dir.glob("#{File.expand_path('..', __FILE__)}/plugins/filters/*.rb").each do |format|
70
+ require format
71
+ end
72
+
73
+
74
+ # Top-level CSL utility functions
75
+
76
+ module CiteProc
77
+
78
+ module_function
79
+
80
+ def default_format; Format.default; end
81
+
82
+ def process(*arguments, &block); Processor.process(*arguments, &block); end
83
+
84
+ end
85
+
86
+ module CSL
87
+
88
+ module_function
89
+
90
+ def default_locale
91
+ Locale.new(Locale.default)
92
+ end
93
+
94
+ def default_style
95
+ Style.new(Style.default)
96
+ end
97
+
98
+ def process(*arguments, &block); CiteProc.process(*arguments, &block); end
99
+
100
+ end
@@ -0,0 +1,57 @@
1
+ module CiteProc
2
+
3
+ # A bibliography is an array of bibliographic entries and, optionally,
4
+ # a list of errors. The bibliography should be format agnostic; it is
5
+ # simply encapsulates two lists.
6
+ class Bibliography
7
+
8
+ def initialize(*args)
9
+ args.each { |argument| parse_argument(argument) }
10
+
11
+ yield self if block_given?
12
+ end
13
+
14
+ def data; @data ||= []; end
15
+ def errors; @errors ||= []; end
16
+ def options; @options ||= {}; end
17
+
18
+ # @data proxy
19
+ [:[], :[]=, :<<, :map, :each, :empty?, :push, :pop, :unshift, :+, :concat].each do |method_id|
20
+ define_method method_id do |*args, &block|
21
+ @data.send(method_id, *args, &block)
22
+ end
23
+ end
24
+
25
+ def to_json
26
+ [options.merge('bibliography-errors' => errors), data].to_json
27
+ end
28
+
29
+ def to_s
30
+ [options['bibstart'] || '<div class="csl-bib-body">', data.map { |d| " <div class=\"csl-entry\">#{d}</div>" }, options['bibend'] || '</div>'].flatten.join("\n")
31
+ end
32
+
33
+ protected
34
+
35
+ def parse_argument(argument)
36
+ case
37
+ when argument.is_a?(String)
38
+ parse_argument(JSON.parse(argument))
39
+ when argument.is_a?(Hash)
40
+ parse_attributes(argument)
41
+ when argument.is_a?(Array) && argument.length == 2 && argument[0].is_a?(Hash) && argument[1].is_a?(Array)
42
+ parse_attributes(argument[0])
43
+ @data = argument[1]
44
+ when argument.is_a?(Array)
45
+ @data = argument
46
+ else
47
+ CiteProc.log.warn "failed to initialize Bibliography from argument #{ argument.inspect }." unless argument.nil?
48
+ end
49
+ end
50
+
51
+ def parse_attributes(attributes)
52
+ @errors = attributes.delete('bibliography-errors') || []
53
+ @options = {}.merge(attributes)
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,149 @@
1
+ module CiteProc
2
+
3
+
4
+ # == CiteProc::Data
5
+ #
6
+ # A minimal citation data object, used as input by both the
7
+ # processCitationCluster() and appendCitationCluster() command, has the
8
+ # following form:
9
+ #
10
+ # {
11
+ # "citationItems": [ { "id": "ITEM-1" } ],
12
+ # "properties": {"noteIndex": 1 }
13
+ # }
14
+ #
15
+ # The citationItems array is a list of one or more citation item objects,
16
+ # each containing an id used to retrieve the bibliographic details of the
17
+ # target resource. A citation item object may contain one or more
18
+ # additional optional values:
19
+ #
20
+ # * locator: a string identifying a page number or other pinpoint location
21
+ # or range within the resource;
22
+ # * label: a label type, indicating whether the locator is to a page, a
23
+ # chapter, or other subdivision of the target resource. Valid labels are
24
+ # defined in the link CSL specification.
25
+ # * suppress-author: if true, author names will not be included in the
26
+ # citation output for this cite;
27
+ # * author-only: if true, only the author name will be included in the
28
+ # citation output for this cite -- this optional parameter provides a
29
+ # means for certain demanding styles that require the processor output
30
+ # to be divided between the main text and a footnote.
31
+ # * prefix: a string to print before this cite item;
32
+ # * suffix: a string to print after this cite item.
33
+ #
34
+ # In the properties portion of a citation, the noteIndex value indicates
35
+ # the footnote number in which the citation is located within the
36
+ # document. Citations within the main text of the document have a
37
+ # noteIndex of zero.
38
+ #
39
+ # The processor will add a number of data items to a citation during
40
+ # processing. Values added at the top level of the citation structure
41
+ # include:
42
+ #
43
+ # * citationID: A unique ID assigned to the citation, for internal use by
44
+ # the processor. This ID may be assigned by the calling application, but
45
+ # it must uniquely identify the citation, and it must not be changed
46
+ # during processing or during an editing session.
47
+ # * sortedItems: This is an array of citation objects and accompanying
48
+ # bibliographic data objects, sorted as required by the configured
49
+ # style. Calling applications should not need to access the data in this
50
+ # array directly.
51
+ #
52
+ # Values added to individual citation item objects may include:
53
+ #
54
+ # * sortkeys: an array of sort keys used by the processor to produce the
55
+ # sorted list in sortedItems. Calling applications should not need to
56
+ # touch this array directly.
57
+ # * position: an integer flag that indicates whether the cite item should
58
+ # be rendered as a first reference, an immediately-following reference
59
+ # (i.e. ibid), an immediately-following reference with locator
60
+ # information, or a subsequent reference.
61
+ # * first-reference-note-number: the number of the noteIndex of the first
62
+ # reference to this resource in the document.
63
+ # * near-note: a boolean flag indicating whether another reference to this
64
+ # resource can be found within a specific number of notes, counting back
65
+ # from the current position. What is "near" in this sense is
66
+ # style-dependent.
67
+ # * unsorted: a boolean flag indicating whether sorting imposed by the
68
+ # style should be suspended for this citation. When true, cites are
69
+ # rendered in the order in which they are presented in citationItems.
70
+ #
71
+ class CitationData
72
+ include Support::Attributes
73
+
74
+ attr_fields %w{ citation-id citation-items properites sorted-items }
75
+
76
+
77
+ def initialize(attributes={})
78
+
79
+ self.key_filter = Hash.new do |hash, key|
80
+ hash[key] = key.to_s.gsub(/([[:lower:]])([[:upper:]])/, '\1-\2').downcase
81
+ end
82
+
83
+ merge!(attributes)
84
+
85
+ yield self if block_given?
86
+ end
87
+
88
+ # @returns a list of citation data
89
+ def self.parse(argument)
90
+ return [] if argument.nil?
91
+ argument = [argument] unless argument.kind_of?(Array)
92
+ argument.map { |d| CitationData.new(d) }
93
+ end
94
+
95
+ #
96
+ # Merges the argument into the citation data. The argument can be a list
97
+ # of citation items (hashes), a single citation item (hash), another
98
+ # citation data instance or hash, or a single id of a citation item.
99
+ #
100
+ def merge!(argument)
101
+ case
102
+ when argument.is_a?(Array) && argument.map(&:class).uniq == [Hash]
103
+ super('citation-items' => argument.map { |argument| Item.new(argument) })
104
+
105
+ when argument.is_a?(Array) && (argument.empty? || argument.map(&:class).uniq == [Item])
106
+ super('citation-items' => argument)
107
+
108
+ when argument.is_a?(Hash)
109
+ argument.has_key?('id') ? super('citation-items' => [Item.new(argument)]) : super(argument)
110
+
111
+ when argument.is_a?(String) || argument.is_a?(Symbol)
112
+ super('citation-items' => [{ 'id' => argument.to_s }])
113
+
114
+ when argument.is_a?(CitationData)
115
+ super(argument.attributes)
116
+
117
+ else
118
+ raise(ArgumentError, "unable to merge #{argument.inspect} into citation data")
119
+ end
120
+ end
121
+
122
+ def citation_items
123
+ attributes['citation-items'] ||= []
124
+ end
125
+
126
+ def populate!(items)
127
+ citation_items.each { |item| item.reverse_merge!(items[item.id.to_s]) }
128
+ self
129
+ end
130
+
131
+ def properties
132
+ self.attributes['properties'] ||= {}
133
+ end
134
+
135
+ [[:items, :citation_items], [:id, :citation_id]].each do |a, m|
136
+ alias_method a, m
137
+ alias_method "#{a}=", "#{m}="
138
+ alias_method "#{a}?", "#{m}?"
139
+ end
140
+
141
+ [:each, :map, :empty?, :first, :last, :sort].each do |method_id|
142
+ define_method method_id do |*args, &block|
143
+ self.items.send(method_id, *args, &block)
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,133 @@
1
+ require 'date'
2
+
3
+ module CiteProc
4
+
5
+
6
+ # == Date Variables
7
+ #
8
+ # Date objects wrap an underlying JavaScript object, within which the
9
+ # "date-parts" element is a nested JavaScript array containing a start date
10
+ # and optional end date, each of which consists of a year, an optional month
11
+ # and an optional day, in that order if present. Additionally, the string
12
+ # fields "season", "literal", as well as the boolean field "circa" are
13
+ # supported.
14
+ #
15
+ class Date < Variable
16
+
17
+ attr_fields %w{ date-parts season circa literal }
18
+
19
+ Variable.date_fields.each { |field| Variable.types[field] = Date }
20
+
21
+ [:year, :month, :day].each_with_index do |method_id, index|
22
+ define_method method_id do
23
+ date_parts[0].nil? ? nil : date_parts[0][index]
24
+ end
25
+
26
+ define_method [method_id, '='].join do |value|
27
+ date_parts[0] = [] if date_parts[0].nil?
28
+ date_parts[0][index] = value.to_i
29
+ end
30
+ end
31
+
32
+ def defaults
33
+ Hash['delimiter', '-']
34
+ end
35
+
36
+ def parse!(argument)
37
+ return super unless argument.is_a?(::Date) || argument.is_a?(String)
38
+ parse_date!(date)
39
+ end
40
+
41
+ def merge!(argument)
42
+ case
43
+ when argument.has_key?('raw')
44
+ parse_date!(argument.delete('raw'))
45
+ argument.delete('date-parts')
46
+ when argument.has_key?('date-parts')
47
+ argument['date-parts'].map! { |parts| parts.map(&:to_i) }
48
+ end
49
+ super
50
+ end
51
+
52
+ def parse_date!(date)
53
+ # TODO find out what the Ruby parser can do
54
+ date = ::Date.parse(date) unless date.is_a?(::Date)
55
+ date_parts[0] = [date.year, date.month, date.day]
56
+ self
57
+ end
58
+
59
+ def date_parts
60
+ attributes['date-parts'] ||= []
61
+ end
62
+
63
+ alias :parts :date_parts
64
+ alias :parts= :date_parts=
65
+
66
+ def range?
67
+ parts[1] && !parts[1].empty?
68
+ end
69
+
70
+ def open_range?
71
+ self.range? && parts[1].uniq == [0]
72
+ end
73
+
74
+ def uncertain!; self['circa'] = true; end
75
+
76
+ def bc?; year && year < 0; end
77
+ def ad?; !bc? && year < 1000; end
78
+
79
+ alias :uncertain? :circa?
80
+
81
+ def from
82
+ parts[0] || []
83
+ end
84
+
85
+ def to
86
+ Date.new('date-parts' => [parts[1] || []])
87
+ end
88
+
89
+ # @returns a value in 0..3 depending on how many of the date parts in the
90
+ # range match.
91
+ def range_match
92
+ parts[0].zip(parts[1] || []).take_while { |p| p[0] == p[1] }.length
93
+ end
94
+
95
+ def display_parts
96
+ rm = range_match
97
+
98
+ case
99
+ when !range? || open_range?
100
+ [%w{day month year}, []]
101
+ when rm == 1
102
+ [%w{day month}, %w{day month year} ]
103
+ when rm == 2
104
+ [%w{day}, %w{day month year} ]
105
+ else
106
+ [%w{day month year}, %w{day month year} ]
107
+ end
108
+ end
109
+
110
+ def display(options={})
111
+ options = defaults.merge(options)
112
+ from.compact.join(options['delimiter'])
113
+ end
114
+
115
+ def to_s
116
+ literal || attributes.inspect
117
+ end
118
+
119
+ def value; self; end
120
+
121
+ def numeric?; false; end
122
+
123
+ def sort_order
124
+ "%04d%02d%02d-%04d%02d%02d" % ((parts[0] + [0,0,0])[0,3] + ((parts[1] || []) + [0,0,0])[0,3])
125
+ end
126
+
127
+ def <=>(other)
128
+ return nil unless other.is_a?(Date)
129
+ [year, sort_order] <=> [other.year, other.sort_order]
130
+ end
131
+ end
132
+
133
+ end