summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Bicking <ian@ianbicking.org>2005-07-17 22:47:53 +0000
committerIan Bicking <ian@ianbicking.org>2005-07-17 22:47:53 +0000
commita0779f87b209c1121e689d832ee2155fa3018ace (patch)
tree4a8377cfd31e9b3e9ce0be661d34cfd0dbb80556
parent408da3824326178153a66dc5c9d3bd29bfe743ee (diff)
downloadpaste-git-a0779f87b209c1121e689d832ee2155fa3018ace.tar.gz
Added better HTML editing abilities
-rw-r--r--examples/filebrowser/pathobj.py155
-rw-r--r--examples/filebrowser/server.conf11
-rw-r--r--examples/filebrowser/templates/edit_html.pt9
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>