2011-05-17 13:29:00 +04:00
|
|
|
|
# coding: utf8
|
|
|
|
|
|
|
|
|
|
# WeasyPrint converts web documents (HTML, CSS, ...) to PDF.
|
|
|
|
|
# Copyright (C) 2011 Simon Sapin
|
|
|
|
|
#
|
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""
|
|
|
|
|
Test the CSS boxes.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2011-08-16 18:01:50 +04:00
|
|
|
|
import contextlib
|
2011-05-17 13:29:00 +04:00
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
from attest import Tests, assert_hook # pylint: disable=W0611
|
2011-05-17 13:29:00 +04:00
|
|
|
|
|
2011-09-02 20:38:11 +04:00
|
|
|
|
from . import resource_filename
|
2011-12-02 14:31:06 +04:00
|
|
|
|
from ..css import validation, page_style
|
2011-09-27 20:11:31 +04:00
|
|
|
|
from . import TestPNGDocument
|
2011-09-02 20:38:11 +04:00
|
|
|
|
from ..formatting_structure import boxes, build
|
2011-05-17 13:29:00 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
SUITE = Tests()
|
2011-05-17 13:29:00 +04:00
|
|
|
|
|
|
|
|
|
|
2011-11-14 17:29:40 +04:00
|
|
|
|
PROPER_CHILDREN = dict((key, tuple(map(tuple, value))) for key, value in {
|
|
|
|
|
# Children can be of *any* type in *one* of the lists.
|
|
|
|
|
boxes.BlockContainerBox: [[boxes.BlockLevelBox], [boxes.LineBox]],
|
|
|
|
|
boxes.LineBox: [[boxes.InlineLevelBox]],
|
|
|
|
|
boxes.InlineBox: [[boxes.InlineLevelBox]],
|
|
|
|
|
boxes.TableBox: [[boxes.TableCaptionBox,
|
|
|
|
|
boxes.TableColumnGroupBox, boxes.TableColumnBox,
|
|
|
|
|
boxes.TableRowGroupBox, boxes.TableRowBox]],
|
2011-11-14 20:42:15 +04:00
|
|
|
|
boxes.InlineTableBox: [[boxes.TableCaptionBox,
|
|
|
|
|
boxes.TableColumnGroupBox, boxes.TableColumnBox,
|
|
|
|
|
boxes.TableRowGroupBox, boxes.TableRowBox]],
|
2011-11-14 17:29:40 +04:00
|
|
|
|
boxes.TableColumnGroupBox: [[boxes.TableColumnBox]],
|
|
|
|
|
boxes.TableRowGroupBox: [[boxes.TableRowBox]],
|
|
|
|
|
boxes.TableRowBox: [[boxes.TableCellBox]],
|
|
|
|
|
}.iteritems())
|
|
|
|
|
|
|
|
|
|
|
2011-05-23 15:59:47 +04:00
|
|
|
|
def serialize(box_list):
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Transform a box list into a structure easier to compare for testing."""
|
2011-05-23 15:59:47 +04:00
|
|
|
|
return [
|
2011-11-14 17:12:13 +04:00
|
|
|
|
(
|
2011-12-02 15:36:20 +04:00
|
|
|
|
box.element_tag,
|
2011-12-05 18:25:42 +04:00
|
|
|
|
('Anon'
|
|
|
|
|
if box.style.anonymous and
|
|
|
|
|
type(box) not in (boxes.TextBox, boxes.LineBox)
|
|
|
|
|
else ''
|
|
|
|
|
) + type(box).__name__[:-3],
|
2011-11-14 17:12:13 +04:00
|
|
|
|
(
|
2011-11-14 20:42:15 +04:00
|
|
|
|
# All concrete boxes are either text, replaced, column or parent.
|
2011-11-08 16:09:19 +04:00
|
|
|
|
box.text if isinstance(box, boxes.TextBox)
|
2011-07-11 14:47:00 +04:00
|
|
|
|
else '<replaced>' if isinstance(box, boxes.ReplacedBox)
|
2011-11-22 16:06:50 +04:00
|
|
|
|
else serialize(getattr(box, 'column_groups', ()) + box.children)))
|
2011-05-23 15:59:47 +04:00
|
|
|
|
for box in box_list
|
|
|
|
|
]
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unwrap_html_body(box):
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Test that the box tree starts with a ``<html>`` and a ``<body>`` blocks.
|
|
|
|
|
|
|
|
|
|
Remove them to simplify further tests. These are always at the root
|
2011-05-19 17:31:34 +04:00
|
|
|
|
of HTML documents.
|
2011-08-24 13:36:10 +04:00
|
|
|
|
|
2011-05-19 17:31:34 +04:00
|
|
|
|
"""
|
2011-12-02 15:36:20 +04:00
|
|
|
|
assert box.element_tag == 'html'
|
2011-07-20 13:35:43 +04:00
|
|
|
|
assert isinstance(box, boxes.BlockBox)
|
2011-05-23 15:59:47 +04:00
|
|
|
|
assert len(box.children) == 1
|
|
|
|
|
|
|
|
|
|
box = box.children[0]
|
2011-05-25 16:59:42 +04:00
|
|
|
|
assert isinstance(box, boxes.BlockBox)
|
2011-12-02 15:36:20 +04:00
|
|
|
|
assert box.element_tag == 'body'
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-05-23 15:59:47 +04:00
|
|
|
|
return box.children
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
|
|
|
|
|
2011-05-19 18:03:50 +04:00
|
|
|
|
def to_lists(box_tree):
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Serialize and unwrap ``<html>`` and ``<body>``."""
|
2011-05-23 15:59:47 +04:00
|
|
|
|
return serialize(unwrap_html_body(box_tree))
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-05-19 18:03:50 +04:00
|
|
|
|
|
2011-08-16 18:01:50 +04:00
|
|
|
|
@contextlib.contextmanager
|
|
|
|
|
def monkeypatch_validation(replacement):
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Create a context manager patching the validation mechanism.
|
|
|
|
|
|
|
|
|
|
This is useful to change the behaviour of the validation for one property
|
|
|
|
|
not yet supported, without affecting the validation for the other
|
|
|
|
|
properties.
|
|
|
|
|
|
|
|
|
|
"""
|
2011-08-16 18:01:50 +04:00
|
|
|
|
real_non_shorthand = validation.validate_non_shorthand
|
|
|
|
|
|
|
|
|
|
def patched_non_shorthand(*args, **kwargs):
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Wraps the validator into ``replacement``."""
|
2011-08-16 18:01:50 +04:00
|
|
|
|
return replacement(real_non_shorthand, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
validation.validate_non_shorthand = patched_non_shorthand
|
|
|
|
|
try:
|
|
|
|
|
yield
|
|
|
|
|
finally:
|
|
|
|
|
validation.validate_non_shorthand = real_non_shorthand
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_inline_block(real_non_shorthand, name, values, required=False):
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Fake validator for inline blocks."""
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if name == 'display' and values[0].value == 'inline-block':
|
|
|
|
|
return [(name, 'inline-block')]
|
2011-08-16 18:01:50 +04:00
|
|
|
|
return real_non_shorthand(name, values, required)
|
|
|
|
|
|
|
|
|
|
|
2011-06-29 23:59:29 +04:00
|
|
|
|
def parse(html_content):
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Parse some HTML, apply stylesheets and transform to boxes."""
|
2011-08-16 18:01:50 +04:00
|
|
|
|
# TODO: remove this patching when inline-block is validated.
|
|
|
|
|
with monkeypatch_validation(validate_inline_block):
|
2011-09-27 20:11:31 +04:00
|
|
|
|
document = TestPNGDocument.from_string(html_content)
|
2011-08-25 19:29:16 +04:00
|
|
|
|
# Dummy filename, but in the right directory.
|
|
|
|
|
document.base_url = resource_filename('<test>')
|
2011-11-17 18:39:30 +04:00
|
|
|
|
box, = build.dom_to_box(document, document.dom)
|
|
|
|
|
return box
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
|
|
|
|
|
2011-11-14 20:42:15 +04:00
|
|
|
|
def parse_all(html_content):
|
|
|
|
|
"""Like parse() but also run all corrections on boxes."""
|
|
|
|
|
document = TestPNGDocument.from_string(html_content)
|
2011-11-16 20:34:02 +04:00
|
|
|
|
document.base_url = resource_filename('<test>')
|
2011-11-14 20:42:15 +04:00
|
|
|
|
box = build.build_formatting_structure(document)
|
|
|
|
|
sanity_checks(box)
|
|
|
|
|
return box
|
|
|
|
|
|
|
|
|
|
|
2011-05-23 18:14:51 +04:00
|
|
|
|
def assert_tree(box, expected):
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Check the box tree equality.
|
|
|
|
|
|
|
|
|
|
The obtained result is prettified in the message in case of failure.
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-05-23 18:14:51 +04:00
|
|
|
|
box: a Box object, starting with <html> and <body> blocks.
|
|
|
|
|
expected: a list of serialized <body> children as returned by to_lists().
|
2011-08-24 13:36:10 +04:00
|
|
|
|
|
2011-05-23 18:14:51 +04:00
|
|
|
|
"""
|
2011-11-14 17:12:13 +04:00
|
|
|
|
assert to_lists(box) == expected
|
2011-05-19 20:22:06 +04:00
|
|
|
|
|
|
|
|
|
|
2011-09-27 13:44:35 +04:00
|
|
|
|
def sanity_checks(box):
|
|
|
|
|
"""Check that the rules regarding boxes are met.
|
|
|
|
|
|
|
|
|
|
This is not required and only helps debugging.
|
|
|
|
|
|
|
|
|
|
- A block container can contain either only block-level boxes or
|
|
|
|
|
only line boxes;
|
|
|
|
|
- Line boxes and inline boxes can only contain inline-level boxes.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
if not isinstance(box, boxes.ParentBox):
|
|
|
|
|
return
|
|
|
|
|
|
2011-11-14 17:29:40 +04:00
|
|
|
|
for class_ in type(box).mro():
|
|
|
|
|
if class_ in PROPER_CHILDREN:
|
|
|
|
|
acceptable_types_lists = PROPER_CHILDREN[class_]
|
|
|
|
|
break
|
2011-11-14 20:42:15 +04:00
|
|
|
|
else:
|
|
|
|
|
raise TypeError
|
2011-09-27 13:44:35 +04:00
|
|
|
|
|
|
|
|
|
assert any(
|
2011-11-14 17:29:40 +04:00
|
|
|
|
all(isinstance(child, acceptable_types) for child in box.children)
|
|
|
|
|
for acceptable_types in acceptable_types_lists
|
2011-11-14 20:42:15 +04:00
|
|
|
|
), (box, box.children)
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
|
|
|
|
for child in box.children:
|
|
|
|
|
sanity_checks(child)
|
2011-09-27 13:44:35 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
@SUITE.test
|
2011-05-19 17:31:34 +04:00
|
|
|
|
def test_box_tree():
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Test the creation of trees from HTML strings."""
|
2011-11-14 17:12:13 +04:00
|
|
|
|
assert_tree(parse('<p>'), [('p', 'Block', [])])
|
2011-05-25 17:27:30 +04:00
|
|
|
|
assert_tree(parse('''
|
|
|
|
|
<style>
|
|
|
|
|
span { display: inline-block }
|
|
|
|
|
</style>
|
2011-08-25 19:29:16 +04:00
|
|
|
|
<p>Hello <em>World <img src="pattern.png"><span>Lipsum</span></em>!</p>
|
2011-05-25 17:27:30 +04:00
|
|
|
|
'''), [
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Text', 'Hello '),
|
|
|
|
|
('em', 'Inline', [
|
|
|
|
|
('em', 'Text', 'World '),
|
2011-12-05 17:24:43 +04:00
|
|
|
|
('img', 'InlineReplaced', '<replaced>'),
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('span', 'InlineBlock', [
|
|
|
|
|
('span', 'Text', 'Lipsum')])]),
|
|
|
|
|
('p', 'Text', '!')])])
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
2011-05-19 18:03:50 +04:00
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
@SUITE.test
|
2011-08-16 14:00:59 +04:00
|
|
|
|
def test_html_entities():
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Test the management of HTML entities."""
|
2011-08-16 14:00:59 +04:00
|
|
|
|
for quote in ['"', '"', '"', '"']:
|
|
|
|
|
assert_tree(parse('<p>{}abc{}'.format(quote, quote)), [
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Text', '"abc"')])])
|
2011-08-16 14:00:59 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
@SUITE.test
|
2011-05-19 20:22:06 +04:00
|
|
|
|
def test_inline_in_block():
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Test the management of inline boxes in block boxes."""
|
2011-05-23 18:56:12 +04:00
|
|
|
|
source = '<div>Hello, <em>World</em>!\n<p>Lipsum.</p></div>'
|
2011-05-19 20:22:06 +04:00
|
|
|
|
expected = [
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('div', 'Block', [
|
|
|
|
|
('div', 'AnonBlock', [
|
|
|
|
|
('div', 'Line', [
|
|
|
|
|
('div', 'Text', 'Hello, '),
|
|
|
|
|
('em', 'Inline', [
|
|
|
|
|
('em', 'Text', 'World')]),
|
|
|
|
|
('div', 'Text', '!\n')])]),
|
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Text', 'Lipsum.')])])])]
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-05-19 20:22:06 +04:00
|
|
|
|
box = parse(source)
|
2011-07-20 13:35:43 +04:00
|
|
|
|
box = build.inline_in_block(box)
|
2011-05-23 18:14:51 +04:00
|
|
|
|
assert_tree(box, expected)
|
2011-05-19 20:22:06 +04:00
|
|
|
|
|
2011-05-19 18:03:50 +04:00
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
@SUITE.test
|
2011-05-19 20:58:39 +04:00
|
|
|
|
def test_block_in_inline():
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Test the management of block boxes in inline boxes."""
|
2011-05-23 15:59:47 +04:00
|
|
|
|
box = parse('''
|
2011-08-17 19:33:19 +04:00
|
|
|
|
<style>
|
|
|
|
|
p { display: inline-block; }
|
|
|
|
|
span { display: block; }
|
|
|
|
|
</style>
|
|
|
|
|
<p>Lorem <em>ipsum <strong>dolor <span>sit</span>
|
|
|
|
|
<span>amet,</span></strong><span><em>consectetur<div/></em></span></em></p>
|
|
|
|
|
''')
|
2011-07-20 13:35:43 +04:00
|
|
|
|
box = build.inline_in_block(box)
|
2011-05-23 18:14:51 +04:00
|
|
|
|
assert_tree(box, [
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('body', 'Line', [
|
|
|
|
|
('p', 'InlineBlock', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Text', 'Lorem '),
|
|
|
|
|
('em', 'Inline', [
|
|
|
|
|
('em', 'Text', 'ipsum '),
|
|
|
|
|
('strong', 'Inline', [
|
|
|
|
|
('strong', 'Text', 'dolor '),
|
|
|
|
|
('span', 'Block', [ # This block is "pulled up"
|
|
|
|
|
('span', 'Line', [
|
|
|
|
|
('span', 'Text', 'sit')])]),
|
2011-05-23 18:56:12 +04:00
|
|
|
|
# No whitespace processing here.
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('strong', 'Text', '\n '),
|
|
|
|
|
('span', 'Block', [ # This block is "pulled up"
|
|
|
|
|
('span', 'Line', [
|
|
|
|
|
('span', 'Text', 'amet,')])])]),
|
|
|
|
|
('span', 'Block', [ # This block is "pulled up"
|
|
|
|
|
('span', 'Line', [
|
|
|
|
|
('em', 'Inline', [
|
|
|
|
|
('em', 'Text', 'consectetur'),
|
|
|
|
|
('div', 'Block', []),
|
2011-08-17 19:33:19 +04:00
|
|
|
|
])])])])])])])])
|
2011-05-25 17:55:08 +04:00
|
|
|
|
|
2011-07-20 13:35:43 +04:00
|
|
|
|
box = build.block_in_inline(box)
|
2011-05-25 17:55:08 +04:00
|
|
|
|
assert_tree(box, [
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('body', 'Line', [
|
|
|
|
|
('p', 'InlineBlock', [
|
|
|
|
|
('p', 'AnonBlock', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Text', 'Lorem '),
|
|
|
|
|
('em', 'Inline', [
|
|
|
|
|
('em', 'Text', 'ipsum '),
|
|
|
|
|
('strong', 'Inline', [
|
|
|
|
|
('strong', 'Text', 'dolor ')])])])]),
|
|
|
|
|
('span', 'Block', [
|
|
|
|
|
('span', 'Line', [
|
|
|
|
|
('span', 'Text', 'sit')])]),
|
|
|
|
|
('p', 'AnonBlock', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('em', 'Inline', [
|
|
|
|
|
('strong', 'Inline', [
|
2011-07-20 13:35:43 +04:00
|
|
|
|
# Whitespace processing not done yet.
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('strong', 'Text', '\n ')])])])]),
|
|
|
|
|
('span', 'Block', [
|
|
|
|
|
('span', 'Line', [
|
|
|
|
|
('span', 'Text', 'amet,')])]),
|
|
|
|
|
|
|
|
|
|
('p', 'AnonBlock', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('em', 'Inline', [
|
|
|
|
|
('strong', 'Inline', [])])])]),
|
|
|
|
|
('span', 'Block', [
|
|
|
|
|
('span', 'AnonBlock', [
|
|
|
|
|
('span', 'Line', [
|
|
|
|
|
('em', 'Inline', [
|
|
|
|
|
('em', 'Text', 'consectetur')])])]),
|
|
|
|
|
('div', 'Block', []),
|
|
|
|
|
('span', 'AnonBlock', [
|
|
|
|
|
('span', 'Line', [
|
|
|
|
|
('em', 'Inline', [])])])]),
|
|
|
|
|
('p', 'AnonBlock', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('em', 'Inline', [])])])])])])
|
2011-05-19 18:03:50 +04:00
|
|
|
|
|
2011-05-23 15:59:47 +04:00
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
@SUITE.test
|
2011-05-23 15:59:47 +04:00
|
|
|
|
def test_styles():
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Test the application of CSS to HTML."""
|
2011-05-23 15:59:47 +04:00
|
|
|
|
box = parse('''
|
|
|
|
|
<style>
|
|
|
|
|
span { display: block; }
|
|
|
|
|
* { margin: 42px }
|
|
|
|
|
html { color: blue }
|
|
|
|
|
</style>
|
|
|
|
|
<p>Lorem <em>ipsum <strong>dolor <span>sit</span>
|
|
|
|
|
<span>amet,</span></strong><span>consectetur</span></em></p>''')
|
2011-07-20 13:35:43 +04:00
|
|
|
|
box = build.inline_in_block(box)
|
|
|
|
|
box = build.block_in_inline(box)
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
|
descendants = list(box.descendants())
|
|
|
|
|
assert len(descendants) == 31
|
|
|
|
|
assert descendants[0] == box
|
|
|
|
|
|
|
|
|
|
for child in descendants:
|
2011-05-23 15:59:47 +04:00
|
|
|
|
# All boxes inherit the color
|
2011-10-08 16:41:12 +04:00
|
|
|
|
assert child.style.color.value == 'blue'
|
2011-05-23 15:59:47 +04:00
|
|
|
|
# Only non-anonymous boxes have margins
|
2011-12-05 18:25:42 +04:00
|
|
|
|
if child.style.anonymous:
|
2011-10-08 16:41:12 +04:00
|
|
|
|
assert child.style.margin_top == 0
|
2011-05-23 15:59:47 +04:00
|
|
|
|
else:
|
2011-10-08 16:41:12 +04:00
|
|
|
|
assert child.style.margin_top == 42
|
2011-05-23 15:59:47 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
@SUITE.test
|
2011-05-23 19:37:37 +04:00
|
|
|
|
def test_whitespace():
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Test the management of white spaces."""
|
2011-05-23 19:37:37 +04:00
|
|
|
|
# TODO: test more cases
|
|
|
|
|
# http://www.w3.org/TR/CSS21/text.html#white-space-model
|
2011-11-14 20:42:15 +04:00
|
|
|
|
assert_tree(parse_all('''
|
2011-11-16 20:34:02 +04:00
|
|
|
|
<p>Lorem \t\r\n ipsum\t<strong> dolor
|
|
|
|
|
<img src=pattern.png> sit</strong>.</p>
|
2011-05-23 19:37:37 +04:00
|
|
|
|
<pre>\t foo\n</pre>
|
|
|
|
|
<pre style="white-space: pre-wrap">\t foo\n</pre>
|
|
|
|
|
<pre style="white-space: pre-line">\t foo\n</pre>
|
2011-11-14 20:42:15 +04:00
|
|
|
|
'''), [
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Text', 'Lorem ipsum '),
|
|
|
|
|
('strong', 'Inline', [
|
2011-11-16 20:34:02 +04:00
|
|
|
|
('strong', 'Text', 'dolor '),
|
2011-12-05 17:24:43 +04:00
|
|
|
|
('img', 'InlineReplaced', '<replaced>'),
|
2011-11-16 20:34:02 +04:00
|
|
|
|
('strong', 'Text', ' sit')]),
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('p', 'Text', '.')])]),
|
|
|
|
|
('pre', 'Block', [
|
|
|
|
|
('pre', 'Line', [
|
2011-05-23 19:37:37 +04:00
|
|
|
|
# pre
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('pre', 'Text', u'\t\xA0\xA0foo\n')])]),
|
|
|
|
|
('pre', 'Block', [
|
|
|
|
|
('pre', 'Line', [
|
2011-05-23 19:37:37 +04:00
|
|
|
|
# pre-wrap
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('pre', 'Text', u'\t\xA0\xA0\u200Bfoo\n')])]),
|
|
|
|
|
('pre', 'Block', [
|
|
|
|
|
('pre', 'Line', [
|
2011-05-23 19:37:37 +04:00
|
|
|
|
# pre-line
|
2011-12-01 17:46:23 +04:00
|
|
|
|
('pre', 'Text', u'foo\n')])])])
|
2011-07-05 13:32:16 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
@SUITE.test
|
2011-07-05 13:32:16 +04:00
|
|
|
|
def test_page_style():
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Test the management of page styles."""
|
2011-09-27 20:11:31 +04:00
|
|
|
|
document = TestPNGDocument.from_string('''
|
2011-07-05 13:32:16 +04:00
|
|
|
|
<style>
|
|
|
|
|
@page { margin: 3px }
|
|
|
|
|
@page :first { margin-top: 20px }
|
|
|
|
|
@page :right { margin-right: 10px; margin-top: 10px }
|
|
|
|
|
@page :left { margin-left: 10px; margin-top: 10px }
|
|
|
|
|
</style>
|
|
|
|
|
''')
|
2011-08-24 13:36:10 +04:00
|
|
|
|
|
2011-07-05 13:32:16 +04:00
|
|
|
|
def assert_page_margins(page_number, top, right, bottom, left):
|
2011-08-24 13:36:10 +04:00
|
|
|
|
"""Check the page margin values."""
|
2011-12-02 14:31:06 +04:00
|
|
|
|
style = page_style(document, page_number)
|
|
|
|
|
assert style.margin_top == top
|
|
|
|
|
assert style.margin_right == right
|
|
|
|
|
assert style.margin_bottom == bottom
|
|
|
|
|
assert style.margin_left == left
|
2011-07-05 13:32:16 +04:00
|
|
|
|
|
2011-08-24 13:36:10 +04:00
|
|
|
|
# Odd numbers are :right pages, even are :left. 1 has :first as well
|
2011-07-05 13:32:16 +04:00
|
|
|
|
assert_page_margins(1, top=20, right=10, bottom=3, left=3)
|
|
|
|
|
assert_page_margins(2, top=10, right=3, bottom=3, left=10)
|
|
|
|
|
assert_page_margins(3, top=10, right=10, bottom=3, left=3)
|
|
|
|
|
assert_page_margins(4, top=10, right=3, bottom=3, left=10)
|
|
|
|
|
assert_page_margins(45, top=10, right=10, bottom=3, left=3)
|
|
|
|
|
assert_page_margins(122, top=10, right=3, bottom=3, left=10)
|
2011-10-21 13:36:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SUITE.test
|
|
|
|
|
def test_text_transform():
|
2011-11-05 02:55:26 +04:00
|
|
|
|
"""Test the text-transform property."""
|
2011-11-14 20:42:15 +04:00
|
|
|
|
assert_tree(parse_all('''
|
2011-10-21 13:36:01 +04:00
|
|
|
|
<style>
|
|
|
|
|
p { text-transform: capitalize }
|
|
|
|
|
p+p { text-transform: uppercase }
|
|
|
|
|
p+p+p { text-transform: lowercase }
|
|
|
|
|
p+p+p+p { text-transform: none }
|
|
|
|
|
</style>
|
|
|
|
|
<p>heLLo wOrlD!</p><p>heLLo wOrlD!</p><p>heLLo wOrlD!</p><p>heLLo wOrlD!</p>
|
2011-11-14 20:42:15 +04:00
|
|
|
|
'''), [
|
2011-11-14 17:12:13 +04:00
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Text', 'Hello World!')])]),
|
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Text', 'HELLO WORLD!')])]),
|
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Text', 'hello world!')])]),
|
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Text', 'heLLo wOrlD!')])]),
|
2011-10-21 13:36:01 +04:00
|
|
|
|
])
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SUITE.test
|
|
|
|
|
def test_tables():
|
2011-11-14 20:42:15 +04:00
|
|
|
|
# Rules in http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
|
|
|
|
|
# Rule 1.3
|
2011-11-15 21:22:00 +04:00
|
|
|
|
# Also table model: http://www.w3.org/TR/CSS21/tables.html#model
|
2011-11-14 20:42:15 +04:00
|
|
|
|
assert_tree(parse_all('''
|
2011-11-14 17:29:40 +04:00
|
|
|
|
<table>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>foo</th>
|
|
|
|
|
<th>bar</th>
|
|
|
|
|
</tr>
|
2011-11-15 21:22:00 +04:00
|
|
|
|
<tfoot></tfoot>
|
|
|
|
|
<thead><th></th></thead>
|
|
|
|
|
<caption style="caption-side: bottom"></caption>
|
|
|
|
|
<thead></thead>
|
|
|
|
|
<col></col>
|
|
|
|
|
<caption>top caption</caption>
|
2011-11-14 17:29:40 +04:00
|
|
|
|
<tr>
|
|
|
|
|
<td>baz</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
2011-11-14 20:42:15 +04:00
|
|
|
|
'''), [
|
2011-11-22 16:06:50 +04:00
|
|
|
|
('table', 'AnonBlock', [
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('caption', 'TableCaption', [
|
|
|
|
|
('caption', 'Line', [
|
|
|
|
|
('caption', 'Text', 'top caption')])]),
|
|
|
|
|
('table', 'Table', [
|
2011-11-17 20:05:55 +04:00
|
|
|
|
('table', 'AnonTableColumnGroup', [
|
2011-12-05 17:21:02 +04:00
|
|
|
|
('col', 'TableColumn', [])]),
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('thead', 'TableRowGroup', [
|
|
|
|
|
('thead', 'AnonTableRow', [
|
|
|
|
|
('th', 'TableCell', [])])]),
|
2011-11-17 20:05:55 +04:00
|
|
|
|
('table', 'AnonTableRowGroup', [
|
|
|
|
|
('tr', 'TableRow', [
|
|
|
|
|
('th', 'TableCell', [
|
|
|
|
|
('th', 'Line', [
|
|
|
|
|
('th', 'Text', 'foo')])]),
|
|
|
|
|
('th', 'TableCell', [
|
|
|
|
|
('th', 'Line', [
|
|
|
|
|
('th', 'Text', 'bar')])])])]),
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('thead', 'TableRowGroup', []),
|
2011-11-17 20:05:55 +04:00
|
|
|
|
('table', 'AnonTableRowGroup', [
|
|
|
|
|
('tr', 'TableRow', [
|
|
|
|
|
('td', 'TableCell', [
|
|
|
|
|
('td', 'Line', [
|
|
|
|
|
('td', 'Text', 'baz')])])])]),
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('tfoot', 'TableRowGroup', [])]),
|
|
|
|
|
('caption', 'TableCaption', [])])])
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
2011-11-14 20:42:15 +04:00
|
|
|
|
# Rules 1.4 and 3.1
|
|
|
|
|
assert_tree(parse_all('''
|
2011-11-14 17:29:40 +04:00
|
|
|
|
<span style="display: table-cell">foo</span>
|
|
|
|
|
<span style="display: table-cell">bar</span>
|
2011-11-14 20:42:15 +04:00
|
|
|
|
'''), [
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('body', 'AnonBlock', [
|
|
|
|
|
('body', 'AnonTable', [
|
2011-11-17 20:05:55 +04:00
|
|
|
|
('body', 'AnonTableRowGroup', [
|
|
|
|
|
('body', 'AnonTableRow', [
|
|
|
|
|
('span', 'TableCell', [
|
|
|
|
|
('span', 'Line', [
|
|
|
|
|
('span', 'Text', 'foo')])]),
|
|
|
|
|
('span', 'TableCell', [
|
|
|
|
|
('span', 'Line', [
|
|
|
|
|
('span', 'Text', 'bar')])])])])])])])
|
2011-11-14 20:42:15 +04:00
|
|
|
|
|
|
|
|
|
# http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
|
|
|
|
|
# Rules 1.1 and 1.2
|
|
|
|
|
assert_tree(parse_all('''
|
|
|
|
|
<span style="display: table-column-group">
|
|
|
|
|
1
|
|
|
|
|
<em style="display: table-column">
|
|
|
|
|
2
|
|
|
|
|
<strong>3</strong>
|
|
|
|
|
</em>
|
|
|
|
|
<strong>4</strong>
|
|
|
|
|
</span>
|
|
|
|
|
'''), [
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('body', 'AnonBlock', [
|
|
|
|
|
('body', 'AnonTable', [
|
|
|
|
|
('span', 'TableColumnGroup', [
|
2011-12-05 17:21:02 +04:00
|
|
|
|
('em', 'TableColumn', [])])])])])
|
2011-11-14 20:42:15 +04:00
|
|
|
|
|
|
|
|
|
# Rules 2.1 then 2.3
|
|
|
|
|
assert_tree(parse_all('<table>foo <div></div></table>'), [
|
2011-11-22 16:06:50 +04:00
|
|
|
|
('table', 'AnonBlock', [
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('table', 'Table', [
|
2011-11-17 20:05:55 +04:00
|
|
|
|
('table', 'AnonTableRowGroup', [
|
|
|
|
|
('table', 'AnonTableRow', [
|
|
|
|
|
('table', 'AnonTableCell', [
|
|
|
|
|
('table', 'AnonBlock', [
|
|
|
|
|
('table', 'Line', [
|
|
|
|
|
('table', 'Text', 'foo ')])]),
|
|
|
|
|
('div', 'Block', [])])])])])])])
|
2011-11-14 20:42:15 +04:00
|
|
|
|
|
|
|
|
|
# Rule 2.2
|
|
|
|
|
assert_tree(parse_all('<thead><div></div><td></td></thead>'), [
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('body', 'AnonBlock', [
|
|
|
|
|
('body', 'AnonTable', [
|
|
|
|
|
('thead', 'TableRowGroup', [
|
|
|
|
|
('thead', 'AnonTableRow', [
|
|
|
|
|
('thead', 'AnonTableCell', [
|
|
|
|
|
('div', 'Block', [])]),
|
|
|
|
|
('td', 'TableCell', [])])])])])])
|
2011-11-14 20:42:15 +04:00
|
|
|
|
|
2011-11-21 20:38:08 +04:00
|
|
|
|
# TODO: re-enable this once we support inline-table
|
|
|
|
|
# # Rule 3.2
|
|
|
|
|
# assert_tree(parse_all('<span><tr></tr></span>'), [
|
|
|
|
|
# ('body', 'Line', [
|
|
|
|
|
# ('span', 'Inline', [
|
|
|
|
|
# ('span', 'AnonInlineBlock', [
|
|
|
|
|
# ('span', 'AnonInlineTable', [
|
|
|
|
|
# ('span', 'AnonTableRowGroup', [
|
|
|
|
|
# ('tr', 'TableRow', [])])])])])])])
|
|
|
|
|
|
|
|
|
|
# # Rule 3.1
|
|
|
|
|
# # Also, rule 1.3 does not apply: whitespace before and after is preserved
|
|
|
|
|
# assert_tree(parse_all('''
|
|
|
|
|
# <span>
|
|
|
|
|
# <em style="display: table-cell"></em>
|
|
|
|
|
# <em style="display: table-cell"></em>
|
|
|
|
|
# </span>
|
|
|
|
|
# '''), [
|
|
|
|
|
# ('body', 'Line', [
|
|
|
|
|
# ('span', 'Inline', [
|
|
|
|
|
# # Whitespace is preserved in table handling, then collapsed
|
|
|
|
|
# # into a single space.
|
|
|
|
|
# ('span', 'Text', ' '),
|
|
|
|
|
# ('span', 'AnonInlineBlock', [
|
|
|
|
|
# ('span', 'AnonInlineTable', [
|
|
|
|
|
# ('span', 'AnonTableRowGroup', [
|
|
|
|
|
# ('span', 'AnonTableRow', [
|
|
|
|
|
# ('em', 'TableCell', []),
|
|
|
|
|
# ('em', 'TableCell', [])])])])]),
|
|
|
|
|
# ('span', 'Text', ' ')])])])
|
2011-11-14 20:42:15 +04:00
|
|
|
|
|
|
|
|
|
# Rule 3.2
|
2011-11-15 18:05:12 +04:00
|
|
|
|
assert_tree(parse_all('<tr></tr>\t<tr></tr>'), [
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('body', 'AnonBlock', [
|
|
|
|
|
('body', 'AnonTable', [
|
2011-11-17 20:05:55 +04:00
|
|
|
|
('body', 'AnonTableRowGroup', [
|
|
|
|
|
('tr', 'TableRow', []),
|
|
|
|
|
('tr', 'TableRow', [])])])])])
|
2011-11-15 18:05:12 +04:00
|
|
|
|
assert_tree(parse_all('<col></col>\n<colgroup></colgroup>'), [
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('body', 'AnonBlock', [
|
|
|
|
|
('body', 'AnonTable', [
|
2011-11-17 20:05:55 +04:00
|
|
|
|
('body', 'AnonTableColumnGroup', [
|
2011-12-05 17:21:02 +04:00
|
|
|
|
('col', 'TableColumn', [])]),
|
2011-11-15 21:22:00 +04:00
|
|
|
|
('colgroup', 'TableColumnGroup', [])])])])
|
2011-11-15 21:58:43 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SUITE.test
|
|
|
|
|
def test_table_style():
|
|
|
|
|
html = parse_all('<table style="margin: 1px; padding: 2px"></table>')
|
|
|
|
|
body, = html.children
|
|
|
|
|
wrapper, = body.children
|
|
|
|
|
table, = wrapper.children
|
|
|
|
|
assert isinstance(wrapper, boxes.BlockBox)
|
|
|
|
|
assert isinstance(table, boxes.TableBox)
|
|
|
|
|
assert wrapper.style.margin_top == 1
|
|
|
|
|
assert wrapper.style.padding_top == 0
|
|
|
|
|
assert table.style.margin_top == 0
|
|
|
|
|
assert table.style.padding_top == 2
|
2011-11-17 18:39:30 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SUITE.test
|
2011-11-22 16:06:50 +04:00
|
|
|
|
def test_column_style():
|
2011-11-17 18:39:30 +04:00
|
|
|
|
html = parse_all('''
|
|
|
|
|
<table>
|
|
|
|
|
<col span=3 style="width: 10px"></col>
|
|
|
|
|
<col span=2></col>
|
|
|
|
|
</table>
|
|
|
|
|
''')
|
|
|
|
|
body, = html.children
|
|
|
|
|
wrapper, = body.children
|
|
|
|
|
table, = wrapper.children
|
2011-11-17 20:05:55 +04:00
|
|
|
|
colgroup, = table.column_groups
|
|
|
|
|
widths = [col.style.width for col in colgroup.children]
|
2011-11-17 18:39:30 +04:00
|
|
|
|
assert widths == [10, 10, 10, 'auto', 'auto']
|
2011-11-17 20:05:55 +04:00
|
|
|
|
assert [col.grid_x for col in colgroup.children] == [0, 1, 2, 3, 4]
|
2011-11-17 18:39:30 +04:00
|
|
|
|
# copies, not the same box object
|
2011-11-17 20:05:55 +04:00
|
|
|
|
assert colgroup.children[0] is not colgroup.children[1]
|
2011-11-17 19:13:00 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SUITE.test
|
|
|
|
|
def test_nested_grid_x():
|
|
|
|
|
html = parse_all('''
|
|
|
|
|
<table>
|
|
|
|
|
<col span=2></col>
|
|
|
|
|
<colgroup span=2></colgroup>
|
|
|
|
|
<colgroup>
|
|
|
|
|
<col></col>
|
|
|
|
|
<col span=2></col>
|
|
|
|
|
</colgroup>
|
|
|
|
|
<col></col>
|
|
|
|
|
</table>
|
|
|
|
|
''')
|
|
|
|
|
body, = html.children
|
|
|
|
|
wrapper, = body.children
|
|
|
|
|
table, = wrapper.children
|
2011-11-17 20:05:55 +04:00
|
|
|
|
grid = [(colgroup.grid_x, [col.grid_x for col in colgroup.children])
|
2011-11-22 16:06:50 +04:00
|
|
|
|
for colgroup in table.column_groups]
|
2011-11-17 20:05:55 +04:00
|
|
|
|
assert grid == [(0, [0, 1]), (2, []), (4, [4, 5, 6]), (7, [7])]
|
2011-11-21 16:51:22 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SUITE.test
|
|
|
|
|
def test_colspan_rowspan():
|
|
|
|
|
"""
|
|
|
|
|
+---+---+---+
|
|
|
|
|
| A | B | C | #
|
|
|
|
|
+---+---+---+
|
|
|
|
|
| D | E | #
|
|
|
|
|
+---+---+ +---+
|
|
|
|
|
| F ...| | | <-- overlap
|
|
|
|
|
+---+---+---+ +
|
|
|
|
|
| H | # # | G |
|
|
|
|
|
+---+---+ + +
|
|
|
|
|
| I | J | # | |
|
|
|
|
|
+---+---+ +---+
|
|
|
|
|
|
|
|
|
|
# empty cells
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
html = parse_all('''
|
|
|
|
|
<table>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>A <td>B <td>C
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>D <td colspan=2 rowspan=2>E
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan=2>F <td rowspan=0>G
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>H
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>I <td>J
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
''')
|
|
|
|
|
body, = html.children
|
|
|
|
|
wrapper, = body.children
|
|
|
|
|
table, = wrapper.children
|
|
|
|
|
group, = table.children
|
|
|
|
|
assert [[c.grid_x for c in row.children] for row in group.children] == [
|
|
|
|
|
[0, 1, 2],
|
|
|
|
|
[0, 1],
|
|
|
|
|
[0, 3],
|
|
|
|
|
[0],
|
|
|
|
|
[0, 1],
|
|
|
|
|
]
|
|
|
|
|
assert [[c.colspan for c in row.children] for row in group.children] == [
|
|
|
|
|
[1, 1, 1],
|
|
|
|
|
[1, 2],
|
|
|
|
|
[2, 1],
|
|
|
|
|
[1],
|
|
|
|
|
[1, 1],
|
|
|
|
|
]
|
|
|
|
|
assert [[c.rowspan for c in row.children] for row in group.children] == [
|
|
|
|
|
[1, 1, 1],
|
|
|
|
|
[1, 2],
|
|
|
|
|
[1, 3],
|
|
|
|
|
[1],
|
|
|
|
|
[1, 1],
|
|
|
|
|
]
|
2011-12-02 19:51:41 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SUITE.test
|
|
|
|
|
def test_before_after():
|
|
|
|
|
"""Test the :before and :after pseudo-elements."""
|
|
|
|
|
assert_tree(parse_all('''
|
|
|
|
|
<style>
|
|
|
|
|
p:before { content: 'a' 'b' }
|
|
|
|
|
p:after { content: 'd' 'e' }
|
|
|
|
|
</style>
|
|
|
|
|
<p>
|
|
|
|
|
c
|
|
|
|
|
</p>
|
|
|
|
|
'''), [
|
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Inline', [
|
2011-12-02 21:02:58 +04:00
|
|
|
|
('p', 'Text', 'ab')]),
|
2011-12-02 19:51:41 +04:00
|
|
|
|
('p', 'Text', ' c '),
|
|
|
|
|
('p', 'Inline', [
|
2011-12-02 21:02:58 +04:00
|
|
|
|
('p', 'Text', 'de')])])])])
|
2011-12-02 19:51:41 +04:00
|
|
|
|
|
|
|
|
|
assert_tree(parse_all('''
|
|
|
|
|
<style>
|
|
|
|
|
a[href]:before { content: '[' attr(href) '] ' }
|
|
|
|
|
</style>
|
|
|
|
|
<p><a href="some url">some text</a></p>
|
|
|
|
|
'''), [
|
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('a', 'Inline', [
|
2011-12-02 21:02:58 +04:00
|
|
|
|
('a', 'Inline', [ # :before
|
|
|
|
|
('a', 'Text', '[some url] ')]),
|
2011-12-02 19:51:41 +04:00
|
|
|
|
('a', 'Text', 'some text')])])])])
|
2011-12-02 21:02:58 +04:00
|
|
|
|
|
|
|
|
|
assert_tree(parse_all(u'''
|
|
|
|
|
<style>
|
|
|
|
|
body { quotes: '«' '»' '“' '”' }
|
|
|
|
|
q:before { content: open-quote ' '}
|
|
|
|
|
q:after { content: ' ' close-quote }
|
|
|
|
|
</style>
|
|
|
|
|
<p><q>Lorem ipsum <q>dolor</q> sit amet</q></p>
|
|
|
|
|
'''), [
|
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('q', 'Inline', [
|
|
|
|
|
('q', 'Inline', [ # :before
|
|
|
|
|
('q', 'Text', u'« ')]),
|
|
|
|
|
('q', 'Text', 'Lorem ipsum '),
|
|
|
|
|
('q', 'Inline', [
|
|
|
|
|
('q', 'Inline', [ # :before
|
|
|
|
|
('q', 'Text', u'“ ')]),
|
|
|
|
|
('q', 'Text', 'dolor'),
|
|
|
|
|
('q', 'Inline', [ # :after
|
|
|
|
|
('q', 'Text', u' ”')])]),
|
|
|
|
|
('q', 'Text', ' sit amet'),
|
|
|
|
|
('q', 'Inline', [ # :after
|
|
|
|
|
('q', 'Text', u' »')])])])])])
|
2011-12-02 21:19:34 +04:00
|
|
|
|
|
|
|
|
|
assert_tree(parse_all('''
|
|
|
|
|
<style>
|
|
|
|
|
p:before { content: 'a' url(pattern.png) 'b'}
|
|
|
|
|
</style>
|
|
|
|
|
<p>c</p>
|
|
|
|
|
'''), [
|
|
|
|
|
('p', 'Block', [
|
|
|
|
|
('p', 'Line', [
|
|
|
|
|
('p', 'Inline', [ # :before
|
|
|
|
|
('p', 'Text', 'a'),
|
2011-12-05 18:25:42 +04:00
|
|
|
|
('p', 'AnonInlineReplaced', '<replaced>'),
|
2011-12-02 21:19:34 +04:00
|
|
|
|
('p', 'Text', 'b')]),
|
|
|
|
|
('p', 'Text', 'c')])])])
|