diff options
| author | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-03-09 17:43:46 +0000 |
|---|---|---|
| committer | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-03-09 17:43:46 +0000 |
| commit | 0b7dd14d1f87e2ecef7aacc39fe4189667ed4fdf (patch) | |
| tree | b77497f9de324d38a9c6341e54d2742633e20055 /django/utils | |
| parent | e17f75551491f5b864c1fc8a97c21d0b2bbf0bcd (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.py | 16 | ||||
| -rw-r--r-- | django/utils/dateformat.py | 6 | ||||
| -rw-r--r-- | django/utils/feedgenerator.py | 16 | ||||
| -rw-r--r-- | django/utils/text.py | 60 |
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 |
