diff --git a/docs/user/text.rst b/docs/user/text.rst index 1f59c8fa3..327f9ddc8 100644 --- a/docs/user/text.rst +++ b/docs/user/text.rst @@ -142,6 +142,33 @@ second and third indented (like sub-bullets) under the first:: p.level = 1 +Applying paragraph bullet formatting +----------------------------- + +The following continues from the previous example, changes the first bullet +to an "x", and changes the second and third sub-bullets to be numbered:: + + from pptx.enum.text import MSO_NUMBERED_BULLET_STYLE + from pptx.util import BulletStyle + + p = text_frame.paragraphs[0] + p.bullet = BulletStyle.custom("x") + + for p in text_frame.paragraphs[1:]: + p.bullet = BulletStyle.numbered(MSO_NUMBERED_BULLET_STYLE.ARABIC_PERIOD) + +The ``.bullet`` attribute can also be used to remove bullet formatting:: + + p = text_frame.add_paragraph() + p.text = "This is not a bullet!" + p.bullet = BulletStyle.NO_BULLET + +Finally, the attribute can also be used to revert a paragraph back to the +slide's default bullet configuration:: + + p.text = "Now it's a bullet again." + p.bullet = BulletStyle.DEFAULT + Applying character formatting ----------------------------- diff --git a/features/steps/text.py b/features/steps/text.py index 5c3692b5d..20422662e 100644 --- a/features/steps/text.py +++ b/features/steps/text.py @@ -6,8 +6,8 @@ from helpers import test_pptx from pptx import Presentation -from pptx.enum.text import PP_ALIGN -from pptx.util import Emu +from pptx.enum.text import MSO_NUMBERED_BULLET_STYLE, PP_ALIGN +from pptx.util import BulletStyle, Emu # given =================================================== @@ -138,6 +138,18 @@ def when_set_hyperlink_address(context): hlink.address = context.address +@when("I assign paragraph.bullet = {value_str}") +def when_I_assign_value_to_paragraph_bullet(context, value_str): + value = { + "x": BulletStyle.custom("x"), + "Default": BulletStyle.DEFAULT, + "No bullet": BulletStyle.NO_BULLET, + "hindiAlphaPeriod": BulletStyle.numbered(MSO_NUMBERED_BULLET_STYLE.HINDI_ALPHA_PERIOD), + }[value_str] + paragraph = context.paragraph + paragraph.bullet = value + + # then ==================================================== @@ -246,3 +258,15 @@ def then_run_text_is_not_a_hyperlink(context): @then("the font name matches the typeface I set") def then_font_name_matches_typeface_I_set(context): assert context.font.name == "Verdana" + +@then("paragraph.bullet == {value}") +def then_paragraph_bullet_is_value(context, value): + expected = { + "x": BulletStyle.custom("x"), + "Default": BulletStyle.DEFAULT, + "No bullet": BulletStyle.NO_BULLET, + "hindiAlphaPeriod": BulletStyle.numbered(MSO_NUMBERED_BULLET_STYLE.HINDI_ALPHA_PERIOD), + }[value] + + actual = context.paragraph.bullet + assert actual == expected, 'paragraph.bullet == "%s"' % actual \ No newline at end of file diff --git a/features/txt-paragraph.feature b/features/txt-paragraph.feature index e0799a792..9b6eb79a5 100644 --- a/features/txt-paragraph.feature +++ b/features/txt-paragraph.feature @@ -93,3 +93,16 @@ Feature: Change paragraph properties | "a\vb\vc" | "a\vb\vc" | | "a\nb\vc" | "a\vb\vc" | | "a\x1Bc" | "a_x001B_c" | + + + Scenario Outline: _Paragraph.bullet setter + Given a _Paragraph object as paragraph + When I assign paragraph.bullet = + Then paragraph.bullet == + + Examples: _Paragraph assigned bullet replacement cases + | value | expected-value | + | x | x | + | No bullet | No bullet | + | Default | Default | + | hindiAlphaPeriod | hindiAlphaPeriod | \ No newline at end of file diff --git a/src/pptx/enum/text.py b/src/pptx/enum/text.py index db266a3c9..66df44658 100644 --- a/src/pptx/enum/text.py +++ b/src/pptx/enum/text.py @@ -1,6 +1,7 @@ """Enumerations used by text and related objects.""" from __future__ import annotations +from enum import Enum, auto from pptx.enum.base import BaseEnum, BaseXmlEnum @@ -228,3 +229,69 @@ class PP_PARAGRAPH_ALIGNMENT(BaseXmlEnum): PP_ALIGN = PP_PARAGRAPH_ALIGNMENT + + +class BulletStyleType(Enum): + """Types of bullet styles.""" + NO_BULLET = auto() + CUSTOM = auto() + NUMBERED = auto() + DEFAULT = auto() + + +class MSO_NUMBERED_BULLET_STYLE(BaseXmlEnum): + """Specifies the style of numbered bullets. + + Example:: + + from pptx.enum.text import MSO_NUMBERED_BULLET_STYLE + + shape.paragraphs[0].bullet = MSO_NUMBERED_BULLET_STYLE.BULLET_ALPHA_UC_PERIOD + + MS API Name: `MsoNumberedBulletStyle` + + https://learn.microsoft.com/sv-se/office/vba/api/office.msonumberedbulletstyle + """ + + ALPHA_LC_PAREN_BOTH = (8, "alphaLCParenBoth", "Lowercase alphabetical bullet with opening and closing parentheses.") + ALPHA_LC_PAREN_RIGHT = (9, "alphaLCParenRight", "Lowercase alphabetical bullet with closing parenthesis.") + ALPHA_LC_PERIOD = (0, "alphaLCPeriod", "Lowercase alphabetical bullet with period.|") + ALPHA_UC_PAREN_BOTH = (10, "alphaUCParenBoth", "Uppercase alphabetical bullet with opening and closing parentheses.") + ALPHA_UC_PAREN_RIGHT = (11, "alphaUCParenRight", "Uppercase alphabetical bullet with closing parenthesis.") + ALPHA_UC_PERIOD = (1, "alphaUCPeriod", "Uppercase alphabetical bullet with period.") + ARABIC_ABJAD_DASH = (24, "arabicAbjadDash", "Arabic Abjad bullet with a dash.") + ARABIC_ALPHA_DASH = (23, "arabicAlphaDash", "Arabic alphabetical bullet with a dash.") + ARABIC_DB_PERIOD = (29, "arabicDBPeriod", "Arabic DB bullet with period.") + ARABIC_DB_PLAIN = (28, "arabicDBPlain", "Plain Arabic DB bullet.") + ARABIC_PAREN_BOTH = (12, "arabicParenBoth", "Arabic bullet with opening and closing parentheses.") + ARABIC_PAREN_RIGHT = (2, "arabicParenRight", "Arabic bullet with closing parenthesis.") + ARABIC_PERIOD = (3, "arabicPeriod", "Arabic bullet with period.") + ARABIC_PLAIN = (13, "arabicPlain", "Plain Arabic bullet.") + CIRCLE_NUM_DB_PLAIN = (18, "circleNumDBPlain", "Circled number bullet.") + CIRCLE_NUM_WD_BLACK_PLAIN = (20, "circleNumWDBlackPlain", "Circled number WD black bullet.") + CIRCLE_NUM_WD_WHITE_PLAIN = (19, "circleNumWDWhitePlain", "Circled number WD white bullet.") + HEBREW_ALPHA_DASH = (25, "hebrewAlphaDash", "Hebrew alphabetical bullet with dash.") + HINDI_ALPHA1_PERIOD = (40, "hindiAlpha1Period", "Hindi alphabetical bullet 1 with period.") + HINDI_ALPHA_PERIOD = (36, "hindiAlphaPeriod", "Hindi alphabetical bullet with period.") + HINDI_NUM_PAREN_RIGHT = (39, "hindiNumParenRight", "Hindi numbered bullet with closing parenthesis.") + HINDI_NUM_PERIOD = (37, "hindiNumPeriod", "Hindi numbered bullet with period.") + KANJI_KOREAN_PERIOD = (27, "kanjiKoreanPeriod", "Korean Kanji bullet with period.") + KANJI_KOREAN_PLAIN = (26, "kanjiKoreanPlain", "Korean Kanji bullet.") + KANJI_SIMP_CHIN_DB_PERIOD = (38, "kanjiSimpChinDBPeriod", "Simplified Chinese Kanji bulllet with period.") + ROMAN_LC_PAREN_BOTH = (4, "romanLCParenBoth", "Lowercase roman bullet with opening and closing parentheses.") + ROMAN_LC_PAREN_RIGHT = (5, "romanLCParenRight", "Lowercase roman bullet with closing parenthesis.") + ROMAN_LC_PERIOD = (6, "romanLCPeriod", "Lowercase roman bullet with period.") + ROMAN_UC_PAREN_BOTH = (14, "romanUCParenBoth", "Uppercase roman bullet with opening and closing parentheses.") + ROMAN_UC_PAREN_RIGHT = (15, "romanUCParenRight", "Uppercase roman bullet with closing parenthesis.") + ROMAN_UC_PERIOD = (7, "romanUCPeriod", "Uppercase roman bullet with period.") + SIMP_CHIN_PERIOD = (17, "simpChinPeriod", "Simplified Chinese bulllet with period.") + SIMP_CHIN_PLAIN = (16, "simpChinPlain", "Simplified Chinese bullet.") + STYLE_MIXED = (-2, "styleMixed", "Return value only; indicates a combination of the other states. ") + THAI_ALPHA_PAREN_BOTH = (32, "thaiAlphaParenBoth", "Thai alphabetical bullet with opening and closing parentheses.") + THAI_ALPHA_PAREN_RIGHT = (31, "thaiAlphaParenRight", "Thai alphabetical bullet with closing parenthesis.") + THAI_ALPHA_PERIOD = (30, "thaiAlphaPeriod", "Thai alphabetical bullet with period.") + THAI_NUM_PAREN_BOTH = (35, "thaiNumParenBoth", "Thai numerical bullet with opening and closing parentheses.") + THAI_NUM_PAREN_RIGHT = (34, "thaiNumParenRight", "Thai numerical bullet with closing parenthesis.") + THAI_NUM_PERIOD = (33, "thaiNumPeriod", "Thai numerical bullet with period.") + TRAD_CHIN_PERIOD = (22, "tradChinPeriod", "Traditional Chinese bulllet with period.") + TRAD_CHIN_PLAIN = (21, "tradChinPlain", "Traditional Chinese bulllet.") diff --git a/src/pptx/oxml/__init__.py b/src/pptx/oxml/__init__.py index 21afaa921..c2cf3b151 100644 --- a/src/pptx/oxml/__init__.py +++ b/src/pptx/oxml/__init__.py @@ -445,6 +445,7 @@ def register_element_cls(nsptagname: str, cls: Type[BaseOxmlElement]): from pptx.oxml.text import ( # noqa: E402 CT_RegularTextRun, + CT_TextAutoNumberBullet, CT_TextBody, CT_TextBodyProperties, CT_TextCharacterProperties, @@ -457,6 +458,8 @@ def register_element_cls(nsptagname: str, cls: Type[BaseOxmlElement]): CT_TextSpacing, CT_TextSpacingPercent, CT_TextSpacingPoint, + CT_TextNoBullet, + CT_TextCharBullet, ) register_element_cls("a:bodyPr", CT_TextBodyProperties) @@ -476,6 +479,9 @@ def register_element_cls(nsptagname: str, cls: Type[BaseOxmlElement]): register_element_cls("a:spcBef", CT_TextSpacing) register_element_cls("a:spcPct", CT_TextSpacingPercent) register_element_cls("a:spcPts", CT_TextSpacingPoint) +register_element_cls('a:buNone', CT_TextNoBullet) +register_element_cls('a:buAutoNum', CT_TextAutoNumberBullet) +register_element_cls('a:buChar', CT_TextCharBullet) register_element_cls("a:txBody", CT_TextBody) register_element_cls("c:txPr", CT_TextBody) register_element_cls("p:txBody", CT_TextBody) diff --git a/src/pptx/oxml/text.py b/src/pptx/oxml/text.py index 0f9ecc152..c6354f0e3 100644 --- a/src/pptx/oxml/text.py +++ b/src/pptx/oxml/text.py @@ -8,9 +8,11 @@ from pptx.enum.lang import MSO_LANGUAGE_ID from pptx.enum.text import ( MSO_AUTO_SIZE, + MSO_NUMBERED_BULLET_STYLE, MSO_TEXT_UNDERLINE_TYPE, MSO_VERTICAL_ANCHOR, PP_PARAGRAPH_ALIGNMENT, + BulletStyleType, ) from pptx.exc import InvalidXmlError from pptx.oxml import parse_xml @@ -26,6 +28,7 @@ ST_TextTypeface, ST_TextWrappingType, XsdBoolean, + XsdString, ) from pptx.oxml.xmlchemy import ( BaseOxmlElement, @@ -38,7 +41,7 @@ ZeroOrOne, ZeroOrOneChoice, ) -from pptx.util import Emu, Length +from pptx.util import BulletStyle, Emu, Length if TYPE_CHECKING: from pptx.oxml.action import CT_Hyperlink @@ -466,9 +469,15 @@ class CT_TextParagraphProperties(BaseOxmlElement): _add_lnSpc: Callable[[], CT_TextSpacing] _add_spcAft: Callable[[], CT_TextSpacing] _add_spcBef: Callable[[], CT_TextSpacing] + _add_buNone: Callable[[], CT_TextNoBullet] + _add_buAutoNum: Callable[[], CT_TextAutoNumberBullet] + _add_buChar: Callable[[], CT_TextCharBullet] _remove_lnSpc: Callable[[], None] _remove_spcAft: Callable[[], None] _remove_spcBef: Callable[[], None] + _remove_buNone: Callable[[], None] + _remove_buAutoNum: Callable[[], CT_TextAutoNumberBullet] + _remove_buChar: Callable[[], None] _tag_seq = ( "a:lnSpc", @@ -501,6 +510,15 @@ class CT_TextParagraphProperties(BaseOxmlElement): defRPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] "a:defRPr", successors=_tag_seq[16:] ) + buNone: CT_TextNoBullet | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:buNone", successors=_tag_seq[11:] + ) + buAutoNum: CT_TextAutoNumberBullet | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:buAutoNum", successors=_tag_seq[12:] + ) + buChar: CT_TextCharBullet | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:buChar", successors=_tag_seq[13:] + ) lvl: int = OptionalAttribute( # pyright: ignore[reportAssignmentType] "lvl", ST_TextIndentLevelType, default=0 ) @@ -534,6 +552,40 @@ def line_spacing(self, value: float | Length | None): else: self._add_lnSpc().set_spcPct(value) + @property + def bullet(self) -> BulletStyle: + """The style of bullet used for this paragraph.""" + buNone = self.buNone + buChar = self.buChar + buAutoNum = self.buAutoNum + + if buChar is not None: + return BulletStyle.custom(buChar.char) + elif buAutoNum is not None: + return BulletStyle.numbered(buAutoNum.val) + elif buNone is not None: + return BulletStyle.NO_BULLET + else: + return BulletStyle.DEFAULT + + @bullet.setter + def bullet(self, value: BulletStyle): + self._remove_buNone() + self._remove_buChar() + self._remove_buAutoNum() + + if value == BulletStyle.DEFAULT: + return + elif value == BulletStyle.NO_BULLET: + self._add_buNone() + elif value.style == BulletStyleType.CUSTOM: + buChar = self._add_buChar() + buChar.char = cast(str, value.value) + elif value.style == BulletStyleType.NUMBERED: + buAutoNum = self._add_buAutoNum() + buAutoNum.val = cast(MSO_NUMBERED_BULLET_STYLE, value.value) + + @property def space_after(self) -> Length | None: """The EMU equivalent of the centipoints value in `./a:spcAft/a:spcPts/@val`.""" @@ -616,3 +668,31 @@ class CT_TextSpacingPoint(BaseOxmlElement): val: Length = RequiredAttribute( # pyright: ignore[reportAssignmentType] "val", ST_TextSpacingPoint ) + + +class CT_TextNoBullet(BaseOxmlElement): + """ + element, specifying that a paragraph should not be bulleted. + """ + pass + + +class CT_TextCharBullet(BaseOxmlElement): + """ + element, specifying that a paragraph should have a character bullet. + """ + + char: str = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "char", XsdString + ) + + +class CT_TextAutoNumberBullet(BaseOxmlElement): + """ + element, specifying that a paragraph should have an automatically + incremented bullet of the specified `type`. + """ + + val: MSO_NUMBERED_BULLET_STYLE = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "type", MSO_NUMBERED_BULLET_STYLE + ) diff --git a/src/pptx/text/text.py b/src/pptx/text/text.py index e139410c2..cd17f73cf 100644 --- a/src/pptx/text/text.py +++ b/src/pptx/text/text.py @@ -7,13 +7,13 @@ from pptx.dml.fill import FillFormat from pptx.enum.dml import MSO_FILL from pptx.enum.lang import MSO_LANGUAGE_ID -from pptx.enum.text import MSO_AUTO_SIZE, MSO_UNDERLINE, MSO_VERTICAL_ANCHOR +from pptx.enum.text import MSO_AUTO_SIZE, MSO_NUMBERED_BULLET_STYLE, MSO_UNDERLINE, MSO_VERTICAL_ANCHOR from pptx.opc.constants import RELATIONSHIP_TYPE as RT from pptx.oxml.simpletypes import ST_TextWrappingType from pptx.shapes import Subshape from pptx.text.fonts import FontFiles from pptx.text.layout import TextFitter -from pptx.util import Centipoints, Emu, Length, Pt, lazyproperty +from pptx.util import BulletStyle, Centipoints, Emu, Length, Pt, lazyproperty if TYPE_CHECKING: from pptx.dml.color import ColorFormat @@ -547,6 +547,28 @@ def line_spacing(self, value: int | float | Length | None): pPr = self._p.get_or_add_pPr() pPr.line_spacing = value + @property + def bullet(self) -> BulletStyle: + """The type of bullet, if any, defined for this paragraph. + + ``BulletStyle.NO_BULLET`` indicates that bullets are explicitly disabled + a paragraph. ``BulletStyle.DEFAULT`` indicates that whether the paragraph + is rendered as a bullet is defined in the slide master or layout. + + The methods ``BulletStyle.custom`` and ``BulletStyle.numbered`` can be + used to create ``BulletStyle``s that control what kind of bullet is used + for the paragraph. + """ + pPr = self._p.pPr + if pPr is None: + return BulletStyle.DEFAULT + return pPr.bullet + + @bullet.setter + def bullet(self, value: BulletStyle): + pPr = self._p.get_or_add_pPr() + pPr.bullet = value + @property def runs(self) -> tuple[_Run, ...]: """Sequence of runs in this paragraph.""" diff --git a/src/pptx/util.py b/src/pptx/util.py index fdec79298..8887fcb20 100644 --- a/src/pptx/util.py +++ b/src/pptx/util.py @@ -5,6 +5,8 @@ import functools from typing import Any, Callable, Generic, TypeVar, cast +from pptx.enum.text import MSO_NUMBERED_BULLET_STYLE, BulletStyleType + class Length(int): """Base class for length classes Inches, Emu, Cm, Mm, and Pt. @@ -102,6 +104,57 @@ def __new__(cls, points: float): return Length.__new__(cls, emu) +class BulletStyle: + """Convenience value class for styling unnumbered bullets. + + ``BulletStyle.NO_BULLET`` indicates that bullets are explicitly disabled + a paragraph. ``BulletStyle.DEFAULT`` indicates that whether the paragraph + is rendered as a bullet is defined in the slide master or layout. + + The methods ``BulletStyle.custom`` and ``BulletStyle.numbered`` can be + used to create ``BulletStyle``s that control what kind of bullet is used + for the paragraph. + """ + + NO_BULLET: BulletStyle = None + DEFAULT: BulletStyle = None + + def __init__(self, style: BulletStyleType, value: str | MSO_NUMBERED_BULLET_STYLE | None = None): + self._style = style + self._value = value + + def __eq__(self, other: object): + if isinstance(other, BulletStyle) and self._style == other._style: + return self._value == other._value + else: + return False + + @property + def value(self) -> str | MSO_NUMBERED_BULLET_STYLE | None: + return self._value + + @property + def style(self) -> BulletStyleType: + return self._style + + @classmethod + def custom(cls, bullet_string: str): + """Defines a bullet that is rendered as ``bullet_string``.""" + return BulletStyle(BulletStyleType.CUSTOM, bullet_string) + + @classmethod + def numbered(cls, style: MSO_NUMBERED_BULLET_STYLE): + """Defines a bullet that is numbered. + + The style of the enumeration is controlled by the ``style`` and + is a ``MSO_NUMBERED_BULLET_STYLE``. + """ + return BulletStyle(BulletStyleType.NUMBERED, style) + +BulletStyle.NO_BULLET = BulletStyle(BulletStyleType.NO_BULLET) +BulletStyle.DEFAULT = BulletStyle(BulletStyleType.DEFAULT) + + _T = TypeVar("_T") diff --git a/tests/text/test_text.py b/tests/text/test_text.py index 3a1a7a0bb..e90dc6205 100644 --- a/tests/text/test_text.py +++ b/tests/text/test_text.py @@ -11,12 +11,12 @@ from pptx.dml.color import ColorFormat from pptx.dml.fill import FillFormat from pptx.enum.lang import MSO_LANGUAGE_ID -from pptx.enum.text import MSO_ANCHOR, MSO_AUTO_SIZE, MSO_UNDERLINE, PP_ALIGN +from pptx.enum.text import MSO_ANCHOR, MSO_AUTO_SIZE, MSO_NUMBERED_BULLET_STYLE, MSO_UNDERLINE, PP_ALIGN from pptx.opc.constants import RELATIONSHIP_TYPE as RT from pptx.opc.package import XmlPart from pptx.shapes.autoshape import Shape from pptx.text.text import Font, TextFrame, _Hyperlink, _Paragraph, _Run -from pptx.util import Inches, Pt +from pptx.util import BulletStyle, Inches, Pt from ..oxml.unitdata.text import a_p, a_t, an_hlinkClick, an_r, an_rPr from ..unitutil.cxml import element, xml @@ -870,6 +870,16 @@ def it_knows_what_text_it_contains(self, text_get_fixture): assert text == expected_value assert isinstance(text, str) + def it_can_change_its_bullet(self, bullet_set_fixture): + paragraph, new_value, expected_xml = bullet_set_fixture + paragraph.bullet = new_value + assert paragraph._element.xml == expected_xml + + def it_knows_its_bullet(self, bullet_get_fixture): + paragraph, expected_bullet = bullet_get_fixture + print(paragraph.bullet, expected_bullet) + assert paragraph.bullet == expected_bullet + @pytest.mark.parametrize( ("p_cxml", "value", "expected_cxml"), [ @@ -1119,6 +1129,47 @@ def text_get_fixture(self, request): p = element(p_cxml) return p, expected_value + + @pytest.fixture( + params=[ + ("a:p", BulletStyle.custom("x"), "a:p/a:pPr/a:buChar{char=x}"), + ("a:p/a:pPr/a:buChar", BulletStyle.custom("x"), "a:p/a:pPr/a:buChar{char=x}"), + ("a:p/a:pPr/a:buNone", BulletStyle.custom("x"), "a:p/a:pPr/a:buChar{char=x}"), + ("a:p/a:pPr/a:buAutoNum", BulletStyle.custom("x"), "a:p/a:pPr/a:buChar{char=x}"), + ("a:p", BulletStyle.NO_BULLET, "a:p/a:pPr/a:buNone"), + ("a:p/a:pPr/a:buChar", BulletStyle.NO_BULLET, "a:p/a:pPr/a:buNone"), + ("a:p/a:pPr/a:buNone", BulletStyle.NO_BULLET, "a:p/a:pPr/a:buNone"), + ("a:p/a:pPr/a:buAutoNum", BulletStyle.NO_BULLET, "a:p/a:pPr/a:buNone"), + ("a:p", BulletStyle.DEFAULT, "a:p/a:pPr"), + ("a:p/a:pPr/a:buNone", BulletStyle.DEFAULT, "a:p/a:pPr"), + ("a:p/a:pPr/a:buChar", BulletStyle.DEFAULT, "a:p/a:pPr"), + ("a:p/a:pPr/a:buAutoNum", BulletStyle.DEFAULT, "a:p/a:pPr"), + ("a:p", BulletStyle.numbered(MSO_NUMBERED_BULLET_STYLE.ROMAN_UC_PERIOD), "a:p/a:pPr/a:buAutoNum{type=romanUCPeriod}"), + ("a:p/a:pPr/a:buChar", BulletStyle.numbered(MSO_NUMBERED_BULLET_STYLE.ALPHA_LC_PERIOD), "a:p/a:pPr/a:buAutoNum{type=alphaLCPeriod}"), + ("a:p/a:pPr/a:buNone", BulletStyle.numbered(MSO_NUMBERED_BULLET_STYLE.ARABIC_ABJAD_DASH), "a:p/a:pPr/a:buAutoNum{type=arabicAbjadDash}"), + ("a:p/a:pPr/a:buAutoNum", BulletStyle.numbered(MSO_NUMBERED_BULLET_STYLE.TRAD_CHIN_PLAIN), "a:p/a:pPr/a:buAutoNum{type=tradChinPlain}"), + ] + ) + def bullet_set_fixture(self, request): + p_cxml, new_value, expected_p_cxml = request.param + paragraph = _Paragraph(element(p_cxml), None) + expected_xml = xml(expected_p_cxml) + return paragraph, new_value, expected_xml + + @pytest.fixture( + params=[ + ("a:p", BulletStyle.DEFAULT), + ("a:p/a:pPr", BulletStyle.DEFAULT), + ("a:p/a:pPr/a:buNone", BulletStyle.NO_BULLET), + ("a:p/a:pPr/a:buChar{char=x}", BulletStyle.custom("x")), + ("a:p/a:pPr/a:buAutoNum{type=romanUCPeriod}", BulletStyle.numbered(MSO_NUMBERED_BULLET_STYLE.ROMAN_UC_PERIOD)), + ] + ) + def bullet_get_fixture(self, request): + p_cxml, expected_bullet = request.param + paragraph = _Paragraph(element(p_cxml), None) + return paragraph, expected_bullet + # fixture components ----------------------------------- @pytest.fixture diff --git a/tests/unitutil/cxml.py b/tests/unitutil/cxml.py index 79e217c20..038cbe021 100644 --- a/tests/unitutil/cxml.py +++ b/tests/unitutil/cxml.py @@ -252,7 +252,7 @@ def grammar(): # np:attr_name=attr_val ---------------------- attr_name = Word(alphas + ":") - attr_val = Word(alphanums + " %-./:_") + attr_val = Word(alphanums + u" %-./:_\u2022") attr_def = Group(attr_name + equal + attr_val) attr_list = open_brace + delimitedList(attr_def) + close_brace