Skip to content

Commit

Permalink
StyledText subclasses: add copy() method
Browse files Browse the repository at this point in the history
StyledText retrieved from style attributes needs to be copied because
we shouldn't reuse the same instance at different locations in the
document tree.

This fixes problems caused by Styled._style_name caching.
  • Loading branch information
brechtm committed Oct 8, 2020
1 parent 664c791 commit 8b80a38
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/rinoh/flowable.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ def initial_state(self, container):
flowables_iter = self.flowables(container)
title_text = self.get_style('title', container)
if title_text:
title = Paragraph(title_text, style='title')
title = Paragraph(title_text.copy(), style='title')
flowables_iter = chain((title, ), flowables_iter)
return GroupedFlowablesState(self, flowables_iter)

Expand Down
3 changes: 2 additions & 1 deletion src/rinoh/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* :func:`format_number`: Format a number according to a given style.
"""
from copy import copy

from .attribute import Attribute, OptionSet, Bool
from .paragraph import ParagraphBase, ParagraphStyle
Expand Down Expand Up @@ -100,7 +101,7 @@ def __init__(self, custom_label=None):
def format_label(self, label, container):
prefix = self.get_style('label_prefix', container) or ''
suffix = self.get_style('label_suffix', container) or ''
return prefix + label + suffix
return copy(prefix) + copy(label) + copy(suffix)


class NumberStyle(LabelStyle):
Expand Down
6 changes: 5 additions & 1 deletion src/rinoh/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def __str__(self):
result += ' ({})'.format(self.style)
return result

def copy(self, parent=None):
return type(self)(self.type, self.link, self.quiet,
style=self.style, parent=parent)

def target_id(self, document):
raise NotImplementedError

Expand Down Expand Up @@ -167,7 +171,7 @@ def __init__(self, target_id_or_flowable, id=None, style=None, parent=None):
self.target_id_or_flowable = target_id_or_flowable

def text(self, container):
return MixedStyledText(self.get_style('text', container), parent=self)
return self.get_style('text', container).copy(parent=self)

def _target_id(self, document):
target_id_or_flowable = self.target_id_or_flowable
Expand Down
3 changes: 1 addition & 2 deletions src/rinoh/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,8 +459,7 @@ def nesting_level(self):
return 0

def configuration_name(self, document):
try:
# raise AttributeError
try: # TODO: make document hashable so @cached can be used?
return self._style_name
except AttributeError:
ruleset = self.configuration_class.get_ruleset(document)
Expand Down
25 changes: 18 additions & 7 deletions src/rinoh/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,12 @@ def wrapped_spans(self, container):
"""Generator yielding all spans in this inline element (flattened)"""
before = self.get_style('before', container)
if before is not None:
before.parent = self.parent
yield from before.wrapped_spans(container)
yield from before.copy(self.parent).wrapped_spans(container)
if not self.get_style('hide', container):
yield from self.spans(container)
after = self.get_style('after', container)
if after is not None:
after.parent = self.parent
yield from after.wrapped_spans(container)
yield from after.copy(self.parent).wrapped_spans(container)

def spans(self, container):
raise NotImplementedError
Expand Down Expand Up @@ -129,7 +127,10 @@ def __iadd__(self, other):
is `None`, this styled text itself is returned."""
return self + other

def copy(self, id=None, style=None, parent=None):
def __copy__(self):
return self.copy()

def copy(self, parent=None):
raise NotImplementedError

@classmethod
Expand Down Expand Up @@ -381,6 +382,9 @@ def __str__(self):
def text(self, container, **kwargs):
return self._text

def copy(self, parent=None):
return type(self)(self._text, style=self.style, parent=parent)


class MixedStyledTextBase(StyledText):
def to_string(self, flowable_target):
Expand Down Expand Up @@ -477,6 +481,10 @@ def items(self):
def children(self, flowable_target):
return self.items

def copy(self, parent=None):
items = [item.copy() for item in self.items]
return type(self)(items, style=self.style, parent=parent)


class ConditionalMixedStyledText(MixedStyledText):
def __init__(self, text_or_items, document_option, style=None, parent=None):
Expand Down Expand Up @@ -578,14 +586,17 @@ class ControlCharacter(Character, metaclass=ControlCharacterMeta):
exception = Exception
all = {}

def __init__(self):
def __init__(self, style=None, parent=None):
"""Initialize this control character with it's unicode `char`."""
super().__init__(self.character)
super().__init__(self.character, style=style, parent=parent)

def __repr__(self):
"""A textual representation of this control character."""
return self.__class__.__name__

def copy(self, parent=None):
return type(self)(style=self.style, parent=parent)

@property
def width(self):
"""Raises the exception associated with this control character.
Expand Down

0 comments on commit 8b80a38

Please sign in to comment.