YATL 中的助手(helpers)

助手(helpers)概览

假设在一个模板中有如下代码:

[[=DIV('this', 'is', 'a', 'test', _id='123', _class='myclass')]]

它被呈现为:

<div id="123" class="myclass">thisisatest</div>

通过复制 _scaffold 应用程序(请参阅 复制 _scaffold 应用 ),然后编辑文件 new_app/template/index.html ,您可以轻松测试这些命令的渲染。

DIV 是一个 helper class ,即可用于以编程方式构建 HTML 的东西。它对应于 HTML 的 <div> 标签。

助手(Helper)可以有:

  • 位置参数,被解释为 开始结束 标记之间包含的对象,如前一个示例中的 thisisatest

  • 下划线开头的命名参数,被解释为 HTML 标签属性(不带下划线),如上例中的 _class_id

  • 命名参数 (不以下划线开头),这些参数是特定于标记的。

helper 也可以使用 * 符号将单个列表或元组作为其组件集来代替使用一组未命名的参数,并且可以使用 ** 符号将单个字典作为其属性集,例如:

[[
contents = ['this', 'is', 'a', 'test']
attributes = {'_id':'123', '_class':'myclass'}
=DIV(*contents, **attributes)
]]

产生与前面相同的输出)。

以下是 YATL 模块中当前可用的助手集(helpers):

A, BEAUTIFY, BODY, CAT, CODE, DIV, EM, FORM, H1, H2, H3, H4, H5, H6, HEAD, HTML, IMG, INPUT, LABEL, LI, METATAG, OL, OPTION, P, PRE, SELECT, SPAN, STRONG, TABLE, TAG, TAGGER, THEAD, TBODY, TD, TEXTAREA, TH, TT, TR, UL, XML, xmlescape, I, META, LINK, TITLE, STYLE, SCRIPT

可用 helper 构建复杂的表达式,然后可以将其序列化为 XML。例如:

[[=DIV(STRONG(I("hello ", "<world>")), _class="myclass")]]

被呈现为:

<div class="myclass"><strong><i>hello &lt;world&gt;</i></strong></div>

也可以使用 __str__xml 方法将 helper 等效地序列化为字符串。这可以直接使用 Python shell 或使用 py4web 的 shell 命令行选项 进行手动测试,例如:

>>> from yatl.helpers import *
>>>
>>> str(DIV("hello world"))
'<div>hello world</div>'
>>> DIV("hello world").xml()
'<div>hello world</div>'

py4web 中的 helper 机制不仅仅是一个在不连接成字符串的情况下生成 HTML 的系统。它提供了文档对象模型(DOM)的服务器端表示。

helper 内包含的组件可以通过其位置进行引用,外层 helper 充当其内部组件的列表:

>>> a = DIV(SPAN('a', 'b'), 'c')
>>> print(a)
<div><span>ab</span>c</div>
>>> del a[1]
>>> a.append(STRONG('x'))
>>> a[0][0] = 'y'
>>> print(a)
<div><span>yb</span><strong>x</strong></div>

helpers 的属性可以通过名称引用,helper 充当其属性作为键的字典:

>>> a = DIV(SPAN('a', 'b'), 'c')
>>> a['_class'] = 's'
>>> a[0]['_class'] = 't'
>>> print(a)
<div class="s"><span class="t">ab</span>c</div>

请注意,可以通过一个名为 a.children 的列表访问完整的组件集;可以通过名为 a.attributes 的字典访问完整的属性集。因此,当 i 是整数时, a[i] 等价于 a.children[i] ;当 s 是字符串时, a[s] 等价于 a.attributes[s]

请注意, helper 的属性作为关键字参数传递给 helper 。然而,在某些情况下,属性名称包含 Python 标识符中不允许的特殊字符(例如连字符),因此不能用作关键字参数名称。例如:

DIV('text', _data-role='collapsible')

将不起作用,因为 “_data-role” 包含连字符,这将产生 Python 语法错误。

在这种情况下,您可以将属性作为字典传递,并使用 Python 函数的 ** 参数表示法,该表示法将字典中的每项(key:value)映射到一组关键字参数中:

>>> print(DIV('text', **{'_data-role': 'collapsible'}))
<div data-role="collapsible">text</div>

您还可以动态创建特殊 TAG:

>>> print(TAG['soap:Body']('whatever', **{'_xmlns:m':'http://www.example.org'}))
<soap:Body xmlns:m="http://www.example.org">whatever</soap:Body>

内置的 helpers

XML

XML 是一个 helper 对象,用于封装 不应被转义 的文本。文本可能包含也可能不包含有效的 XML;例如,它可以包含 JavaScript 。

在下面示例中的文本被转义了:

>>> print(DIV("<strong>hello</strong>"))
<div>&lt;strong&gt;hello&lt;/strong&gt;</div>

使用 XML 能防止被转义:

>>> print(DIV(XML("<strong>hello</strong>")))
<div><strong>hello</strong></div>

有时,你需要渲染存储在一个变量中的 HTML,但是其中可能包含像 script 这类不安全的标签:

>>> print(XML('<script>alert("unsafe!")</script>'))
<script>alert("unsafe!")</script>

像这样未转义的可执行输入(例如,在博客的评论正文中输入)是不安全的,因为它可用于对页面的其他访问者生成跨站脚本(XSS)攻击。在这种情况下,py4web 的 XML helper 可以净化我们的文本,以防止注入和转义所有标签,但明确允许的标签除外。以下是一个示例:

>>> print(XML('<script>alert("unsafe!")</script>', sanitize=True))
&lt;script&gt;alert(&quot;unsafe!&quot;)&lt;/script&gt;

默认情况下, XML 构造函数认为某些标记的内容及其某些属性是安全的。您可以使用可选的 permitted_tagsallowed_attributes 参数覆盖默认值。以下是 XML helper 的可选参数的默认值。

XML(text, sanitize=False,
    permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li',
        'ol', 'ul', 'p', 'cite', 'code', 'pre', 'img/',
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'table', 'tr', 'td',
        'div', 'strong', 'span'],
    allowed_attributes={'a': ['href', 'title', 'target'],
        'img': ['src', 'alt'], 'blockquote': ['type'], 'td': ['colspan']})

A

此 helper 用于构建链接。

>>> print(A('<click>', XML('<strong>me</strong>'),
            _href='http://www.py4web.com'))
<a href="http://www.py4web.com">&lt;click&gt;<strong>me</strong></a>

BODY

此 helper 构成页面的主体。

>>> print(BODY('<hello>', XML('<strong>world</strong>'), _bgcolor='red'))
<body bgcolor="red">&lt;hello&gt;<strong>world</strong></body>

CAT

此 helper 连接其它 helpers 。

>>> print(CAT('Here is a ', A('link', _href='target'), ', and here is some ', STRONG('bold text'), '.'))
Here is a <a href="target">link</a>, and here is some <strong>bold text</strong>.

DIV

这是内容划分元素。

>>> print(DIV('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<div id="0" class="test">&lt;hello&gt;<strong>world</strong></div>

EM

强调其内容。

>>> print(EM('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<em id="0" class="test">&lt;hello&gt;<strong>world</strong></em>

FORM

使用此 helper 为用户输入制作表单。稍后将在专门的 表单(Forms) 章节中详细讨论。

>>> print(FORM(INPUT(_type='submit'), _action='', _method='post'))
<form action="" method="post"><input type="submit"/></form>

H1, H2, H3, H4, H5, H6

这些 helpers 用于段落标题和副标题。

>>> print(H1('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<h1 id="0" class="test">&lt;hello&gt;<strong>world</strong></h1>

HTML

用于标记一个 HTML 页面。

>>> print(HTML(BODY('<hello>', XML('<strong>world</strong>'))))
<html><body>&lt;hello&gt;<strong>world</strong></body></html>

I

此 helper 将其内容设置为斜体。

>>> print(I('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<i id="0" class="test">&lt;hello&gt;<strong>world</strong></i>

IMG

它可以用来将图像嵌入 HTML。

>>> print(IMG(_src='http://example.com/image.png', _alt='test'))
<img alt="test" src="http://example.com/image.png"/>

以下是 A、 IMG 和 URL 等 helpers 的组合,用于包含带有链接的静态图像:

>>> print(A(IMG(_src=URL('static', 'logo.png'), _alt="My Logo"),
... _href=URL('default', 'index')))
<a href="/default/index"><img alt="My Logo" src="/static/logo.png"/></a>

INPUT

创建一个 <input.../> 标签。输入标签可能不包含其他标记,并且由 /> 而不是 > 结束。输入标签有一个可选的属性 _type 可以设置为 “text”(默认值)、“submit”、“checkbox” 或 “radio”。

>>> print(INPUT(_name='test', _value='a'))
<input name="test" value="a"/>

对于单选按钮,使用 _checked 属性以默认选中对应项目:

>>> for v in ['a', 'b', 'c']:
...     print(INPUT(_type='radio', _name='test', _value=v, _checked=v=='b'), v)
...
<input name="test" type="radio" value="a"/> a
<input checked="checked" name="test" type="radio" value="b"/> b
<input name="test" type="radio" value="c"/> c

对于复选框也是如此:

>>> print(INPUT(_type='checkbox', _name='test', _value='a', _checked=True))
<input checked="checked" name="test" type="checkbox" value="a"/>
>>> print(INPUT(_type='checkbox', _name='test', _value='a', _checked=False))
<input name="test" type="checkbox" value="a"/>

LABEL

它被用于为 INPUT 字段创建 LABEL 标签。

>>> print(LABEL('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<label id="0" class="test">&lt;hello&gt;<strong>world</strong></label>

LI

它构成一个列表项,应包含在 ULOL 标签中。

>>> print(LI('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<li id="0" class="test">&lt;hello&gt;<strong>world</strong></li>

OL

它代表有序列表。列表应包含 LI 标签。

>>> print(OL(LI('<hello>'), LI(XML('<strong>world</strong>')), _class='test', _id=0))
<ol class="test" id="0"><li>&lt;hello&gt;</li><li><strong>world</strong></li></ol>

OPTION

这仅用于 SELECT 的参数。

>>> print(OPTION('<hello>', XML('<strong>world</strong>'), _value='a'))
<option value="a">&lt;hello&gt;<strong>world</strong></option>

对于选定的选项,使用 _selected 属性:

>>> print(OPTION('Thank You', _value='ok', _selected=True))
<option selected="selected" value="ok">Thank You</option>

P

这是用来标记段落的。

>>> print(P('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<p id="0" class="test">&lt;hello&gt;<strong>world</strong></p>

PRE

生成一个 <pre>...</pre> 标签,以用于显示预格式化的文本。 CODE helper 通常更适合用于展示代码。

>>> print(SELECT(OPTION('first', _value='1'), OPTION('second', _value='2'), _class='test', _id=0))
<pre id="0" class="test">&lt;hello&gt;<strong>world</strong></pre>

SCRIPT

这用于包含或链接脚本,如 JavaScript。

>>> print(SCRIPT('console.log("hello world");', _type='text/javascript'))
<script type="text/javascript">console.log("hello world");</script>

SELECT

生成一个 <select>...</select> 标签。这和 OPTION helper 一起使用。

>>> print(SELECT(OPTION('first', _value='1'), OPTION('second', _value='2'),
... _class='test', _id=0))
<select class="test" id="0"><option value="1">first</option><option value="2">second</option></select>

SPAN

类似于 DIV ,但用于标记内联(行内的,而不是 )内容。

>>> print(SPAN('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<span id="0" class="test">&lt;hello&gt;<strong>world</strong></span>

STYLE

类似于 SCRIPT ,但用于包含或链接 CSS 代码。这里包括 CSS:

>>> print(STYLE(XML('body {color: white}')))
<style>body {color: white}</style>

这里是链接:

>>> print(STYLE(_src='style.css'))
<style src="style.css"></style>

TABLE, TR, TD

这些标签(以及可选的 THEADTBODY helpers)用于构建 HTML 表格。

>>> print(TABLE(TR(TD('a'), TD('b')), TR(TD('c'), TD('d'))))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>

TR 需要有 TD 内容。

使用 Python 函数的 * 参数符号很容易将 Python 数组转换为 HTML 表格,该符号将列表元素映射到函数的位置参数。

在这里,我们将手动逐行实现:

>>> table = [['a', 'b'], ['c', 'd']]
>>> print(TABLE(TR(*map(TD, table[0])), TR(*map(TD, table[1]))))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>

在这里,我们使用迭代方式处理所有行:

>>> table = [['a', 'b'], ['c', 'd']]
>>> print(TABLE(*[TR(*map(TD, rows)) for rows in table]))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>

TBODY

这用于标记表格主体中包含的行,而不是页眉或页脚中的行。这是可选的。

>>> print(TBODY(TR(TD('<hello>')), _class='test', _id=0))
<tbody id="0" class="test"><tr><td>&lt;hello&gt;</td></tr></tbody>

TEXTAREA

此 helper 创建了一个 <textarea>...</textarea> 标签。

>>> print(TEXTAREA('<hello>', XML('<strong>world</strong>'), _class='test',
... _cols="40", _rows="10"))
<textarea class="test" cols="40" rows="10">&lt;hello&gt;<strong>world</strong></textarea>

TH

这用于在表头中代替 TD

>>> print(TH('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<th id="0" class="test">&lt;hello&gt;<strong>world</strong></th>

THEAD

这用于标记表头的行。

>>> print(THEAD(TR(TH('<hello>')), _class='test', _id=0))
<thead id="0" class="test"><tr><th>&lt;hello&gt;</th></tr></thead>

TITLE

这用于在 HTML 头部中标记页面的标题。

>>> print(TITLE('<hello>', XML('<strong>world</strong>')))
<title>&lt;hello&gt;<strong>world</strong></title>

TT

将文本标记为打字机(等宽)文本。

>>> print(TT('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<tt id="0" class="test">&lt;hello&gt;<strong>world</strong></tt>

UL

它代表无序列表。列表应包含 LI 标签。

>>> print(UL(LI('<hello>'), LI(XML('<strong>world</strong>')), _class='test', _id=0))
<ul class="test" id="0"><li>&lt;hello&gt;</li><li><strong>world</strong></li></ul>

URL

URL helper 不是 yatl 包的一部分,而是由 py4web 提供的。

自定义 helper

TAG

有时需要生成自定义 XML 的标记。为此,py4web 提供了 TAG ,这是一个通用的标签生成器。

[[=TAG.name('a', 'b', _c='d')]]

生成以下 XML:

<name c="d">ab</name>

参数 “a”、“b” 和 “d” 会自动被转义;使用 XML helper 可以控制此行为。使用 TAG 可以生成 API 尚未提供的 HTML/XML 标记。TAG 可以嵌套,并使用 str() 进行序列化。等效语法为:

[[=TAG['name']('a', 'b', _c='d')]]

可以使用 TAG helper 生成自闭标签。标记名称必须以 “/” 结尾。

[[=TAG['link/'](_href='http://py4web.com')]]

生成以下 XML:

<link ref="http://py4web.com"/>

请注意, TAG 是一个对象, TAG.nameTAG['name'] 是一个返回 helper 实例的函数。

BEAUTIFY

BEAUTIFY 用于构建复合对象的 HTML 表示,包括列表、元组和字典:

[[=BEAUTIFY({"a": ["hello", STRONG("world")], "b": (1, 2)})]]

BEAUTIFY 返回一个可序列化为 XML 的类 XML 对象,能使其构造函数参数具有美观的表示形式。在这种情况下,如下的 XML 内容:

{"a": ["hello", STRONG("world")], "b": (1, 2)}

将呈现为:

<table><tbody>
<tr><th>a</th><td><ul><li>hello</li><li><strong>world</strong></li></ul></td></tr>
<tr><th>b</th><td>(1, 2)</td></tr>
</tbody></table>

服务器端的 DOM

正如我们已经看到的,py4web 中的 helper 机制还提供了文档对象模型(DOM)的服务器端表示。

children

每个 helper 对象都将其组件列表保存在 children 属性中。

>>> CAT('hello', STRONG('world')).children
['hello', <yatl.helpers.TAGGER object at 0x7fa533ff7640>]

find

为了帮助搜索 DOM ,所有 helpers 都有一个带有以下签名的 find 方法:

def find(self, query=None, **kargs)

它返回与提供的参数匹配的所有组件。

一个非常简单的 query 参数值可以是标签名称:

>>> a = DIV(DIV(SPAN('x'), 3, DIV(SPAN('y'))))
>>> for c in a.find('span', first_only=True): c[0]='z'
>>> print(a)  # We should .xml() here instead of print
<div><div><span>z</span>3<div><span>y</span></div></div></div>
>>> for c in a.find('span'): c[0]='z'
>>> print(a)
<div><div><span>z</span>3<div><span>z</span></div></div></div>

它还支持与 jQuery 兼容的语法,接受以下表达式:

这是一些示例:

>>> a = DIV(SPAN(A('hello', **{'_id': '1-1', '_u:v': '$'})), P('world', _class='this is a test'))
>>> for e in a.find('div a#1-1, p.is'): print(e)
<a id="1-1" u:v="$">hello</a>
<p class="this is a test">world</p>
>>> for e in a.find('#1-1'): print(e)
<a id="1-1" u:v="$">hello</a>
>>> a.find('a[u:v=$]')[0].xml()
'<a id="1-1" u:v="$">hello</a>'
>>> a = FORM(INPUT(_type='text'), SELECT(OPTION(0)), TEXTAREA())
>>> for c in a.find('input, select, textarea'): c['_disabled'] = True
>>> a.xml()
'<form><input disabled="disabled" type="text"/><select disabled="disabled"><option>0</option></select><textarea disabled="disabled"></textarea></form>'
>>> for c in a.find('input, select, textarea'): c['_disabled'] = False
>>> a.xml()
'<form><input type="text"/><select><option>0</option></select><textarea></textarea></form>'

通过指定 replace 参数,也可以替换或删除匹配的元素(注意,原始匹配元素的列表仍然像往常一样返回)。

>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.find('span.abc', replace=P('x', _class='xyz'))
>>> print(a)
<div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>

replace 可以是一个可调用函数,它将接收传递的原始元素,并应返回一个新元素来替换它。

>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.find('span.abc', replace=lambda el: P(el[0], _class='xyz'))
>>> print(a)
<div><div><p class="xyz">x</p><div><p class="xyz">y</p><p class="xyz">z</p></div></div></div>

如果是 replace=None ,则匹配的元素将被完全删除。

>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.find('span', text='y', replace=None)
>>> print(a)
<div><div><span class="abc">x</span><div><span class="abc"></span><span class="abc">z</span></div></div></div>

如果指定了 text 参数,则将在元素中搜索与 text 匹配的文本组件,并替换任何匹配的文本元素(如果未指定 replace ,则忽略 text ,当您只需要搜索文本元素时,请使用 find 参数)。

find 参数一样, text 可以是字符串或编译后的正则表达式。

>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.find(text=re.compile('x|y|z'), replace='hello')
>>> print(a)
<div><div><span class="abc">hello</span><div><span class="abc">hello</span><span class="abc">hello</span></div></div></div>

如果其他属性与 text 一起指定,则只会搜索与指定属性匹配的组件以查找文本。

>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc'))))
>>> b = a.find('span.efg', text=re.compile('x|y|z'), replace='hello')
>>> print(a)
<div><div><span class="abc">x</span><div><span class="efg">hello</span><span class="abc">z</span></div></div></div>

使用 注入

通常,所有代码都应从控制器程序调用,只有必要的数据才会传递给模板用来显示。但有时传递变量甚至使用 python 函数作为从模板中调用的 helper 是有用的。

在这种情况下,您可以使用 py4web.utils.factories 中的夹具 Inject

这是一个注入变量的简单示例:

from py4web.utils.factories import Inject

my_var = "Example variable to be passed to a Template"

...

@unauthenticated("index", "index.html")
@action.uses(Inject(my_var=my_var))
def index():

   ...

然后在 index.html 中,你可以使用注入的变量:

[[=my_var]]

您还可以使用 Inject 向 auth.enable 行添加变量;这样,auth 表单就可以访问该变量。

auth.enable(uses=(session, T, db, Inject(TIMEOFFSET=settings.TIMEOFFSET)))

注入(Inject) 的一个更复杂的用法是将 python 函数传递给模板。例如,如果你的 helper 函数被命名为 sidebar_menu ,它位于你的应用程序的 libs/helpers.py 模块中,你可以在 controllers.py 中使用它:

from py4web.utils.factories import Inject
from .libs.helpers import sidebar_menu

@action(...)
@action.uses("index.html", Inject(sidebar_menu=sidebar_menu))
def index(): ....

或者

from py4web.utils.factories import Inject
from .libs import helpers

@action(...)
@action.uses(Inject(**vars(helpers)), "index.html")
def index(): ....

然后,您可以以一种干净的方式在 index.html 模板中添加所需的代码:

[[=sidebar_menu]]