source: tracformsplugin/trunk/tracforms/util.py

Last change on this file was 16946, checked in by Ryan J Ollos, 6 years ago

TracForms 0.5dev: Revise r16384 from compatibility with Trac < 1.0.2

Refs #13319.

File size: 5.4 KB
RevLine 
[9984]1# -*- coding: utf-8 -*-
[9967]2
3# 2011 Steffen Hoffmann
4
5import htmlentitydefs
6import re
[16384]7import codecs
[9967]8
[16384]9from trac.resource import ResourceSystem
[16946]10from trac.util.html import html as tag
[16384]11from trac.util.text import to_unicode
[10000]12
[16387]13from api import tag_
[16384]14from compat import json
[10021]15
[16384]16__all__ = ['parse_history', 'resource_from_page', 'xml_escape',
17           'xml_unescape']
[9967]18
19
[10224]20def parse_history(changes, fieldwise=False):
21    """Versatile history parser for TracForms.
22
23    Returns either a list of dicts for changeset display in form view or
24    a dict of field change lists for stepwise form reset.
25    """
[16384]26    field_history = {}
[10224]27    history = []
[16384]28    if fieldwise is not False:
29        def _add_change(field_history, field, author, time, old, new):
30            if field not in field_history.keys():
31                field_history[field] = [{'author': author, 'time': time,
[10224]32                                        'old': old, 'new': new}]
33            else:
[16384]34                field_history[field].append({'author': author, 'time': time,
[10224]35                                            'old': old, 'new': new})
[16384]36            return field_history
[10224]37
38    new_fields = None
[16384]39    last_author = last_change = None
[10224]40    for changeset in changes:
41        # break down old and new version
42        try:
43            old_fields = json.loads(changeset.get('old_state', '{}'))
44        except ValueError:
45            # skip invalid history
46            old_fields = {}
47            pass
48        if new_fields is None:
49            # first loop cycle: only load values for comparison next time
50            new_fields = old_fields
51            last_author = changeset['author']
52            last_change = changeset['time']
53            continue
54        updated_fields = {}
55        for field, old_value in old_fields.iteritems():
56            new_value = new_fields.get(field)
57            if new_value != old_value:
[16384]58                if fieldwise is False:
[10224]59                    change = _render_change(old_value, new_value)
60                    if change is not None:
61                        updated_fields[field] = change
62                else:
[16384]63                    field_history = _add_change(field_history, field,
64                                                last_author, last_change,
65                                                old_value, new_value)
[10224]66        for field in new_fields:
67            if old_fields.get(field) is None:
[16384]68                if fieldwise is False:
[10224]69                    change = _render_change(None, new_fields[field])
70                    if change is not None:
71                        updated_fields[field] = change
72                else:
[16384]73                    field_history = _add_change(field_history, field,
74                                                last_author, last_change,
75                                                None, new_fields[field])
[10224]76        new_fields = old_fields
77        history.append({'author': last_author,
[11561]78                        'time': last_change,
[10224]79                        'changes': updated_fields})
80        last_author = changeset['author']
81        last_change = changeset['time']
[16384]82    return fieldwise is False and history or field_history
[10224]83
[16384]84
[10224]85def _render_change(old, new):
86    rendered = None
87    if old and not new:
[16385]88        rendered = tag_("%(value)s reset to default value",
89                        value=tag.em(old))
[10224]90    elif new and not old:
[16385]91        rendered = tag_("from default value set to %(value)s",
92                        value=tag.em(new))
[10224]93    elif old and new:
94        if len(old) < 20 and len(new) < 20:
[16385]95            rendered = tag_("changed from %(old)s to %(new)s",
96                            old=tag.em(old), new=tag.em(new))
[10224]97        else:
98            nbsp = tag.br()
99            # TRANSLATOR: same as before, but with additional line breaks
[16385]100            rendered = tag_("changed from %(old)s to %(new)s",
101                            old=tag.em(nbsp, old), new=tag.em(nbsp, new))
[10224]102    return rendered
103
[16384]104
[10924]105# adapted from code published by Daniel Goldberg on 09-Dec-2008 at
106# http://stackoverflow.com/questions/354038
107def is_number(s):
108    try:
109        float(s)
110        return True
111    except (TypeError, ValueError):
112        return False
[10224]113
[16384]114
[9967]115# code from an article published by Uche Ogbuji on 15-Jun-2005 at
116# http://www.xml.com/pub/a/2005/06/15/py-xml.html
117def xml_escape(text):
[16384]118    enc = codecs.getencoder('us-ascii')
[9967]119    return enc(to_unicode(text), 'xmlcharrefreplace')[0]
120
[16384]121
[9967]122# adapted from code published by John J. Lee on 06-Jun-2007 at
123# http://www.velocityreviews.com/forums
124#   /t511850-how-do-you-htmlentities-in-python.html
125unichresc_RE = re.compile(r'&#?[A-Za-z0-9]+?;')
126
[16384]127
[9967]128def xml_unescape(text):
129    return unichresc_RE.sub(_replace_entities, text)
130
[16384]131
[9967]132def _unescape_charref(ref):
133    name = ref[2:-1]
134    base = 10
[9985]135    # DEVEL: gain 20 % performance by omitting hex references
[9967]136    if name.startswith("x"):
137        name = name[1:]
138        base = 16
139    return unichr(int(name, base))
140
[16384]141
[9967]142def _replace_entities(match):
143    ent = match.group()
144    if ent[1] == "#":
[9985]145        return _unescape_charref(ent)
[9967]146    repl = htmlentitydefs.name2codepoint.get(ent[1:-1])
147    if repl is not None:
148        repl = unichr(repl)
149    else:
150        repl = ent
151    return repl
152
[16384]153
[10000]154def resource_from_page(env, page):
155    resource_realm = None
156    resources = ResourceSystem(env)
157    for realm in resources.get_known_realms():
158        if page.startswith('/' + realm):
159            resource_realm = realm
160            break
161    if resource_realm is not None:
[10096]162        return (resource_realm,
163                re.sub('/' + resource_realm, '', page).lstrip('/'))
[10000]164    else:
165        return page, None
Note: See TracBrowser for help on using the repository browser.