source: wikiextrasplugin/tags/1.3.1/tracwikiextras/util.py

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

TracWikiExtras 1.3.1dev: Make imports compatible with Trac < 1.0.2

Refs #13374.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 8.9 KB
Line 
1# -*- coding: iso-8859-1 -*-
2#
3# Copyright (C) 2011 Mikael Relbe <mikael@relbe.se>
4# All rights reserved.
5#
6# Copyright (C) 2006 Christian Boos <cboos@neuf.fr>
7# All rights reserved.
8#
9# This software is licensed as described in the file COPYING, which
10# you should have received as part of this distribution. The terms
11# are also available at http://trac.edgewall.com/license.html.
12#
13# Author: Christian Boos <cboos@neuf.fr>
14#         Mikael Relbe <mikael@relbe.se>
15
16import re
17import string
18
19from trac.util.html import Markup, html as tag
20
21from trac.util import arity
22from trac.util.compat import sorted
23from trac.util.html import TracHTMLSanitizer
24if hasattr(TracHTMLSanitizer, 'sanitize_attrs'):
25    sanitizer = TracHTMLSanitizer()
26    from trac.util.html import Element
27else:
28    sanitizer = None
29    from genshi.builder import Stream
30from trac.wiki.api import WikiSystem
31
32
33def prepare_regexp(d):
34    syms = d.keys()
35    syms.sort(lambda a, b: cmp(len(b), len(a)))
36    return "|".join([r'%s%s%s'
37                     % (r'\b' if re.match(r'\w', s[0]) else '',
38                        re.escape(s),
39                        r'\b' if re.match(r'\w', s[-1]) else '')
40                     for s in syms])
41
42
43def render_table(items, colspec, render_item, colspace=1):
44    try:
45        columns = max(int(colspec), 1)
46    except Exception:
47        columns = 3
48
49    nbsp = Markup('&nbsp;')
50    # hdr = [tag.th(nbsp, 'Display'), tag.th('Markup', nbsp)]
51    spacer_style = 'width:%dem;border:none' % colspace
52    # Find max width to make value cols equally wide
53    width = 0
54    for i in items:
55        if (isinstance(i, str) or isinstance(i, unicode)) and len(i) > width:
56            width = len(i)
57    value_style = 'border:none'
58    #noinspection PyUnusedLocal
59    if width:
60        # empirical...
61        value_style = '%s;width:%dem' % (value_style, width*2/3)
62
63    def render_def(s):
64        rendered = s and render_item(s) or None
65        if isinstance(s, str):
66            s = Markup(s.replace('&', '&amp;'))
67        return [tag.td(rendered, nbsp, style='border:none'),
68                tag.td(tag.kbd(s), style=value_style)]
69
70    return tag.table(#tag.tr((hdr + [tag.td(style=spacer_style)]) *
71                     #       (columns-1) + hdr),
72                     [tag.tr([render_def(s) + [tag.td(style=spacer_style)]
73                              for s in row[:-1]] + render_def(row[-1]))
74                      for row in group_over(sorted(items), columns)],
75                     class_='wiki', style='border:none')
76
77
78def group_over(iterable, num, predicate=None):
79    """Combines the elements produced by the given iterable so that every `n`
80    items are returned as a tuple.
81
82    >>> items = [1, 2, 3, 4]
83    >>> for item in group(items, 2):
84    ...     print item
85    (1, 3)
86    (2, 4)
87
88    The last tuple is padded with `None` values if its' length is smaller than
89    `num`.
90
91    >>> items = [1, 2, 3, 4, 5]
92    >>> for item in group(items, 2):
93    ...     print item
94    (1, 4)
95    (2, 5)
96    (3, None)
97
98    The optional `predicate` parameter can be used to flag elements that should
99    not be packed together with other items. Only those elements where the
100    predicate function returns True are grouped with other elements, otherwise
101    they are returned as a tuple of length 1:
102
103    >>> items = [1, 2, 3, 4, 5, 6, 7, 8, 9]
104    >>> for item in group(items, 2, lambda x: x != 4):
105    ...     print item
106    (1, 3)
107    (2, None)
108    (4,)
109    (5, 8)
110    (6, 9)
111    (7, None)
112    """
113    # This function was inspired by trac.util.presentation.group().
114    num = max(num, 1)
115    # Split into sections at delimited by predicate
116    sections = [] # a list of lists of tuple (flush, [items])
117    buf = []
118    for item in iterable:
119        flush = predicate and not predicate(item)
120        if buf and flush:
121            sections.append((False, buf))
122            buf = []
123        if flush:
124            sections.append((True, [item]))
125        else:
126            buf.append(item)
127    if buf:
128        sections.append((False, buf))
129    # Return section by section
130    buf = []
131    for (flush, items) in sections:
132        if flush:
133            yield tuple(items)
134        else:
135            # iterate rows
136            rows = len(items) // num
137            if len(items) % num:
138                rows += 1
139            for r in range(0, rows):
140                # build column
141                for c in range(0, num):
142                    p = r + c * rows
143                    if p < len(items):
144                        buf.append(items[p])
145                    else:
146                        buf.append(None)
147                yield tuple(buf)
148                del buf[:]
149
150
151def reduce_names(names, keep=40):
152    """Reduce the number of names in an alphabetically balanced manner.
153
154    The reduction is made on a letter-by-letter basis with the goal to keep a
155    balanced number of words with different letters on each position. Longer
156    words are removed before shorter, letters between lowest and highest
157    order are removed first.
158
159    :param names: List of strings.
160    :param keep: Max number of strings to keep.
161
162    :ret: A reduced, alphabetically balanced, list of strings.
163    """
164    if len(names) < keep:
165        return names
166    elif keep < 1:
167        return []
168
169    def build_tree(names):
170        """Create a Huffman tree based on names
171
172        The tree is a dict where keys denotes a letter in the name, and the
173        value is a node in the tree:
174            tree :== {char: [count, tree] or {None: None} when leaf
175        where count is the number of leafs in the sub tree.
176        """
177        def insert(letters, tree):
178            if letters:
179                key = letters[0]
180                if key in tree:
181                    # add tail to sub tree
182                    value = tree[key]
183                    subtree = value[1]
184                    if insert(letters[1:], subtree):
185                        value[0] += 1 # increase count
186                        return True
187                else:
188                    # insert into new sub tree
189                    subtree = {}
190                    insert(letters[1:], subtree)
191                    tree[key] = [1, subtree]
192                    return True
193            elif not None in tree:
194                # end of word
195                tree[None] = None
196                return True
197
198        # build_tree
199        tree = {}
200        for name in names:
201            insert(name, tree)
202        return tree
203
204    def remove_one(tree):
205        """Remove one leaf from the largest child node in the tree."""
206        c = 0 # longest count
207        chars = [] # candidate chars to remove
208        keys = tree.keys()
209        keys.sort()
210        # search candidate chars to remove
211        for key in keys:
212            if tree[key]:
213                count = tree[key][0]
214                if count > c:
215                    c = count
216                    chars = [key]
217                elif count == c:
218                    chars.append(key)
219        # remove
220        char = None
221        if chars:
222            # look for highest-valued punctuation
223            for c in reversed(chars):
224                if c in string.punctuation:
225                    char = c
226                    break
227            else:
228                # remove middle char
229                char = chars[len(chars) // 2]
230        if char is None:
231            del tree[None]
232        else:
233            remove_one(tree[char][1]) # subtree
234            tree[char][0] -= 1
235            if not tree[char][0]:
236                del tree[char]
237
238    def size_of(tree):
239        """Return the number of leafs in the tree"""
240        n = 0
241        for value in tree.itervalues():
242            if value:
243                n += value[0]
244        return n
245
246    def get_names(tree, letters='', buf=None):
247        """Return a list with names based on Huffman tree"""
248        if buf is None:
249            buf=[]
250        for key, value in tree.iteritems():
251            if key is None:
252                buf.append(letters)
253            else:
254                subtree = value[1]
255                get_names(subtree, letters+key, buf)
256        return buf
257
258    # reduce_names
259    tree = build_tree(names)
260    while size_of(tree) > keep:
261        remove_one(tree)
262    return get_names(tree)
263
264
265if sanitizer:
266    def sanitize_attrib(env, element):
267        if not WikiSystem(env).render_unsafe_content:
268            if arity(sanitizer.sanitize_attrs) == 1:
269                sanitized = sanitizer.sanitize_attrs(element.attrib)
270            else:  # Trac 1.3.2+
271                sanitized = sanitizer.sanitize_attrs(element.tag,
272                                                     element.attrib)
273            element = Element(element.tag, **sanitized)
274        return element
275else:
276    def sanitize_attrib(env, element):
277        if not WikiSystem(env).render_unsafe_content:
278            sanitized = getattr(tag, element.tag.localname)
279            for k, data, pos in (Stream(element) | TracHTMLSanitizer()):
280                sanitized.attrib = data[1]
281                break  # only look at START
282            element = sanitized
283        return element
Note: See TracBrowser for help on using the repository browser.