1 #!/usr/bin/python2.5 |
|
2 # |
|
3 # Copyright 2008 the Melange authors. |
|
4 # |
|
5 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 # you may not use this file except in compliance with the License. |
|
7 # You may obtain a copy of the License at |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
11 # Unless required by applicable law or agreed to in writing, software |
|
12 # distributed under the License is distributed on an "AS IS" BASIS, |
|
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 # See the License for the specific language governing permissions and |
|
15 # limitations under the License. |
|
16 |
|
17 """Helpers for displaying arbitrarily nested menus as HTML lists. |
|
18 """ |
|
19 |
|
20 __authors__ = [ |
|
21 '"Todd Larsen" <tlarsen@google.com>', |
|
22 ] |
|
23 |
|
24 |
|
25 class HtmlMenu: |
|
26 """Ordered collection of MenuItem objects as <p>...</p> paragraphs. |
|
27 """ |
|
28 ITEM_PREFIX_FMT = '%(indent)s<p>' |
|
29 ITEM_SUFFIX_FMT = '%(indent)s</p>' |
|
30 |
|
31 def __init__(self, menu, item_class=None): |
|
32 """Wraps an soc.logic.menu.Menu in order to render it as HTML. |
|
33 |
|
34 Args: |
|
35 menu: an soc.logic.menu.Menu object |
|
36 item_class: style used to render the MenuItems contained in menu; |
|
37 default is None, which causes AHrefMenuItem to be used |
|
38 """ |
|
39 self._menu = menu |
|
40 |
|
41 # workaround for circular dependency between AHrefMenuItem and this class |
|
42 if not item_class: |
|
43 item_class = AHrefMenuItem |
|
44 |
|
45 self._item_class = item_class |
|
46 |
|
47 def getHtmlTags(self, indent): |
|
48 """Returns list of HTML tags for arbitrarily nested items in the menu. |
|
49 |
|
50 Args: |
|
51 indent: string prepended to the beginning of each line of output |
|
52 (usually consists entirely of spaces) |
|
53 |
|
54 Returns: |
|
55 a list of strings that can be joined with '\n' into a single string |
|
56 to produce an entire <ul>...</ul> list in HTML |
|
57 """ |
|
58 tags = [] |
|
59 |
|
60 if self._menu.items: |
|
61 tags.append(self.ITEM_PREFIX_FMT % {'indent': indent}) |
|
62 |
|
63 for item in self._menu.items: |
|
64 tags.extend(self._item_class( |
|
65 item, menu_class=self.__class__).getHtmlTags(indent + ' ')) |
|
66 |
|
67 tags.append(self.ITEM_SUFFIX_FMT % {'indent': indent}) |
|
68 |
|
69 return tags |
|
70 |
|
71 def __str__(self): |
|
72 return '\n'.join(self.getHtmlTags('')) |
|
73 |
|
74 |
|
75 class UlMenu(HtmlMenu): |
|
76 """Ordered collection of MenuItem objects as a <ul> list. |
|
77 """ |
|
78 ITEM_PREFIX_FMT = '%(indent)s<ul>' |
|
79 ITEM_SUFFIX_FMT = '%(indent)s</ul>' |
|
80 |
|
81 def __init__(self, menu, item_class=None): |
|
82 """Wraps an soc.logic.menu.Menu in order to render it as HTML. |
|
83 |
|
84 Args: |
|
85 menu: an soc.logic.menu.Menu object |
|
86 item_class: style used to render the MenuItems contained in menu; |
|
87 default is None, which causes LiMenuItem to be used |
|
88 """ |
|
89 # workaround for circular dependency between LiMenuItem and this class |
|
90 if not item_class: |
|
91 item_class = LiMenuItem |
|
92 |
|
93 HtmlMenu.__init__(self, menu, item_class=item_class) |
|
94 |
|
95 |
|
96 class HtmlMenuItem: |
|
97 """Base class for specific MenuItem wrappers used by HtmlMenu sub-classes. |
|
98 """ |
|
99 |
|
100 def __init__(self, item, menu_class=HtmlMenu): |
|
101 """Wraps an soc.logic.menu.MenuItem in order to render it as HTML. |
|
102 |
|
103 Args: |
|
104 item: an soc.logic.menu.MenuItem object to wrap, in order to produce |
|
105 a representation of it as HTML tags later |
|
106 menu_class: a class derived from HtmlMenu, used to style any sub-menu |
|
107 of the MenuItem; default is HtmlMenu |
|
108 """ |
|
109 self._item = self.escapeItem(item) |
|
110 self._menu_class = menu_class |
|
111 |
|
112 def getItemHtmlTags(self, indent): |
|
113 """Returns list of HTML tags for the menu item itself. |
|
114 |
|
115 This method is intended to be overridden by sub-classes. |
|
116 |
|
117 Args: |
|
118 indent: string prepended to the beginning of each line of output |
|
119 (usually consists entirely of spaces) |
|
120 |
|
121 Returns: |
|
122 a list of strings that can be joined with '\n' into a single string |
|
123 to produce: |
|
124 <b>name</b> value <i>(annotation)</i> |
|
125 with value and/or <i>(annotation)</i> omitted if either is missing |
|
126 """ |
|
127 # TODO(tlarsen): implement "selected" style |
|
128 |
|
129 tags = ['%s<b>%s</b>' % (indent, self._item.name)] |
|
130 |
|
131 if self._item.value: |
|
132 tags.append('%s%s' % (indent, self._item.value)) |
|
133 |
|
134 if self._item.annotation: |
|
135 tags.append('%s<i>(%s)</i>' % (indent, self._item.annotation)) |
|
136 |
|
137 return tags |
|
138 |
|
139 def getSubMenuHtmlTags(self, indent): |
|
140 """Returns list of HTML tags for any sub-menu, if one exists. |
|
141 |
|
142 Args: |
|
143 indent: string prepended to the beginning of each line of output |
|
144 (usually consists entirely of spaces) |
|
145 |
|
146 Returns: |
|
147 an empty list if there is no sub-menu |
|
148 -OR- |
|
149 the list of HTML tags that render the entire sub-menu (depends on the |
|
150 menu_class that was provided to __init__() |
|
151 """ |
|
152 if not self._item.sub_menu: |
|
153 return [] |
|
154 |
|
155 return self._menu_class(self._item.sub_menu, |
|
156 item_class=self.__class__).getHtmlTags(indent) |
|
157 |
|
158 def getHtmlTags(self, indent): |
|
159 """Returns list of HTML tags for a menu item (and possibly its sub-menus). |
|
160 |
|
161 Args: |
|
162 indent: string prepended to the beginning of each line of output |
|
163 (usually consists entirely of spaces) |
|
164 |
|
165 Returns: |
|
166 a list of strings that can be joined with '\n' into a single string |
|
167 to produce an HTML representation of the wrapped MenuItem, with |
|
168 arbitrarily nested sub-menus possibly appended |
|
169 """ |
|
170 return self.getItemHtmlTags(indent) + self.getSubMenuHtmlTags(indent) |
|
171 |
|
172 def escapeItem(self, item): |
|
173 """HTML-escapes possibly user-supplied fields to prevent XSS. |
|
174 |
|
175 Args: |
|
176 item: an soc.logic.menu.MenuItem that is altered in-place; the |
|
177 fields that are potentially user-provided (name, value, annotation) |
|
178 are escaped using self.escapeText() |
|
179 |
|
180 Returns: |
|
181 the originally supplied item, for convenience, so that this method can |
|
182 be combined with an assignment |
|
183 """ |
|
184 item.name = self.escapeText(item.name) |
|
185 item.value = self.escapeText(item.value) |
|
186 item.annotation = self.escapeText(item.annotation) |
|
187 return item |
|
188 |
|
189 def escapeText(self, text): |
|
190 """ |
|
191 """ |
|
192 # TODO(tlarsen): user-supplied content *must* be escaped to prevent XSS |
|
193 return text |
|
194 |
|
195 def __str__(self): |
|
196 return '\n'.join(self.getHtmlTags('')) |
|
197 |
|
198 |
|
199 class AHrefMenuItem(HtmlMenuItem): |
|
200 """Provides HTML menu item properties as attributes as an <a href> link. |
|
201 """ |
|
202 |
|
203 def getItemHtmlTags(self, indent): |
|
204 """Returns list of HTML tags for the menu item itself. |
|
205 |
|
206 Args: |
|
207 indent: string prepended to the beginning of each line of output |
|
208 (usually consists entirely of spaces) |
|
209 |
|
210 Returns: |
|
211 a list of strings that can be joined with '\n' into a single string |
|
212 to produce an <a href="...">...</a> link, or just the MenuItem.name |
|
213 as plain text if there was no AHrefMenuItem.value URL |
|
214 """ |
|
215 # TODO(tlarsen): implement "selected" style |
|
216 |
|
217 if not self._item.value: |
|
218 # if no URL, then not a link, so just display item.name as text |
|
219 return ['%s<span>%s</span>' % (indent, self._item.name)] |
|
220 |
|
221 # URL supplied, so make an <a href="item.value">item.name</a> link |
|
222 return ['%s<span><a href="%s">%s</a></span>' % (indent, self._item.value, self._item.name)] |
|
223 |
|
224 class LiMenuItem(AHrefMenuItem): |
|
225 """Provides HTML menu item properties as attributes as an <li> list item. |
|
226 """ |
|
227 |
|
228 def __init__(self, item, menu_class=UlMenu): |
|
229 """Wraps an soc.logic.menu.MenuItem in order to render it as HTML. |
|
230 |
|
231 Args: |
|
232 item: an soc.logic.menu.MenuItem object to wrap, in order to produce |
|
233 a representation of it as HTML tags later |
|
234 menu_class: a class derived from HtmlMenu, used to style any sub-menu |
|
235 of the MenuItem; default is UlMenu |
|
236 """ |
|
237 AHrefMenuItem.__init__(self, item, menu_class=menu_class) |
|
238 |
|
239 def getHtmlTags(self, indent): |
|
240 """Returns <a href> link wrapped as an <li> list item. |
|
241 |
|
242 See also AHrefMenuItem.getHtmlTags(). |
|
243 """ |
|
244 if self._item.sub_menu: |
|
245 css_class = 'expandable' |
|
246 else: |
|
247 css_class = 'leaf' |
|
248 |
|
249 return (['%s<li class="%s">' % (indent, css_class)] |
|
250 + AHrefMenuItem.getHtmlTags(self, indent + ' ') |
|
251 + ['%s</li>' % indent]) |
|