diff options
author | Ian Bicking <ian@ianbicking.org> | 2005-07-17 22:47:53 +0000 |
---|---|---|
committer | Ian Bicking <ian@ianbicking.org> | 2005-07-17 22:47:53 +0000 |
commit | a0779f87b209c1121e689d832ee2155fa3018ace (patch) | |
tree | 4a8377cfd31e9b3e9ce0be661d34cfd0dbb80556 | |
parent | 408da3824326178153a66dc5c9d3bd29bfe743ee (diff) | |
download | paste-git-a0779f87b209c1121e689d832ee2155fa3018ace.tar.gz |
Added better HTML editing abilities
-rw-r--r-- | examples/filebrowser/pathobj.py | 155 | ||||
-rw-r--r-- | examples/filebrowser/server.conf | 11 | ||||
-rw-r--r-- | examples/filebrowser/templates/edit_html.pt | 9 |
3 files changed, 169 insertions, 6 deletions
diff --git a/examples/filebrowser/pathobj.py b/examples/filebrowser/pathobj.py index 15a7628..8b57504 100644 --- a/examples/filebrowser/pathobj.py +++ b/examples/filebrowser/pathobj.py @@ -1,6 +1,11 @@ import re import os import mimetypes +import itertools +import random +import urllib +import cgi +import htmlentitydefs from paste.wareweb import dispatch, public from paste.httpexceptions import * import sitepage @@ -10,6 +15,13 @@ view_servlet_module = None class BadFilePath(Exception): pass +def html_quote(v): + return cgi.escape(v, 1) +def html_unquote(v, encoding='UTF8'): + for name, codepoint in htmlentitydefs.name2codepoint.iteritems(): + v = v.replace('&%s;' % name, unichr(codepoint).encode(encoding)) + return v + class PathContext(object): path_classes = {} @@ -134,17 +146,16 @@ class Path(sitepage.SitePage): self.set_header('Content-type', self.mimetype) content = self.read() if (self.mimetype.startswith('text/html') - and servlet.fields.get('html') == 'clean'): + and self.fields.get('html') == 'clean'): for bad_regex in bad_regexes: content = self.bad_regex.sub('', content) self.write(content) def action_save(self): content = self.fields.content - f = open(self.filename, 'wb') - f.write(content) - f.close() - self.message.write('%i bytes saved' % len(content)) + self.write_text(content) + self.message.write('%i bytes saved to %s' + % (len(content), self.basename)) self.redirect(str(self.pathurl(action='view'))) def read(self): @@ -152,6 +163,11 @@ class Path(sitepage.SitePage): content = f.read() f.close() return content + + def write_text(self, content): + f = open(self.filename, 'wb') + f.write(content) + f.close() PathContext.register_class(Path) @@ -192,6 +208,129 @@ class HTMLFile(TextFile): def action_view(self): self.view = 'view_html.pt' + def action_edit(self): + super(HTMLFile, self).action_edit() + content = self.read() + head, body, tail = self.split_content(content) + self.options.content = body + props = self.read_properties(head) + op_props = self.options.properties = [] + for name, value in sorted(props.items()): + op_props.append({ + 'description': name, + 'input_name': 'property_' + name, + 'type': 'text', + 'value': value}) + + def action_save(self): + # Unlike Path.save, this doesn't save the entire contents + # of the file + content = self.fields.content + props = {} + for name, value in self.fields.items(): + if name.startswith('property_'): + props[name[len('property_'):]] = value + self.save(content, props) + self.message.write('File %s saved' % self.basename) + self.redirect(str(self.pathurl(action='view'))) + + def save(self, content, props): + current = self.read() + head, body, tail = self.split_content(current) + new_head = self.save_properties(head, props) + new_content = new_head + content + tail + self.write_text(new_content) + + def get_regexes(self, name, default=()): + regex = self.config.get(name) + if regex is None: + regex = [] + if not isinstance(regex, (list, tuple)): + regex = [regex] + regex = list(regex) + if default: + regex.extend(default) + for i in range(len(regex)): + if isinstance(regex[i], (str, unicode)): + regex[i] = re.compile(regex[i], re.I) + return regex + + def first_match(self, regexes, body): + for regex in regexes: + m = regex.search(body) + if m: + return m + return None + + def split_content(self, content): + body_start_regex = self.get_regexes( + 'body_start_regex', ['<body.*?>']) + body_end_regex = self.get_regexes( + 'body_end_regex', ['</body>']) + m = self.first_match(body_start_regex, content) + if m: + head = content[:m.end()] + content = content[m.end():] + else: + head = '' + m = self.first_match(body_end_regex, content) + if m: + tail = content[m.start():] + content = content[:m.start()] + else: + tail = '' + return head, content, tail + + title_re = re.compile('<title>(.*?)</title>', re.I|re.S) + def read_properties(self, head): + prop_regex = self.get_regexes( + 'property_regex') + props = {} + m = self.title_re.search(head) + if m: + props['title'] = m.group(1) + for regex in prop_regex: + for match in regex.finditer(head): + groups = match.groupdict() + if 'value' in groups: + value = groups['value'] + elif 'html_value' in groups: + value = html_unquote(groups['html_value']) + elif 'url_value' in groups: + value = urllib.unquote(groups['url_value']) + props[match.group('name')] = value + return props + + def save_properties(self, head, props): + prop_regex = self.get_regexes( + 'property_regex') + prop_tmpl = self.config['property_template'] + if 'title' in props: + title_html = '<title>%s</title>' % html_quote(props['title']) + m = self.title_re.search(head) + if m: + head = head[:m.start()] + title_html + head[m.end():] + del props['title'] + else: + head = title_html + head + for name, value in props.items(): + new_prop = prop_tmpl % { + 'name': name, + 'html_value': html_quote(value), + 'url_value': urllib.quote(value), + 'value': value} + for regex in prop_regex: + for match in regex.finditer(head): + if name != match.group('name'): + continue + head = (head[:match.start()] + new_prop + + head[match.end():]) + break + else: + head = new_prop + head + return head + + PathContext.register_class(HTMLFile) class Dir(Path): @@ -200,6 +339,8 @@ class Dir(Path): isdir = True + upload_id = itertools.count(random.randint(0, 15000)) + def setup_file(self): files = [] for filename in sorted(os.listdir(self.filename)): @@ -216,6 +357,10 @@ class Dir(Path): if path_servlet.isdir: files[-1]['name'] += '/' self.options.files = files + self.options.upload_id = self.upload_id.next() + self.options.upload_url = self.globalurl( + 'uploader', upload_id=self.options.upload_id, + redirect='/') def action_view_raw(self): self.action_view() diff --git a/examples/filebrowser/server.conf b/examples/filebrowser/server.conf index da36acb..81ce746 100644 --- a/examples/filebrowser/server.conf +++ b/examples/filebrowser/server.conf @@ -10,7 +10,7 @@ publish_dir = os.path.join(root_path, 'web') ## Server options: -verbose = False +verbose = True # The name of the server-type to start: server = 'wsgiutils' # If true, files will be regularly polled and the server restarted @@ -24,3 +24,12 @@ browse_path = os.path.join(root_path, 'test-data') if os.path.exists(os.path.join(root_path, 'local.conf')): include('local.conf') + +# This is an example that uses SSIs for the page structure: +body_start_regex = [ + r'<!--#include virtual="/lib/header.ssi" -->'] +body_end_regex = [ + r'<!--#include virtual="/lib/footer.ssi" -->'] +property_regex = [ + r'<!--#set\s+var="(?P<name>[^"]*)"\s+value="(?P<html_value>[^"]*)"\s+-->'] +property_template = '<!--#set var="%(name)s" value="%(html_value)s" -->\n' diff --git a/examples/filebrowser/templates/edit_html.pt b/examples/filebrowser/templates/edit_html.pt index 10abb71..03d8dbd 100644 --- a/examples/filebrowser/templates/edit_html.pt +++ b/examples/filebrowser/templates/edit_html.pt @@ -13,6 +13,15 @@ xinha_editors = ['txt-content']; <form tal:attributes="action options/action" method="POST"> <input type="hidden" name="action" value="save"> Edit:<br> + +<table tal:condition="options/properties | nothing"> + <tr tal:repeat="prop options/properties"> + <td tal:content="prop/description">name</td> + <td><input tal:attributes="type prop/type; name prop/input_name; + value prop/value"></td> + </tr> +</table> + <textarea name="content" id="txt-content" cols=60 style="width: 100%" tal:attributes="rows options/ta_height" tal:content="options/content"></textarea> |