Enumerations

Overview

A large number of settings in PowerPoint are a selection between a small, finite set of discrete choices. Often, in the XML, these are represented by a simple type defined as an enumeration of a set of short string values. This page describes the strategy for providing access to these sets of values in the various parts of the library that require them.

Notes

  • The general strategy is to provide a set of classes, one for each enumeration, that serves as the single source for all reference and behavior for that enumeration.

  • These enumerations correspond one-to-one with Microsoft API enumerations.

  • The naming is based on the Microsoft enumeration. .. all-caps snake case from the original mixed-case.

Definitions

member

What constitutes a member of an enumeration? What attributes do all members have in common?

out-of-band member

Is there such a thing? Or is there just an out-of-band value?

get-value

… value returned to indicate the current state of a property

set-value

… value passed to indicate the desired state of a property

Feature requirements

Design principle: All required values and mappings are defined in the enumeration class. All required names, data structures, and methods are generated by the metaclass. They don’t have to be generated manually.

  • [X] feature: dotted name access

    enumeration values can be accessed from an enumeration class with dotted notation, e.g. MSO_FOO.BAR_BAZ

  • [X] feature: enumeration value behaves as an int

  • [X] feature: __str__ value of enum member is the member name and int value

    In order to provide the developer with a directly readable name for an enumeration value (rather than a raw int), each enum values shall have a __str__ method that returns the member name. E.g.:

    >>> print("text_frame.auto_size is '%s'" % text_frame.auto_size)
    text_frame.auto_size is 'SHAPE_TO_FIT_TEXT (2)'
    
  • [X] feature: out-of-band values – like None, True, and False

    From time to time it is desireable to allow return and setting values that are not named members of the enumeration to provide a more Pythonic protocol for a property that uses the enumeration. For example:

    >>> run.underline
    None
    >>> run.underline = True
    >>> str(run.underline)
    'SINGLE'
    >>> run.underline = False
    >>> str(run.underline)
    'NONE'
    >>> run.underline = WD_UNDERLINE.DOUBLE
    >>> str(run.underline)
    'DOUBLE'
    
  • [X] feature: alias decorator

    one or more convenience aliases can be declared for an Enumeration subclass by use of an @alias decorator

  • [X] feature: setting validation

    In order to allow invalid input values to raise ValueError exceptions at the API method level, the enumeration shall provide an is_valid_setting() class method that returns False if the value passed as an argument is not a member of the enumeration. E.g.:

    assert MSO_AUTO_SIZE.is_member('foobar') is False
    
  • [X] feature: to_XML translation

    • [X] issue: Not all enumerations have mappings to XML attribute values.

      Consider creating an XmlEnumeration subclass that provides this extra feature.

    • [X] issue: There exist ‘return-only’ enumeration values, out-of-band

      values used to indicate none of the other values apply, for example MIXED = -2 to indicate the call was made on a range that contains items not all having the same value.

      To provide a single authoritative mapping from enumeration member values and the XML attribute values they correspond to, the Enumeration class shall provide a to_xml(member_value) method that returns the XML value corresponding to the passed member value.

      The method shall raise ValueError if the passed value is not a member of the enumeration.

      The special XML value None indicates the attribute should be removed altogether and should be returned where that is the appropriate behavior.

      Where the Python value is an out-of-band value such as None, True, or False, there must be a valid XML value or None as the mapping.

  • [X] feature: docstrings

    To provide built-in documentation for different options and to provide a source for option descriptions for a generated documentation page, each enumeration member shall have as its docstring a description of the member and the behavior that setting specifies in its context.

  • [X] feature: auto-generate documentation

    • [X] add __ms_name__ var => ‘MsoAutoSize’

    • [X] add __url__ var => ‘http:// …’

      In order to provide single-source for both code and documentation, the Enumeration class shall provide a documentation page generation method that generates Sphinx RST source for a documentation page for the enumeration. This page source shall be available as either the docstring (__doc__) value of the class or its __str__ value. The supplied docstring shall be pre-pended to the generated portion after the top matter such as page title and reference link are generated.

  • [X] convert all existing enums to new type to make sure they all work

    • [X] MSO_AUTO_SIZE

    • [X] MSO_VERTICAL_ANCHOR (__init__ -> enum.text)

    • [X] MSO_FILL_TYPE (__init__ -> enum.dml)

    • [X] MSO_COLOR_TYPE (__init__ -> enum.dml)

    • [X] others …

    • [>] PP_PARAGRAPH_ALIGNMENT (constants -> enum.text)

    • [X] TEXT_ALIGN_TYPE (constants -> delete)

    • [X] TEXT_ANCHORING_TYPE (constants -> delete)

    • [X] MSO_SHAPE_TYPE (constants.MSO -> enum.shapes.MSO_SHAPE_TYPE)

    • [X] MSO.ANCHOR_* (constants -> MSO_VERTICAL_ANCHOR)

    • [X] PP_PLACEHOLDER_TYPE: PH_TYPE_* in spec.py PpPlaceholderType

    • [X] ST_Direction: PH_ORIENT_*

    • [X] ST_PlaceholderSize: PH_SZ_*

    • [X] MSO_AUTO_SHAPE_TYPE (constants -> enum.shapes)

    • [X] What is pml_parttypes doing in spec? I thought that went a long time

      ago? must be dead code now.

    • [X] fix all enum references in documentation

    • [X] adjust all examples that use enum or constants

    • [X] spec: pretty sure default content types belong in opc somewhere

  • [ ] … starting to think that combining to_xml() and from_xml() into enums

    is not the best idea:

    • [ ] There are two separate concerns: (1) provide a symbolic reference for

      use in the API to specify an enumerated option. (2) provide one of potentially several mappings between that value and values in different domains.

    • [ ] could create custom Enumeration subclasses to accommodate special

      cases where there are mappings beyond to and from an XML attribute value.

    • [ ] could create value objects that perhaps encapsulate those mappings.

      I suppose that’s what Enumeration is now.

  • [X] issue: does to/from XML translation replace an ST_* class in oxml?

    (Probably can’t fully replace, need validation and other bits as well. Might be best to wait until a few examples have accumulated.)

    In order to support DRY translation between enumeration values and XML simple type (ST_*) attribute values, the Enumeration class shall provide both to_xml(enum_value) and from_xml(attr_value) methods where enumeration values map to XML attribute values.

  • [ ] issue: how handle case where more than one XML to name mapping is

    possible? Like both True and SINGLE set underline to ‘single’? Where if anywhere does that cause a conflict?

    The other direction of this is when more than one XML value should map to None.

  • [ ] issue: how add clsdict._xml_to_member dict unconditionally to

    subclasses of XmlEnumeration? use __init__ instead of __new__ for that part:

    def __init__(cls, clsname, bases, clsdict):
        cls._collect_valid_settings = []
    
    • init time might be too late, no handy ref to metaclass?

    • could catch __new__ on separate XmlMappedEnumMember metaclass and add the attribute(s) then.

  • [ ] deprecate prior versions:

    • [ ] MSO_VERTICAL_ANCHOR from enum/__init__.py

  • [ ] consider adding a feature whereby aliases can be defined for an enum

    member name

  • [ ] consider what requirements an ST_* enumeration might have, aside from

    or in addition to an API enumeration

  • [ ] need a page or whatever in the user guide to talk about how fills are

    applied and give example code

  • [ ] how could enumeration documentation be generated automatically from

    enumerations as part of the Sphinx run?

Code Sketches

enabling an @alias decorator to define aliases for an enumeration class:

#!/usr/bin/env python

from __future__ import absolute_import, print_function


def alias(*aliases):
    """
    Decorating a class with @alias('FOO', 'BAR', ..) allows the class to
    be referenced by each of the names provided as arguments.
    """
    def decorator(cls):
        for alias in aliases:
            globals()[alias] = cls
        return cls
    return decorator


@alias('BAR', 'BAZ')
class FOO(object):
    BAR = 'Foobarish'


print("FOO.BAR => '%s'" % FOO.BAR)
print("BAR.BAR => '%s'" % BAR.BAR)  # noqa
print("BAZ.BAR => '%s'" % BAZ.BAR)  # noqa