9 def initialize(fileName)
17 parseFileName!(fileName)
20 def self.canHandle?(fileName)
25 #puts "Filename: " + fileName.to_s
26 lowerName = fileName.downcase()
28 if lowerName.end_with?(".epub")
43 result.push('<b>' + @title + '</b>')
45 result.push('<i>(Unknown title)</i>')
48 result.push(@author.to_s())
53 seriesInfo.push(@series.to_s)
56 seriesInfo.push(@volume.to_s)
58 if seriesInfo.length > 0
59 result.push(seriesInfo.join(' '))
62 return result.join('<br/>')
68 data.push('author="' + @author.to_s + '"')
71 data.push('series="' + @series + '"')
74 data.push('volume="' + @volume + '"')
77 data.push('title="' + @title + '"')
80 data.push(@cover.inspect())
83 data.push('path="' + @path + '"')
85 return '(Book:' + data.join(',') + ')'
98 return /[[:upper:]]/.match(c)
102 def massageAuthor(input)
108 input.each_char do |c|
109 if isUpper?(c) and (result.length > 0)
118 # Returns (series, volumeNo, titleText)
120 def processTitle(input)
125 arr = input.split('_')
131 matchData = (arr[0]).match(/^([A-Z]+)([0-9]+)$/)
133 capt = matchData.captures
139 pos = arr[-1].rindex('.')
141 arr[-1] = arr[-1].slice(0, pos)
144 title = arr.join(' ')
146 return series, vol, title
150 def parseFileName!(fileName)
151 parts = fileName.split('/')
152 (@series, @volume, @title) = processTitle(parts[-1])
154 @author = massageAuthor(parts[-2])
157 if fileName.downcase.end_with?(".epub")
163 def scanEpub!(fileName)
164 #puts 'Scanning "' + fileName.to_s + '"...'
166 Zip::File.open(fileName) do |zipfile|
167 entry = zipfile.find_entry('META-INF/container.xml')
171 contXml = zipfile.read('META-INF/container.xml')
172 contDoc = Nokogiri::XML(contXml)
173 opfPath = contDoc.css("container rootfiles rootfile")[0]['full-path']
175 scanOpf!(zipfile, opfPath)
177 rescue Zip::Error => exc
178 puts 'ERROR processing file "' + fileName + '":'
185 def scanOpf!(zipfile, opfPath)
188 opfXml = zipfile.read(opfPath)
189 opfDoc = Nokogiri::XML(opfXml)
194 creator = opfDoc.css('dc|creator', 'dc' => 'http://purl.org/dc/elements/1.1/')
195 if (nil != creator) and (creator.length > 0)
196 roleNode = creator.attr('role')
198 role = roleNode.value
199 if ('aut' == role) and (creator.children.length > 0) and (nil != creator.children[0])
200 name = creator.children[0].content
201 parts = name.split(' ')
204 givenNames = parts[0..-2].join(' ')
205 @author = Author.new(surname, givenNames)
207 @author = Author.new(name, '')
213 #---------------------------------------
214 # Other metadata: series, volume, cover
216 metas = opfDoc.css('package metadata meta')
219 content = m['content']
221 if 'calibre:series' == name
223 elsif 'calibre:series-index' == name
225 elsif 'cover' == name
233 @cover = loadCover(zipfile, opfPath, opfDoc, coverId)
237 def loadCover(zipfile, opfPath, opfDoc, coverId)
240 coverId = "cover-image"
243 items = opfDoc.css('package manifest item')
247 mimeType = i['media-type']
250 entry = zipfile.find_entry(href)
253 # Although the epub standard requires the path to be relative
254 # to the base of the epub (zip), some books encountered in the
255 # wild have been found to use a bath relative to the location
257 parts = opfPath.split('/')
258 opfBasePath = opfPath.split('/')[0..-2].join('/')
259 coverPath = opfBasePath + '/' + href
260 entry = zipfile.find_entry(coverPath)
264 puts 'WARNING! Cover image "' + href + '" not found in file "' + @path + '".'
267 entry.get_input_stream() do |is|
268 return Cover.new(is, href, mimeType)