mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
opds(fix): unescape name and value
This commit is contained in:
@@ -20,62 +20,56 @@ local unescape_map = {
|
||||
local gsub = string.gsub
|
||||
local function unescape(str)
|
||||
return gsub(str, '(&(#?)([%d%a]+);)', function(orig, n, s)
|
||||
return unescape_map[s] or n=="#" and util.unichar(tonumber(s)) or orig
|
||||
if unescape_map[s] then
|
||||
return unescape_map[s]
|
||||
elseif n == "#" then -- unescape unicode
|
||||
return util.unichar(tonumber(s))
|
||||
else
|
||||
return orig
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function OPDSParser:createFlatXTable(xlex, currentelement)
|
||||
currentelement = currentelement or {}
|
||||
function OPDSParser:createFlatXTable(xlex, curr_element)
|
||||
curr_element = curr_element or {}
|
||||
|
||||
local currentattributename = nil;
|
||||
local attribute_count = 0;
|
||||
local curr_attr_name;
|
||||
local attr_count = 0;
|
||||
|
||||
-- start reading the thing
|
||||
local txt
|
||||
for event, offset, size in xlex:Lexemes() do
|
||||
txt = ffi.string(xlex.buf + offset, size)
|
||||
if event == luxl.EVENT_START then
|
||||
if txt ~= "xml" then
|
||||
-- does current element already have something
|
||||
-- with this name?
|
||||
|
||||
if event == luxl.EVENT_START and txt ~= "xml" then
|
||||
-- does current element already have something
|
||||
-- with this name?
|
||||
|
||||
-- if it does, if it's a table, add to it
|
||||
-- if it doesn't, then add a table
|
||||
local tab = self:createFlatXTable(xlex)
|
||||
if txt == "entry" or txt == "link" then
|
||||
if currentelement[txt] == nil then
|
||||
currentelement[txt] = {}
|
||||
-- if it does, if it's a table, add to it
|
||||
-- if it doesn't, then add a table
|
||||
local tab = self:createFlatXTable(xlex)
|
||||
if txt == "entry" or txt == "link" then
|
||||
if curr_element[txt] == nil then
|
||||
curr_element[txt] = {}
|
||||
end
|
||||
table.insert(curr_element[txt], tab)
|
||||
elseif type(curr_element) == "table" then
|
||||
curr_element[txt] = tab
|
||||
end
|
||||
table.insert(currentelement[txt], tab)
|
||||
elseif type(currentelement) == "table" then
|
||||
currentelement[txt] = tab
|
||||
end
|
||||
end
|
||||
|
||||
if event == luxl.EVENT_ATTR_NAME then
|
||||
currentattributename = txt
|
||||
end
|
||||
|
||||
if event == luxl.EVENT_ATTR_VAL then
|
||||
currentelement[currentattributename] = txt
|
||||
attribute_count = attribute_count + 1;
|
||||
currentattributename = nil
|
||||
end
|
||||
|
||||
if event == luxl.EVENT_TEXT then
|
||||
--if attribute_count < 1 then
|
||||
-- return txt
|
||||
--end
|
||||
|
||||
currentelement = unescape(txt)
|
||||
end
|
||||
|
||||
if event == luxl.EVENT_END then
|
||||
return currentelement
|
||||
elseif event == luxl.EVENT_ATTR_NAME then
|
||||
curr_attr_name = unescape(txt)
|
||||
elseif event == luxl.EVENT_ATTR_VAL then
|
||||
curr_element[curr_attr_name] = unescape(txt)
|
||||
attr_count = attr_count + 1;
|
||||
curr_attr_name = nil
|
||||
elseif event == luxl.EVENT_TEXT then
|
||||
curr_element = unescape(txt)
|
||||
elseif event == luxl.EVENT_END then
|
||||
return curr_element
|
||||
end
|
||||
end
|
||||
|
||||
return currentelement
|
||||
return curr_element
|
||||
end
|
||||
|
||||
function OPDSParser:parse(text)
|
||||
|
||||
@@ -367,74 +367,83 @@ end
|
||||
|
||||
function OPDSBrowser:genItemTableFromCatalog(catalog, item_url)
|
||||
local item_table = {}
|
||||
if catalog then
|
||||
local feed = catalog.feed or catalog
|
||||
local function build_href(href)
|
||||
--DEBUG("building href", item_url, href)
|
||||
return url.absolute(item_url, href)
|
||||
end
|
||||
local hrefs = {}
|
||||
if feed.link then
|
||||
for _, link in ipairs(feed.link) do
|
||||
if link.type ~= nil then
|
||||
if link.type:find(self.catalog_type) or
|
||||
link.type:find(self.search_type) then
|
||||
if link.rel and link.href then
|
||||
hrefs[link.rel] = build_href(link.href)
|
||||
end
|
||||
if not catalog then
|
||||
return item_table
|
||||
end
|
||||
|
||||
local feed = catalog.feed or catalog
|
||||
|
||||
local function build_href(href)
|
||||
return url.absolute(item_url, href)
|
||||
end
|
||||
|
||||
local hrefs = {}
|
||||
if feed.link then
|
||||
for _, link in ipairs(feed.link) do
|
||||
if link.type ~= nil then
|
||||
if link.type:find(self.catalog_type) or
|
||||
link.type:find(self.search_type) then
|
||||
if link.rel and link.href then
|
||||
hrefs[link.rel] = build_href(link.href)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
item_table.hrefs = hrefs
|
||||
if feed.entry then
|
||||
for _, entry in ipairs(feed.entry) do
|
||||
local item = {}
|
||||
item.acquisitions = {}
|
||||
if entry.link then
|
||||
for _, link in ipairs(entry.link) do
|
||||
if link.type:find(self.catalog_type) and (not link.rel or link.rel == "subsection" or link.rel == "http://opds-spec.org/sort/popular" or link.rel == "http://opds-spec.org/sort/new") then
|
||||
item.url = build_href(link.href)
|
||||
end
|
||||
if link.rel and link.rel:match(self.acquisition_rel) then
|
||||
table.insert(item.acquisitions, {
|
||||
type = link.type,
|
||||
--DEBUG("building acquisition url", link);
|
||||
href = build_href(link.href),
|
||||
})
|
||||
end
|
||||
if link.rel == self.thumbnail_rel then
|
||||
item.thumbnail = build_href(link.href)
|
||||
end
|
||||
if link.rel == self.image_rel then
|
||||
item.image = build_href(link.href)
|
||||
end
|
||||
end
|
||||
item_table.hrefs = hrefs
|
||||
|
||||
if not feed.entry then
|
||||
return item_table
|
||||
end
|
||||
|
||||
for _, entry in ipairs(feed.entry) do
|
||||
local item = {}
|
||||
item.acquisitions = {}
|
||||
if entry.link then
|
||||
for _, link in ipairs(entry.link) do
|
||||
if link.type:find(self.catalog_type)
|
||||
and (not link.rel
|
||||
or link.rel == "subsection"
|
||||
or link.rel == "http://opds-spec.org/sort/popular"
|
||||
or link.rel == "http://opds-spec.org/sort/new") then
|
||||
item.url = build_href(link.href)
|
||||
end
|
||||
if link.rel then
|
||||
if link.rel:match(self.acquisition_rel) then
|
||||
table.insert(item.acquisitions, {
|
||||
type = link.type,
|
||||
href = build_href(link.href),
|
||||
})
|
||||
elseif link.rel == self.thumbnail_rel then
|
||||
item.thumbnail = build_href(link.href)
|
||||
elseif link.rel == self.image_rel then
|
||||
item.image = build_href(link.href)
|
||||
end
|
||||
end
|
||||
local title = "Unknown"
|
||||
if type(entry.title) == "string" then
|
||||
title = entry.title
|
||||
elseif type(entry.title) == "table" then
|
||||
if type(entry.title.type) == "string" and entry.title.div ~= "" then
|
||||
title = entry.title.div
|
||||
end
|
||||
end
|
||||
if title == "Unknown" then
|
||||
DEBUG("Cannot handle title", entry.title)
|
||||
end
|
||||
local author = "Unknown Author"
|
||||
if type(entry.author) == "table" and entry.author.name then
|
||||
author = entry.author.name
|
||||
end
|
||||
item.text = title
|
||||
item.title = title
|
||||
item.author = author
|
||||
item.id = entry.id
|
||||
item.content = entry.content
|
||||
item.updated = entry.updated
|
||||
table.insert(item_table, item)
|
||||
end
|
||||
end
|
||||
local title = "Unknown"
|
||||
if type(entry.title) == "string" then
|
||||
title = entry.title
|
||||
elseif type(entry.title) == "table" then
|
||||
if type(entry.title.type) == "string" and entry.title.div ~= "" then
|
||||
title = entry.title.div
|
||||
end
|
||||
end
|
||||
if title == "Unknown" then
|
||||
DEBUG("Cannot handle title", entry.title)
|
||||
end
|
||||
local author = "Unknown Author"
|
||||
if type(entry.author) == "table" and entry.author.name then
|
||||
author = entry.author.name
|
||||
end
|
||||
item.text = title
|
||||
item.title = title
|
||||
item.author = author
|
||||
item.id = entry.id
|
||||
item.content = entry.content
|
||||
item.updated = entry.updated
|
||||
table.insert(item_table, item)
|
||||
end
|
||||
return item_table
|
||||
end
|
||||
|
||||
@@ -48,7 +48,7 @@ which contains *all* Project Gutenberg metadata in one RDF/XML file.
|
||||
<id>http://m.gutenberg.org/ebooks/search.opds/?sort_order=random</id>
|
||||
<title>Random</title>
|
||||
<content type="text">Random books.</content>
|
||||
<link type="application/atom+xml;profile=opds-catalog" rel="subsection" href="/ebooks/search.opds/?sort_order=random"/>
|
||||
<link type="application/atom+xml;profile=opds-catalog" rel="subsection" href="/ebooks/search.opds/?sort_order=random&limit=5"/>
|
||||
<link type="image/png" rel="http://opds-spec.org/image/thumbnail" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAW5SURBVHjaYvz//z8DCPj4+DB8+/aNgZGRkYGJiYnhz58/DCA5VlZWhu/fv4PYXry8vCkqKiquT58+vf3lyxc7dnb2L//+/QPrAQEQvWXLFjAbIIBYGLAAkIF///4FGS4EpPMkJSWjraysFJydnVkUFRUZjhw5Yjh58uTdP3/+tGRjY8NmBANAAKEYDLL99+/fQAczuXBycpbr6OiY2dra8hgZGYHcw3D06BGGp0+fMFhZWYMstpgyZcrhX79+2QJdzgDzOQwABBDcYKDr1IBeyVBTU/MzMTFRsrS0ZNTW1gYHBShoZs6cy1BTU8ugpKTCsGTJPAYnJyeQHptZs2YdBDrGGajuD7LBAAEEN1hGRuZgRESEhJaWFoOUlBRY7MePHwxfv35lYGNjZXB2tmdQVFzCICAgwMDDw83w4cMHBgcHB5Av7YCG7wAa7gJyBAwABBATksHiNjY2YENBEQf0IjgyeHh4GDZv3s4QFBTOMG/ePKC8GAMXFxdYDhTZIJenpKQ4A321GaQPBgACCG4wMCL+nTp1iuHt27cMLCwsYO+DNIMwKMLs7JwYlJXVgJb+BatnZmaGG+7i4sIQGxvvAwzrdTDzAAIIOfL+Ab3DfPXqVQZgGINdBYrMly9fMrx69YyhsDADaLAyAzCZgRwBNJQJaDkowv+Dfecf6MXw7Pljf5hhAAHEhGwwyBWgNHvnzh1wcgO5esaMOQzh4dEMRUXlYDmQgawsbMBwZ2fg4GBlEBHlZODg+s3w888HBm5uLrhhAAHEgpR2/4GSDCgYPn/+zPDo0SMGeXl5hsjIcAYtLV0GGWlJoPdBKQRoIfMfoMVfGYBuZTh99iZQjJlBVU2F4dOHT/BABgggFBeDDAa5kp+fH2z4mzdvGNSAGkJD/RgsrUyBrgQZ+p7hx8/nwGT9jeHihdsMkaG5DCuWrWPg4ORk+P7jx1+YYQABxITsYiYmRrB3QeEMipgPHz4yfPr0ASj3luHP38cMd++eY3j56ikorzCwsLIxPHr8kkFGTo7B08uN4euX70A1iGQBEEDIBv9lZWVhePfuA0NpaQ0wnG8zsLNzAGP9HdCSLwxnzlxncHdPZVi/djeDIJ8ww4/vfxhsbA0YFizqYdDT12b48vUbMMX8gxsMEEBwg4FB8B8Uw6KiosAsawNUBMqi/6EFDBvD6VM3GUBZ18zciGHhog0MiQmFQDW/GWRkJRj+gXPuP4a/f/7+gpkHEEDwyAPmsG+/fv0RBMV0aWku2JCfP38xsHMwM/z8/Y0hMNCWITDInoGTi5shI72VgZuHgYEVmDLWrdvFICTEz6CurgFKSb9h5gEEENzFwKKw8/z5c/+4gBr/gX0EKg7Bjmb48/svg7AoHzBlALM643+GsooUhjnz+4DBc5MhKbaA4eCBo+Bg+/P790+YeQABBDcY6OXJx44dmwqKOE5OLgaUwgqYdn/+/MuwdNkGhhs37jD4BzgxSEqKMbx/+4EhMjqYISIqFJiKvoIiD54qAAIIbjAHBwcwObHl7dy5c9fdu3eBiZ0bXBSCzOfkZGfYtu0EQ0x0IcPRI2cYfgP1f/z4hcHH35GhqbWC4d3bjwxrV6378enTp/Uw8wACCKU8BuU8YPYM3LRp01kxMVENWVkFhn9/vwPLaGDE/P3HkJWTwBAU7A20kAnowu8Mx46cY7hy8drbe/fub//+4/tMYDFwBGYWQAAxwgro6OhoeGEPLC7FFBTkr6ampouIifMBI/A7MN1yAF3KxHD39kOGE8fP/blw4fKDt6/fTANG2HxgivrACixaQQ5bunQp2ByAAMJaNQFTxKuHDx/5r1ixYm9mdjLH369/GC5dusBw8sT5r3fu3D/+4cP7LhYW5t1MQIOYWZgZgIUBhhkAAYTVYBAAhvexmzdv5M2ZvXDij+/fP92//2gZMEku4uPjuwBKiuDkwgBNNlgAQIABAEwOYZ0sPGU2AAAAAElFTkSuQmCC"/>
|
||||
</entry>
|
||||
</feed>
|
||||
@@ -236,7 +236,6 @@ describe("OPDS module #nocov", function()
|
||||
it("should parse OPDS navigation catalog", function()
|
||||
local catalog = OPDSParser:parse(navigation_sample)
|
||||
local feed = catalog.feed
|
||||
--DEBUG(feed)
|
||||
assert.truthy(feed)
|
||||
assert.are.same(feed.title, "Project Gutenberg")
|
||||
local entries = feed.entry
|
||||
@@ -245,17 +244,18 @@ describe("OPDS module #nocov", function()
|
||||
local entry = entries[3]
|
||||
assert.are.same(entry.title, "Random")
|
||||
assert.are.same(entry.id, "http://m.gutenberg.org/ebooks/search.opds/?sort_order=random")
|
||||
assert.are.same(
|
||||
"/ebooks/search.opds/?sort_order=random&limit=5",
|
||||
entry.link[1].href)
|
||||
end)
|
||||
it("should parse OPDS acquisition catalog", function()
|
||||
local catalog = OPDSParser:parse(acquisition_sample)
|
||||
local feed = catalog.feed
|
||||
--DEBUG(feed)
|
||||
assert.truthy(feed)
|
||||
local entries = feed.entry
|
||||
assert.truthy(entries)
|
||||
assert.are.same(#entries, 2)
|
||||
local entry = entries[2]
|
||||
--DEBUG(entry)
|
||||
assert.are.same(entry.title, "1000 Mythological Characters Briefly Described")
|
||||
assert.are.same(entry.link[1].href, "//www.gutenberg.org/ebooks/42474.epub.images")
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user