Merge pull request from GHSA-g8q7-xv52-hf9f
Prevent XML Denial of Service Attacks
This commit is contained in:
commit
f57a01b20f
10 changed files with 180 additions and 180 deletions
|
@ -3,7 +3,7 @@
|
||||||
feedgen.entry
|
feedgen.entry
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2020, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
@ -13,10 +13,11 @@ from datetime import datetime
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import dateutil.tz
|
import dateutil.tz
|
||||||
import warnings
|
import warnings
|
||||||
from lxml import etree
|
|
||||||
|
from lxml.etree import CDATA # nosec - adding CDATA entry is safe
|
||||||
|
|
||||||
from feedgen.compat import string_types
|
from feedgen.compat import string_types
|
||||||
from feedgen.util import ensure_format, formatRFC2822
|
from feedgen.util import ensure_format, formatRFC2822, xml_fromstring, xml_elem
|
||||||
|
|
||||||
|
|
||||||
def _add_text_elm(entry, data, name):
|
def _add_text_elm(entry, data, name):
|
||||||
|
@ -24,7 +25,7 @@ def _add_text_elm(entry, data, name):
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
elm = etree.SubElement(entry, name)
|
elm = xml_elem(name, entry)
|
||||||
type_ = data.get('type')
|
type_ = data.get('type')
|
||||||
if data.get('src'):
|
if data.get('src'):
|
||||||
if name != 'content':
|
if name != 'content':
|
||||||
|
@ -34,16 +35,14 @@ def _add_text_elm(entry, data, name):
|
||||||
elif data.get(name):
|
elif data.get(name):
|
||||||
# Surround xhtml with a div tag, parse it and embed it
|
# Surround xhtml with a div tag, parse it and embed it
|
||||||
if type_ == 'xhtml':
|
if type_ == 'xhtml':
|
||||||
elm.append(etree.fromstring(
|
xhtml = '<div xmlns="http://www.w3.org/1999/xhtml">' \
|
||||||
'<div xmlns="http://www.w3.org/1999/xhtml">' +
|
+ data.get(name) + '</div>'
|
||||||
data.get(name) + '</div>'))
|
elm.append(xml_fromstring(xhtml))
|
||||||
elif type_ == 'CDATA':
|
elif type_ == 'CDATA':
|
||||||
elm.text = etree.CDATA(
|
elm.text = CDATA(data.get(name))
|
||||||
data.get(name))
|
|
||||||
# Parse XML and embed it
|
# Parse XML and embed it
|
||||||
elif type_ and (type_.endswith('/xml') or type_.endswith('+xml')):
|
elif type_ and (type_.endswith('/xml') or type_.endswith('+xml')):
|
||||||
elm.append(etree.fromstring(
|
elm.append(xml_fromstring(data[name]))
|
||||||
data[name]))
|
|
||||||
# Embed the text in escaped form
|
# Embed the text in escaped form
|
||||||
elif not type_ or type_.startswith('text') or type_ == 'html':
|
elif not type_ or type_.startswith('text') or type_ == 'html':
|
||||||
elm.text = data.get(name)
|
elm.text = data.get(name)
|
||||||
|
@ -102,14 +101,14 @@ class FeedEntry(object):
|
||||||
|
|
||||||
def atom_entry(self, extensions=True):
|
def atom_entry(self, extensions=True):
|
||||||
'''Create an ATOM entry and return it.'''
|
'''Create an ATOM entry and return it.'''
|
||||||
entry = etree.Element('entry')
|
entry = xml_elem('entry')
|
||||||
if not (self.__atom_id and self.__atom_title and self.__atom_updated):
|
if not (self.__atom_id and self.__atom_title and self.__atom_updated):
|
||||||
raise ValueError('Required fields not set')
|
raise ValueError('Required fields not set')
|
||||||
id = etree.SubElement(entry, 'id')
|
id = xml_elem('id', entry)
|
||||||
id.text = self.__atom_id
|
id.text = self.__atom_id
|
||||||
title = etree.SubElement(entry, 'title')
|
title = xml_elem('title', entry)
|
||||||
title.text = self.__atom_title
|
title.text = self.__atom_title
|
||||||
updated = etree.SubElement(entry, 'updated')
|
updated = xml_elem('updated', entry)
|
||||||
updated.text = self.__atom_updated.isoformat()
|
updated.text = self.__atom_updated.isoformat()
|
||||||
|
|
||||||
# An entry must contain an alternate link if there is no content
|
# An entry must contain an alternate link if there is no content
|
||||||
|
@ -125,20 +124,20 @@ class FeedEntry(object):
|
||||||
# Atom requires a name. Skip elements without.
|
# Atom requires a name. Skip elements without.
|
||||||
if not a.get('name'):
|
if not a.get('name'):
|
||||||
continue
|
continue
|
||||||
author = etree.SubElement(entry, 'author')
|
author = xml_elem('author', entry)
|
||||||
name = etree.SubElement(author, 'name')
|
name = xml_elem('name', author)
|
||||||
name.text = a.get('name')
|
name.text = a.get('name')
|
||||||
if a.get('email'):
|
if a.get('email'):
|
||||||
email = etree.SubElement(author, 'email')
|
email = xml_elem('email', author)
|
||||||
email.text = a.get('email')
|
email.text = a.get('email')
|
||||||
if a.get('uri'):
|
if a.get('uri'):
|
||||||
uri = etree.SubElement(author, 'uri')
|
uri = xml_elem('uri', author)
|
||||||
uri.text = a.get('uri')
|
uri.text = a.get('uri')
|
||||||
|
|
||||||
_add_text_elm(entry, self.__atom_content, 'content')
|
_add_text_elm(entry, self.__atom_content, 'content')
|
||||||
|
|
||||||
for l in self.__atom_link or []:
|
for l in self.__atom_link or []:
|
||||||
link = etree.SubElement(entry, 'link', href=l['href'])
|
link = xml_elem('link', entry, href=l['href'])
|
||||||
if l.get('rel'):
|
if l.get('rel'):
|
||||||
link.attrib['rel'] = l['rel']
|
link.attrib['rel'] = l['rel']
|
||||||
if l.get('type'):
|
if l.get('type'):
|
||||||
|
@ -153,7 +152,7 @@ class FeedEntry(object):
|
||||||
_add_text_elm(entry, self.__atom_summary, 'summary')
|
_add_text_elm(entry, self.__atom_summary, 'summary')
|
||||||
|
|
||||||
for c in self.__atom_category or []:
|
for c in self.__atom_category or []:
|
||||||
cat = etree.SubElement(entry, 'category', term=c['term'])
|
cat = xml_elem('category', entry, term=c['term'])
|
||||||
if c.get('scheme'):
|
if c.get('scheme'):
|
||||||
cat.attrib['scheme'] = c['scheme']
|
cat.attrib['scheme'] = c['scheme']
|
||||||
if c.get('label'):
|
if c.get('label'):
|
||||||
|
@ -164,32 +163,31 @@ class FeedEntry(object):
|
||||||
# Atom requires a name. Skip elements without.
|
# Atom requires a name. Skip elements without.
|
||||||
if not c.get('name'):
|
if not c.get('name'):
|
||||||
continue
|
continue
|
||||||
contrib = etree.SubElement(entry, 'contributor')
|
contrib = xml_elem('contributor', entry)
|
||||||
name = etree.SubElement(contrib, 'name')
|
name = xml_elem('name', contrib)
|
||||||
name.text = c.get('name')
|
name.text = c.get('name')
|
||||||
if c.get('email'):
|
if c.get('email'):
|
||||||
email = etree.SubElement(contrib, 'email')
|
email = xml_elem('email', contrib)
|
||||||
email.text = c.get('email')
|
email.text = c.get('email')
|
||||||
if c.get('uri'):
|
if c.get('uri'):
|
||||||
uri = etree.SubElement(contrib, 'uri')
|
uri = xml_elem('uri', contrib)
|
||||||
uri.text = c.get('uri')
|
uri.text = c.get('uri')
|
||||||
|
|
||||||
if self.__atom_published:
|
if self.__atom_published:
|
||||||
published = etree.SubElement(entry, 'published')
|
published = xml_elem('published', entry)
|
||||||
published.text = self.__atom_published.isoformat()
|
published.text = self.__atom_published.isoformat()
|
||||||
|
|
||||||
if self.__atom_rights:
|
if self.__atom_rights:
|
||||||
rights = etree.SubElement(entry, 'rights')
|
rights = xml_elem('rights', entry)
|
||||||
rights.text = self.__atom_rights
|
rights.text = self.__atom_rights
|
||||||
|
|
||||||
if self.__atom_source:
|
if self.__atom_source:
|
||||||
source = etree.SubElement(entry, 'source')
|
source = xml_elem('source', entry)
|
||||||
if self.__atom_source.get('title'):
|
if self.__atom_source.get('title'):
|
||||||
source_title = etree.SubElement(source, 'title')
|
source_title = xml_elem('title', source)
|
||||||
source_title.text = self.__atom_source['title']
|
source_title.text = self.__atom_source['title']
|
||||||
if self.__atom_source.get('link'):
|
if self.__atom_source.get('link'):
|
||||||
etree.SubElement(source, 'link',
|
xml_elem('link', source, href=self.__atom_source['link'])
|
||||||
href=self.__atom_source['link'])
|
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
for ext in self.__extensions.values() or []:
|
for ext in self.__extensions.values() or []:
|
||||||
|
@ -200,60 +198,59 @@ class FeedEntry(object):
|
||||||
|
|
||||||
def rss_entry(self, extensions=True):
|
def rss_entry(self, extensions=True):
|
||||||
'''Create a RSS item and return it.'''
|
'''Create a RSS item and return it.'''
|
||||||
entry = etree.Element('item')
|
entry = xml_elem('item')
|
||||||
if not (self.__rss_title or
|
if not (self.__rss_title or
|
||||||
self.__rss_description or
|
self.__rss_description or
|
||||||
self.__rss_content):
|
self.__rss_content):
|
||||||
raise ValueError('Required fields not set')
|
raise ValueError('Required fields not set')
|
||||||
if self.__rss_title:
|
if self.__rss_title:
|
||||||
title = etree.SubElement(entry, 'title')
|
title = xml_elem('title', entry)
|
||||||
title.text = self.__rss_title
|
title.text = self.__rss_title
|
||||||
if self.__rss_link:
|
if self.__rss_link:
|
||||||
link = etree.SubElement(entry, 'link')
|
link = xml_elem('link', entry)
|
||||||
link.text = self.__rss_link
|
link.text = self.__rss_link
|
||||||
if self.__rss_description and self.__rss_content:
|
if self.__rss_description and self.__rss_content:
|
||||||
description = etree.SubElement(entry, 'description')
|
description = xml_elem('description', entry)
|
||||||
description.text = self.__rss_description
|
description.text = self.__rss_description
|
||||||
XMLNS_CONTENT = 'http://purl.org/rss/1.0/modules/content/'
|
XMLNS_CONTENT = 'http://purl.org/rss/1.0/modules/content/'
|
||||||
content = etree.SubElement(entry, '{%s}encoded' % XMLNS_CONTENT)
|
content = xml_elem('{%s}encoded' % XMLNS_CONTENT, entry)
|
||||||
content.text = etree.CDATA(self.__rss_content['content']) \
|
content.text = CDATA(self.__rss_content['content']) \
|
||||||
if self.__rss_content.get('type', '') == 'CDATA' \
|
if self.__rss_content.get('type', '') == 'CDATA' \
|
||||||
else self.__rss_content['content']
|
else self.__rss_content['content']
|
||||||
elif self.__rss_description:
|
elif self.__rss_description:
|
||||||
description = etree.SubElement(entry, 'description')
|
description = xml_elem('description', entry)
|
||||||
description.text = self.__rss_description
|
description.text = self.__rss_description
|
||||||
elif self.__rss_content:
|
elif self.__rss_content:
|
||||||
description = etree.SubElement(entry, 'description')
|
description = xml_elem('description', entry)
|
||||||
description.text = etree.CDATA(self.__rss_content['content']) \
|
description.text = CDATA(self.__rss_content['content']) \
|
||||||
if self.__rss_content.get('type', '') == 'CDATA' \
|
if self.__rss_content.get('type', '') == 'CDATA' \
|
||||||
else self.__rss_content['content']
|
else self.__rss_content['content']
|
||||||
for a in self.__rss_author or []:
|
for a in self.__rss_author or []:
|
||||||
author = etree.SubElement(entry, 'author')
|
author = xml_elem('author', entry)
|
||||||
author.text = a
|
author.text = a
|
||||||
if self.__rss_guid.get('guid'):
|
if self.__rss_guid.get('guid'):
|
||||||
guid = etree.SubElement(entry, 'guid')
|
guid = xml_elem('guid', entry)
|
||||||
guid.text = self.__rss_guid['guid']
|
guid.text = self.__rss_guid['guid']
|
||||||
permaLink = str(self.__rss_guid.get('permalink', False)).lower()
|
permaLink = str(self.__rss_guid.get('permalink', False)).lower()
|
||||||
guid.attrib['isPermaLink'] = permaLink
|
guid.attrib['isPermaLink'] = permaLink
|
||||||
for cat in self.__rss_category or []:
|
for cat in self.__rss_category or []:
|
||||||
category = etree.SubElement(entry, 'category')
|
category = xml_elem('category', entry)
|
||||||
category.text = cat['value']
|
category.text = cat['value']
|
||||||
if cat.get('domain'):
|
if cat.get('domain'):
|
||||||
category.attrib['domain'] = cat['domain']
|
category.attrib['domain'] = cat['domain']
|
||||||
if self.__rss_comments:
|
if self.__rss_comments:
|
||||||
comments = etree.SubElement(entry, 'comments')
|
comments = xml_elem('comments', entry)
|
||||||
comments.text = self.__rss_comments
|
comments.text = self.__rss_comments
|
||||||
if self.__rss_enclosure:
|
if self.__rss_enclosure:
|
||||||
enclosure = etree.SubElement(entry, 'enclosure')
|
enclosure = xml_elem('enclosure', entry)
|
||||||
enclosure.attrib['url'] = self.__rss_enclosure['url']
|
enclosure.attrib['url'] = self.__rss_enclosure['url']
|
||||||
enclosure.attrib['length'] = self.__rss_enclosure['length']
|
enclosure.attrib['length'] = self.__rss_enclosure['length']
|
||||||
enclosure.attrib['type'] = self.__rss_enclosure['type']
|
enclosure.attrib['type'] = self.__rss_enclosure['type']
|
||||||
if self.__rss_pubDate:
|
if self.__rss_pubDate:
|
||||||
pubDate = etree.SubElement(entry, 'pubDate')
|
pubDate = xml_elem('pubDate', entry)
|
||||||
pubDate.text = formatRFC2822(self.__rss_pubDate)
|
pubDate.text = formatRFC2822(self.__rss_pubDate)
|
||||||
if self.__rss_source:
|
if self.__rss_source:
|
||||||
source = etree.SubElement(entry, 'source',
|
source = xml_elem('source', entry, url=self.__rss_source['url'])
|
||||||
url=self.__rss_source['url'])
|
|
||||||
source.text = self.__rss_source['title']
|
source.text = self.__rss_source['title']
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
|
|
|
@ -13,9 +13,8 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseExtension
|
from feedgen.ext.base import BaseExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
|
|
||||||
class DcBaseExtension(BaseExtension):
|
class DcBaseExtension(BaseExtension):
|
||||||
|
@ -45,10 +44,10 @@ class DcBaseExtension(BaseExtension):
|
||||||
def extend_ns(self):
|
def extend_ns(self):
|
||||||
return {'dc': 'http://purl.org/dc/elements/1.1/'}
|
return {'dc': 'http://purl.org/dc/elements/1.1/'}
|
||||||
|
|
||||||
def _extend_xml(self, xml_elem):
|
def _extend_xml(self, xml_element):
|
||||||
'''Extend xml_elem with set DC fields.
|
'''Extend xml_element with set DC fields.
|
||||||
|
|
||||||
:param xml_elem: etree element
|
:param xml_element: etree element
|
||||||
'''
|
'''
|
||||||
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
|
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
|
||||||
|
|
||||||
|
@ -58,8 +57,8 @@ class DcBaseExtension(BaseExtension):
|
||||||
'identifier']:
|
'identifier']:
|
||||||
if hasattr(self, '_dcelem_%s' % elem):
|
if hasattr(self, '_dcelem_%s' % elem):
|
||||||
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
||||||
node = etree.SubElement(xml_elem,
|
node = xml_elem('{%s}%s' % (DCELEMENTS_NS, elem),
|
||||||
'{%s}%s' % (DCELEMENTS_NS, elem))
|
xml_element)
|
||||||
node.text = val
|
node.text = val
|
||||||
|
|
||||||
def extend_atom(self, atom_feed):
|
def extend_atom(self, atom_feed):
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
import numbers
|
import numbers
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from feedgen.ext.base import BaseEntryExtension
|
from feedgen.ext.base import BaseEntryExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
|
|
||||||
class GeoRSSPolygonInteriorWarning(Warning):
|
class GeoRSSPolygonInteriorWarning(Warning):
|
||||||
|
@ -86,49 +86,43 @@ class GeoEntryExtension(BaseEntryExtension):
|
||||||
GEO_NS = 'http://www.georss.org/georss'
|
GEO_NS = 'http://www.georss.org/georss'
|
||||||
|
|
||||||
if self.__point:
|
if self.__point:
|
||||||
point = etree.SubElement(entry, '{%s}point' % GEO_NS)
|
point = xml_elem('{%s}point' % GEO_NS, entry)
|
||||||
point.text = self.__point
|
point.text = self.__point
|
||||||
|
|
||||||
if self.__line:
|
if self.__line:
|
||||||
line = etree.SubElement(entry, '{%s}line' % GEO_NS)
|
line = xml_elem('{%s}line' % GEO_NS, entry)
|
||||||
line.text = self.__line
|
line.text = self.__line
|
||||||
|
|
||||||
if self.__polygon:
|
if self.__polygon:
|
||||||
polygon = etree.SubElement(entry, '{%s}polygon' % GEO_NS)
|
polygon = xml_elem('{%s}polygon' % GEO_NS, entry)
|
||||||
polygon.text = self.__polygon
|
polygon.text = self.__polygon
|
||||||
|
|
||||||
if self.__box:
|
if self.__box:
|
||||||
box = etree.SubElement(entry, '{%s}box' % GEO_NS)
|
box = xml_elem('{%s}box' % GEO_NS, entry)
|
||||||
box.text = self.__box
|
box.text = self.__box
|
||||||
|
|
||||||
if self.__featuretypetag:
|
if self.__featuretypetag:
|
||||||
featuretypetag = etree.SubElement(
|
featuretypetag = xml_elem('{%s}featuretypetag' % GEO_NS, entry)
|
||||||
entry,
|
|
||||||
'{%s}featuretypetag' % GEO_NS
|
|
||||||
)
|
|
||||||
featuretypetag.text = self.__featuretypetag
|
featuretypetag.text = self.__featuretypetag
|
||||||
|
|
||||||
if self.__relationshiptag:
|
if self.__relationshiptag:
|
||||||
relationshiptag = etree.SubElement(
|
relationshiptag = xml_elem('{%s}relationshiptag' % GEO_NS, entry)
|
||||||
entry,
|
|
||||||
'{%s}relationshiptag' % GEO_NS
|
|
||||||
)
|
|
||||||
relationshiptag.text = self.__relationshiptag
|
relationshiptag.text = self.__relationshiptag
|
||||||
|
|
||||||
if self.__featurename:
|
if self.__featurename:
|
||||||
featurename = etree.SubElement(entry, '{%s}featurename' % GEO_NS)
|
featurename = xml_elem('{%s}featurename' % GEO_NS, entry)
|
||||||
featurename.text = self.__featurename
|
featurename.text = self.__featurename
|
||||||
|
|
||||||
if self.__elev:
|
if self.__elev:
|
||||||
elevation = etree.SubElement(entry, '{%s}elev' % GEO_NS)
|
elevation = xml_elem('{%s}elev' % GEO_NS, entry)
|
||||||
elevation.text = str(self.__elev)
|
elevation.text = str(self.__elev)
|
||||||
|
|
||||||
if self.__floor:
|
if self.__floor:
|
||||||
floor = etree.SubElement(entry, '{%s}floor' % GEO_NS)
|
floor = xml_elem('{%s}floor' % GEO_NS, entry)
|
||||||
floor.text = str(self.__floor)
|
floor.text = str(self.__floor)
|
||||||
|
|
||||||
if self.__radius:
|
if self.__radius:
|
||||||
radius = etree.SubElement(entry, '{%s}radius' % GEO_NS)
|
radius = xml_elem('{%s}radius' % GEO_NS, entry)
|
||||||
radius.text = str(self.__radius)
|
radius.text = str(self.__radius)
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
|
|
|
@ -10,10 +10,8 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseEntryExtension, BaseExtension
|
from feedgen.ext.base import BaseEntryExtension, BaseExtension
|
||||||
from feedgen.util import ensure_format
|
from feedgen.util import ensure_format, xml_elem
|
||||||
|
|
||||||
MEDIA_NS = 'http://search.yahoo.com/mrss/'
|
MEDIA_NS = 'http://search.yahoo.com/mrss/'
|
||||||
|
|
||||||
|
@ -45,10 +43,10 @@ class MediaEntryExtension(BaseEntryExtension):
|
||||||
# Define current media:group
|
# Define current media:group
|
||||||
group = groups.get(media_content.get('group'))
|
group = groups.get(media_content.get('group'))
|
||||||
if group is None:
|
if group is None:
|
||||||
group = etree.SubElement(entry, '{%s}group' % MEDIA_NS)
|
group = xml_elem('{%s}group' % MEDIA_NS, entry)
|
||||||
groups[media_content.get('group')] = group
|
groups[media_content.get('group')] = group
|
||||||
# Add content
|
# Add content
|
||||||
content = etree.SubElement(group, '{%s}content' % MEDIA_NS)
|
content = xml_elem('{%s}content' % MEDIA_NS, group)
|
||||||
for attr in ('url', 'fileSize', 'type', 'medium', 'isDefault',
|
for attr in ('url', 'fileSize', 'type', 'medium', 'isDefault',
|
||||||
'expression', 'bitrate', 'framerate', 'samplingrate',
|
'expression', 'bitrate', 'framerate', 'samplingrate',
|
||||||
'channels', 'duration', 'height', 'width', 'lang'):
|
'channels', 'duration', 'height', 'width', 'lang'):
|
||||||
|
@ -59,10 +57,10 @@ class MediaEntryExtension(BaseEntryExtension):
|
||||||
# Define current media:group
|
# Define current media:group
|
||||||
group = groups.get(media_thumbnail.get('group'))
|
group = groups.get(media_thumbnail.get('group'))
|
||||||
if group is None:
|
if group is None:
|
||||||
group = etree.SubElement(entry, '{%s}group' % MEDIA_NS)
|
group = xml_elem('{%s}group' % MEDIA_NS, entry)
|
||||||
groups[media_thumbnail.get('group')] = group
|
groups[media_thumbnail.get('group')] = group
|
||||||
# Add thumbnails
|
# Add thumbnails
|
||||||
thumbnail = etree.SubElement(group, '{%s}thumbnail' % MEDIA_NS)
|
thumbnail = xml_elem('{%s}thumbnail' % MEDIA_NS, group)
|
||||||
for attr in ('url', 'height', 'width', 'time'):
|
for attr in ('url', 'height', 'width', 'time'):
|
||||||
if media_thumbnail.get(attr):
|
if media_thumbnail.get(attr):
|
||||||
thumbnail.set(attr, media_thumbnail[attr])
|
thumbnail.set(attr, media_thumbnail[attr])
|
||||||
|
|
|
@ -10,11 +10,9 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.compat import string_types
|
from feedgen.compat import string_types
|
||||||
from feedgen.ext.base import BaseExtension
|
from feedgen.ext.base import BaseExtension
|
||||||
from feedgen.util import ensure_format
|
from feedgen.util import ensure_format, xml_elem
|
||||||
|
|
||||||
|
|
||||||
class PodcastExtension(BaseExtension):
|
class PodcastExtension(BaseExtension):
|
||||||
|
@ -47,11 +45,11 @@ class PodcastExtension(BaseExtension):
|
||||||
channel = rss_feed[0]
|
channel = rss_feed[0]
|
||||||
|
|
||||||
if self.__itunes_author:
|
if self.__itunes_author:
|
||||||
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
|
author = xml_elem('{%s}author' % ITUNES_NS, channel)
|
||||||
author.text = self.__itunes_author
|
author.text = self.__itunes_author
|
||||||
|
|
||||||
if self.__itunes_block is not None:
|
if self.__itunes_block is not None:
|
||||||
block = etree.SubElement(channel, '{%s}block' % ITUNES_NS)
|
block = xml_elem('{%s}block' % ITUNES_NS, channel)
|
||||||
block.text = 'yes' if self.__itunes_block else 'no'
|
block.text = 'yes' if self.__itunes_block else 'no'
|
||||||
|
|
||||||
for c in self.__itunes_category or []:
|
for c in self.__itunes_category or []:
|
||||||
|
@ -60,45 +58,42 @@ class PodcastExtension(BaseExtension):
|
||||||
category = channel.find(
|
category = channel.find(
|
||||||
'{%s}category[@text="%s"]' % (ITUNES_NS, c.get('cat')))
|
'{%s}category[@text="%s"]' % (ITUNES_NS, c.get('cat')))
|
||||||
if category is None:
|
if category is None:
|
||||||
category = etree.SubElement(channel,
|
category = xml_elem('{%s}category' % ITUNES_NS, channel)
|
||||||
'{%s}category' % ITUNES_NS)
|
|
||||||
category.attrib['text'] = c.get('cat')
|
category.attrib['text'] = c.get('cat')
|
||||||
|
|
||||||
if c.get('sub'):
|
if c.get('sub'):
|
||||||
subcategory = etree.SubElement(category,
|
subcategory = xml_elem('{%s}category' % ITUNES_NS, category)
|
||||||
'{%s}category' % ITUNES_NS)
|
|
||||||
subcategory.attrib['text'] = c.get('sub')
|
subcategory.attrib['text'] = c.get('sub')
|
||||||
|
|
||||||
if self.__itunes_image:
|
if self.__itunes_image:
|
||||||
image = etree.SubElement(channel, '{%s}image' % ITUNES_NS)
|
image = xml_elem('{%s}image' % ITUNES_NS, channel)
|
||||||
image.attrib['href'] = self.__itunes_image
|
image.attrib['href'] = self.__itunes_image
|
||||||
|
|
||||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||||
explicit = etree.SubElement(channel, '{%s}explicit' % ITUNES_NS)
|
explicit = xml_elem('{%s}explicit' % ITUNES_NS, channel)
|
||||||
explicit.text = self.__itunes_explicit
|
explicit.text = self.__itunes_explicit
|
||||||
|
|
||||||
if self.__itunes_complete in ('yes', 'no'):
|
if self.__itunes_complete in ('yes', 'no'):
|
||||||
complete = etree.SubElement(channel, '{%s}complete' % ITUNES_NS)
|
complete = xml_elem('{%s}complete' % ITUNES_NS, channel)
|
||||||
complete.text = self.__itunes_complete
|
complete.text = self.__itunes_complete
|
||||||
|
|
||||||
if self.__itunes_new_feed_url:
|
if self.__itunes_new_feed_url:
|
||||||
new_feed_url = etree.SubElement(channel,
|
new_feed_url = xml_elem('{%s}new-feed-url' % ITUNES_NS, channel)
|
||||||
'{%s}new-feed-url' % ITUNES_NS)
|
|
||||||
new_feed_url.text = self.__itunes_new_feed_url
|
new_feed_url.text = self.__itunes_new_feed_url
|
||||||
|
|
||||||
if self.__itunes_owner:
|
if self.__itunes_owner:
|
||||||
owner = etree.SubElement(channel, '{%s}owner' % ITUNES_NS)
|
owner = xml_elem('{%s}owner' % ITUNES_NS, channel)
|
||||||
owner_name = etree.SubElement(owner, '{%s}name' % ITUNES_NS)
|
owner_name = xml_elem('{%s}name' % ITUNES_NS, owner)
|
||||||
owner_name.text = self.__itunes_owner.get('name')
|
owner_name.text = self.__itunes_owner.get('name')
|
||||||
owner_email = etree.SubElement(owner, '{%s}email' % ITUNES_NS)
|
owner_email = xml_elem('{%s}email' % ITUNES_NS, owner)
|
||||||
owner_email.text = self.__itunes_owner.get('email')
|
owner_email.text = self.__itunes_owner.get('email')
|
||||||
|
|
||||||
if self.__itunes_subtitle:
|
if self.__itunes_subtitle:
|
||||||
subtitle = etree.SubElement(channel, '{%s}subtitle' % ITUNES_NS)
|
subtitle = xml_elem('{%s}subtitle' % ITUNES_NS, channel)
|
||||||
subtitle.text = self.__itunes_subtitle
|
subtitle.text = self.__itunes_subtitle
|
||||||
|
|
||||||
if self.__itunes_summary:
|
if self.__itunes_summary:
|
||||||
summary = etree.SubElement(channel, '{%s}summary' % ITUNES_NS)
|
summary = xml_elem('{%s}summary' % ITUNES_NS, channel)
|
||||||
summary.text = self.__itunes_summary
|
summary.text = self.__itunes_summary
|
||||||
|
|
||||||
return rss_feed
|
return rss_feed
|
||||||
|
|
|
@ -10,9 +10,8 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseEntryExtension
|
from feedgen.ext.base import BaseEntryExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
|
|
||||||
class PodcastEntryExtension(BaseEntryExtension):
|
class PodcastEntryExtension(BaseEntryExtension):
|
||||||
|
@ -40,43 +39,43 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||||
|
|
||||||
if self.__itunes_author:
|
if self.__itunes_author:
|
||||||
author = etree.SubElement(entry, '{%s}author' % ITUNES_NS)
|
author = xml_elem('{%s}author' % ITUNES_NS, entry)
|
||||||
author.text = self.__itunes_author
|
author.text = self.__itunes_author
|
||||||
|
|
||||||
if self.__itunes_block is not None:
|
if self.__itunes_block is not None:
|
||||||
block = etree.SubElement(entry, '{%s}block' % ITUNES_NS)
|
block = xml_elem('{%s}block' % ITUNES_NS, entry)
|
||||||
block.text = 'yes' if self.__itunes_block else 'no'
|
block.text = 'yes' if self.__itunes_block else 'no'
|
||||||
|
|
||||||
if self.__itunes_image:
|
if self.__itunes_image:
|
||||||
image = etree.SubElement(entry, '{%s}image' % ITUNES_NS)
|
image = xml_elem('{%s}image' % ITUNES_NS, entry)
|
||||||
image.attrib['href'] = self.__itunes_image
|
image.attrib['href'] = self.__itunes_image
|
||||||
|
|
||||||
if self.__itunes_duration:
|
if self.__itunes_duration:
|
||||||
duration = etree.SubElement(entry, '{%s}duration' % ITUNES_NS)
|
duration = xml_elem('{%s}duration' % ITUNES_NS, entry)
|
||||||
duration.text = self.__itunes_duration
|
duration.text = self.__itunes_duration
|
||||||
|
|
||||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||||
explicit = etree.SubElement(entry, '{%s}explicit' % ITUNES_NS)
|
explicit = xml_elem('{%s}explicit' % ITUNES_NS, entry)
|
||||||
explicit.text = self.__itunes_explicit
|
explicit.text = self.__itunes_explicit
|
||||||
|
|
||||||
if self.__itunes_is_closed_captioned is not None:
|
if self.__itunes_is_closed_captioned is not None:
|
||||||
is_closed_captioned = etree.SubElement(
|
is_closed_captioned = xml_elem(
|
||||||
entry, '{%s}isClosedCaptioned' % ITUNES_NS)
|
'{%s}isClosedCaptioned' % ITUNES_NS, entry)
|
||||||
if self.__itunes_is_closed_captioned:
|
if self.__itunes_is_closed_captioned:
|
||||||
is_closed_captioned.text = 'yes'
|
is_closed_captioned.text = 'yes'
|
||||||
else:
|
else:
|
||||||
is_closed_captioned.text = 'no'
|
is_closed_captioned.text = 'no'
|
||||||
|
|
||||||
if self.__itunes_order is not None and self.__itunes_order >= 0:
|
if self.__itunes_order is not None and self.__itunes_order >= 0:
|
||||||
order = etree.SubElement(entry, '{%s}order' % ITUNES_NS)
|
order = xml_elem('{%s}order' % ITUNES_NS, entry)
|
||||||
order.text = str(self.__itunes_order)
|
order.text = str(self.__itunes_order)
|
||||||
|
|
||||||
if self.__itunes_subtitle:
|
if self.__itunes_subtitle:
|
||||||
subtitle = etree.SubElement(entry, '{%s}subtitle' % ITUNES_NS)
|
subtitle = xml_elem('{%s}subtitle' % ITUNES_NS, entry)
|
||||||
subtitle.text = self.__itunes_subtitle
|
subtitle.text = self.__itunes_subtitle
|
||||||
|
|
||||||
if self.__itunes_summary:
|
if self.__itunes_summary:
|
||||||
summary = etree.SubElement(entry, '{%s}summary' % ITUNES_NS)
|
summary = xml_elem('{%s}summary' % ITUNES_NS, entry)
|
||||||
summary.text = self.__itunes_summary
|
summary.text = self.__itunes_summary
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,8 @@ See below for details
|
||||||
http://web.resource.org/rss/1.0/modules/syndication/
|
http://web.resource.org/rss/1.0/modules/syndication/
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseExtension
|
from feedgen.ext.base import BaseExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
SYNDICATION_NS = 'http://purl.org/rss/1.0/modules/syndication/'
|
SYNDICATION_NS = 'http://purl.org/rss/1.0/modules/syndication/'
|
||||||
PERIOD_TYPE = ('hourly', 'daily', 'weekly', 'monthly', 'yearly')
|
PERIOD_TYPE = ('hourly', 'daily', 'weekly', 'monthly', 'yearly')
|
||||||
|
@ -20,7 +19,7 @@ PERIOD_TYPE = ('hourly', 'daily', 'weekly', 'monthly', 'yearly')
|
||||||
|
|
||||||
def _set_value(channel, name, value):
|
def _set_value(channel, name, value):
|
||||||
if value:
|
if value:
|
||||||
newelem = etree.SubElement(channel, '{%s}' % SYNDICATION_NS + name)
|
newelem = xml_elem('{%s}' % SYNDICATION_NS + name, channel)
|
||||||
newelem.text = value
|
newelem.text = value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,8 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseEntryExtension, BaseExtension
|
from feedgen.ext.base import BaseEntryExtension, BaseExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
TORRENT_NS = 'http://xmlns.ezrss.it/0.1/dtd/'
|
TORRENT_NS = 'http://xmlns.ezrss.it/0.1/dtd/'
|
||||||
|
|
||||||
|
@ -41,30 +40,29 @@ class TorrentEntryExtension(BaseEntryExtension):
|
||||||
:param feed: The RSS item XML element to use.
|
:param feed: The RSS item XML element to use.
|
||||||
'''
|
'''
|
||||||
if self.__torrent_filename:
|
if self.__torrent_filename:
|
||||||
filename = etree.SubElement(entry, '{%s}filename' % TORRENT_NS)
|
filename = xml_elem('{%s}filename' % TORRENT_NS, entry)
|
||||||
filename.text = self.__torrent_filename
|
filename.text = self.__torrent_filename
|
||||||
|
|
||||||
if self.__torrent_contentlength:
|
if self.__torrent_contentlength:
|
||||||
contentlength = etree.SubElement(entry,
|
contentlength = xml_elem('{%s}contentlength' % TORRENT_NS, entry)
|
||||||
'{%s}contentlength' % TORRENT_NS)
|
|
||||||
contentlength.text = self.__torrent_contentlength
|
contentlength.text = self.__torrent_contentlength
|
||||||
|
|
||||||
if self.__torrent_infohash:
|
if self.__torrent_infohash:
|
||||||
infohash = etree.SubElement(entry, '{%s}infohash' % TORRENT_NS)
|
infohash = xml_elem('{%s}infohash' % TORRENT_NS, entry)
|
||||||
infohash.text = self.__torrent_infohash
|
infohash.text = self.__torrent_infohash
|
||||||
magnet = etree.SubElement(entry, '{%s}magneturi' % TORRENT_NS)
|
magnet = xml_elem('{%s}magneturi' % TORRENT_NS, entry)
|
||||||
magnet.text = 'magnet:?xt=urn:btih:' + self.__torrent_infohash
|
magnet.text = 'magnet:?xt=urn:btih:' + self.__torrent_infohash
|
||||||
|
|
||||||
if self.__torrent_seeds:
|
if self.__torrent_seeds:
|
||||||
seeds = etree.SubElement(entry, '{%s}seed' % TORRENT_NS)
|
seeds = xml_elem('{%s}seed' % TORRENT_NS, entry)
|
||||||
seeds.text = self.__torrent_seeds
|
seeds.text = self.__torrent_seeds
|
||||||
|
|
||||||
if self.__torrent_peers:
|
if self.__torrent_peers:
|
||||||
peers = etree.SubElement(entry, '{%s}peers' % TORRENT_NS)
|
peers = xml_elem('{%s}peers' % TORRENT_NS, entry)
|
||||||
peers.text = self.__torrent_peers
|
peers.text = self.__torrent_peers
|
||||||
|
|
||||||
if self.__torrent_verified:
|
if self.__torrent_verified:
|
||||||
verified = etree.SubElement(entry, '{%s}verified' % TORRENT_NS)
|
verified = xml_elem('{%s}verified' % TORRENT_NS, entry)
|
||||||
verified.text = self.__torrent_verified
|
verified.text = self.__torrent_verified
|
||||||
|
|
||||||
def filename(self, torrent_filename=None):
|
def filename(self, torrent_filename=None):
|
||||||
|
|
109
feedgen/feed.py
109
feedgen/feed.py
|
@ -3,7 +3,7 @@
|
||||||
feedgen.feed
|
feedgen.feed
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
:copyright: 2013-2016, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2020, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
|
|
||||||
|
@ -14,12 +14,12 @@ from datetime import datetime
|
||||||
|
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import dateutil.tz
|
import dateutil.tz
|
||||||
from lxml import etree
|
from lxml import etree # nosec - not using this for parsing
|
||||||
|
|
||||||
import feedgen.version
|
import feedgen.version
|
||||||
from feedgen.compat import string_types
|
from feedgen.compat import string_types
|
||||||
from feedgen.entry import FeedEntry
|
from feedgen.entry import FeedEntry
|
||||||
from feedgen.util import ensure_format, formatRFC2822
|
from feedgen.util import ensure_format, formatRFC2822, xml_elem
|
||||||
|
|
||||||
_feedgen_version = feedgen.version.version_str
|
_feedgen_version = feedgen.version.version_str
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class FeedGenerator(object):
|
||||||
self.__atom_contributor = None
|
self.__atom_contributor = None
|
||||||
self.__atom_generator = {
|
self.__atom_generator = {
|
||||||
'value': 'python-feedgen',
|
'value': 'python-feedgen',
|
||||||
'uri': 'http://lkiesow.github.io/python-feedgen',
|
'uri': 'https://lkiesow.github.io/python-feedgen',
|
||||||
'version': feedgen.version.version_str} # {value*,uri,version}
|
'version': feedgen.version.version_str} # {value*,uri,version}
|
||||||
self.__atom_icon = None
|
self.__atom_icon = None
|
||||||
self.__atom_logo = None
|
self.__atom_logo = None
|
||||||
|
@ -95,7 +95,7 @@ class FeedGenerator(object):
|
||||||
if ext.get('atom'):
|
if ext.get('atom'):
|
||||||
nsmap.update(ext['inst'].extend_ns())
|
nsmap.update(ext['inst'].extend_ns())
|
||||||
|
|
||||||
feed = etree.Element('feed',
|
feed = xml_elem('feed',
|
||||||
xmlns='http://www.w3.org/2005/Atom',
|
xmlns='http://www.w3.org/2005/Atom',
|
||||||
nsmap=nsmap)
|
nsmap=nsmap)
|
||||||
if self.__atom_feed_xml_lang:
|
if self.__atom_feed_xml_lang:
|
||||||
|
@ -108,11 +108,11 @@ class FeedGenerator(object):
|
||||||
([] if self.__atom_updated else ['updated'])
|
([] if self.__atom_updated else ['updated'])
|
||||||
missing = ', '.join(missing)
|
missing = ', '.join(missing)
|
||||||
raise ValueError('Required fields not set (%s)' % missing)
|
raise ValueError('Required fields not set (%s)' % missing)
|
||||||
id = etree.SubElement(feed, 'id')
|
id = xml_elem('id', feed)
|
||||||
id.text = self.__atom_id
|
id.text = self.__atom_id
|
||||||
title = etree.SubElement(feed, 'title')
|
title = xml_elem('title', feed)
|
||||||
title.text = self.__atom_title
|
title.text = self.__atom_title
|
||||||
updated = etree.SubElement(feed, 'updated')
|
updated = xml_elem('updated', feed)
|
||||||
updated.text = self.__atom_updated.isoformat()
|
updated.text = self.__atom_updated.isoformat()
|
||||||
|
|
||||||
# Add author elements
|
# Add author elements
|
||||||
|
@ -120,18 +120,18 @@ class FeedGenerator(object):
|
||||||
# Atom requires a name. Skip elements without.
|
# Atom requires a name. Skip elements without.
|
||||||
if not a.get('name'):
|
if not a.get('name'):
|
||||||
continue
|
continue
|
||||||
author = etree.SubElement(feed, 'author')
|
author = xml_elem('author', feed)
|
||||||
name = etree.SubElement(author, 'name')
|
name = xml_elem('name', author)
|
||||||
name.text = a.get('name')
|
name.text = a.get('name')
|
||||||
if a.get('email'):
|
if a.get('email'):
|
||||||
email = etree.SubElement(author, 'email')
|
email = xml_elem('email', author)
|
||||||
email.text = a.get('email')
|
email.text = a.get('email')
|
||||||
if a.get('uri'):
|
if a.get('uri'):
|
||||||
uri = etree.SubElement(author, 'uri')
|
uri = xml_elem('uri', author)
|
||||||
uri.text = a.get('uri')
|
uri.text = a.get('uri')
|
||||||
|
|
||||||
for l in self.__atom_link or []:
|
for l in self.__atom_link or []:
|
||||||
link = etree.SubElement(feed, 'link', href=l['href'])
|
link = xml_elem('link', feed, href=l['href'])
|
||||||
if l.get('rel'):
|
if l.get('rel'):
|
||||||
link.attrib['rel'] = l['rel']
|
link.attrib['rel'] = l['rel']
|
||||||
if l.get('type'):
|
if l.get('type'):
|
||||||
|
@ -144,7 +144,7 @@ class FeedGenerator(object):
|
||||||
link.attrib['length'] = l['length']
|
link.attrib['length'] = l['length']
|
||||||
|
|
||||||
for c in self.__atom_category or []:
|
for c in self.__atom_category or []:
|
||||||
cat = etree.SubElement(feed, 'category', term=c['term'])
|
cat = xml_elem('category', feed, term=c['term'])
|
||||||
if c.get('scheme'):
|
if c.get('scheme'):
|
||||||
cat.attrib['scheme'] = c['scheme']
|
cat.attrib['scheme'] = c['scheme']
|
||||||
if c.get('label'):
|
if c.get('label'):
|
||||||
|
@ -155,18 +155,18 @@ class FeedGenerator(object):
|
||||||
# Atom requires a name. Skip elements without.
|
# Atom requires a name. Skip elements without.
|
||||||
if not c.get('name'):
|
if not c.get('name'):
|
||||||
continue
|
continue
|
||||||
contrib = etree.SubElement(feed, 'contributor')
|
contrib = xml_elem('contributor', feed)
|
||||||
name = etree.SubElement(contrib, 'name')
|
name = xml_elem('name', contrib)
|
||||||
name.text = c.get('name')
|
name.text = c.get('name')
|
||||||
if c.get('email'):
|
if c.get('email'):
|
||||||
email = etree.SubElement(contrib, 'email')
|
email = xml_elem('email', contrib)
|
||||||
email.text = c.get('email')
|
email.text = c.get('email')
|
||||||
if c.get('uri'):
|
if c.get('uri'):
|
||||||
uri = etree.SubElement(contrib, 'uri')
|
uri = xml_elem('uri', contrib)
|
||||||
uri.text = c.get('uri')
|
uri.text = c.get('uri')
|
||||||
|
|
||||||
if self.__atom_generator and self.__atom_generator.get('value'):
|
if self.__atom_generator and self.__atom_generator.get('value'):
|
||||||
generator = etree.SubElement(feed, 'generator')
|
generator = xml_elem('generator', feed)
|
||||||
generator.text = self.__atom_generator['value']
|
generator.text = self.__atom_generator['value']
|
||||||
if self.__atom_generator.get('uri'):
|
if self.__atom_generator.get('uri'):
|
||||||
generator.attrib['uri'] = self.__atom_generator['uri']
|
generator.attrib['uri'] = self.__atom_generator['uri']
|
||||||
|
@ -174,19 +174,19 @@ class FeedGenerator(object):
|
||||||
generator.attrib['version'] = self.__atom_generator['version']
|
generator.attrib['version'] = self.__atom_generator['version']
|
||||||
|
|
||||||
if self.__atom_icon:
|
if self.__atom_icon:
|
||||||
icon = etree.SubElement(feed, 'icon')
|
icon = xml_elem('icon', feed)
|
||||||
icon.text = self.__atom_icon
|
icon.text = self.__atom_icon
|
||||||
|
|
||||||
if self.__atom_logo:
|
if self.__atom_logo:
|
||||||
logo = etree.SubElement(feed, 'logo')
|
logo = xml_elem('logo', feed)
|
||||||
logo.text = self.__atom_logo
|
logo.text = self.__atom_logo
|
||||||
|
|
||||||
if self.__atom_rights:
|
if self.__atom_rights:
|
||||||
rights = etree.SubElement(feed, 'rights')
|
rights = xml_elem('rights', feed)
|
||||||
rights.text = self.__atom_rights
|
rights.text = self.__atom_rights
|
||||||
|
|
||||||
if self.__atom_subtitle:
|
if self.__atom_subtitle:
|
||||||
subtitle = etree.SubElement(feed, 'subtitle')
|
subtitle = xml_elem('subtitle', feed)
|
||||||
subtitle.text = self.__atom_subtitle
|
subtitle.text = self.__atom_subtitle
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
|
@ -255,8 +255,8 @@ class FeedGenerator(object):
|
||||||
nsmap.update({'atom': 'http://www.w3.org/2005/Atom',
|
nsmap.update({'atom': 'http://www.w3.org/2005/Atom',
|
||||||
'content': 'http://purl.org/rss/1.0/modules/content/'})
|
'content': 'http://purl.org/rss/1.0/modules/content/'})
|
||||||
|
|
||||||
feed = etree.Element('rss', version='2.0', nsmap=nsmap)
|
feed = xml_elem('rss', version='2.0', nsmap=nsmap)
|
||||||
channel = etree.SubElement(feed, 'channel')
|
channel = xml_elem('channel', feed)
|
||||||
if not (self.__rss_title and
|
if not (self.__rss_title and
|
||||||
self.__rss_link and
|
self.__rss_link and
|
||||||
self.__rss_description):
|
self.__rss_description):
|
||||||
|
@ -265,18 +265,17 @@ class FeedGenerator(object):
|
||||||
([] if self.__rss_description else ['description'])
|
([] if self.__rss_description else ['description'])
|
||||||
missing = ', '.join(missing)
|
missing = ', '.join(missing)
|
||||||
raise ValueError('Required fields not set (%s)' % missing)
|
raise ValueError('Required fields not set (%s)' % missing)
|
||||||
title = etree.SubElement(channel, 'title')
|
title = xml_elem('title', channel)
|
||||||
title.text = self.__rss_title
|
title.text = self.__rss_title
|
||||||
link = etree.SubElement(channel, 'link')
|
link = xml_elem('link', channel)
|
||||||
link.text = self.__rss_link
|
link.text = self.__rss_link
|
||||||
desc = etree.SubElement(channel, 'description')
|
desc = xml_elem('description', channel)
|
||||||
desc.text = self.__rss_description
|
desc.text = self.__rss_description
|
||||||
for ln in self.__atom_link or []:
|
for ln in self.__atom_link or []:
|
||||||
# It is recommended to include a atom self link in rss documents…
|
# It is recommended to include a atom self link in rss documents…
|
||||||
if ln.get('rel') == 'self':
|
if ln.get('rel') == 'self':
|
||||||
selflink = etree.SubElement(
|
selflink = xml_elem('{http://www.w3.org/2005/Atom}link',
|
||||||
channel, '{http://www.w3.org/2005/Atom}link',
|
channel, href=ln['href'], rel='self')
|
||||||
href=ln['href'], rel='self')
|
|
||||||
if ln.get('type'):
|
if ln.get('type'):
|
||||||
selflink.attrib['type'] = ln['type']
|
selflink.attrib['type'] = ln['type']
|
||||||
if ln.get('hreflang'):
|
if ln.get('hreflang'):
|
||||||
|
@ -288,12 +287,12 @@ class FeedGenerator(object):
|
||||||
break
|
break
|
||||||
if self.__rss_category:
|
if self.__rss_category:
|
||||||
for cat in self.__rss_category:
|
for cat in self.__rss_category:
|
||||||
category = etree.SubElement(channel, 'category')
|
category = xml_elem('category', channel)
|
||||||
category.text = cat['value']
|
category.text = cat['value']
|
||||||
if cat.get('domain'):
|
if cat.get('domain'):
|
||||||
category.attrib['domain'] = cat['domain']
|
category.attrib['domain'] = cat['domain']
|
||||||
if self.__rss_cloud:
|
if self.__rss_cloud:
|
||||||
cloud = etree.SubElement(channel, 'cloud')
|
cloud = xml_elem('cloud', channel)
|
||||||
cloud.attrib['domain'] = self.__rss_cloud.get('domain')
|
cloud.attrib['domain'] = self.__rss_cloud.get('domain')
|
||||||
cloud.attrib['port'] = self.__rss_cloud.get('port')
|
cloud.attrib['port'] = self.__rss_cloud.get('port')
|
||||||
cloud.attrib['path'] = self.__rss_cloud.get('path')
|
cloud.attrib['path'] = self.__rss_cloud.get('path')
|
||||||
|
@ -301,69 +300,69 @@ class FeedGenerator(object):
|
||||||
'registerProcedure')
|
'registerProcedure')
|
||||||
cloud.attrib['protocol'] = self.__rss_cloud.get('protocol')
|
cloud.attrib['protocol'] = self.__rss_cloud.get('protocol')
|
||||||
if self.__rss_copyright:
|
if self.__rss_copyright:
|
||||||
copyright = etree.SubElement(channel, 'copyright')
|
copyright = xml_elem('copyright', channel)
|
||||||
copyright.text = self.__rss_copyright
|
copyright.text = self.__rss_copyright
|
||||||
if self.__rss_docs:
|
if self.__rss_docs:
|
||||||
docs = etree.SubElement(channel, 'docs')
|
docs = xml_elem('docs', channel)
|
||||||
docs.text = self.__rss_docs
|
docs.text = self.__rss_docs
|
||||||
if self.__rss_generator:
|
if self.__rss_generator:
|
||||||
generator = etree.SubElement(channel, 'generator')
|
generator = xml_elem('generator', channel)
|
||||||
generator.text = self.__rss_generator
|
generator.text = self.__rss_generator
|
||||||
if self.__rss_image:
|
if self.__rss_image:
|
||||||
image = etree.SubElement(channel, 'image')
|
image = xml_elem('image', channel)
|
||||||
url = etree.SubElement(image, 'url')
|
url = xml_elem('url', image)
|
||||||
url.text = self.__rss_image.get('url')
|
url.text = self.__rss_image.get('url')
|
||||||
title = etree.SubElement(image, 'title')
|
title = xml_elem('title', image)
|
||||||
title.text = self.__rss_image.get('title', self.__rss_title)
|
title.text = self.__rss_image.get('title', self.__rss_title)
|
||||||
link = etree.SubElement(image, 'link')
|
link = xml_elem('link', image)
|
||||||
link.text = self.__rss_image.get('link', self.__rss_link)
|
link.text = self.__rss_image.get('link', self.__rss_link)
|
||||||
if self.__rss_image.get('width'):
|
if self.__rss_image.get('width'):
|
||||||
width = etree.SubElement(image, 'width')
|
width = xml_elem('width', image)
|
||||||
width.text = self.__rss_image.get('width')
|
width.text = self.__rss_image.get('width')
|
||||||
if self.__rss_image.get('height'):
|
if self.__rss_image.get('height'):
|
||||||
height = etree.SubElement(image, 'height')
|
height = xml_elem('height', image)
|
||||||
height.text = self.__rss_image.get('height')
|
height.text = self.__rss_image.get('height')
|
||||||
if self.__rss_image.get('description'):
|
if self.__rss_image.get('description'):
|
||||||
description = etree.SubElement(image, 'description')
|
description = xml_elem('description', image)
|
||||||
description.text = self.__rss_image.get('description')
|
description.text = self.__rss_image.get('description')
|
||||||
if self.__rss_language:
|
if self.__rss_language:
|
||||||
language = etree.SubElement(channel, 'language')
|
language = xml_elem('language', channel)
|
||||||
language.text = self.__rss_language
|
language.text = self.__rss_language
|
||||||
if self.__rss_lastBuildDate:
|
if self.__rss_lastBuildDate:
|
||||||
lastBuildDate = etree.SubElement(channel, 'lastBuildDate')
|
lastBuildDate = xml_elem('lastBuildDate', channel)
|
||||||
|
|
||||||
lastBuildDate.text = formatRFC2822(self.__rss_lastBuildDate)
|
lastBuildDate.text = formatRFC2822(self.__rss_lastBuildDate)
|
||||||
if self.__rss_managingEditor:
|
if self.__rss_managingEditor:
|
||||||
managingEditor = etree.SubElement(channel, 'managingEditor')
|
managingEditor = xml_elem('managingEditor', channel)
|
||||||
managingEditor.text = self.__rss_managingEditor
|
managingEditor.text = self.__rss_managingEditor
|
||||||
if self.__rss_pubDate:
|
if self.__rss_pubDate:
|
||||||
pubDate = etree.SubElement(channel, 'pubDate')
|
pubDate = xml_elem('pubDate', channel)
|
||||||
pubDate.text = formatRFC2822(self.__rss_pubDate)
|
pubDate.text = formatRFC2822(self.__rss_pubDate)
|
||||||
if self.__rss_rating:
|
if self.__rss_rating:
|
||||||
rating = etree.SubElement(channel, 'rating')
|
rating = xml_elem('rating', channel)
|
||||||
rating.text = self.__rss_rating
|
rating.text = self.__rss_rating
|
||||||
if self.__rss_skipHours:
|
if self.__rss_skipHours:
|
||||||
skipHours = etree.SubElement(channel, 'skipHours')
|
skipHours = xml_elem('skipHours', channel)
|
||||||
for h in self.__rss_skipHours:
|
for h in self.__rss_skipHours:
|
||||||
hour = etree.SubElement(skipHours, 'hour')
|
hour = xml_elem('hour', skipHours)
|
||||||
hour.text = str(h)
|
hour.text = str(h)
|
||||||
if self.__rss_skipDays:
|
if self.__rss_skipDays:
|
||||||
skipDays = etree.SubElement(channel, 'skipDays')
|
skipDays = xml_elem('skipDays', channel)
|
||||||
for d in self.__rss_skipDays:
|
for d in self.__rss_skipDays:
|
||||||
day = etree.SubElement(skipDays, 'day')
|
day = xml_elem('day', skipDays)
|
||||||
day.text = d
|
day.text = d
|
||||||
if self.__rss_textInput:
|
if self.__rss_textInput:
|
||||||
textInput = etree.SubElement(channel, 'textInput')
|
textInput = xml_elem('textInput', channel)
|
||||||
textInput.attrib['title'] = self.__rss_textInput.get('title')
|
textInput.attrib['title'] = self.__rss_textInput.get('title')
|
||||||
textInput.attrib['description'] = \
|
textInput.attrib['description'] = \
|
||||||
self.__rss_textInput.get('description')
|
self.__rss_textInput.get('description')
|
||||||
textInput.attrib['name'] = self.__rss_textInput.get('name')
|
textInput.attrib['name'] = self.__rss_textInput.get('name')
|
||||||
textInput.attrib['link'] = self.__rss_textInput.get('link')
|
textInput.attrib['link'] = self.__rss_textInput.get('link')
|
||||||
if self.__rss_ttl:
|
if self.__rss_ttl:
|
||||||
ttl = etree.SubElement(channel, 'ttl')
|
ttl = xml_elem('ttl', channel)
|
||||||
ttl.text = str(self.__rss_ttl)
|
ttl.text = str(self.__rss_ttl)
|
||||||
if self.__rss_webMaster:
|
if self.__rss_webMaster:
|
||||||
webMaster = etree.SubElement(channel, 'webMaster')
|
webMaster = xml_elem('webMaster', channel)
|
||||||
webMaster.text = self.__rss_webMaster
|
webMaster.text = self.__rss_webMaster
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
|
|
|
@ -10,6 +10,28 @@
|
||||||
'''
|
'''
|
||||||
import locale
|
import locale
|
||||||
import sys
|
import sys
|
||||||
|
import lxml # nosec - we configure a safe parser below
|
||||||
|
|
||||||
|
# Configure a safe parser which does not allow XML entity expansion
|
||||||
|
parser = lxml.etree.XMLParser(
|
||||||
|
attribute_defaults=False,
|
||||||
|
dtd_validation=False,
|
||||||
|
load_dtd=False,
|
||||||
|
no_network=True,
|
||||||
|
recover=False,
|
||||||
|
remove_pis=True,
|
||||||
|
resolve_entities=False,
|
||||||
|
huge_tree=False)
|
||||||
|
|
||||||
|
|
||||||
|
def xml_fromstring(xmlstring):
|
||||||
|
return lxml.etree.fromstring(xmlstring, parser) # nosec - safe parser
|
||||||
|
|
||||||
|
|
||||||
|
def xml_elem(name, parent=None, **kwargs):
|
||||||
|
if parent is not None:
|
||||||
|
return lxml.etree.SubElement(parent, name, **kwargs)
|
||||||
|
return lxml.etree.Element(name, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
||||||
|
|
Loading…
Reference in a new issue