321 lines
10 KiB
Python
321 lines
10 KiB
Python
"""Custom element classes related to the styles part."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from docx.enum.style import WD_STYLE_TYPE
|
|
from docx.oxml.simpletypes import ST_DecimalNumber, ST_OnOff, ST_String
|
|
from docx.oxml.xmlchemy import (
|
|
BaseOxmlElement,
|
|
OptionalAttribute,
|
|
RequiredAttribute,
|
|
ZeroOrMore,
|
|
ZeroOrOne,
|
|
)
|
|
|
|
|
|
def styleId_from_name(name):
|
|
"""Return the style id corresponding to `name`, taking into account special-case
|
|
names such as 'Heading 1'."""
|
|
return {
|
|
"caption": "Caption",
|
|
"heading 1": "Heading1",
|
|
"heading 2": "Heading2",
|
|
"heading 3": "Heading3",
|
|
"heading 4": "Heading4",
|
|
"heading 5": "Heading5",
|
|
"heading 6": "Heading6",
|
|
"heading 7": "Heading7",
|
|
"heading 8": "Heading8",
|
|
"heading 9": "Heading9",
|
|
}.get(name, name.replace(" ", ""))
|
|
|
|
|
|
class CT_LatentStyles(BaseOxmlElement):
|
|
"""`w:latentStyles` element, defining behavior defaults for latent styles and
|
|
containing `w:lsdException` child elements that each override those defaults for a
|
|
named latent style."""
|
|
|
|
lsdException = ZeroOrMore("w:lsdException", successors=())
|
|
|
|
count = OptionalAttribute("w:count", ST_DecimalNumber)
|
|
defLockedState = OptionalAttribute("w:defLockedState", ST_OnOff)
|
|
defQFormat = OptionalAttribute("w:defQFormat", ST_OnOff)
|
|
defSemiHidden = OptionalAttribute("w:defSemiHidden", ST_OnOff)
|
|
defUIPriority = OptionalAttribute("w:defUIPriority", ST_DecimalNumber)
|
|
defUnhideWhenUsed = OptionalAttribute("w:defUnhideWhenUsed", ST_OnOff)
|
|
|
|
def bool_prop(self, attr_name):
|
|
"""Return the boolean value of the attribute having `attr_name`, or |False| if
|
|
not present."""
|
|
value = getattr(self, attr_name)
|
|
if value is None:
|
|
return False
|
|
return value
|
|
|
|
def get_by_name(self, name):
|
|
"""Return the `w:lsdException` child having `name`, or |None| if not found."""
|
|
found = self.xpath('w:lsdException[@w:name="%s"]' % name)
|
|
if not found:
|
|
return None
|
|
return found[0]
|
|
|
|
def set_bool_prop(self, attr_name, value):
|
|
"""Set the on/off attribute having `attr_name` to `value`."""
|
|
setattr(self, attr_name, bool(value))
|
|
|
|
|
|
class CT_LsdException(BaseOxmlElement):
|
|
"""``<w:lsdException>`` element, defining override visibility behaviors for a named
|
|
latent style."""
|
|
|
|
locked = OptionalAttribute("w:locked", ST_OnOff)
|
|
name = RequiredAttribute("w:name", ST_String)
|
|
qFormat = OptionalAttribute("w:qFormat", ST_OnOff)
|
|
semiHidden = OptionalAttribute("w:semiHidden", ST_OnOff)
|
|
uiPriority = OptionalAttribute("w:uiPriority", ST_DecimalNumber)
|
|
unhideWhenUsed = OptionalAttribute("w:unhideWhenUsed", ST_OnOff)
|
|
|
|
def delete(self):
|
|
"""Remove this `w:lsdException` element from the XML document."""
|
|
self.getparent().remove(self)
|
|
|
|
def on_off_prop(self, attr_name):
|
|
"""Return the boolean value of the attribute having `attr_name`, or |None| if
|
|
not present."""
|
|
return getattr(self, attr_name)
|
|
|
|
def set_on_off_prop(self, attr_name, value):
|
|
"""Set the on/off attribute having `attr_name` to `value`."""
|
|
setattr(self, attr_name, value)
|
|
|
|
|
|
class CT_Style(BaseOxmlElement):
|
|
"""A ``<w:style>`` element, representing a style definition."""
|
|
|
|
_tag_seq = (
|
|
"w:name",
|
|
"w:aliases",
|
|
"w:basedOn",
|
|
"w:next",
|
|
"w:link",
|
|
"w:autoRedefine",
|
|
"w:hidden",
|
|
"w:uiPriority",
|
|
"w:semiHidden",
|
|
"w:unhideWhenUsed",
|
|
"w:qFormat",
|
|
"w:locked",
|
|
"w:personal",
|
|
"w:personalCompose",
|
|
"w:personalReply",
|
|
"w:rsid",
|
|
"w:pPr",
|
|
"w:rPr",
|
|
"w:tblPr",
|
|
"w:trPr",
|
|
"w:tcPr",
|
|
"w:tblStylePr",
|
|
)
|
|
name = ZeroOrOne("w:name", successors=_tag_seq[1:])
|
|
basedOn = ZeroOrOne("w:basedOn", successors=_tag_seq[3:])
|
|
next = ZeroOrOne("w:next", successors=_tag_seq[4:])
|
|
uiPriority = ZeroOrOne("w:uiPriority", successors=_tag_seq[8:])
|
|
semiHidden = ZeroOrOne("w:semiHidden", successors=_tag_seq[9:])
|
|
unhideWhenUsed = ZeroOrOne("w:unhideWhenUsed", successors=_tag_seq[10:])
|
|
qFormat = ZeroOrOne("w:qFormat", successors=_tag_seq[11:])
|
|
locked = ZeroOrOne("w:locked", successors=_tag_seq[12:])
|
|
pPr = ZeroOrOne("w:pPr", successors=_tag_seq[17:])
|
|
rPr = ZeroOrOne("w:rPr", successors=_tag_seq[18:])
|
|
del _tag_seq
|
|
|
|
type: WD_STYLE_TYPE | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
|
"w:type", WD_STYLE_TYPE
|
|
)
|
|
styleId: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
|
"w:styleId", ST_String
|
|
)
|
|
default = OptionalAttribute("w:default", ST_OnOff)
|
|
customStyle = OptionalAttribute("w:customStyle", ST_OnOff)
|
|
|
|
@property
|
|
def basedOn_val(self):
|
|
"""Value of `w:basedOn/@w:val` or |None| if not present."""
|
|
basedOn = self.basedOn
|
|
if basedOn is None:
|
|
return None
|
|
return basedOn.val
|
|
|
|
@basedOn_val.setter
|
|
def basedOn_val(self, value):
|
|
if value is None:
|
|
self._remove_basedOn()
|
|
else:
|
|
self.get_or_add_basedOn().val = value
|
|
|
|
@property
|
|
def base_style(self):
|
|
"""Sibling CT_Style element this style is based on or |None| if no base style or
|
|
base style not found."""
|
|
basedOn = self.basedOn
|
|
if basedOn is None:
|
|
return None
|
|
styles = self.getparent()
|
|
base_style = styles.get_by_id(basedOn.val)
|
|
if base_style is None:
|
|
return None
|
|
return base_style
|
|
|
|
def delete(self):
|
|
"""Remove this `w:style` element from its parent `w:styles` element."""
|
|
self.getparent().remove(self)
|
|
|
|
@property
|
|
def locked_val(self):
|
|
"""Value of `w:locked/@w:val` or |False| if not present."""
|
|
locked = self.locked
|
|
if locked is None:
|
|
return False
|
|
return locked.val
|
|
|
|
@locked_val.setter
|
|
def locked_val(self, value):
|
|
self._remove_locked()
|
|
if bool(value) is True:
|
|
locked = self._add_locked()
|
|
locked.val = value
|
|
|
|
@property
|
|
def name_val(self):
|
|
"""Value of ``<w:name>`` child or |None| if not present."""
|
|
name = self.name
|
|
if name is None:
|
|
return None
|
|
return name.val
|
|
|
|
@name_val.setter
|
|
def name_val(self, value):
|
|
self._remove_name()
|
|
if value is not None:
|
|
name = self._add_name()
|
|
name.val = value
|
|
|
|
@property
|
|
def next_style(self):
|
|
"""Sibling CT_Style element identified by the value of `w:name/@w:val` or |None|
|
|
if no value is present or no style with that style id is found."""
|
|
next = self.next
|
|
if next is None:
|
|
return None
|
|
styles = self.getparent()
|
|
return styles.get_by_id(next.val) # None if not found
|
|
|
|
@property
|
|
def qFormat_val(self):
|
|
"""Value of `w:qFormat/@w:val` or |False| if not present."""
|
|
qFormat = self.qFormat
|
|
if qFormat is None:
|
|
return False
|
|
return qFormat.val
|
|
|
|
@qFormat_val.setter
|
|
def qFormat_val(self, value):
|
|
self._remove_qFormat()
|
|
if bool(value):
|
|
self._add_qFormat()
|
|
|
|
@property
|
|
def semiHidden_val(self):
|
|
"""Value of ``<w:semiHidden>`` child or |False| if not present."""
|
|
semiHidden = self.semiHidden
|
|
if semiHidden is None:
|
|
return False
|
|
return semiHidden.val
|
|
|
|
@semiHidden_val.setter
|
|
def semiHidden_val(self, value):
|
|
self._remove_semiHidden()
|
|
if bool(value) is True:
|
|
semiHidden = self._add_semiHidden()
|
|
semiHidden.val = value
|
|
|
|
@property
|
|
def uiPriority_val(self):
|
|
"""Value of ``<w:uiPriority>`` child or |None| if not present."""
|
|
uiPriority = self.uiPriority
|
|
if uiPriority is None:
|
|
return None
|
|
return uiPriority.val
|
|
|
|
@uiPriority_val.setter
|
|
def uiPriority_val(self, value):
|
|
self._remove_uiPriority()
|
|
if value is not None:
|
|
uiPriority = self._add_uiPriority()
|
|
uiPriority.val = value
|
|
|
|
@property
|
|
def unhideWhenUsed_val(self):
|
|
"""Value of `w:unhideWhenUsed/@w:val` or |False| if not present."""
|
|
unhideWhenUsed = self.unhideWhenUsed
|
|
if unhideWhenUsed is None:
|
|
return False
|
|
return unhideWhenUsed.val
|
|
|
|
@unhideWhenUsed_val.setter
|
|
def unhideWhenUsed_val(self, value):
|
|
self._remove_unhideWhenUsed()
|
|
if bool(value) is True:
|
|
unhideWhenUsed = self._add_unhideWhenUsed()
|
|
unhideWhenUsed.val = value
|
|
|
|
|
|
class CT_Styles(BaseOxmlElement):
|
|
"""``<w:styles>`` element, the root element of a styles part, i.e. styles.xml."""
|
|
|
|
_tag_seq = ("w:docDefaults", "w:latentStyles", "w:style")
|
|
latentStyles = ZeroOrOne("w:latentStyles", successors=_tag_seq[2:])
|
|
style = ZeroOrMore("w:style", successors=())
|
|
del _tag_seq
|
|
|
|
def add_style_of_type(self, name, style_type, builtin):
|
|
"""Return a newly added `w:style` element having `name` and `style_type`.
|
|
|
|
`w:style/@customStyle` is set based on the value of `builtin`.
|
|
"""
|
|
style = self.add_style()
|
|
style.type = style_type
|
|
style.customStyle = None if builtin else True
|
|
style.styleId = styleId_from_name(name)
|
|
style.name_val = name
|
|
return style
|
|
|
|
def default_for(self, style_type):
|
|
"""Return `w:style[@w:type="*{style_type}*][-1]` or |None| if not found."""
|
|
default_styles_for_type = [
|
|
s for s in self._iter_styles() if s.type == style_type and s.default
|
|
]
|
|
if not default_styles_for_type:
|
|
return None
|
|
# spec calls for last default in document order
|
|
return default_styles_for_type[-1]
|
|
|
|
def get_by_id(self, styleId: str) -> CT_Style | None:
|
|
"""`w:style` child where @styleId = `styleId`.
|
|
|
|
|None| if not found.
|
|
"""
|
|
xpath = f'w:style[@w:styleId="{styleId}"]'
|
|
return next(iter(self.xpath(xpath)), None)
|
|
|
|
def get_by_name(self, name: str) -> CT_Style | None:
|
|
"""`w:style` child with `w:name` grandchild having value `name`.
|
|
|
|
|None| if not found.
|
|
"""
|
|
xpath = 'w:style[w:name/@w:val="%s"]' % name
|
|
return next(iter(self.xpath(xpath)), None)
|
|
|
|
def _iter_styles(self):
|
|
"""Generate each of the `w:style` child elements in document order."""
|
|
return (style for style in self.xpath("w:style"))
|