citeproc-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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