summaryrefslogtreecommitdiff
path: root/django/utils
diff options
context:
space:
mode:
authorBoulder Sprinters <boulder-sprinters@djangoproject.com>2007-03-09 17:43:46 +0000
committerBoulder Sprinters <boulder-sprinters@djangoproject.com>2007-03-09 17:43:46 +0000
commit0b7dd14d1f87e2ecef7aacc39fe4189667ed4fdf (patch)
treeb77497f9de324d38a9c6341e54d2742633e20055 /django/utils
parente17f75551491f5b864c1fc8a97c21d0b2bbf0bcd (diff)
boulder-oracle-sprint: Merged to trunk [4692].
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@4695 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/utils')
-rw-r--r--django/utils/datastructures.py16
-rw-r--r--django/utils/dateformat.py6
-rw-r--r--django/utils/feedgenerator.py16
-rw-r--r--django/utils/text.py60
4 files changed, 93 insertions, 5 deletions
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
index 9bec7a5df7..e924e4edbc 100644
--- a/django/utils/datastructures.py
+++ b/django/utils/datastructures.py
@@ -16,8 +16,11 @@ class MergeDict(object):
def __contains__(self, key):
return self.has_key(key)
+
+ def __copy__(self):
+ return self.__class__(*self.dicts)
- def get(self, key, default):
+ def get(self, key, default=None):
try:
return self[key]
except KeyError:
@@ -42,6 +45,10 @@ class MergeDict(object):
if dict.has_key(key):
return True
return False
+
+ def copy(self):
+ """ returns a copy of this object"""
+ return self.__copy__()
class SortedDict(dict):
"A dictionary that keeps its keys in the order in which they're inserted."
@@ -85,6 +92,13 @@ class SortedDict(dict):
"Returns the value of the item at the given zero-based index."
return self[self.keyOrder[index]]
+ def copy(self):
+ "Returns a copy of this object."
+ # This way of initialising the copy means it works for subclasses, too.
+ obj = self.__class__(self)
+ obj.keyOrder = self.keyOrder
+ return obj
+
class MultiValueDictKeyError(KeyError):
pass
diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py
index 00eb9fe617..e3712175af 100644
--- a/django/utils/dateformat.py
+++ b/django/utils/dateformat.py
@@ -16,7 +16,7 @@ from django.utils.tzinfo import LocalTimezone
from calendar import isleap, monthrange
import re, time
-re_formatchars = re.compile(r'(?<!\\)([aABdDfFgGhHiIjlLmMnNOPrsStTUwWyYzZ])')
+re_formatchars = re.compile(r'(?<!\\)([aAbBdDfFgGhHiIjlLmMnNOPrsStTUwWyYzZ])')
re_escaped = re.compile(r'\\(.)')
class Formatter(object):
@@ -110,6 +110,10 @@ class DateFormat(TimeFormat):
if hasattr(self.data, 'hour') and not self.timezone:
self.timezone = LocalTimezone(dt)
+ def b(self):
+ "Month, textual, 3 letters, lowercase; e.g. 'jan'"
+ return MONTHS_3[self.data.month]
+
def d(self):
"Day of the month, 2 digits with leading zeros; i.e. '01' to '31'"
return '%02d' % self.data.day
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index 2eb27a40b7..9397789d6a 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -40,7 +40,7 @@ class SyndicationFeed(object):
"Base class for all syndication feeds. Subclasses should provide write()"
def __init__(self, title, link, description, language=None, author_email=None,
author_name=None, author_link=None, subtitle=None, categories=None,
- feed_url=None):
+ feed_url=None, feed_copyright=None):
self.feed = {
'title': title,
'link': link,
@@ -52,12 +52,13 @@ class SyndicationFeed(object):
'subtitle': subtitle,
'categories': categories or (),
'feed_url': feed_url,
+ 'feed_copyright': feed_copyright,
}
self.items = []
def add_item(self, title, link, description, author_email=None,
author_name=None, author_link=None, pubdate=None, comments=None,
- unique_id=None, enclosure=None, categories=()):
+ unique_id=None, enclosure=None, categories=(), item_copyright=None):
"""
Adds an item to the feed. All args are expected to be Python Unicode
objects except pubdate, which is a datetime.datetime object, and
@@ -75,6 +76,7 @@ class SyndicationFeed(object):
'unique_id': unique_id,
'enclosure': enclosure,
'categories': categories or (),
+ 'item_copyright': item_copyright,
})
def num_items(self):
@@ -128,6 +130,8 @@ class RssFeed(SyndicationFeed):
handler.addQuickElement(u"language", self.feed['language'])
for cat in self.feed['categories']:
handler.addQuickElement(u"category", cat)
+ if self.feed['feed_copyright'] is not None:
+ handler.addQuickElement(u"copyright", self.feed['feed_copyright'])
self.write_items(handler)
self.endChannelElement(handler)
handler.endElement(u"rss")
@@ -212,6 +216,8 @@ class Atom1Feed(SyndicationFeed):
handler.addQuickElement(u"subtitle", self.feed['subtitle'])
for cat in self.feed['categories']:
handler.addQuickElement(u"category", "", {u"term": cat})
+ if self.feed['feed_copyright'] is not None:
+ handler.addQuickElement(u"rights", self.feed['feed_copyright'])
self.write_items(handler)
handler.endElement(u"feed")
@@ -252,10 +258,14 @@ class Atom1Feed(SyndicationFeed):
u"length": item['enclosure'].length,
u"type": item['enclosure'].mime_type})
- # Categories:
+ # Categories.
for cat in item['categories']:
handler.addQuickElement(u"category", u"", {u"term": cat})
+ # Rights.
+ if item['item_copyright'] is not None:
+ handler.addQuickElement(u"rights", item['item_copyright'])
+
handler.endElement(u"entry")
# This isolates the decision of what the system default is, so calling code can
diff --git a/django/utils/text.py b/django/utils/text.py
index 217f42491b..1c1c456e2d 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -41,6 +41,66 @@ def truncate_words(s, num):
words.append('...')
return ' '.join(words)
+def truncate_html_words(s, num):
+ """
+ Truncates html to a certain number of words (not counting tags and comments).
+ Closes opened tags if they were correctly closed in the given html.
+ """
+ length = int(num)
+ if length <= 0:
+ return ''
+ html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input')
+ # Set up regular expressions
+ re_words = re.compile(r'&.*?;|<.*?>|([A-Za-z0-9][\w-]*)')
+ re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>')
+ # Count non-HTML words and keep note of open tags
+ pos = 0
+ ellipsis_pos = 0
+ words = 0
+ open_tags = []
+ while words <= length:
+ m = re_words.search(s, pos)
+ if not m:
+ # Checked through whole string
+ break
+ pos = m.end(0)
+ if m.group(1):
+ # It's an actual non-HTML word
+ words += 1
+ if words == length:
+ ellipsis_pos = pos
+ continue
+ # Check for tag
+ tag = re_tag.match(m.group(0))
+ if not tag or ellipsis_pos:
+ # Don't worry about non tags or tags after our truncate point
+ continue
+ closing_tag, tagname, self_closing = tag.groups()
+ tagname = tagname.lower() # Element names are always case-insensitive
+ if self_closing or tagname in html4_singlets:
+ pass
+ elif closing_tag:
+ # Check for match in open tags list
+ try:
+ i = open_tags.index(tagname)
+ except ValueError:
+ pass
+ else:
+ # SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags
+ open_tags = open_tags[i+1:]
+ else:
+ # Add it to the start of the open tags list
+ open_tags.insert(0, tagname)
+ if words <= length:
+ # Don't try to close tags if we don't need to truncate
+ return s
+ out = s[:ellipsis_pos] + ' ...'
+ # Close any tags still open
+ for tag in open_tags:
+ out += '</%s>' % tag
+ # Return string
+ return out
+
def get_valid_filename(s):
"""
Returns the given string converted to a string that can be used for a clean