epub-cfi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d8aca7ef76271077d0c4026b20d05df02926a9ca
4
+ data.tar.gz: 14dabd7b660a4047585ffa63cb2ac31bd109125f
5
+ SHA512:
6
+ metadata.gz: 8eb1f1a5a5a5ba3e6fca187ffcadad7b796bc0ea680b5a2ef5b2659a4c5ed95986b57593c9622a0450033d084e00cf3ef6bef36c9e73ea15cf2b80ab4a011502
7
+ data.tar.gz: 213f294bb142f9ef4dd5402d0d3d8a702cdca7156d0cd7b298a0d0f512b1ac239593f9908b12d1b9235fc5b24205237c557308b81d8c702726c382104814b9d0
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ COPYING.txt
@@ -0,0 +1,8 @@
1
+ /lib/epub/cfi/parser.tab.rb
2
+ /.bundle
3
+ /.yardoc/
4
+ /Gemfile.lock
5
+ /doc/
6
+ /pkg/
7
+ /vendor/cache/*.gem
8
+ /coverage/
@@ -0,0 +1,19 @@
1
+ before_script:
2
+ - ruby -v
3
+ - which ruby
4
+ - gem install racc rake rubygems-tasks yard test-unit test-unit-notify pretty_backtrace simplecov
5
+
6
+ test:2.2:
7
+ image: ruby:2.2
8
+ script:
9
+ rake test
10
+
11
+ test:2.3:
12
+ image: ruby:2.3
13
+ script:
14
+ rake test
15
+
16
+ test:2.4:
17
+ image: ruby:2.4
18
+ script:
19
+ rake test
@@ -0,0 +1 @@
1
+ --markup markdown --title "EPUB CFI Documentation" --protected
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2017-03-12
2
+
3
+ * Initial release:
4
+
@@ -0,0 +1,85 @@
1
+ EPUB CFI
2
+ ========
3
+
4
+ * [Homepage](https://gitlab.com/KitaitiMakoto/epub-cfi)
5
+ * [Documentation](http://rubydoc.info/gems/epub-cfi/frames)
6
+ * [Email](mailto:KitaitiMakoto at gmail.com)
7
+
8
+ Description
9
+ -----------
10
+
11
+ Parser and builder implementation for EPUB CFI defined at http://www.idpf.org/epub/linking/cfi/
12
+
13
+ Extracted from [EPUB Parser][].
14
+
15
+ [EPUB Parser]: http://www.rubydoc.info/gems/epub-parser/file/docs/Home.markdown
16
+
17
+ Features
18
+ --------
19
+
20
+ * Parses EPUB CFI string.
21
+ * Builds EPUB CFI(implemented but currently useless).
22
+ * Converts EPUB CFI object to a string.
23
+
24
+ Examples
25
+ --------
26
+
27
+ require 'epub/cfi'
28
+
29
+ location = EPUB::CFI.parse("epubcfi(/6/14[chap05ref]!/4[body01]/10/2/1:3[2^[1^]])")
30
+ # Or location = EPUB::CFI("epubcfi(/6/14[chap05ref]!/4[body01]/10/2/1:3[2^[1^]])")
31
+ # => #<EPUB::CFI::Location:/6/14[chap05ref]!/4[body01]/10/2/1:3[2^[1^]]>
32
+ location.to_s # => "epubcfi(/6/14[chap05ref]!/4[body01]/10/2/1:3[2^[1^]])"
33
+
34
+ location1 = EPUB::CFI("/6/4[chap01ref]!/4[body01]/10[para05]/1:3[xx,y]")
35
+ location2 = EPUB::CFI("/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[yyy]")
36
+ location1 < location2 # => true
37
+ location1 <=> location1 # => -1
38
+ # location2 appears earlier than location1
39
+
40
+ range = EPUB::CFI("/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4")
41
+ # => #<EPUB::CFI::Location:/6/4[chap01ref]!/4[body01]/10[para05]/2/1:1>..#<EPUB::CFI::Location:/6/4[chap01ref]!/4[body01]/10[para05]/3:4>
42
+ range.kind_of? ::Range # => true
43
+ range.begin
44
+ # => #<EPUB::CFI::Location:/6/4[chap01ref]!/4[body01]/10[para05]/2/1:1>
45
+ range.end
46
+ # => #<EPUB::CFI::Location:/6/4[chap01ref]!/4[body01]/10[para05]/3:4>
47
+ range.start_subpath
48
+ # => "/2/1:1"
49
+ range.end_subpath
50
+ # => "/3:4"
51
+ range.parent_path
52
+ # => #<EPUB::CFI::Location:/6/4[chap01ref]!/4[body01]/10[para05]>
53
+ range.to_s # => "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)"
54
+
55
+ builtin_range = location2..location1
56
+ # => #<EPUB::CFI::Location:/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[yyy]>..#<EPUB::CFI::Location:/6/4[chap01ref]!/4[body01]/10[para05]/1:3[xx,y]>
57
+ builtin_range.to_s
58
+ # => "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[yyy])..epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/1:3[xx,y])"
59
+ builtin_range.kind_of? ::Range # => true
60
+ builtin_range.kind_of? EPUB::CFI::Range # => false
61
+ cfi_range = EPUB::CFI::Range.new(location2, location1)
62
+ # => #<EPUB::CFI::Location:/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[yyy]>..#<EPUB::CFI::Location:/6/4[chap01ref]!/4[body01]/10[para05]/1:3[xx,y]>
63
+ cfi_range.kind_of? ::Range # => true
64
+ cfi_range.kind_of? EPUB::CFI::Range # => true
65
+
66
+ Install
67
+ -------
68
+
69
+ $ gem install epub-cfi
70
+
71
+ See also
72
+ --------
73
+
74
+ * [EPUB Canonical Fragment Identifiers 1.1][spec]
75
+ * [EPUB Parser][]
76
+
77
+ [spec]: http://www.idpf.org/epub/linking/cfi/
78
+ [EPUB Parser]: http://www.rubydoc.info/gems/epub-parser/file/docs/Home.markdown
79
+
80
+ Copyright
81
+ ---------
82
+
83
+ Copyright (c) 2017 KITAITI Makoto
84
+
85
+ See {file:COPYING.txt} for details.
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ require 'rake'
3
+
4
+ task :default => "lib/epub/cfi/parser.tab.rb"
5
+
6
+ file "lib/epub/cfi/parser.tab.rb" do |target|
7
+ sh "racc #{target.name.sub("tab.rb", "y")}"
8
+ end
9
+
10
+ require "rake/clean"
11
+ CLEAN.include "lib/epub/cfi/parser.tab.rb"
12
+
13
+ require 'rubygems/tasks'
14
+ Gem::Tasks.new
15
+ task :build => [:clean, "lib/epub/cfi/parser.tab.rb"]
16
+
17
+ require 'rake/testtask'
18
+ Rake::TestTask.new do |test|
19
+ test.libs << 'test'
20
+ test.pattern = 'test/**/test_*.rb'
21
+ test.verbose = true
22
+ end
23
+ task :test => "lib/epub/cfi/parser.tab.rb"
24
+
25
+ require 'yard'
26
+ YARD::Rake::YardocTask.new
27
+ task :doc => :yard
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'epub/cfi/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = "epub-cfi"
9
+ gem.version = EPUB::CFI::VERSION
10
+ gem.summary = %q{EPUB CFI parser and builder}
11
+ gem.description = %q{Parser and builder implementation for EPUB CFI defined at http://www.idpf.org/epub/linking/cfi/}
12
+ gem.license = "LGPL"
13
+ gem.authors = ["KITAITI Makoto"]
14
+ gem.email = "KitaitiMakoto@gmail.com"
15
+ gem.homepage = "https://gitlab.com/KitaitiMakoto/epub-cfi"
16
+
17
+ gem.files = `git ls-files`.split($/) << "lib/epub/cfi/parser.tab.rb"
18
+
19
+ `git submodule --quiet foreach --recursive pwd`.split($/).each do |submodule|
20
+ submodule.sub!("#{Dir.pwd}/",'')
21
+
22
+ Dir.chdir(submodule) do
23
+ `git ls-files`.split($/).map do |subpath|
24
+ gem.files << File.join(submodule,subpath)
25
+ end
26
+ end
27
+ end
28
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
29
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
30
+ gem.require_paths = ['lib']
31
+
32
+ gem.add_runtime_dependency "racc"
33
+
34
+ gem.add_development_dependency 'rake'
35
+ gem.add_development_dependency 'rubygems-tasks'
36
+ gem.add_development_dependency 'yard'
37
+ gem.add_development_dependency "test-unit"
38
+ gem.add_development_dependency "test-unit-notify"
39
+ gem.add_development_dependency "pretty_backtrace"
40
+ gem.add_development_dependency "simplecov"
41
+ end
@@ -0,0 +1,309 @@
1
+ require 'epub/cfi/version'
2
+
3
+ module EPUB
4
+ module CFI
5
+ SPECIAL_CHARS = '^[](),;=' # "5E", "5B", "5D", "28", "29", "2C", "3B", "3D"
6
+ RE_ESCAPED_SPECIAL_CHARS = Regexp.escape(SPECIAL_CHARS)
7
+
8
+ class << self
9
+ def escape(string)
10
+ string.gsub(/([#{RE_ESCAPED_SPECIAL_CHARS}])/o, '^\1')
11
+ end
12
+
13
+ def unescape(string)
14
+ string.gsub(/\^([#{RE_ESCAPED_SPECIAL_CHARS}])/o, '\1')
15
+ end
16
+ end
17
+
18
+ class Location
19
+ include Comparable
20
+
21
+ attr_reader :paths
22
+
23
+ def initialize(paths=[])
24
+ @paths = paths
25
+ end
26
+
27
+ def initialize_copy(original)
28
+ @paths = original.paths.collect(&:dup)
29
+ end
30
+
31
+ def <=>(other)
32
+ index = 0
33
+ other_paths = other.paths
34
+ cmp = nil
35
+ paths.each do |path|
36
+ other_path = other_paths[index]
37
+ return 1 unless other_path
38
+ cmp = path <=> other_path
39
+ break unless cmp == 0
40
+ index += 1
41
+ end
42
+
43
+ unless cmp == 0
44
+ if cmp == 1 and paths[index].offset and other_paths[index + 1]
45
+ return nil
46
+ else
47
+ return cmp
48
+ end
49
+ end
50
+
51
+ return nil if paths.last.offset && other_paths[index]
52
+
53
+ return -1 if other_paths[index]
54
+
55
+ 0
56
+ end
57
+
58
+ def path_string
59
+ paths.join('!')
60
+ end
61
+
62
+ def to_s
63
+ "epubcfi(#{path_string})"
64
+ end
65
+
66
+ def inspect
67
+ "#<#{self.class}:#{path_string}>"
68
+ end
69
+
70
+ def join(*other_paths)
71
+ new_paths = paths.dup
72
+ other_paths.each do |path|
73
+ new_paths << path
74
+ end
75
+ self.class.new(new_paths)
76
+ end
77
+ end
78
+
79
+ class Path
80
+ attr_reader :steps, :offset
81
+
82
+ def initialize(steps=[], offset=nil)
83
+ @steps, @offset = steps, offset
84
+ end
85
+
86
+ def initialize_copy(original)
87
+ @steps = original.steps.collect(&:dup)
88
+ @offset = original.offset.dup if original.offset
89
+ end
90
+
91
+ def to_s
92
+ @string_cache ||= (steps.join + offset.to_s)
93
+ end
94
+
95
+ def <=>(other)
96
+ other_steps = other.steps
97
+ index = 0
98
+ steps.each do |step|
99
+ other_step = other_steps[index]
100
+ return 1 unless other_step
101
+ cmp = step <=> other_step
102
+ return cmp unless cmp == 0
103
+ index += 1
104
+ end
105
+
106
+ return -1 if other_steps[index]
107
+
108
+ other_offset = other.offset
109
+ if offset
110
+ if other_offset
111
+ offset <=> other_offset
112
+ else
113
+ 1
114
+ end
115
+ else
116
+ if other_offset
117
+ -1
118
+ else
119
+ 0
120
+ end
121
+ end
122
+ end
123
+
124
+ def each_step_with_instruction
125
+ yield [step, nil]
126
+ local_path.each_step_with_instruction do |s, instruction|
127
+ yield [s, instruction]
128
+ end
129
+ self
130
+ end
131
+ end
132
+
133
+ class Range < ::Range
134
+ attr_accessor :parent_path, :start_subpath, :end_subpath
135
+
136
+ # @todo consider the case subpaths are redirected path
137
+ # @todo FIXME: too dirty
138
+ class << self
139
+ def from_parent_and_start_and_end(parent_path, start_subpath, end_subpath)
140
+ start_str = start_subpath.join
141
+ end_str = end_subpath.join
142
+
143
+ first_paths = parent_path.collect(&:dup)
144
+ if start_subpath
145
+ offset_of_first = start_subpath.last.offset
146
+ offset_of_first = offset_of_first.dup if offset_of_first
147
+ last_of_first_paths = first_paths.pop
148
+ first_paths << last_of_first_paths
149
+ last_of_first_paths.steps.concat start_subpath.shift.steps
150
+ first_paths.concat start_subpath
151
+ first_paths.last.instance_variable_set :@offset, offset_of_first
152
+ end
153
+ offset_of_last = end_subpath.last.offset
154
+ offset_of_last = offset_of_last.dup if offset_of_last
155
+ last_paths = parent_path.collect(&:dup)
156
+ last_of_last_paths = last_paths.pop
157
+ last_paths << last_of_last_paths
158
+ last_of_last_paths.steps.concat end_subpath.shift.steps
159
+ last_paths.concat end_subpath
160
+ last_paths.last.instance_variable_set :@offset, offset_of_last
161
+
162
+ first = CFI::Location.new(first_paths)
163
+ last = CFI::Location.new(last_paths)
164
+
165
+ new_range = new(first, last)
166
+
167
+ new_range.parent_path = Location.new(parent_path)
168
+ new_range.start_subpath = start_str
169
+ new_range.end_subpath = end_str
170
+
171
+ new_range
172
+ end
173
+ end
174
+
175
+ def to_s
176
+ @string_cache ||= "epubcfi(#{@parent_path.path_string},#{@start_subpath},#{@end_subpath})"
177
+ end
178
+ end
179
+
180
+ class Step
181
+ attr_reader :value, :assertion
182
+ alias step value
183
+
184
+ def initialize(value, assertion=nil)
185
+ @value, @assertion = value, assertion
186
+ @string_cache = nil
187
+ end
188
+
189
+ def initialize_copy(original)
190
+ @value = original.value
191
+ @assertion = original.assertion.dup if original.assertion
192
+ end
193
+
194
+ def to_s
195
+ @string_cache ||= "/#{value}#{assertion}" # need escape?
196
+ end
197
+
198
+ def <=>(other)
199
+ value <=> other.value
200
+ end
201
+
202
+ def element?
203
+ value.even?
204
+ end
205
+
206
+ def character_data?
207
+ value.odd?
208
+ end
209
+ end
210
+
211
+ class IDAssertion
212
+ attr_reader :id, :parameters
213
+
214
+ def initialize(id, parameters={})
215
+ @id, @parameters = id, parameters
216
+ @string_cache = nil
217
+ end
218
+
219
+ def to_s
220
+ return @string_cache if @string_cache
221
+ string_cache = '['
222
+ string_cache << CFI.escape(id) if id
223
+ parameters.each_pair do |key, values|
224
+ value = values.join(',')
225
+ string_cache << ";#{CFI.escape(key)}=#{CFI.escape(value)}"
226
+ end
227
+ string_cache << ']'
228
+ @string_cache = string_cache
229
+ end
230
+ end
231
+
232
+ class TextLocationAssertion
233
+ attr_reader :preceded, :followed, :parameters
234
+
235
+ def initialize(preceded=nil, followed=nil, parameters={})
236
+ @preceded, @followed, @parameters = preceded, followed, parameters
237
+ @string_cache = nil
238
+ end
239
+
240
+ def to_s
241
+ return @string_cache if @string_cache
242
+ string_cache = '['
243
+ string_cache << CFI.escape(preceded) if preceded
244
+ string_cache << ',' << CFI.escape(followed) if followed
245
+ parameters.each_pair do |key, values|
246
+ value = values.join(',')
247
+ string_cache << ";#{CFI.escape(key)}=#{CFI.escape(value)}"
248
+ end
249
+ string_cache << ']'
250
+ @string_cache = string_cache
251
+ end
252
+ end
253
+
254
+ class CharacterOffset
255
+ attr_reader :value, :assertion
256
+ alias offset value
257
+
258
+ def initialize(value, assertion=nil)
259
+ @value, @assertion = value, assertion
260
+ @string_cache = nil
261
+ end
262
+
263
+ def to_s
264
+ @string_cache ||= ":#{value}#{assertion}" # need escape?
265
+ end
266
+
267
+ def <=>(other)
268
+ value <=> other.value
269
+ end
270
+ end
271
+
272
+ class TemporalSpatialOffset
273
+ attr_reader :temporal, :x, :y, :assertion
274
+
275
+ def initialize(temporal=nil, x=nil, y=nil, assertion=nil)
276
+ raise RangeError, "dimension must be in 0..100 but passed #{x}" unless (0.0..100.0).cover?(x) if x
277
+ raise RangeError, "dimension must be in 0..100 but passed #{y}" unless (0.0..100.0).cover?(y) if y
278
+ warn "Assertion is passed to #{__class__} but cannot know how to handle with it: #{assertion}" if assertion
279
+ @temporal, @x, @y, @assertion = temporal, x, y, assertion
280
+ @string_cache = nil
281
+ end
282
+
283
+ def to_s
284
+ return @string_cache if @string_cache
285
+ string_cache = ''
286
+ string_cache << "~#{temporal}" if temporal
287
+ string_cache << "@#{x}:#{y}" if x or y
288
+ @string_cache = string_cache
289
+ end
290
+
291
+ # @note should split the class to spatial offset and temporal-spatial offset?
292
+ def <=>(other)
293
+ return -1 if temporal.nil? and other.temporal
294
+ return 1 if temporal and other.temporal.nil?
295
+ cmp = temporal <=> other.temporal
296
+ return cmp unless cmp == 0
297
+ return -1 if y.nil? and other.y
298
+ return 1 if y and other.y.nil?
299
+ cmp = y <=> other.y
300
+ return cmp unless cmp == 0
301
+ return -1 if x.nil? and other.x
302
+ return 1 if x and other.x.nil?
303
+ cmp = x <=> other.x
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ require "epub/cfi/parser"