--- a/common/html4zope.py Tue Feb 17 22:05:39 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-# Author: David Goodger
-# Contact: goodger@users.sourceforge.net
-# Revision: $Revision: 1.2 $
-# Date: $Date: 2005-07-04 16:36:50 $
-# Copyright: This module has been placed in the public domain.
-
-"""
-Simple HyperText Markup Language document tree Writer.
-
-The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
-HTML version 1.0 Transitional DTD (*almost* strict). The output contains a
-minimum of formatting information. A cascading style sheet ("default.css" by
-default) is required for proper viewing with a modern graphical browser.
-
-http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
-"""
-
-__docformat__ = 'reStructuredText'
-
-from logilab.mtconverter import html_escape
-
-from docutils import nodes
-from docutils.writers.html4css1 import Writer as CSS1Writer
-from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
-import os
-
-default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
-
-class Writer(CSS1Writer):
- """css writer using our html translator"""
- def __init__(self, base_url):
- CSS1Writer.__init__(self)
- self.translator_class = URLBinder(base_url, HTMLTranslator)
-
- def apply_template(self):
- """overriding this is necessary with docutils >= 0.5"""
- return self.visitor.astext()
-
-class URLBinder:
- def __init__(self, url, klass):
- self.base_url = url
- self.translator_class = HTMLTranslator
-
- def __call__(self, document):
- translator = self.translator_class(document)
- translator.base_url = self.base_url
- return translator
-
-class HTMLTranslator(CSS1HTMLTranslator):
- """ReST tree to html translator"""
-
- def astext(self):
- """return the extracted html"""
- return ''.join(self.body)
-
- def visit_title(self, node):
- """Only 6 section levels are supported by HTML."""
- if isinstance(node.parent, nodes.topic):
- self.body.append(
- self.starttag(node, 'p', '', CLASS='topic-title'))
- if node.parent.hasattr('id'):
- self.body.append(
- self.starttag({}, 'a', '', name=node.parent['id']))
- self.context.append('</a></p>\n')
- else:
- self.context.append('</p>\n')
- elif self.section_level == 0:
- # document title
- self.head.append('<title>%s</title>\n'
- % self.encode(node.astext()))
- self.body.append(self.starttag(node, 'h%d' % default_level, '',
- CLASS='title'))
- self.context.append('</h%d>\n' % default_level)
- else:
- self.body.append(
- self.starttag(node, 'h%s' % (
- default_level+self.section_level-1), ''))
- atts = {}
- if node.hasattr('refid'):
- atts['class'] = 'toc-backref'
- atts['href'] = '%s#%s' % (self.base_url, node['refid'])
- self.body.append(self.starttag({}, 'a', '', **atts))
- self.context.append('</a></h%s>\n' % (
- default_level+self.section_level-1))
-
- def visit_subtitle(self, node):
- """format a subtitle"""
- if isinstance(node.parent, nodes.sidebar):
- self.body.append(self.starttag(node, 'p', '',
- CLASS='sidebar-subtitle'))
- self.context.append('</p>\n')
- else:
- self.body.append(
- self.starttag(node, 'h%s' % (default_level+1), '',
- CLASS='subtitle'))
- self.context.append('</h%s>\n' % (default_level+1))
-
- def visit_document(self, node):
- """syt: i don't want the enclosing <div class="document">"""
- def depart_document(self, node):
- """syt: i don't want the enclosing <div class="document">"""
-
- def visit_reference(self, node):
- """syt: i want absolute urls"""
- if node.has_key('refuri'):
- href = node['refuri']
- if ( self.settings.cloak_email_addresses
- and href.startswith('mailto:')):
- href = self.cloak_mailto(href)
- self.in_mailto = 1
- else:
- assert node.has_key('refid'), \
- 'References must have "refuri" or "refid" attribute.'
- href = '%s#%s' % (self.base_url, node['refid'])
- atts = {'href': href, 'class': 'reference'}
- if not isinstance(node.parent, nodes.TextElement):
- assert len(node) == 1 and isinstance(node[0], nodes.image)
- atts['class'] += ' image-reference'
- self.body.append(self.starttag(node, 'a', '', **atts))
-
- ## override error messages to avoid XHTML problems ########################
- def visit_problematic(self, node):
- pass
-
- def depart_problematic(self, node):
- pass
-
- def visit_system_message(self, node):
- backref_text = ''
- if len(node['backrefs']):
- backrefs = node['backrefs']
- if len(backrefs) == 1:
- backref_text = '; <em>backlink</em>'
- else:
- i = 1
- backlinks = []
- for backref in backrefs:
- backlinks.append(str(i))
- i += 1
- backref_text = ('; <em>backlinks: %s</em>'
- % ', '.join(backlinks))
- if node.hasattr('line'):
- line = ', line %s' % node['line']
- else:
- line = ''
- a_start = a_end = ''
- error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
- a_start, node['type'], node['level'], a_end,
- self.encode(node['source']), line, backref_text)
- self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
-
- def depart_system_message(self, node):
- pass
--- a/common/mttransforms.py Tue Feb 17 22:05:39 2009 +0100
+++ b/common/mttransforms.py Tue Feb 17 22:34:18 2009 +0100
@@ -31,16 +31,7 @@
output = 'text/html'
def _convert(self, trdata):
return html_publish(trdata.appobject, trdata.data)
-
-class ept_to_html(Transform):
- inputs = ('text/cubicweb-page-template',)
- output = 'text/html'
- output_encoding = 'utf-8'
- def _convert(self, trdata):
- from cubicweb.common.tal import compile_template
- value = trdata.encode(self.output_encoding)
- return trdata.appobject.tal_render(compile_template(value), {})
-
+
# Instantiate and configure the transformation engine
@@ -49,7 +40,23 @@
ENGINE = TransformEngine()
ENGINE.add_transform(rest_to_html())
ENGINE.add_transform(html_to_html())
-ENGINE.add_transform(ept_to_html())
+
+try:
+ from cubicweb.common.tal import compile_template
+except ImportError:
+ HAS_TAL = False
+else:
+ HAS_TAL = True
+
+ class ept_to_html(Transform):
+ inputs = ('text/cubicweb-page-template',)
+ output = 'text/html'
+ output_encoding = 'utf-8'
+ def _convert(self, trdata):
+ value = trdata.encode(self.output_encoding)
+ return trdata.appobject.tal_render(compile_template(value), {})
+
+ ENGINE.add_transform(ept_to_html())
if register_pil_transforms(ENGINE, verb=False):
HAS_PIL_TRANSFORMS = True
--- a/common/rest.py Tue Feb 17 22:05:39 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +0,0 @@
-"""rest publishing functions
-
-contains some functions and setup of docutils for cubicweb
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cStringIO import StringIO
-from itertools import chain
-from logging import getLogger
-from os.path import join
-
-from docutils import statemachine, nodes, utils, io
-from docutils.core import publish_string
-from docutils.parsers.rst import Parser, states, directives
-from docutils.parsers.rst.roles import register_canonical_role, set_classes
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.common.html4zope import Writer
-
-# We provide our own parser as an attempt to get rid of
-# state machine reinstanciation
-
-import re
-# compile states.Body patterns
-for k, v in states.Body.patterns.items():
- if isinstance(v, str):
- states.Body.patterns[k] = re.compile(v)
-
-# register ReStructured Text mimetype / extensions
-import mimetypes
-mimetypes.add_type('text/rest', '.rest')
-mimetypes.add_type('text/rest', '.rst')
-
-
-LOGGER = getLogger('cubicweb.rest')
-
-def eid_reference_role(role, rawtext, text, lineno, inliner,
- options={}, content=[]):
- try:
- try:
- eid_num, rest = text.split(u':', 1)
- except:
- eid_num, rest = text, '#'+text
- eid_num = int(eid_num)
- if eid_num < 0:
- raise ValueError
- except ValueError:
- msg = inliner.reporter.error(
- 'EID number must be a positive number; "%s" is invalid.'
- % text, line=lineno)
- prb = inliner.problematic(rawtext, rawtext, msg)
- return [prb], [msg]
- # Base URL mainly used by inliner.pep_reference; so this is correct:
- context = inliner.document.settings.context
- refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
- ref = refedentity.absolute_url()
- set_classes(options)
- return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
- **options)], []
-
-register_canonical_role('eid', eid_reference_role)
-
-
-def card_reference_role(role, rawtext, text, lineno, inliner,
- options={}, content=[]):
- text = text.strip()
- try:
- wikiid, rest = text.split(u':', 1)
- except:
- wikiid, rest = text, text
- context = inliner.document.settings.context
- cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
- {'id': wikiid})
- if cardrset:
- ref = cardrset.get_entity(0, 0).absolute_url()
- else:
- schema = context.schema
- if schema.eschema('Card').has_perm(context.req, 'add'):
- ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
- else:
- ref = '#'
- set_classes(options)
- return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
- **options)], []
-
-register_canonical_role('card', card_reference_role)
-
-
-def winclude_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- """Include a reST file as part of the content of this reST file.
-
- same as standard include directive but using config.locate_doc_resource to
- get actual file to include.
-
- Most part of this implementation is copied from `include` directive defined
- in `docutils.parsers.rst.directives.misc`
- """
- context = state.document.settings.context
- source = state_machine.input_lines.source(
- lineno - state_machine.input_offset - 1)
- #source_dir = os.path.dirname(os.path.abspath(source))
- fid = arguments[0]
- for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
- context.config.available_languages()):
- rid = '%s_%s.rst' % (fid, lang)
- resourcedir = context.config.locate_doc_file(rid)
- if resourcedir:
- break
- else:
- severe = state_machine.reporter.severe(
- 'Problems with "%s" directive path:\nno resource matching %s.'
- % (name, fid),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [severe]
- path = join(resourcedir, rid)
- encoding = options.get('encoding', state.document.settings.input_encoding)
- try:
- state.document.settings.record_dependencies.add(path)
- include_file = io.FileInput(
- source_path=path, encoding=encoding,
- error_handler=state.document.settings.input_encoding_error_handler,
- handle_io_errors=None)
- except IOError, error:
- severe = state_machine.reporter.severe(
- 'Problems with "%s" directive path:\n%s: %s.'
- % (name, error.__class__.__name__, error),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [severe]
- try:
- include_text = include_file.read()
- except UnicodeError, error:
- severe = state_machine.reporter.severe(
- 'Problem with "%s" directive:\n%s: %s'
- % (name, error.__class__.__name__, error),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [severe]
- if options.has_key('literal'):
- literal_block = nodes.literal_block(include_text, include_text,
- source=path)
- literal_block.line = 1
- return literal_block
- else:
- include_lines = statemachine.string2lines(include_text,
- convert_whitespace=1)
- state_machine.insert_input(include_lines, path)
- return []
-
-winclude_directive.arguments = (1, 0, 1)
-winclude_directive.options = {'literal': directives.flag,
- 'encoding': directives.encoding}
-directives.register_directive('winclude', winclude_directive)
-
-class CubicWebReSTParser(Parser):
- """The (customized) reStructuredText parser."""
-
- def __init__(self):
- self.initial_state = 'Body'
- self.state_classes = states.state_classes
- self.inliner = states.Inliner()
- self.statemachine = states.RSTStateMachine(
- state_classes=self.state_classes,
- initial_state=self.initial_state,
- debug=0)
-
- def parse(self, inputstring, document):
- """Parse `inputstring` and populate `document`, a document tree."""
- self.setup_parse(inputstring, document)
- inputlines = statemachine.string2lines(inputstring,
- convert_whitespace=1)
- self.statemachine.run(inputlines, document, inliner=self.inliner)
- self.finish_parse()
-
-
-_REST_PARSER = CubicWebReSTParser()
-
-def rest_publish(context, data):
- """publish a string formatted as ReStructured Text to HTML
-
- :type context: a cubicweb application object
-
- :type data: str
- :param data: some ReST text
-
- :rtype: unicode
- :return:
- the data formatted as HTML or the original data if an error occured
- """
- req = context.req
- if isinstance(data, unicode):
- encoding = 'unicode'
- else:
- encoding = req.encoding
- settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
- 'warning_stream': StringIO(), 'context': context,
- # dunno what's the max, severe is 4, and we never want a crash
- # (though try/except may be a better option...)
- 'halt_level': 10,
- }
- if context:
- if hasattr(req, 'url'):
- base_url = req.url()
- elif hasattr(context, 'absolute_url'):
- base_url = context.absolute_url()
- else:
- base_url = req.base_url()
- else:
- base_url = None
- try:
- return publish_string(writer=Writer(base_url=base_url),
- parser=_REST_PARSER, source=data,
- settings_overrides=settings)
- except Exception:
- LOGGER.exception('error while publishing ReST text')
- if not isinstance(data, unicode):
- data = unicode(data, encoding, 'replace')
- return html_escape(req._('error while publishing ReST text')
- + '\n\n' + data)
--- a/common/tal.py Tue Feb 17 22:05:39 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-"""provides simpleTAL extensions for CubicWeb
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-__docformat__ = "restructuredtext en"
-
-import sys
-import re
-from os.path import exists, isdir, join
-from logging import getLogger
-from StringIO import StringIO
-
-from simpletal import simpleTAL, simpleTALES
-
-from logilab.common.decorators import cached
-
-LOGGER = getLogger('cubicweb.tal')
-
-
-class LoggerAdapter(object):
- def __init__(self, tal_logger):
- self.tal_logger = tal_logger
-
- def debug(self, msg):
- LOGGER.debug(msg)
-
- def warn(self, msg):
- LOGGER.warning(msg)
-
- def __getattr__(self, attrname):
- return getattr(self.tal_logger, attrname)
-
-
-class CubicWebContext(simpleTALES.Context):
- """add facilities to access entity / resultset"""
-
- def __init__(self, options=None, allowPythonPath=1):
- simpleTALES.Context.__init__(self, options, allowPythonPath)
- self.log = LoggerAdapter(self.log)
-
- def update(self, context):
- for varname, value in context.items():
- self.addGlobal(varname, value)
-
- def addRepeat(self, name, var, initialValue):
- simpleTALES.Context.addRepeat(self, name, var, initialValue)
-
-# XXX FIXME need to find a clean to define OPCODE values for extensions
-I18N_CONTENT = 18
-I18N_REPLACE = 19
-RQL_EXECUTE = 20
-# simpleTAL uses the OPCODE values to define priority over commands.
-# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
-# we can't use the same OPCODE for two different commands without changing
-# the simpleTAL implementation. Another solution would be to totally override
-# the REPEAT implementation with the ITER one, but some specific operations
-# (involving len() for instance) are not implemented for ITER, so we prefer
-# to keep both implementations for now, and to fool simpleTAL by using a float
-# number between 3 and 4
-TAL_ITER = 3.1
-
-
-# FIX simpleTAL HTML 4.01 stupidity
-# (simpleTAL never closes tags like INPUT, IMG, HR ...)
-simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
-
-class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
- """extends default compiler by adding i18n:content commands"""
-
- def __init__(self):
- simpleTAL.HTMLTemplateCompiler.__init__(self)
- self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
- self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
- self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
- self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
-
- def setTALPrefix(self, prefix):
- simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
- self.tal_attribute_map['i18n:content'] = I18N_CONTENT
- self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
- self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
- self.tal_attribute_map['tal:iter'] = TAL_ITER
-
- def compile_cmd_i18n_content(self, argument):
- # XXX tal:content structure=, text= should we support this ?
- structure_flag = 0
- return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
-
- def compile_cmd_i18n_replace(self, argument):
- # XXX tal:content structure=, text= should we support this ?
- structure_flag = 0
- return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
-
- def compile_cmd_rql(self, argument):
- return (RQL_EXECUTE, (argument, self.endTagSymbol))
-
- def compile_cmd_tal_iter(self, argument):
- original_id, (var_name, expression, end_tag_symbol) = \
- simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
- return (TAL_ITER, (var_name, expression, self.endTagSymbol))
-
- def getTemplate(self):
- return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
-
- def compileCmdAttributes (self, argument):
- """XXX modified to support single attribute
- definition ending by a ';'
-
- backport this to simpleTAL
- """
- # Compile tal:attributes into attribute command
- # Argument: [(attributeName, expression)]
-
- # Break up the list of attribute settings first
- commandArgs = []
- # We only want to match semi-colons that are not escaped
- argumentSplitter = re.compile(r'(?<!;);(?!;)')
- for attributeStmt in argumentSplitter.split(argument):
- if not attributeStmt.strip():
- continue
- # remove any leading space and un-escape any semi-colons
- attributeStmt = attributeStmt.lstrip().replace(';;', ';')
- # Break each attributeStmt into name and expression
- stmtBits = attributeStmt.split(' ')
- if (len (stmtBits) < 2):
- # Error, badly formed attributes command
- msg = "Badly formed attributes command '%s'. Attributes commands must be of the form: 'name expression[;name expression]'" % argument
- self.log.error(msg)
- raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
- attName = stmtBits[0]
- attExpr = " ".join(stmtBits[1:])
- commandArgs.append((attName, attExpr))
- return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
-
-
-class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
- """provides implementation for interpreting cubicweb extensions"""
- def __init__(self):
- simpleTAL.TemplateInterpreter.__init__(self)
- self.commandHandler[I18N_CONTENT] = self.cmd_i18n
- self.commandHandler[TAL_ITER] = self.cmdRepeat
- # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
-
- def cmd_i18n(self, command, args):
- """i18n:content and i18n:replace implementation"""
- string, replace_flag, structure_flag, end_symbol = args
- if replace_flag:
- self.outputTag = 0
- result = self.context.globals['_'](string)
- self.tagContent = (0, result)
- self.movePCForward = self.symbolTable[end_symbol]
- self.programCounter += 1
-
-
-class CubicWebTemplate(simpleTAL.HTMLTemplate):
- """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
- """
- def expand(self, context, outputFile):
- interpreter = CubicWebTemplateInterpreter()
- interpreter.initialise(context, outputFile)
- simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
- interpreter=interpreter)
-
- def expandInline(self, context, outputFile, interpreter):
- """ Internally used when expanding a template that is part of a context."""
- try:
- interpreter.execute(self)
- except UnicodeError, unierror:
- LOGGER.exception(str(unierror))
- raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
-
-
-def compile_template(template):
- """compiles a TAL template string
- :type template: unicode
- :param template: a TAL-compliant template string
- """
- string_buffer = StringIO(template)
- compiler = CubicWebTemplateCompiler()
- compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
- return compiler.getTemplate()
-
-
-def compile_template_file(filepath):
- """compiles a TAL template file
- :type filepath: str
- :param template: path of the file to compile
- """
- fp = file(filepath)
- file_content = unicode(fp.read()) # template file should be pure ASCII
- fp.close()
- return compile_template(file_content)
-
-
-def evaluatePython (self, expr):
- if not self.allowPythonPath:
- return self.false
- globals = {}
- for name, value in self.globals.items():
- if isinstance (value, simpleTALES.ContextVariable):
- value = value.rawValue()
- globals[name] = value
- globals['path'] = self.pythonPathFuncs.path
- globals['string'] = self.pythonPathFuncs.string
- globals['exists'] = self.pythonPathFuncs.exists
- globals['nocall'] = self.pythonPathFuncs.nocall
- globals['test'] = self.pythonPathFuncs.test
- locals = {}
- for name, value in self.locals.items():
- if (isinstance (value, simpleTALES.ContextVariable)):
- value = value.rawValue()
- locals[name] = value
- # XXX precompile expr will avoid late syntax error
- try:
- result = eval(expr, globals, locals)
- except Exception, ex:
- ex = ex.__class__('in %r: %s' % (expr, ex))
- raise ex, None, sys.exc_info()[-1]
- if (isinstance (result, simpleTALES.ContextVariable)):
- return result.value()
- return result
-
-simpleTALES.Context.evaluatePython = evaluatePython
-
-
-class talbased(object):
- def __init__(self, filename, write=True):
-## if not osp.isfile(filepath):
-## # print "[tal.py] just for tests..."
-## # get parent frame
-## directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
-## filepath = osp.join(directory, filepath)
- self.filename = filename
- self.write = write
-
- def __call__(self, viewfunc):
- def wrapped(instance, *args, **kwargs):
- variables = viewfunc(instance, *args, **kwargs)
- html = instance.tal_render(self._compiled_template(instance), variables)
- if self.write:
- instance.w(html)
- else:
- return html
- return wrapped
-
- def _compiled_template(self, instance):
- for fileordirectory in instance.config.vregistry_path():
- filepath = join(fileordirectory, self.filename)
- if isdir(fileordirectory) and exists(filepath):
- return compile_template_file(filepath)
- raise Exception('no such template %s' % self.filename)
- _compiled_template = cached(_compiled_template, 0)
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/html4zope.py Tue Feb 17 22:34:18 2009 +0100
@@ -0,0 +1,153 @@
+# Author: David Goodger
+# Contact: goodger@users.sourceforge.net
+# Revision: $Revision: 1.2 $
+# Date: $Date: 2005-07-04 16:36:50 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Simple HyperText Markup Language document tree Writer.
+
+The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
+HTML version 1.0 Transitional DTD (*almost* strict). The output contains a
+minimum of formatting information. A cascading style sheet ("default.css" by
+default) is required for proper viewing with a modern graphical browser.
+
+http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
+"""
+
+__docformat__ = 'reStructuredText'
+
+from logilab.mtconverter import html_escape
+
+from docutils import nodes
+from docutils.writers.html4css1 import Writer as CSS1Writer
+from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
+import os
+
+default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
+
+class Writer(CSS1Writer):
+ """css writer using our html translator"""
+ def __init__(self, base_url):
+ CSS1Writer.__init__(self)
+ self.translator_class = URLBinder(base_url, HTMLTranslator)
+
+ def apply_template(self):
+ """overriding this is necessary with docutils >= 0.5"""
+ return self.visitor.astext()
+
+class URLBinder:
+ def __init__(self, url, klass):
+ self.base_url = url
+ self.translator_class = HTMLTranslator
+
+ def __call__(self, document):
+ translator = self.translator_class(document)
+ translator.base_url = self.base_url
+ return translator
+
+class HTMLTranslator(CSS1HTMLTranslator):
+ """ReST tree to html translator"""
+
+ def astext(self):
+ """return the extracted html"""
+ return ''.join(self.body)
+
+ def visit_title(self, node):
+ """Only 6 section levels are supported by HTML."""
+ if isinstance(node.parent, nodes.topic):
+ self.body.append(
+ self.starttag(node, 'p', '', CLASS='topic-title'))
+ if node.parent.hasattr('id'):
+ self.body.append(
+ self.starttag({}, 'a', '', name=node.parent['id']))
+ self.context.append('</a></p>\n')
+ else:
+ self.context.append('</p>\n')
+ elif self.section_level == 0:
+ # document title
+ self.head.append('<title>%s</title>\n'
+ % self.encode(node.astext()))
+ self.body.append(self.starttag(node, 'h%d' % default_level, '',
+ CLASS='title'))
+ self.context.append('</h%d>\n' % default_level)
+ else:
+ self.body.append(
+ self.starttag(node, 'h%s' % (
+ default_level+self.section_level-1), ''))
+ atts = {}
+ if node.hasattr('refid'):
+ atts['class'] = 'toc-backref'
+ atts['href'] = '%s#%s' % (self.base_url, node['refid'])
+ self.body.append(self.starttag({}, 'a', '', **atts))
+ self.context.append('</a></h%s>\n' % (
+ default_level+self.section_level-1))
+
+ def visit_subtitle(self, node):
+ """format a subtitle"""
+ if isinstance(node.parent, nodes.sidebar):
+ self.body.append(self.starttag(node, 'p', '',
+ CLASS='sidebar-subtitle'))
+ self.context.append('</p>\n')
+ else:
+ self.body.append(
+ self.starttag(node, 'h%s' % (default_level+1), '',
+ CLASS='subtitle'))
+ self.context.append('</h%s>\n' % (default_level+1))
+
+ def visit_document(self, node):
+ """syt: i don't want the enclosing <div class="document">"""
+ def depart_document(self, node):
+ """syt: i don't want the enclosing <div class="document">"""
+
+ def visit_reference(self, node):
+ """syt: i want absolute urls"""
+ if node.has_key('refuri'):
+ href = node['refuri']
+ if ( self.settings.cloak_email_addresses
+ and href.startswith('mailto:')):
+ href = self.cloak_mailto(href)
+ self.in_mailto = 1
+ else:
+ assert node.has_key('refid'), \
+ 'References must have "refuri" or "refid" attribute.'
+ href = '%s#%s' % (self.base_url, node['refid'])
+ atts = {'href': href, 'class': 'reference'}
+ if not isinstance(node.parent, nodes.TextElement):
+ assert len(node) == 1 and isinstance(node[0], nodes.image)
+ atts['class'] += ' image-reference'
+ self.body.append(self.starttag(node, 'a', '', **atts))
+
+ ## override error messages to avoid XHTML problems ########################
+ def visit_problematic(self, node):
+ pass
+
+ def depart_problematic(self, node):
+ pass
+
+ def visit_system_message(self, node):
+ backref_text = ''
+ if len(node['backrefs']):
+ backrefs = node['backrefs']
+ if len(backrefs) == 1:
+ backref_text = '; <em>backlink</em>'
+ else:
+ i = 1
+ backlinks = []
+ for backref in backrefs:
+ backlinks.append(str(i))
+ i += 1
+ backref_text = ('; <em>backlinks: %s</em>'
+ % ', '.join(backlinks))
+ if node.hasattr('line'):
+ line = ', line %s' % node['line']
+ else:
+ line = ''
+ a_start = a_end = ''
+ error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
+ a_start, node['type'], node['level'], a_end,
+ self.encode(node['source']), line, backref_text)
+ self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
+
+ def depart_system_message(self, node):
+ pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/rest.py Tue Feb 17 22:34:18 2009 +0100
@@ -0,0 +1,223 @@
+"""rest publishing functions
+
+contains some functions and setup of docutils for cubicweb
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cStringIO import StringIO
+from itertools import chain
+from logging import getLogger
+from os.path import join
+
+from docutils import statemachine, nodes, utils, io
+from docutils.core import publish_string
+from docutils.parsers.rst import Parser, states, directives
+from docutils.parsers.rst.roles import register_canonical_role, set_classes
+
+from logilab.mtconverter import html_escape
+
+from cubicweb.ext.html4zope import Writer
+
+# We provide our own parser as an attempt to get rid of
+# state machine reinstanciation
+
+import re
+# compile states.Body patterns
+for k, v in states.Body.patterns.items():
+ if isinstance(v, str):
+ states.Body.patterns[k] = re.compile(v)
+
+# register ReStructured Text mimetype / extensions
+import mimetypes
+mimetypes.add_type('text/rest', '.rest')
+mimetypes.add_type('text/rest', '.rst')
+
+
+LOGGER = getLogger('cubicweb.rest')
+
+def eid_reference_role(role, rawtext, text, lineno, inliner,
+ options={}, content=[]):
+ try:
+ try:
+ eid_num, rest = text.split(u':', 1)
+ except:
+ eid_num, rest = text, '#'+text
+ eid_num = int(eid_num)
+ if eid_num < 0:
+ raise ValueError
+ except ValueError:
+ msg = inliner.reporter.error(
+ 'EID number must be a positive number; "%s" is invalid.'
+ % text, line=lineno)
+ prb = inliner.problematic(rawtext, rawtext, msg)
+ return [prb], [msg]
+ # Base URL mainly used by inliner.pep_reference; so this is correct:
+ context = inliner.document.settings.context
+ refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
+ ref = refedentity.absolute_url()
+ set_classes(options)
+ return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+ **options)], []
+
+register_canonical_role('eid', eid_reference_role)
+
+
+def card_reference_role(role, rawtext, text, lineno, inliner,
+ options={}, content=[]):
+ text = text.strip()
+ try:
+ wikiid, rest = text.split(u':', 1)
+ except:
+ wikiid, rest = text, text
+ context = inliner.document.settings.context
+ cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
+ {'id': wikiid})
+ if cardrset:
+ ref = cardrset.get_entity(0, 0).absolute_url()
+ else:
+ schema = context.schema
+ if schema.eschema('Card').has_perm(context.req, 'add'):
+ ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
+ else:
+ ref = '#'
+ set_classes(options)
+ return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+ **options)], []
+
+register_canonical_role('card', card_reference_role)
+
+
+def winclude_directive(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ """Include a reST file as part of the content of this reST file.
+
+ same as standard include directive but using config.locate_doc_resource to
+ get actual file to include.
+
+ Most part of this implementation is copied from `include` directive defined
+ in `docutils.parsers.rst.directives.misc`
+ """
+ context = state.document.settings.context
+ source = state_machine.input_lines.source(
+ lineno - state_machine.input_offset - 1)
+ #source_dir = os.path.dirname(os.path.abspath(source))
+ fid = arguments[0]
+ for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
+ context.config.available_languages()):
+ rid = '%s_%s.rst' % (fid, lang)
+ resourcedir = context.config.locate_doc_file(rid)
+ if resourcedir:
+ break
+ else:
+ severe = state_machine.reporter.severe(
+ 'Problems with "%s" directive path:\nno resource matching %s.'
+ % (name, fid),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
+ path = join(resourcedir, rid)
+ encoding = options.get('encoding', state.document.settings.input_encoding)
+ try:
+ state.document.settings.record_dependencies.add(path)
+ include_file = io.FileInput(
+ source_path=path, encoding=encoding,
+ error_handler=state.document.settings.input_encoding_error_handler,
+ handle_io_errors=None)
+ except IOError, error:
+ severe = state_machine.reporter.severe(
+ 'Problems with "%s" directive path:\n%s: %s.'
+ % (name, error.__class__.__name__, error),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
+ try:
+ include_text = include_file.read()
+ except UnicodeError, error:
+ severe = state_machine.reporter.severe(
+ 'Problem with "%s" directive:\n%s: %s'
+ % (name, error.__class__.__name__, error),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
+ if options.has_key('literal'):
+ literal_block = nodes.literal_block(include_text, include_text,
+ source=path)
+ literal_block.line = 1
+ return literal_block
+ else:
+ include_lines = statemachine.string2lines(include_text,
+ convert_whitespace=1)
+ state_machine.insert_input(include_lines, path)
+ return []
+
+winclude_directive.arguments = (1, 0, 1)
+winclude_directive.options = {'literal': directives.flag,
+ 'encoding': directives.encoding}
+directives.register_directive('winclude', winclude_directive)
+
+class CubicWebReSTParser(Parser):
+ """The (customized) reStructuredText parser."""
+
+ def __init__(self):
+ self.initial_state = 'Body'
+ self.state_classes = states.state_classes
+ self.inliner = states.Inliner()
+ self.statemachine = states.RSTStateMachine(
+ state_classes=self.state_classes,
+ initial_state=self.initial_state,
+ debug=0)
+
+ def parse(self, inputstring, document):
+ """Parse `inputstring` and populate `document`, a document tree."""
+ self.setup_parse(inputstring, document)
+ inputlines = statemachine.string2lines(inputstring,
+ convert_whitespace=1)
+ self.statemachine.run(inputlines, document, inliner=self.inliner)
+ self.finish_parse()
+
+
+_REST_PARSER = CubicWebReSTParser()
+
+def rest_publish(context, data):
+ """publish a string formatted as ReStructured Text to HTML
+
+ :type context: a cubicweb application object
+
+ :type data: str
+ :param data: some ReST text
+
+ :rtype: unicode
+ :return:
+ the data formatted as HTML or the original data if an error occured
+ """
+ req = context.req
+ if isinstance(data, unicode):
+ encoding = 'unicode'
+ else:
+ encoding = req.encoding
+ settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
+ 'warning_stream': StringIO(), 'context': context,
+ # dunno what's the max, severe is 4, and we never want a crash
+ # (though try/except may be a better option...)
+ 'halt_level': 10,
+ }
+ if context:
+ if hasattr(req, 'url'):
+ base_url = req.url()
+ elif hasattr(context, 'absolute_url'):
+ base_url = context.absolute_url()
+ else:
+ base_url = req.base_url()
+ else:
+ base_url = None
+ try:
+ return publish_string(writer=Writer(base_url=base_url),
+ parser=_REST_PARSER, source=data,
+ settings_overrides=settings)
+ except Exception:
+ LOGGER.exception('error while publishing ReST text')
+ if not isinstance(data, unicode):
+ data = unicode(data, encoding, 'replace')
+ return html_escape(req._('error while publishing ReST text')
+ + '\n\n' + data)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/tal.py Tue Feb 17 22:34:18 2009 +0100
@@ -0,0 +1,256 @@
+"""provides simpleTAL extensions for CubicWeb
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__docformat__ = "restructuredtext en"
+
+import sys
+import re
+from os.path import exists, isdir, join
+from logging import getLogger
+from StringIO import StringIO
+
+from simpletal import simpleTAL, simpleTALES
+
+from logilab.common.decorators import cached
+
+LOGGER = getLogger('cubicweb.tal')
+
+
+class LoggerAdapter(object):
+ def __init__(self, tal_logger):
+ self.tal_logger = tal_logger
+
+ def debug(self, msg):
+ LOGGER.debug(msg)
+
+ def warn(self, msg):
+ LOGGER.warning(msg)
+
+ def __getattr__(self, attrname):
+ return getattr(self.tal_logger, attrname)
+
+
+class CubicWebContext(simpleTALES.Context):
+ """add facilities to access entity / resultset"""
+
+ def __init__(self, options=None, allowPythonPath=1):
+ simpleTALES.Context.__init__(self, options, allowPythonPath)
+ self.log = LoggerAdapter(self.log)
+
+ def update(self, context):
+ for varname, value in context.items():
+ self.addGlobal(varname, value)
+
+ def addRepeat(self, name, var, initialValue):
+ simpleTALES.Context.addRepeat(self, name, var, initialValue)
+
+# XXX FIXME need to find a clean to define OPCODE values for extensions
+I18N_CONTENT = 18
+I18N_REPLACE = 19
+RQL_EXECUTE = 20
+# simpleTAL uses the OPCODE values to define priority over commands.
+# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
+# we can't use the same OPCODE for two different commands without changing
+# the simpleTAL implementation. Another solution would be to totally override
+# the REPEAT implementation with the ITER one, but some specific operations
+# (involving len() for instance) are not implemented for ITER, so we prefer
+# to keep both implementations for now, and to fool simpleTAL by using a float
+# number between 3 and 4
+TAL_ITER = 3.1
+
+
+# FIX simpleTAL HTML 4.01 stupidity
+# (simpleTAL never closes tags like INPUT, IMG, HR ...)
+simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
+
+class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
+ """extends default compiler by adding i18n:content commands"""
+
+ def __init__(self):
+ simpleTAL.HTMLTemplateCompiler.__init__(self)
+ self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
+ self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
+ self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
+ self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
+
+ def setTALPrefix(self, prefix):
+ simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
+ self.tal_attribute_map['i18n:content'] = I18N_CONTENT
+ self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
+ self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
+ self.tal_attribute_map['tal:iter'] = TAL_ITER
+
+ def compile_cmd_i18n_content(self, argument):
+ # XXX tal:content structure=, text= should we support this ?
+ structure_flag = 0
+ return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
+
+ def compile_cmd_i18n_replace(self, argument):
+ # XXX tal:content structure=, text= should we support this ?
+ structure_flag = 0
+ return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
+
+ def compile_cmd_rql(self, argument):
+ return (RQL_EXECUTE, (argument, self.endTagSymbol))
+
+ def compile_cmd_tal_iter(self, argument):
+ original_id, (var_name, expression, end_tag_symbol) = \
+ simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
+ return (TAL_ITER, (var_name, expression, self.endTagSymbol))
+
+ def getTemplate(self):
+ return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
+
+ def compileCmdAttributes (self, argument):
+ """XXX modified to support single attribute
+ definition ending by a ';'
+
+ backport this to simpleTAL
+ """
+ # Compile tal:attributes into attribute command
+ # Argument: [(attributeName, expression)]
+
+ # Break up the list of attribute settings first
+ commandArgs = []
+ # We only want to match semi-colons that are not escaped
+ argumentSplitter = re.compile(r'(?<!;);(?!;)')
+ for attributeStmt in argumentSplitter.split(argument):
+ if not attributeStmt.strip():
+ continue
+ # remove any leading space and un-escape any semi-colons
+ attributeStmt = attributeStmt.lstrip().replace(';;', ';')
+ # Break each attributeStmt into name and expression
+ stmtBits = attributeStmt.split(' ')
+ if (len (stmtBits) < 2):
+ # Error, badly formed attributes command
+ msg = "Badly formed attributes command '%s'. Attributes commands must be of the form: 'name expression[;name expression]'" % argument
+ self.log.error(msg)
+ raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
+ attName = stmtBits[0]
+ attExpr = " ".join(stmtBits[1:])
+ commandArgs.append((attName, attExpr))
+ return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
+
+
+class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
+ """provides implementation for interpreting cubicweb extensions"""
+ def __init__(self):
+ simpleTAL.TemplateInterpreter.__init__(self)
+ self.commandHandler[I18N_CONTENT] = self.cmd_i18n
+ self.commandHandler[TAL_ITER] = self.cmdRepeat
+ # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
+
+ def cmd_i18n(self, command, args):
+ """i18n:content and i18n:replace implementation"""
+ string, replace_flag, structure_flag, end_symbol = args
+ if replace_flag:
+ self.outputTag = 0
+ result = self.context.globals['_'](string)
+ self.tagContent = (0, result)
+ self.movePCForward = self.symbolTable[end_symbol]
+ self.programCounter += 1
+
+
+class CubicWebTemplate(simpleTAL.HTMLTemplate):
+ """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
+ """
+ def expand(self, context, outputFile):
+ interpreter = CubicWebTemplateInterpreter()
+ interpreter.initialise(context, outputFile)
+ simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
+ interpreter=interpreter)
+
+ def expandInline(self, context, outputFile, interpreter):
+ """ Internally used when expanding a template that is part of a context."""
+ try:
+ interpreter.execute(self)
+ except UnicodeError, unierror:
+ LOGGER.exception(str(unierror))
+ raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
+
+
+def compile_template(template):
+ """compiles a TAL template string
+ :type template: unicode
+ :param template: a TAL-compliant template string
+ """
+ string_buffer = StringIO(template)
+ compiler = CubicWebTemplateCompiler()
+ compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
+ return compiler.getTemplate()
+
+
+def compile_template_file(filepath):
+ """compiles a TAL template file
+ :type filepath: str
+ :param template: path of the file to compile
+ """
+ fp = file(filepath)
+ file_content = unicode(fp.read()) # template file should be pure ASCII
+ fp.close()
+ return compile_template(file_content)
+
+
+def evaluatePython (self, expr):
+ if not self.allowPythonPath:
+ return self.false
+ globals = {}
+ for name, value in self.globals.items():
+ if isinstance (value, simpleTALES.ContextVariable):
+ value = value.rawValue()
+ globals[name] = value
+ globals['path'] = self.pythonPathFuncs.path
+ globals['string'] = self.pythonPathFuncs.string
+ globals['exists'] = self.pythonPathFuncs.exists
+ globals['nocall'] = self.pythonPathFuncs.nocall
+ globals['test'] = self.pythonPathFuncs.test
+ locals = {}
+ for name, value in self.locals.items():
+ if (isinstance (value, simpleTALES.ContextVariable)):
+ value = value.rawValue()
+ locals[name] = value
+ # XXX precompile expr will avoid late syntax error
+ try:
+ result = eval(expr, globals, locals)
+ except Exception, ex:
+ ex = ex.__class__('in %r: %s' % (expr, ex))
+ raise ex, None, sys.exc_info()[-1]
+ if (isinstance (result, simpleTALES.ContextVariable)):
+ return result.value()
+ return result
+
+simpleTALES.Context.evaluatePython = evaluatePython
+
+
+class talbased(object):
+ def __init__(self, filename, write=True):
+## if not osp.isfile(filepath):
+## # print "[tal.py] just for tests..."
+## # get parent frame
+## directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
+## filepath = osp.join(directory, filepath)
+ self.filename = filename
+ self.write = write
+
+ def __call__(self, viewfunc):
+ def wrapped(instance, *args, **kwargs):
+ variables = viewfunc(instance, *args, **kwargs)
+ html = instance.tal_render(self._compiled_template(instance), variables)
+ if self.write:
+ instance.w(html)
+ else:
+ return html
+ return wrapped
+
+ def _compiled_template(self, instance):
+ for fileordirectory in instance.config.vregistry_path():
+ filepath = join(fileordirectory, self.filename)
+ if isdir(fileordirectory) and exists(filepath):
+ return compile_template_file(filepath)
+ raise Exception('no such template %s' % self.filename)
+ _compiled_template = cached(_compiled_template, 0)
+