1#!/usr/bin/env python
2#coding:utf-8
3# Purpose: text objects
4# Created: 03.01.2011
5# Copyright (C) 2011, Manfred Moitzi
6# License: MIT license
7from __future__ import unicode_literals, print_function, division
8__author__ = "mozman <mozman@gmx.at>"
9
10from.compatibility import is_string
11from .xmlns import CN, register_class, subelement, wrap
12from .base import GenericWrapper, safelen
13from .whitespaces import encode_whitespaces
14from .protection import random_protection_key
15from .propertymixins import StringProperty, TextNumberingMixin, BooleanProperty
16from .propertymixins import IntegerWithLowerLimitProperty
17
18
19@register_class
20class Span(GenericWrapper):
21    TAG = CN('text:span')
22    style_name = StringProperty(CN('text:style-name'))
23
24    def __init__(self, text="", style_name="", xmlnode=None):
25        super(Span, self).__init__(xmlnode)
26        if xmlnode is None:
27            if style_name:
28                self.style_name = style_name
29            if text:
30                self.append_text(text)
31
32    @property
33    def textlen(self):
34        # NOTE: do not cache this value before you can guarantee that
35        # you detect ALL text changes in this node and all of it child nodes.
36        length = safelen(self.xmlnode.text)
37        for element in iter(self):
38            length += (element.textlen + safelen(element.xmlnode.tail))
39        return length
40
41    def plaintext(self):
42        # NOTE: do not cache this value before you can guarantee that
43        # you detect ALL text changes in this node and all of it child nodes.
44        text = [self.xmlnode.text]
45        for element in iter(self):
46            text.append(element.plaintext())
47            text.append(element.xmlnode.tail)
48        return "".join(filter(None, text))
49
50    def append_text(self, text):
51        def append(text, new):
52            return text + new if text else new
53
54        for tag in encode_whitespaces(text):
55            if is_string(tag):
56                if len(self.xmlnode) > 0:
57                    lastchild = self[-1]
58                    lastchild.tail = append(lastchild.tail, tag)
59                else:
60                    self.text = append(self.text, tag)
61            else:
62                self.append(tag)
63
64
65@register_class
66class Paragraph(Span):
67    TAG = CN('text:p')
68    cond_style_name = StringProperty(CN('text:cond-style-name'))
69    ID = StringProperty(CN('text:id'))
70
71@register_class
72class NumberedParagraph(GenericWrapper, TextNumberingMixin):
73    TAG = CN('text:numbered-paragraph')
74    level = IntegerWithLowerLimitProperty(CN('text:level'), 1)
75
76    def __init__(self, paragraph=None, xmlnode=None):
77        super(NumberedParagraph, self).__init__(xmlnode)
78        if xmlnode is None:
79            if paragraph is not None:
80                if isinstance(paragraph, GenericWrapper):
81                    self.append(paragraph)
82                else:
83                    raise TypeError("Parameter 'paragraph' has to be a subclass of class 'GenericWrapper'")
84
85    @property
86    def content(self):
87        p = self.xmlnode.find(CN('text:h'))
88        if p is None:
89            p = subelement(self.xmlnode, CN('text:p'))
90        return wrap(p)
91
92@register_class
93class Heading(Span, TextNumberingMixin):
94    TAG = CN('text:h')
95    outline_level = IntegerWithLowerLimitProperty(CN('text:outline-level'), 1)
96    restart_numbering = BooleanProperty(CN('text:restart-numbering'))
97    suppress_numbering = BooleanProperty(CN('text:is-list-header'))
98
99    def __init__(self, text="", outline_level=1, style_name="", xmlnode=None):
100        super(Heading, self).__init__(text, style_name, xmlnode)
101        if xmlnode is None:
102            self.outline_level = outline_level
103
104@register_class
105class Hyperlink(Span):
106    TAG = CN('text:a')
107    name = StringProperty(CN('office:name'))
108    href = StringProperty(CN('xlink:href'))
109
110    def __init__(self, href="", text="", style_name="", xmlnode=None):
111        super(Hyperlink, self).__init__(text, style_name, xmlnode)
112        if xmlnode is None:
113            if href: self.href = href
114            self.target_frame = '_blank'
115
116    @property
117    def target_frame(self):
118        return self.get_attr(CN('office:target-frame-name'))
119    @target_frame.setter
120    def target_frame(self, framename):
121        self.set_attr(CN('office:target-frame-name'), framename)
122        show = 'new' if framename == '_blank' else 'replace'
123        self.set_attr(CN('xlink:show'), show)
124
125
126@register_class
127class ListHeader(GenericWrapper):
128    TAG = CN('text:list-header')
129
130    def __init__(self, text="", xmlnode=None):
131        super(ListHeader, self).__init__(xmlnode)
132        if xmlnode is None:
133            if text:
134                self.append(Paragraph(text))
135
136    def plaintext(self):
137        return '\n'.join([e.plaintext() for e in iter(self)])
138
139
140@register_class
141class ListItem(ListHeader, TextNumberingMixin):
142    TAG = CN('text:list-item')
143
144
145@register_class
146class List(GenericWrapper):
147    TAG = CN('text:list')
148    style_name = StringProperty(CN('text:style-name'))
149    continue_numbering = BooleanProperty(CN('text:continue-numbering'))
150
151    def __init__(self, style_name="", xmlnode=None):
152        super(List, self).__init__(xmlnode)
153        if xmlnode is None:
154            if style_name:
155                self.style_name = style_name
156
157    @property
158    def header(self):
159        h = self.xmlnode.find(CN('text:list-header'))
160        return wrap(h) if h is not None else None
161    @header.setter
162    def header(self, header):
163        if header.kind != 'ListHeader':
164            raise TypeError("param 'header' is not a list header.")
165        oldheader = self.xmlnode.find(CN('text:list-header'))
166        if oldheader is not None:
167            self.xmlnode.remove(oldheader)
168        self.insert(0, header) # should be first child node
169
170    def iteritems(self):
171        return self.findall(CN('text:list-item'))
172
173
174@register_class
175class Section(GenericWrapper):
176    TAG = CN('text:section')
177    style_name = StringProperty(CN('text:style-name'))
178    name = StringProperty(CN('text:name'))
179
180    def __init__(self, name="", style_name="", xmlnode=None):
181        super(Section, self).__init__(xmlnode)
182        if xmlnode is None:
183            if style_name:
184                self.style_name = style_name
185            if name:
186                self.name = name
187
188    @property
189    def protected(self):
190        return self.get_bool_attr(CN('text:protected'))
191    @protected.setter
192    def protected(self, value):
193        self.set_bool_attr(CN('text:protected'), value)
194        if self.protected:
195            self.set_attr(CN('text:protection-key'), random_protection_key())
196