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 <world></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><strong>hello</strong></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))
<script>alert("unsafe!")</script>
默认情况下, XML 构造函数认为某些标记的内容及其某些属性是安全的。您可以使用可选的 permitted_tags 和 allowed_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"><click><strong>me</strong></a>
BODY
此 helper 构成页面的主体。
>>> print(BODY('<hello>', XML('<strong>world</strong>'), _bgcolor='red'))
<body bgcolor="red"><hello><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"><hello><strong>world</strong></div>
EM
强调其内容。
>>> print(EM('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<em id="0" class="test"><hello><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"><hello><strong>world</strong></h1>
HEAD
用于标记 HTML 页面的头部。
>>> print(HEAD(TITLE('<hello>', XML('<strong>world</strong>'))))
<head><title><hello><strong>world</strong></title></head>
HTML
用于标记一个 HTML 页面。
>>> print(HTML(BODY('<hello>', XML('<strong>world</strong>'))))
<html><body><hello><strong>world</strong></body></html>
I
此 helper 将其内容设置为斜体。
>>> print(I('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<i id="0" class="test"><hello><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"><hello><strong>world</strong></label>
LI
它构成一个列表项,应包含在 UL 或 OL 标签中。
>>> print(LI('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<li id="0" class="test"><hello><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><hello></li><li><strong>world</strong></li></ol>
OPTION
这仅用于 SELECT 的参数。
>>> print(OPTION('<hello>', XML('<strong>world</strong>'), _value='a'))
<option value="a"><hello><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"><hello><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"><hello><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"><hello><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
这些标签(以及可选的 THEAD 和 TBODY 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><hello></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"><hello><strong>world</strong></textarea>
TH
这用于在表头中代替 TD 。
>>> print(TH('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<th id="0" class="test"><hello><strong>world</strong></th>
THEAD
这用于标记表头的行。
>>> print(THEAD(TR(TH('<hello>')), _class='test', _id=0))
<thead id="0" class="test"><tr><th><hello></th></tr></thead>
TITLE
这用于在 HTML 头部中标记页面的标题。
>>> print(TITLE('<hello>', XML('<strong>world</strong>')))
<title><hello><strong>world</strong></title>
TT
将文本标记为打字机(等宽)文本。
>>> print(TT('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<tt id="0" class="test"><hello><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><hello></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.name 或 TAG['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 兼容的语法,接受以下表达式:
jQuery 的多项选择器 ,例如 “Selector 1,Selector 2,Selector N”
jQuery 的后代选择器 ,例如 “ancestor descendant”
jQuery 的 ID 选择 , 例如 “#id”
jQuery 的样式的 Class 选择器 , 例如 “.class”
jQuery 的属性等于选择器,例如 “[name=value]” ,请注意,这里的值必须取消引号
这是一些示例:
>>> 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]]