diff --git a/weasy/boxes.py b/weasy/boxes.py index 0809e8b3..d654fdd5 100644 --- a/weasy/boxes.py +++ b/weasy/boxes.py @@ -83,13 +83,13 @@ class Box(object): self.parent = None self.children = [] self._init_style() - + def _init_style(self): # Computed values # Copying might not be needed, but let’s be careful with mutable # objects. self.style = self.element.style.copy() - + def add_child(self, child, index=None): """ Add the new child to this box’s children list and set this box as the @@ -100,7 +100,7 @@ class Box(object): self.children.append(child) else: self.children.insert(index, child) - + def descendants(self): """A flat generator for a box, its children and descendants.""" yield self @@ -144,7 +144,7 @@ class BlockContainerBox(Box): class BlockBox(BlockContainerBox, BlockLevelBox): """ A block-level box that is also a block container. - + A non-replaced element with a 'display' value of 'block', 'list-item' generates a block box. """ @@ -168,10 +168,10 @@ class AnonymousBox(Box): class AnonymousBlockBox(AnonymousBox, BlockBox): """ Wraps inline-level boxes where block-level boxes are needed. - + Block containers (eventually) contain either only block-level boxes or only inline-level boxes. When they initially contain both, consecutive - inline-level boxes are wrapped in an anonymous block box by + inline-level boxes are wrapped in an anonymous block box by ``boxes.inline_in_block()``. """ @@ -180,7 +180,7 @@ class LineBox(AnonymousBox): """ Eventually a line in an inline formatting context. Can only contain inline-level boxes. - + In early stages of building the box tree a single line box contains many consecutive inline boxes and will be split later when wrapping lines. """ @@ -189,7 +189,7 @@ class LineBox(AnonymousBox): class InlineLevelBox(Box): """ A box that participates in an inline formatting context. - + An inline-level box that is not an inline box (see below) is said to be "atomic". Such boxes are inline-blocks, replaced elements and inline tables. @@ -202,7 +202,7 @@ class InlineBox(InlineLevelBox): """ A box who participates in an inline formatting context and whose content also participates in that inline formatting context. - + A non-replaced element with a 'display' value of 'inline' generates an inline box. """ @@ -211,7 +211,7 @@ class InlineBox(InlineLevelBox): class TextBox(AnonymousBox, InlineBox): """ A box that contains only text and has no box children. - + Any text in the document ends up in a text box. What CSS calls "anonymous inline boxes" are also text boxes. """ @@ -262,13 +262,13 @@ class InlineLevelReplacedBox(ReplacedBox, InlineLevelBox): def dom_to_box(element): """ Converts a DOM element (and its children) into a box (with children). - + Eg. - +

Some emphasised text.

- + gives (not actual syntax) - + BlockBox[ TextBox('Some '), InlineBox[ @@ -276,13 +276,13 @@ def dom_to_box(element): ], TextBox(' text.'), ] - + TextBox`es are anonymous inline boxes: http://www.w3.org/TR/CSS21/visuren.html#anonymous """ display = element.style.display # TODO: should be the used value assert display != 'none' - + replacement = replaced.get_replaced_element(element) if replacement: if display in ('block', 'list-item', 'table'): @@ -305,7 +305,7 @@ def dom_to_box(element): box = InlineBlockBox(element) else: raise NotImplementedError('Unsupported display: ' + display) - + if element.text: box.add_child(TextBox(element, element.text)) for child_element in element: @@ -313,7 +313,7 @@ def dom_to_box(element): box.add_child(dom_to_box(child_element)) if child_element.tail: box.add_child(TextBox(element, child_element.tail)) - + return box @@ -326,10 +326,10 @@ def process_whitespace(box): for box in box.descendants(): if not (hasattr(box, 'text') and box.text): continue - + text = box.text handling = box.style.white_space - + text = re.sub('[\t\r ]*\n[\t\r ]*', '\n', text) if handling in ('pre', 'pre-wrap'): # \xA0 is the non-breaking space @@ -344,7 +344,7 @@ def process_whitespace(box): # or no character # CSS3: http://www.w3.org/TR/css3-text/#line-break-transform text = text.replace('\n', ' ') - + if handling in ('normal', 'nowrap', 'pre-line'): text = text.replace('\t', ' ') text = re.sub(' +', ' ', text) @@ -353,7 +353,7 @@ def process_whitespace(box): following_collapsible_space = text.endswith(' ') else: following_collapsible_space = False - + box.text = text @@ -362,14 +362,14 @@ def inline_in_block(box): Consecutive inline-level boxes in a block container box are wrapped into a line box, itself wrapped into an anonymous block box. (This line box will be broken into multiple lines later.) - + The box tree is changed *in place*. - + This is the first case in http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level - + Eg. - + BlockBox[ TextBox('Some '), InlineBox[TextBox('text')], @@ -377,9 +377,9 @@ def inline_in_block(box): TextBox('More text'), ] ] - + is turned into - + BlockBox[ AnonymousBlockBox[ LineBox[ @@ -399,7 +399,7 @@ def inline_in_block(box): if not isinstance(box, BlockContainerBox): return - + line_box = LineBox(box.element) children = box.children box.children = [] @@ -438,9 +438,9 @@ def block_in_inline(box): This is the second case in http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level - + Eg. - + BlockBox[ LineBox[ InlineBox[ @@ -457,7 +457,7 @@ def block_in_inline(box): ] ] ] - + is turned into BlockBox[ @@ -497,7 +497,7 @@ def block_in_inline(box): if not (isinstance(box, BlockLevelBox) and box.parent and isinstance(box.parent, InlineBox)): return - + # Find all ancestry until a line box. inline_parents = [] for parent in box.ancestors(): @@ -506,7 +506,7 @@ def block_in_inline(box): assert isinstance(parent, LineBox) parent_line_box = parent break - + # Add an anonymous block level box before the block box if isinstance(parent_line_box.parent, AnonymousBlockBox): previous_anonymous_box = parent_line_box.parent @@ -535,7 +535,7 @@ def block_in_inline(box): for parent in box.ancestors(): if parent == parent_line_box: break - + next_children = parent.children[splitter_box.index + 1:] parent.children = parent.children[:splitter_box.index + 1] @@ -544,10 +544,8 @@ def block_in_inline(box): splitter_box = parent clone_box = clone_box.parent - + # Put the block element before the next_anonymous_box box.parent.children.remove(box) previous_anonymous_box.parent.add_child( box, previous_anonymous_box.index + 1) - - diff --git a/weasy/css/computed_values.py b/weasy/css/computed_values.py index 02bb5826..34cb9f5b 100644 --- a/weasy/css/computed_values.py +++ b/weasy/css/computed_values.py @@ -97,7 +97,7 @@ class DummyPropertyValue(list): """ A list that quacks like a PropertyValue. """ - + @property def value(self): return ' '.join(value.cssText for value in self) @@ -122,7 +122,7 @@ def compute_font_size(element): assert len(style['font-size']) == 1 value = style['font-size'][0] value_text = value.value - + # TODO: once we ignore invalid declarations, turn these ValueError’s into # assert False, 'Declaration should have been ignored' if value_text in FONT_SIZE_KEYWORDS: @@ -171,7 +171,7 @@ def compute_length(value, font_size): raise ValueError('The ex unit is not supported yet.', value.cssText) elif value.dimension is not None: raise ValueError('Unknown length unit', value.value, repr(value.type)) - + def compute_lengths(element): """ @@ -192,7 +192,7 @@ def compute_line_height(element): style = element.style assert len(style['line-height']) == 1 value = style['line-height'][0] - + # TODO: negative values are illegal if value.type == 'NUMBER': height = style.font_size * value.value @@ -249,7 +249,7 @@ def compute_display_float(element): elif style.float == 'none' and element.getparent() is not None: # Case 5 return - + # Cases 2, 3, 4 display = style.display if display == 'inline-table': @@ -336,7 +336,7 @@ def compute_content(element): # (ie. :first-line and :first-letter) # Assume the same as elements. style.content = 'normal' - + def compute_values(element): """ @@ -357,4 +357,3 @@ def compute_values(element): # http://www.w3.org/TR/CSS21/visudet.html#propdef-height # TODO: percentages for vertical-align. What about line-height: normal? # TODO: clip - diff --git a/weasy/css/html4_default.css b/weasy/css/html4_default.css index bf5017d6..22a01213 100644 --- a/weasy/css/html4_default.css +++ b/weasy/css/html4_default.css @@ -71,7 +71,7 @@ center { text-align: center } /*:focus { outline: thin dotted invert } 'border' shorthand is not implemented yet TODO: revert when it is */ -:focus { outline-width: thin; outline-style: dotted; +:focus { outline-width: thin; outline-style: dotted; outline-color: invert } /* Begin bidirectionality settings (do not change) */ diff --git a/weasy/css/inheritance.py b/weasy/css/inheritance.py index 907b5a02..c752617f 100644 --- a/weasy/css/inheritance.py +++ b/weasy/css/inheritance.py @@ -25,12 +25,12 @@ from cssutils.css import PropertyValue r""" Built with: - + print '\n'.join( lxml.html.parse('http://www.w3.org/TR/CSS21/propidx.html') .xpath('//table//td[5][contains(text(), "yes")]/../td[1]/a/span/text()') ).replace("'", '') - + adding some line breaks and removing shorthand properties. """ # Do not list shorthand properties here as we handle them before inheritance: diff --git a/weasy/css/initial_values.py b/weasy/css/initial_values.py index c72c04a6..a13dc351 100644 --- a/weasy/css/initial_values.py +++ b/weasy/css/initial_values.py @@ -28,10 +28,10 @@ from .shorthands import expand_shorthands_in_declaration r""" Built with: - + print '\n'.join( ' %s: %s;' % ( - prop.strip("'"), + prop.strip("'"), tr.xpath('td[3]/text()')[0].strip() ) for tr in lxml.html.parse('http://www.w3.org/TR/CSS21/propidx.html') @@ -157,7 +157,7 @@ def handle_initial_values(element): style[name] = initial # Special cases for initial values that can not be expressed as CSS - + # border-color: same as color for name in ('border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'): diff --git a/weasy/css/shorthands.py b/weasy/css/shorthands.py index 8f09cea9..0266c340 100644 --- a/weasy/css/shorthands.py +++ b/weasy/css/shorthands.py @@ -32,7 +32,7 @@ def expand_four_sides(name, values): if len(values) == 1: values *= 4 elif len(values) == 2: - values *= 2 # (bottom, left) defaults to (top, right) + values *= 2 # (bottom, left) defaults to (top, right) elif len(values) == 3: values.append(values[1]) # left defaults to right elif len(values) != 4: @@ -88,7 +88,7 @@ def expand_background(name, values): def expand_font(name, values): # TODO - # [ [ <'font-style'> || <'font-variant'> || <'font-weight'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] | caption | icon | menu | message-box | small-caption | status-bar | inherit + # [ [ <'font-style'> || <'font-variant'> || <'font-weight'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] | caption | icon | menu | message-box | small-caption | status-bar | inherit raise NotImplementedError @@ -129,7 +129,7 @@ def expand_shorthands_in_declaration(style): for prop in style: if prop.name in SHORTHANDS: expander = SHORTHANDS[prop.name] - for new_name, new_value in expander(prop.name, + for new_name, new_value in expander(prop.name, list(prop.propertyValue)): if not isinstance(new_value, basestring): new_value = new_value.cssText diff --git a/weasy/replaced.py b/weasy/replaced.py index fc0205b1..09831419 100644 --- a/weasy/replaced.py +++ b/weasy/replaced.py @@ -38,7 +38,7 @@ class Replacement(object): """ Abstract base class for replaced elements """ - + def __init__(self, element): self.element = element diff --git a/weasy/tests/test_boxes.py b/weasy/tests/test_boxes.py index 81ac49f2..bec57120 100644 --- a/weasy/tests/test_boxes.py +++ b/weasy/tests/test_boxes.py @@ -61,14 +61,14 @@ def unwrap_html_body(box): box = box.children[0] assert isinstance(box, boxes.BlockBox) assert box.element.tag == 'body' - + return box.children def to_lists(box_tree): """Serialize and unwrap and .""" return serialize(unwrap_html_body(box_tree)) - + def parse(html_content): """ @@ -98,7 +98,7 @@ def assert_tree(box, expected): """ Test box tree equality with the prettified obtained result in the message in case of failure. - + box: a Box object, starting with and blocks. expected: a list of serialized children as returned by to_lists(). """ @@ -139,7 +139,7 @@ def test_inline_in_block(): ('p', 'block', [ ('p', 'line', [ ('p', 'text', 'Lipsum.')])])])] - + box = parse(source) boxes.inline_in_block(box) assert_tree(box, expected) @@ -206,7 +206,7 @@ def test_block_in_inline(): ('span', 'block', [ ('span', 'line', [ ('span', 'text', 'amet,')])]), - + ('p', 'anon_block', [ ('p', 'line', [ ('em', 'inline', [ @@ -231,7 +231,7 @@ def test_styles(): amet,consectetur

''') boxes.inline_in_block(box) boxes.block_in_inline(box) - + for child in box.descendants(): # All boxes inherit the color assert child.style.color == 'blue' diff --git a/weasy/tests/test_css.py b/weasy/tests/test_css.py index d5cd5a6a..3b58ebf1 100644 --- a/weasy/tests/test_css.py +++ b/weasy/tests/test_css.py @@ -41,7 +41,7 @@ def test_style_dict(): @suite.test def test_find_stylesheets(): document = parse_html('doc1.html') - + sheets = list(css.find_stylesheets(document)) assert len(sheets) == 2 # Also test that stylesheets are in tree order @@ -53,7 +53,7 @@ def test_find_stylesheets(): assert len(rules) == 8 # Also test appearance order assert [rule.selectorText for rule in rules] \ - == ['li', 'p', 'ul', 'a', 'a:after', ':first', 'ul', + == ['li', 'p', 'ul', 'a', 'a:after', ':first', 'ul', 'body > h1:first-child'] @@ -83,41 +83,41 @@ def test_annotate_document(): user_stylesheet = cssutils.parseFile(resource_filename('user.css')) ua_stylesheet = cssutils.parseFile(resource_filename('mini_ua.css')) document = parse_html('doc1.html') - + css.annotate_document(document, [user_stylesheet], [ua_stylesheet]) - + # Element objects behave a lists of their children head, body = document h1, p, ul = body li = list(ul) a, = li[0] after = a.pseudo_elements['after'] - + assert h1.style['background-image'][0].absolute_uri == 'file://' \ + os.path.abspath(resource_filename('logo_small.png')) - + assert h1.style.font_weight == '700' - + sides = ('-top', '-right', '-bottom', '-left') # 32px = 1em * font-size: 2em * initial 16px for side, expected_value in zip(sides, ('32px', '0', '32px', '0')): assert p.style['margin' + side].value == expected_value - + # 32px = 2em * initial 16px for side, expected_value in zip(sides, ('32px', '32px', '32px', '32px')): assert ul.style['margin' + side].value == expected_value - + # thick = 5px, 0.25 inches = 96*.25 = 24px for side, expected_value in zip(sides, ('0', '5px', '0', '24px')): assert ul.style['border' + side + '-width'].value == expected_value - + # 32px = 2em * initial 16px # 64px = 4em * initial 16px for side, expected_value in zip(sides, ('32px', '0', '32px', '64px')): assert li[0].style['margin' + side].value == expected_value - + assert a.style.text_decoration == 'underline' - + color = a.style['color'][0] assert (color.red, color.green, color.blue, color.alpha) == (255, 0, 0, 1)