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