1# encoding: utf-8
2
3"""
4Custom element classes related to text runs (CT_R).
5"""
6
7from ..ns import qn
8from ..simpletypes import ST_BrClear, ST_BrType
9from ..xmlchemy import (
10    BaseOxmlElement, OptionalAttribute, ZeroOrMore, ZeroOrOne
11)
12
13
14class CT_Br(BaseOxmlElement):
15    """
16    ``<w:br>`` element, indicating a line, page, or column break in a run.
17    """
18    type = OptionalAttribute('w:type', ST_BrType)
19    clear = OptionalAttribute('w:clear', ST_BrClear)
20
21
22class CT_R(BaseOxmlElement):
23    """
24    ``<w:r>`` element, containing the properties and text for a run.
25    """
26    rPr = ZeroOrOne('w:rPr')
27    t = ZeroOrMore('w:t')
28    br = ZeroOrMore('w:br')
29    cr = ZeroOrMore('w:cr')
30    tab = ZeroOrMore('w:tab')
31    drawing = ZeroOrMore('w:drawing')
32
33    def _insert_rPr(self, rPr):
34        self.insert(0, rPr)
35        return rPr
36
37    def add_t(self, text):
38        """
39        Return a newly added ``<w:t>`` element containing *text*.
40        """
41        t = self._add_t(text=text)
42        if len(text.strip()) < len(text):
43            t.set(qn('xml:space'), 'preserve')
44        return t
45
46    def add_drawing(self, inline_or_anchor):
47        """
48        Return a newly appended ``CT_Drawing`` (``<w:drawing>``) child
49        element having *inline_or_anchor* as its child.
50        """
51        drawing = self._add_drawing()
52        drawing.append(inline_or_anchor)
53        return drawing
54
55    def clear_content(self):
56        """
57        Remove all child elements except the ``<w:rPr>`` element if present.
58        """
59        content_child_elms = self[1:] if self.rPr is not None else self[:]
60        for child in content_child_elms:
61            self.remove(child)
62
63    @property
64    def style(self):
65        """
66        String contained in w:val attribute of <w:rStyle> grandchild, or
67        |None| if that element is not present.
68        """
69        rPr = self.rPr
70        if rPr is None:
71            return None
72        return rPr.style
73
74    @style.setter
75    def style(self, style):
76        """
77        Set the character style of this <w:r> element to *style*. If *style*
78        is None, remove the style element.
79        """
80        rPr = self.get_or_add_rPr()
81        rPr.style = style
82
83    @property
84    def text(self):
85        """
86        A string representing the textual content of this run, with content
87        child elements like ``<w:tab/>`` translated to their Python
88        equivalent.
89        """
90        text = ''
91        for child in self:
92            if child.tag == qn('w:t'):
93                t_text = child.text
94                text += t_text if t_text is not None else ''
95            elif child.tag == qn('w:tab'):
96                text += '\t'
97            elif child.tag in (qn('w:br'), qn('w:cr')):
98                text += '\n'
99        return text
100
101    @text.setter
102    def text(self, text):
103        self.clear_content()
104        _RunContentAppender.append_to_run_from_text(self, text)
105
106
107class CT_Text(BaseOxmlElement):
108    """
109    ``<w:t>`` element, containing a sequence of characters within a run.
110    """
111
112
113class _RunContentAppender(object):
114    """
115    Service object that knows how to translate a Python string into run
116    content elements appended to a specified ``<w:r>`` element. Contiguous
117    sequences of regular characters are appended in a single ``<w:t>``
118    element. Each tab character ('\t') causes a ``<w:tab/>`` element to be
119    appended. Likewise a newline or carriage return character ('\n', '\r')
120    causes a ``<w:cr>`` element to be appended.
121    """
122    def __init__(self, r):
123        self._r = r
124        self._bfr = []
125
126    @classmethod
127    def append_to_run_from_text(cls, r, text):
128        """
129        Create a "one-shot" ``_RunContentAppender`` instance and use it to
130        append the run content elements corresponding to *text* to the
131        ``<w:r>`` element *r*.
132        """
133        appender = cls(r)
134        appender.add_text(text)
135
136    def add_text(self, text):
137        """
138        Append the run content elements corresponding to *text* to the
139        ``<w:r>`` element of this instance.
140        """
141        for char in text:
142            self.add_char(char)
143        self.flush()
144
145    def add_char(self, char):
146        """
147        Process the next character of input through the translation finite
148        state maching (FSM). There are two possible states, buffer pending
149        and not pending, but those are hidden behind the ``.flush()`` method
150        which must be called at the end of text to ensure any pending
151        ``<w:t>`` element is written.
152        """
153        if char == '\t':
154            self.flush()
155            self._r.add_tab()
156        elif char in '\r\n':
157            self.flush()
158            self._r.add_br()
159        else:
160            self._bfr.append(char)
161
162    def flush(self):
163        text = ''.join(self._bfr)
164        if text:
165            self._r.add_t(text)
166        del self._bfr[:]
167