import datetime
import pytz
import re
from lxml import etree
from regluit.core import models
from regluit.core.cc import ccinfo
from regluit.bisac import Bisac
from .crosswalks import relator_contrib, iso639
feed_xml = """
"""
bisac = Bisac()
def text_node(tag, text, attrib={}):
node = etree.Element(tag, attrib=attrib)
node.text = text
return node
def onix_feed(facet, max=None):
feed = etree.fromstring(feed_xml)
feed.append(header(facet))
works = facet.works[0:max] if max else facet.works
for work in works:
editions = models.Edition.objects.filter(work=work,ebooks__isnull=False)
editions = facet.facet_object.filter_model("Edition",editions).distinct()
for edition in editions:
edition_prod = product(edition, facet.facet_object)
if edition_prod is not None:
feed.append(edition_prod)
return etree.tostring(feed, pretty_print=True)
def onix_feed_for_work(work):
feed = etree.fromstring(feed_xml)
feed.append(header(work))
for edition in models.Edition.objects.filter(work=work,ebooks__isnull=False).distinct():
edition_prod = product(edition)
if edition_prod is not None:
feed.append(product(edition))
return etree.tostring(feed, pretty_print=True)
def header(facet=None):
header_node = etree.Element("Header")
sender_node = etree.Element("Sender")
sender_node.append(text_node("SenderName", "unglue.it"))
sender_node.append(text_node("EmailAddress", "unglueit@ebookfoundation.org"))
header_node.append(sender_node)
header_node.append(text_node("SentDateTime", pytz.utc.localize(datetime.datetime.utcnow()).strftime('%Y%m%dT%H%M%SZ')))
header_node.append(text_node("MessageNote", facet.title if facet else "Unglue.it Editions"))
return header_node
def product(edition, facet=None):
ebooks=facet.filter_model("Ebook",edition.ebooks.filter(active=True)) if facet else edition.ebooks.filter(active=True)
ebooks=ebooks.order_by('-created')
# Just because an edition satisfies 2 facets with multiple ebooks doesn't mean that there is a single ebook satisfies both facets
if not ebooks.exists():
return None
work=edition.work
product_node = etree.Element("Product")
product_node.append(text_node("RecordReference", "it.unglue.work.%s.%s" % (work.id, edition.id)))
product_node.append(text_node("NotificationType", "03" )) # final
ident_node = etree.SubElement(product_node, "ProductIdentifier")
ident_node.append(text_node("ProductIDType", "01" )) #proprietary
ident_node.append(text_node("IDTypeName", "unglue.it edition id" )) #proprietary
ident_node.append(text_node("IDValue", unicode(edition.id) ))
# wrong isbn better than no isbn
isbn = edition.isbn_13 if edition.isbn_13 else edition.work.first_isbn_13()
if isbn:
ident_node = etree.SubElement(product_node, "ProductIdentifier")
ident_node.append(text_node("ProductIDType", "03" )) #proprietary
ident_node.append(text_node("IDValue", isbn ))
# Descriptive Detail Block
descriptive_node = etree.SubElement(product_node, "DescriptiveDetail")
descriptive_node.append(text_node("ProductComposition", "00" )) # single item
descriptive_node.append(text_node("ProductForm", "ED" )) # download
ebook = None
latest_ebooks = []
ebook_formats = []
for ebook in ebooks:
if ebook.format not in ebook_formats:
ebook_formats.append(ebook.format)
latest_ebooks.append(ebook)
if ebook.format=='epub':
descriptive_node.append(text_node("ProductFormDetail", "E101" ))
elif ebook.format=='pdf':
descriptive_node.append(text_node("ProductFormDetail", "E107" ))
elif ebook.format=='mobi':
descriptive_node.append(text_node("ProductFormDetail", "E116" ))
if ebook.rights:
license_node = etree.SubElement(descriptive_node, "EpubLicense")
license_node.append(text_node("EpubLicenseName", ebook.rights ))
lic_expr_node = etree.SubElement(license_node, "EpubLicenseExpression")
lic_expr_node.append(text_node("EpubLicenseExpressionType", '01' )) #human readable
lic_expr_node.append(text_node("EpubLicenseExpressionLink", ccinfo(ebook.rights).url ))
title_node = etree.SubElement(descriptive_node, "TitleDetail")
title_node.append(text_node("TitleType", '01' )) #distinctive title
title_el = etree.SubElement(title_node, "TitleElement")
title_el.append(text_node("TitleElementLevel", '01' ))
title_el.append(text_node("TitleText", edition.title ))
contrib_i = 0
for contrib in edition.relators.all():
contrib_i+=1
contrib_node = etree.SubElement(descriptive_node, "Contributor")
contrib_node.append(text_node("SequenceNumber", unicode(contrib_i )))
contrib_node.append(text_node("ContributorRole", relator_contrib.get(contrib.relation.code,"") ))
contrib_node.append(text_node("PersonName", contrib.author.name))
contrib_node.append(text_node("PersonNameInverted", contrib.author.last_name_first))
(lang, locale) = (edition.work.language, None)
if '_' in lang:
(lang, locale) = lang.split('_')
if len(lang)==2:
lang = iso639.get(lang, None)
if lang:
lang_node = etree.SubElement(descriptive_node, "Language")
lang_node.append(text_node("LanguageRole", "01"))
lang_node.append(text_node("LanguageCode", lang))
if locale:
lang_node.append(text_node("CountryCode", locale))
for subject in work.subjects.all():
subj_node = etree.SubElement(descriptive_node, "Subject")
if subject.authority == 'lcsh':
subj_node.append(text_node("SubjectSchemeIdentifier", "04"))
subj_node.append(text_node("SubjectHeadingText", subject.name))
elif subject.authority == 'lcc':
subj_node.append(text_node("SubjectSchemeIdentifier", "03"))
subj_node.append(text_node("SubjectCode", subject.name))
elif subject.authority == 'bisacsh':
subj_node.append(text_node("SubjectSchemeIdentifier", "10"))
subj_node.append(text_node("SubjectCode", bisac.code(subject.name)))
subj_node.append(text_node("SubjectHeadingText", subject.name))
else:
subj_node.append(text_node("SubjectSchemeIdentifier", "20"))
subj_node.append(text_node("SubjectHeadingText", subject.name))
# audience range composite
if work.age_level:
range_match = re.search(r'(\d?\d?)-(\d?\d?)', work.age_level)
if range_match:
audience_range_node = etree.SubElement(descriptive_node, "AudienceRange")
audience_range_node.append(text_node("AudienceRangeQualifier", "17")) #Interest age, years
if range_match.group(1):
audience_range_node.append(text_node("AudienceRangePrecision", "03")) #from
audience_range_node.append(text_node("AudienceRangeValue", range_match.group(1)))
if range_match.group(2):
audience_range_node.append(text_node("AudienceRangePrecision", "04")) #from
audience_range_node.append(text_node("AudienceRangeValue", range_match.group(2)))
# Collateral Detail Block
coll_node = etree.SubElement(product_node, "CollateralDetail")
desc_node = etree.SubElement(coll_node, "TextContent")
desc_node.append(text_node("TextType", '03')) # description
desc_node.append(text_node("ContentAudience", '00')) #unrestricted
desc = (work.description if work.description else '') + '
Listed by Unglue.it.' % work.id
try :
content = etree.XML("
" + desc + "
")
content_node = etree.SubElement(desc_node, "Text", attrib={"textformat":"05"}) #xhtml
content_node.append(content)
except etree.XMLSyntaxError:
content_node = etree.SubElement(desc_node, "Text", attrib={"textformat":"02"}) #html
content_node.text = etree.CDATA(desc)
supp_node = etree.SubElement(coll_node, "SupportingResource")
supp_node.append(text_node("ResourceContentType", '01')) #front cover
supp_node.append(text_node("ContentAudience", '00')) #unrestricted
supp_node.append(text_node("ResourceMode", '03')) #image
cover_node = etree.SubElement(supp_node, "ResourceVersion")
cover_node.append(text_node("ResourceForm", '01')) #linkable
coverfeat_node = etree.SubElement(cover_node, "ResourceVersionFeature")
coverfeat_node.append(text_node("ResourceVersionFeatureType", '01')) #image format
coverfeat_node.append(text_node("FeatureValue", 'D502')) #jpeg
cover_node.append(text_node("ResourceLink", edition.cover_image_thumbnail())) #link
# Publishing Detail Block
pubdetail_node = etree.SubElement(product_node, "PublishingDetail")
if edition.publisher_name:
pub_node = etree.SubElement(pubdetail_node, "Publisher")
pub_node.append(text_node("PublishingRole", '01')) #publisher
pub_node.append(text_node("PublisherName", edition.publisher_name.name))
pubdetail_node.append(text_node("PublishingStatus", '00')) #unspecified
#consumers really want a pub date
publication_date = edition.publication_date if edition.publication_date else edition.work.earliest_publication_date
if publication_date:
pubdate_node = etree.SubElement(pubdetail_node, "PublishingDate")
pubdate_node.append(text_node("PublishingDateRole", '01')) #nominal pub date
pubdate_node.append(text_node("Date", publication_date.replace('-','')))
# Product Supply Block
supply_node = etree.SubElement(product_node,"ProductSupply")
market_node = etree.SubElement(supply_node,"Market")
terr_node = etree.SubElement(market_node,"Territory")
terr_node.append(text_node("RegionsIncluded", 'WORLD'))
supply_detail_node = etree.SubElement(supply_node,"SupplyDetail")
supplier_node = etree.SubElement(supply_detail_node,"Supplier")
supplier_node.append(text_node("SupplierRole", '11')) #non-exclusive distributer
supplier_node.append(text_node("SupplierName", 'Unglue.it')) #non-exclusive distributer
for ebook in latest_ebooks:
website_node = etree.SubElement(supplier_node,"Website")
website_node.append(text_node("WebsiteRole", '29')) #full content
website_node.append(text_node("WebsiteDescription", '%s file download' % ebook.format, attrib={'textformat':'06'})) #full content
website_node.append(text_node("WebsiteLink", ebook.download_url)) #full content
supply_detail_node.append(text_node("ProductAvailability", '20')) #Available
price_node = etree.SubElement(supply_detail_node,"Price")
price_node.append(text_node("PriceType", '01')) #retail excluding tax
price_node.append(text_node("PriceAmount", '0.00')) #retail excluding tax
price_node.append(text_node("CurrencyCode", 'USD')) #retail excluding tax
return product_node