Skip to content

Commit

Permalink
Document the rest of the important parts of the API.
Browse files Browse the repository at this point in the history
Some minor whitespace cleanup along the way.
  • Loading branch information
jamadden committed Nov 3, 2017
1 parent c9cbf3c commit d052e52
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 76 deletions.
5 changes: 5 additions & 0 deletions docs/htmltalparser.rst
@@ -0,0 +1,5 @@
============================
Parsing and Compiling HTML
============================

.. automodule:: zope.tal.htmltalparser
30 changes: 30 additions & 0 deletions docs/index.rst
@@ -1,11 +1,41 @@
.. include:: ../README.rst

Using ``zope.tal`` requires three steps: choosing an expression engine
(usually :mod:`zope.tales`), creating a generator and parser, and then
interpreting the compiled program::

from io import StringIO
from zope.tal.talgenerator import TALGenerator
from zope.tal.htmltalparser import HTMLTALParser
from zope.tal.talinterpreter import TALInterpreter

compiler = None # Will use a compiler for a dummy language
source_file = '<string>'
source_text = '<html><body><p>Hi</p></body></html>'
gen = TALGenerator(compiler, source_file=source_file)
parser = TALParser(gen)
parser.parseString(source_text)
program, macros = parser.getCode()

output = StringIO()
context = None # Usually will create a zope.tales context
interpreter = TALInterpreter(self.program, macros, context, stream=output)
interpreter()
result = output.getvalue()

These aspects are all brought together in :mod:`zope.pagetemplate`.

API Documentation:

.. toctree::
:maxdepth: 2

interfaces
taldefs
talgenerator
htmltalparser
talparser
talinterpreter

.. toctree::
:maxdepth: 1
Expand Down
5 changes: 5 additions & 0 deletions docs/taldefs.rst
@@ -0,0 +1,5 @@
====================
Common Definitions
====================

.. automodule:: zope.tal.taldefs
5 changes: 5 additions & 0 deletions docs/talgenerator.rst
@@ -0,0 +1,5 @@
==========================
Generating Compiled Code
==========================

.. automodule:: zope.tal.talgenerator
5 changes: 5 additions & 0 deletions docs/talinterpreter.rst
@@ -0,0 +1,5 @@
============================
Interpreting Compiled Code
============================

.. automodule:: zope.tal.talinterpreter
7 changes: 7 additions & 0 deletions docs/talparser.rst
@@ -0,0 +1,7 @@
===========================
Parsing and Compiling XML
===========================

.. automodule:: zope.tal.talparser

.. autoclass:: zope.tal.xmlparser.XMLParser
56 changes: 38 additions & 18 deletions src/zope/tal/htmltalparser.py
Expand Up @@ -11,7 +11,9 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Parse HTML and compile to TALInterpreter intermediate code.
"""
Parse HTML and compile to :class:`~.TALInterpreter` intermediate code, using
a :class:`~.TALGenerator`.
"""

# When Python 3 becomes mainstream please swap the try and except parts.
Expand All @@ -28,6 +30,7 @@
# so here's a copy taken from Python 3.4:
class HTMLParseError(Exception):
def __init__(self, msg, position=(None, None)):
Exception.__init__(self)
assert msg
self.msg = msg
self.lineno = position[0]
Expand All @@ -50,30 +53,30 @@ def __str__(self):
if 'convert_charrefs' in HTMLParser.__init__.__code__.co_names:
_html_parser_extras['convert_charrefs'] = False # pragma: NO COVER py34


#: List of Boolean attributes in HTML that may be given in
#: minimized form (e.g. ``<img ismap>`` rather than ``<img ismap="">``)
#: From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
BOOLEAN_HTML_ATTRS = frozenset([
# List of Boolean attributes in HTML that may be given in
# minimized form (e.g. <img ismap> rather than <img ismap="">)
# From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
"compact", "nowrap", "ismap", "declare", "noshade", "checked",
"disabled", "readonly", "multiple", "selected", "noresize",
"defer"
])

#: List of HTML tags with an empty content model; these are
#: rendered in minimized form, e.g. ``<img />``.
#: From http://www.w3.org/TR/xhtml1/#dtds
EMPTY_HTML_TAGS = frozenset([
# List of HTML tags with an empty content model; these are
# rendered in minimized form, e.g. <img />.
# From http://www.w3.org/TR/xhtml1/#dtds
"base", "meta", "link", "hr", "br", "param", "img", "area",
"input", "col", "basefont", "isindex", "frame",
])

#: List of HTML elements that close open paragraph-level elements
#: and are themselves paragraph-level.
PARA_LEVEL_HTML_TAGS = frozenset([
# List of HTML elements that close open paragraph-level elements
# and are themselves paragraph-level.
"h1", "h2", "h3", "h4", "h5", "h6", "p",
])

#: Tags that automatically close other tags.
BLOCK_CLOSING_TAG_MAP = {
"tr": frozenset(["tr", "td", "th"]),
"td": frozenset(["td", "th"]),
Expand All @@ -83,12 +86,13 @@ def __str__(self):
"dt": frozenset(["dd", "dt"]),
}

#: List of HTML tags that denote larger sections than paragraphs.
BLOCK_LEVEL_HTML_TAGS = frozenset([
# List of HTML tags that denote larger sections than paragraphs.
"blockquote", "table", "tr", "th", "td", "thead", "tfoot", "tbody",
"noframe", "ul", "ol", "li", "dl", "dt", "dd", "div",
])

#: Section level HTML tags
SECTION_LEVEL_HTML_TAGS = PARA_LEVEL_HTML_TAGS.union(BLOCK_LEVEL_HTML_TAGS)

TIGHTEN_IMPLICIT_CLOSE_TAGS = PARA_LEVEL_HTML_TAGS.union(BLOCK_CLOSING_TAG_MAP)
Expand Down Expand Up @@ -127,39 +131,55 @@ def __init__(self, tagstack, tag, position=(None, None)):
HTMLParseError.__init__(self, msg, position)

class HTMLTALParser(HTMLParser):
"""
Parser for HTML.
After you call either :meth:`parseFile` and :meth:`parseString`
you can retrieve the compiled program using :meth:`getCode`.
"""

# External API

def __init__(self, gen=None):
"""
:keyword TALGenerator gen: The configured (with an expression compiler)
code generator to use. If one is not given, a default will be used.
"""
HTMLParser.__init__(self, **_html_parser_extras)
if gen is None:
gen = TALGenerator(xml=0)
self.gen = gen
self.tagstack = []
self.nsstack = []
self.nsdict = {'tal': ZOPE_TAL_NS,
'metal': ZOPE_METAL_NS,
'i18n': ZOPE_I18N_NS,
}
self.nsdict = {
'tal': ZOPE_TAL_NS,
'metal': ZOPE_METAL_NS,
'i18n': ZOPE_I18N_NS,
}

def parseFile(self, file):
f = open(file)
data = f.read()
f.close()
"""Parse data in the given file."""
with open(file) as f:
data = f.read()

try:
self.parseString(data)
except TALError as e:
e.setFile(file)
raise

def parseString(self, data):
"""Parse data in the given string."""
self.feed(data)
self.close()
while self.tagstack:
self.implied_endtag(self.tagstack[-1], 2)
assert self.nsstack == [], self.nsstack

def getCode(self):
"""
After parsing, this returns ``(program, macros)``.
"""
return self.gen.getCode()

# Overriding HTMLParser methods
Expand Down
47 changes: 31 additions & 16 deletions src/zope/tal/taldefs.py
Expand Up @@ -17,20 +17,26 @@
from zope.tal.interfaces import ITALExpressionErrorInfo
from zope.interface import implementer


#: Version of the specification we implement.
TAL_VERSION = "1.6"

XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace
XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations
#: URI for XML namespace
XML_NS = "http://www.w3.org/XML/1998/namespace"
#: URI for XML NS declarations
XMLNS_NS = "http://www.w3.org/2000/xmlns/"

#: TAL namespace URI
ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal"
#: METAL namespace URI
ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal"
#: I18N namespace URI
ZOPE_I18N_NS = "http://xml.zope.org/namespaces/i18n"

# This RE must exactly match the expression of the same name in the
# zope.i18n.simpletranslationservice module:
NAME_RE = "[a-zA-Z_][-a-zA-Z0-9_]*"

#: Known METAL attributes
KNOWN_METAL_ATTRIBUTES = frozenset([
"define-macro",
"extend-macro",
Expand All @@ -39,6 +45,7 @@
"fill-slot",
])

#: Known TAL attributes
KNOWN_TAL_ATTRIBUTES = frozenset([
"define",
"condition",
Expand All @@ -53,6 +60,7 @@
# like <tal:x>, <metal:y>, <i18n:z>
])

#: Known I18N attributes
KNOWN_I18N_ATTRIBUTES = frozenset([
"translate",
"domain",
Expand All @@ -66,8 +74,12 @@
])

class TALError(Exception):
"""
A base exception for errors raised by this implementation.
"""

def __init__(self, msg, position=(None, None)):
Exception.__init__(self)
assert msg != ""
self.msg = msg
self.lineno = position[0]
Expand All @@ -88,17 +100,20 @@ def __str__(self):
return result

class METALError(TALError):
pass
"""An error parsing on running METAL macros."""

class TALExpressionError(TALError):
pass
"""An error parsing or running a TAL expression."""

class I18NError(TALError):
pass
"""An error parsing a I18N expression."""


@implementer(ITALExpressionErrorInfo)
class ErrorInfo(object):
"""
Default implementation of :class:`zope.tal.interfaces.ITALExpressionErrorInfo`.
"""

def __init__(self, err, position=(None, None)):
if isinstance(err, Exception):
Expand All @@ -115,18 +130,18 @@ def __init__(self, err, position=(None, None)):
_subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S)

def parseAttributeReplacements(arg, xml):
dict = {}
attr_dict = {}
for part in splitParts(arg):
m = _attr_re.match(part)
if not m:
raise TALError("Bad syntax in attributes: %r" % part)
name, expr = m.groups()
if not xml:
name = name.lower()
if name in dict:
if name in attr_dict:
raise TALError("Duplicate attribute name in attributes: %r" % part)
dict[name] = expr
return dict
attr_dict[name] = expr
return attr_dict

def parseSubstitution(arg, position=(None, None)):
m = _subst_re.match(arg)
Expand All @@ -151,26 +166,26 @@ def isCurrentVersion(program):
version = getProgramVersion(program)
return version == TAL_VERSION

def isinstance_(ob, type):
def isinstance_(ob, kind):
# Proxy-friendly and faster isinstance_ check for new-style objects
try:
return type in ob.__class__.__mro__
return kind in ob.__class__.__mro__
except AttributeError:
return False


def getProgramMode(program):
version = getProgramVersion(program)
if (version == TAL_VERSION and isinstance_(program[1], tuple) and
len(program[1]) == 2):
if (version == TAL_VERSION and isinstance_(program[1], tuple)
and len(program[1]) == 2):
opcode, mode = program[1]
if opcode == "mode":
return mode
return None

def getProgramVersion(program):
if (len(program) >= 2 and
isinstance_(program[0], tuple) and len(program[0]) == 2):
if (len(program) >= 2
and isinstance_(program[0], tuple) and len(program[0]) == 2):
opcode, version = program[0]
if opcode == "version":
return version
Expand Down

0 comments on commit d052e52

Please sign in to comment.