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
Line 
1# -*- coding: utf-8 -*-
2
3# 2011 Steffen Hoffmann
4
5import htmlentitydefs
6import re
7import codecs
8
9from trac.resource import ResourceSystem
10from trac.util.html import html as tag
11from trac.util.text import to_unicode
12
13from api import tag_
14from compat import json
15
16__all__ = ['parse_history', 'resource_from_page', 'xml_escape',
17           'xml_unescape']
18
19
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    """
26    field_history = {}
27    history = []
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,
32                                        'old': old, 'new': new}]
33            else:
34                field_history[field].append({'author': author, 'time': time,
35                                            'old': old, 'new': new})
36            return field_history
37
38    new_fields = None
39    last_author = last_change = None
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:
58                if fieldwise is False:
59                    change = _render_change(old_value, new_value)
60                    if change is not None:
61                        updated_fields[field] = change
62                else:
63                    field_history = _add_change(field_history, field,
64                                                last_author, last_change,
65                                                old_value, new_value)
66        for field in new_fields:
67            if old_fields.get(field) is None:
68                if fieldwise is False:
69                    change = _render_change(None, new_fields[field])
70                    if change is not None:
71                        updated_fields[field] = change
72                else:
73                    field_history = _add_change(field_history, field,
74                                                last_author, last_change,
75                                                None, new_fields[field])
76        new_fields = old_fields
77        history.append({'author': last_author,
78                        'time': last_change,
79                        'changes': updated_fields})
80        last_author = changeset['author']
81        last_change = changeset['time']
82    return fieldwise is False and history or field_history
83
84
85def _render_change(old, new):
86    rendered = None
87    if old and not new:
88        rendered = tag_("%(value)s reset to default value",
89                        value=tag.em(old))
90    elif new and not old:
91        rendered = tag_("from default value set to %(value)s",
92                        value=tag.em(new))
93    elif old and new:
94        if len(old) < 20 and len(new) < 20:
95            rendered = tag_("changed from %(old)s to %(new)s",
96                            old=tag.em(old), new=tag.em(new))
97        else:
98            nbsp = tag.br()
99            # TRANSLATOR: same as before, but with additional line breaks
100            rendered = tag_("changed from %(old)s to %(new)s",
101                            old=tag.em(nbsp, old), new=tag.em(nbsp, new))
102    return rendered
103
104
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
113
114
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):
118    enc = codecs.getencoder('us-ascii')
119    return enc(to_unicode(text), 'xmlcharrefreplace')[0]
120
121
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
127
128def xml_unescape(text):
129    return unichresc_RE.sub(_replace_entities, text)
130
131
132def _unescape_charref(ref):
133    name = ref[2:-1]
134    base = 10
135    # DEVEL: gain 20 % performance by omitting hex references
136    if name.startswith("x"):
137        name = name[1:]
138        base = 16
139    return unichr(int(name, base))
140
141
142def _replace_entities(match):
143    ent = match.group()
144    if ent[1] == "#":
145        return _unescape_charref(ent)
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
153
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:
162        return (resource_realm,
163                re.sub('/' + resource_realm, '', page).lstrip('/'))
164    else:
165        return page, None
Note: See TracBrowser for help on using the repository browser.