def transition(slide, spec: Union[str, dict], data: dict): ''' Apply transition on slide based on spec. python-pptx does not have this feature, so we tweak XML directly. :arg Slide slide: slide to apply the transition to :arg dict/str spec: type of transition to apply. ``config.yaml`` lists transitions and options. It can also be a dict specifying ``type`` (type of transition), ``duration`` (in seconds) and ``advance`` (auto-advance after seconds) :arg dict data: data context for the ``spec`` expression ''' if spec is None: return # Convert spec into this format: {type: ..., advance: ..., duration: ...} if isinstance(spec, str): spec = {'type': spec} if not isinstance(spec, dict): raise ValueError('transition: %r is not a str or dict' % spec) # conf is from config.yaml, and has all transition types and options conf = commands.conf type = commands.expr(spec.get('type', None), data) if type is not None: # Parse the type into OXML: "glitter diamond left" -> <glitter pattern="diamond" dir="r"/> tag, *options = type.split() attrs = {} if tag in conf['transition-alias']: attrs.update(conf['transition-alias'][tag]) tag = attrs.pop('tag') if tag not in conf['transition']: raise ValueError('transition.type: %s is an unknown transition' % type) trans = conf['transition'][tag] options = trans['default'] if (not options and 'default' in trans) else options for option in options: if option not in trans: raise ValueError( 'transition.type: "%s" has invalid option %s' % (type, option)) attrs.update(trans[option]) # Remove existing transition el = slide.element.find(qn('mc:AlternateContent')) if el is not None: slide.element.remove(el) # Add transition OXML # TODO: fails on slides with equations, zoom, or any other mc:alternateContent if tag != 'none': ns = trans.get('ns', 'p') attrs = ' '.join('%s="%s"' % (k, v) for k, v in attrs.items()) xml = conf['transition-tmpl'][ns].format(tag=tag, attrs=attrs) el = parse_xml(xml)[0] slide.element.append(el) # Add attributes for duration: and advance: for key, attr in (('duration', qn('p14:dur')), ('advance', 'advTm')): val = commands.expr(spec.get(key, None), data) if val is not None: trans = slide.element.find( 'mc:AlternateContent/mc:Choice/p:transition', _nsmap) if trans is not None: trans.set(attr, '%s' % int(float(val) * 1000))
def iter_shape_elms(self): """ Generate each child of this ``<p:spTree>`` element that corresponds to a shape, in the sequence they appear in the XML. """ for elm in self.iterchildren(): if elm.tag in self._shape_tags: yield elm if elm.tag == qn("ve:AlternateContent"): choice = elm.find(qn("ve:Choice")) if choice != None: for content in choice.iterchildren(): #Choose first, among proper items if content.tag in self._shape_tags: yield content break else: fallback = elm.find(qn("ve:Fallback")) if fallback != None: for content in fallback.iterchildren(): # Choose first, among proper items if content.tag in self._shape_tags: yield content break
def set_font(run, font_name): run.font.name = font_name if run.font._rPr.find(qn('a:ea')) is not None: run.font._rPr.find(qn('a:ea')).set('typeface', run.font.name) else: element = run.font._rPr.makeelement(qn('a:ea')) element.set('typeface', run.font.name) run.font._rPr.append(element)
def clear_lum(self): """ Return self after removing any <a:lumMod> and <a:lumOff> child elements. """ lum_tagnames = (qn('a:lumMod'), qn('a:lumOff')) for child in self.getchildren(): if child.tag in lum_tagnames: self.remove(child) return self
def set_text_frame_font(text_frame): for paragraph in text_frame.paragraphs: for run in paragraph.runs: if run.font.name in fonts_to_be_replaced: run.font.name = fonts_to_be_replaced[run.font.name] if run.font._rPr.find(qn('a:ea')) is not None: run.font._rPr.find(qn('a:ea')).set('typeface', run.font.name) else: element = run.font._rPr.makeelement(qn('a:ea')) element.set('typeface', run.font.name) run.font._rPr.append(element) elif run.font.name is None: run.font.name = '思源黑体'
def fill_opacity(fill: FillFormat, val: Union[int, float, None]) -> None: '''Set the FillFormat opacity to a number''' if fill.type != MSO_FILL.SOLID: raise ValueError('Cannot set opacity: %r on non-solid fill type %r' % (val, fill.type)) for tag in ('hslClr', 'sysClr', 'srgbClr', 'prstClr', 'scrgbClr', 'schemeClr'): color = fill._xPr.find('.//' + qn('a:%s' % tag)) if color is not None: alpha = color.find(qn('a:alpha')) if alpha is None: alpha = OxmlElement('a:alpha') color.append(alpha) alpha.set('val', ST_Percentage.convert_to_xml(val)) break
def _get_str_prop(self, name): """Return string value of *name* property.""" # explicit class reference avoids another pass through getattribute tag = qn(CT_CoreProperties._str_tags[name]) if not hasattr(self, tag): return '' return getattr(self, tag).text
def new_placeholder_sp(id_, name, ph_type, orient, sz, idx): """ Return a new ``<p:sp>`` element tree configured as a placeholder shape. """ xml = CT_Shape._ph_sp_tmpl % (id_, name) sp = parse_xml_bytes(xml) # placeholder shapes get a "no group" lock SubElement(sp.nvSpPr.cNvSpPr, 'a:spLocks') sp.nvSpPr.cNvSpPr[qn('a:spLocks')].set('noGrp', '1') # placeholder (ph) element attributes values vary by type ph = SubElement(sp.nvSpPr.nvPr, 'p:ph') if ph_type != PH_TYPE_OBJ: ph.set('type', ph_type) if orient != PH_ORIENT_HORZ: ph.set('orient', orient) if sz != PH_SZ_FULL: ph.set('sz', sz) if idx != 0: ph.set('idx', str(idx)) placeholder_types_that_have_a_text_frame = ( PH_TYPE_TITLE, PH_TYPE_CTRTITLE, PH_TYPE_SUBTITLE, PH_TYPE_BODY, PH_TYPE_OBJ) if ph_type in placeholder_types_that_have_a_text_frame: sp.append(CT_TextBody.new_txBody()) objectify.deannotate(sp, cleanup_namespaces=True) return sp
def paragraphs(self): """ Immutable sequence of |_Paragraph| instances corresponding to the paragraphs in this text frame. A text frame always contains at least one paragraph. """ return tuple([_Paragraph(p, self) for p in self._txBody[qn('a:p')]])
def notify_height_changed(self): """ Called by a row when its height changes, triggering the graphic frame to recalculate its total height (as the sum of the row heights). """ new_table_height = sum([row.height for row in self.rows]) self._graphicFrame.xfrm[qn('a:ext')].set('cy', str(new_table_height))
def add_sldId(self, rId): """ Return a reference to a newly created <p:sldId> child element having its r:id attribute set to *rId*. """ sldId = SubElement(self, 'p:sldId', id=self._next_id) sldId.set(qn('r:id'), rId) return sldId
def offset(self, value): if self._element.tag != qn("c:catAx"): raise ValueError("only a category axis has an offset") self._element._remove_lblOffset() if value == 100: return lblOffset = self._element._add_lblOffset() lblOffset.val = value
def notify_width_changed(self): """ Called by a column when its width changes, triggering the graphic frame to recalculate its total width (as the sum of the column widths). """ new_table_width = sum([col.width for col in self.columns]) self._graphicFrame.xfrm[qn('a:ext')].set('cx', str(new_table_width))
def spPr(self): """ The required <a:spPr> child element, raises if not present. """ spPr = self.find(qn('p:spPr')) if spPr is None: # TODO: this should be ValidationError, not ValueError raise ValueError("pic element missing required spPr child") return spPr
def _NotesSlideShapeFactory(shape_elm, parent): """ Return an instance of the appropriate shape proxy class for *shape_elm* on a notes slide. """ tag_name = shape_elm.tag if tag_name == qn("p:sp") and shape_elm.has_ph_elm: return NotesSlidePlaceholder(shape_elm, parent) return BaseShapeFactory(shape_elm, parent)
def remove_child_r_elms(self): """ Return self after removing all <a:r> child elements. """ children = self.getchildren() for child in children: if child.tag == qn("a:r"): self.remove(child) return self
def _MasterShapeFactory(shape_elm, parent): """ Return an instance of the appropriate shape proxy class for *shape_elm* on a slide master. """ tag_name = shape_elm.tag if tag_name == qn('p:sp') and shape_elm.has_ph_elm: return MasterPlaceholder(shape_elm, parent) return BaseShapeFactory(shape_elm, parent)
def set_tooltip(type, prs, slide, shape, val): if val is None: return link = get_or_add_link(shape, type, 'hlinkClick') rid, action = link.get(qn('r:id'), None), link.get('action', None) # Note: link was just created, or has no action, we can't set a tooltip. So create a next: link if rid is None or action is conf['link-action']['noaction']: link = set_link(type, 'hlinkClick', prs, slide, shape, 'next') link.set('tooltip', val)
def BaseShapeFactory(shape_elm, parent): """ Return an instance of the appropriate shape proxy class for *shape_elm*. """ tag = shape_elm.tag if tag == qn('p:pic'): videoFiles = shape_elm.xpath('./p:nvPicPr/p:nvPr/a:videoFile') if videoFiles: return Movie(shape_elm, parent) return Picture(shape_elm, parent) shape_cls = { qn('p:cxnSp'): Connector, qn('p:sp'): Shape, qn('p:graphicFrame'): GraphicFrame, }.get(tag, BaseShape) return shape_cls(shape_elm, parent)
def _set_revision(self, value): """Set integer value of revision property to *value*""" if not isinstance(value, int) or value < 1: tmpl = "revision property requires positive int, got '%s'" raise ValueError(tmpl % value) tag = qn('cp:revision') setattr(self, tag, str(value)) # objectify will leave in a py: namespace without this cleanup elm = getattr(self, tag) objectify.deannotate(elm, cleanup_namespaces=True)
def _SlidePlaceholderFactory(shape_elm, parent): """ Return a placeholder shape of the appropriate type for *shape_elm*. """ tag = shape_elm.tag if tag == qn("p:sp"): Constructor = { PP_PLACEHOLDER.BITMAP: PicturePlaceholder, PP_PLACEHOLDER.CHART: ChartPlaceholder, PP_PLACEHOLDER.PICTURE: PicturePlaceholder, PP_PLACEHOLDER.TABLE: TablePlaceholder, }.get(shape_elm.ph_type, SlidePlaceholder) elif tag == qn("p:graphicFrame"): Constructor = PlaceholderGraphicFrame elif tag == qn("p:pic"): Constructor = PlaceholderPicture else: Constructor = BaseShapeFactory return Constructor(shape_elm, parent)
def _first_successor_in(self, *successor_tagnames): """ Return the first child with tag in *successor_tagnames*, or None if not found. """ for tagname in successor_tagnames: successor = self.find(qn(tagname)) if successor is not None: return successor return None
def _first_child_found_in(self, *tagnames): """ Return the first child found with tag in *tagnames*, or None if not found. """ for tagname in tagnames: child = self.find(qn(tagname)) if child is not None: return child return None
def get_or_add_link(shape, type, event): # el is the lxml element of the shape. Can be ._element or ._run el = getattr(shape, conf['link-attrs'][type]['el']) # parent is the container inside which we insert the link. Can be p:cNvPr or a:rPr parent = el.find('.//' + conf['link-attrs'][type]['tag'], _nsmap) # Create link if required. (Else preserve all attributes one existing link, like tooltip) link = parent.find(qn('a:%s' % event)) if link is None: link = OxmlElement('a:%s' % event) parent.insert(0, link) return link
def _add_solidFill(self): """ Return a newly added <a:solidFill> child element. """ solidFill = Element("a:solidFill") ln = self.find(qn("a:ln")) if ln is not None: self.insert(1, solidFill) else: self.insert(0, solidFill) return solidFill
def _resize(elements, n): '''Ensure that a tr, tc or gridCol list has n children by cloning or deleting last element''' for index in range(len(elements), n, -1): elements[index - 1].delete() for index in range(len(elements), n): last_element = copy.deepcopy(elements[-1]) # When copying the last element, remove the a:extLst. This may have a rowId and colId # Note: Anand isn't sure if this removes any useful properties. See https://bit.ly/2ZYDw7B for ext_lst in last_element.findall('.//' + qn('a:extLst')): ext_lst.getparent().remove(ext_lst) elements[-1].addnext(last_element)
def BaseShapeFactory(shape_elm, parent): """ Return an instance of the appropriate shape proxy class for *shape_elm*. """ tag = shape_elm.tag if tag == qn("p:pic"): videoFiles = shape_elm.xpath("./p:nvPicPr/p:nvPr/a:videoFile") if videoFiles: return Movie(shape_elm, parent) return Picture(shape_elm, parent) shape_cls = { qn("p:cxnSp"): Connector, qn("p:grpSp"): GroupShape, qn("p:sp"): Shape, qn("p:graphicFrame"): GraphicFrame, }.get(tag, BaseShape) return shape_cls(shape_elm, parent)
def _set_str_prop(self, name, value): """Set string value of *name* property to *value*""" value = str(value) if len(value) > 255: tmpl = ("exceeded 255 char max length of property '%s', got:" "\n\n'%s'") raise ValueError(tmpl % (name, value)) tag = qn(CT_CoreProperties._str_tags[name]) setattr(self, tag, value) # objectify will leave in a py: namespace without this cleanup elm = getattr(self, tag) objectify.deannotate(elm, cleanup_namespaces=True)
def _set_date_prop(self, name, value): """Set datetime value of *name* property to *value*""" if not isinstance(value, datetime): tmpl = ("'%s' property requires <type 'datetime.datetime'> objec" "t, got %s") raise ValueError(tmpl % (name, type(value))) tagname = CT_CoreProperties._date_tags[name] tag = qn(tagname) dt_str = value.strftime('%Y-%m-%dT%H:%M:%SZ') setattr(self, tag, dt_str) # objectify will leave in a py: namespace without this cleanup elm = getattr(self, tag) objectify.deannotate(elm, cleanup_namespaces=True) if name in ('created', 'modified'): # these two require an explicit 'xsi:type' attribute # first and last line are a hack required to add the xsi # namespace to the root element rather than each child element # in which it is referenced self.set(qn('xsi:foo'), 'bar') self[tag].set(qn('xsi:type'), 'dcterms:W3CDTF') del self.attrib[qn('xsi:foo')]
def _add_noFill(self): """ Return a newly added <a:noFill> child element, assuming no other fill EG_FillProperties element is present. """ noFill = Element("a:noFill") ln = self.find(qn("a:ln")) if ln is not None: self.insert(1, noFill) else: self.insert(0, noFill) return noFill
def _clear_color_choice(self): """ Remove the EG_ColorChoice child element, e.g. <a:schemeClr>. """ eg_colorchoice_tagnames = ( 'a:scrgbClr', 'a:srgbClr', 'a:hslClr', 'a:sysClr', 'a:schemeClr', 'a:prstClr' ) for tagname in eg_colorchoice_tagnames: element = self.find(qn(tagname)) if element is not None: self.remove(element)
def _set_hlinkClick(self, value): """ For *value* is None, remove the ``<a:hlinkClick>`` child. For all other values, raise |ValueError|. """ if value is not None: tmpl = "only None can be assigned to optional element, got '%s'" raise ValueError(tmpl % value) # value is None ---------------- hlinkClick = self.find(qn("a:hlinkClick")) if hlinkClick is not None: self.remove(hlinkClick)
def set_link(type, event, prs, slide, shape, val): if val is None: return link = get_or_add_link(shape, type, event) # Set link's r:id= and action= based on the type of the link if val in conf['link-action']: # it's a ppaction:// rid = link.get(qn('r:id'), None) if rid in slide.part.rels: slide.part.drop_rel(rid) link.set(qn('r:id'), '') link.set('action', conf['link-action'][val]) elif isinstance(val, int) or val.isdigit(): # it's a slide slide_part = prs.slides[int(val) - 1].part link.set(qn('r:id'), slide.part.relate_to(slide_part, RT.SLIDE, False)) link.set('action', 'ppaction://hlinksldjump') elif urlparse(val).netloc: # it's a URL link.set(qn('r:id'), slide.part.relate_to(val, RT.HYPERLINK, True)) link.attrib.pop('action', '') elif os.path.splitext( val)[-1].lower() in conf['link-ppt-files']: # it's a PPT link.set(qn('r:id'), slide.part.relate_to(val, RT.HYPERLINK, True)) link.set('action', 'ppaction://hlinkpres?slideindex=1&slidetitle=') else: # it's a file link.set(qn('r:id'), slide.part.relate_to(val, RT.HYPERLINK, True)) link.set('action', 'ppaction://hlinkfile') return link
def flatten_group_transforms(shape, x=0, y=0, sx=1, sy=1): ''' Groups re-scale child shapes. So changes to size and position of child have unexpected results. To avoid this, ensure child rect (a:chOff, a:chExt) is same as group rect (a:off, a:ext). See https://stackoverflow.com/a/56485145/100904 ''' if isinstance(shape, pptx.shapes.group.GroupShape): # Get group and child rect shapes xfrm = shape.element.find(qn('p:grpSpPr')).find(qn('a:xfrm')) off, ext = xfrm.find(qn('a:off')), xfrm.find(qn('a:ext')) choff, chext = xfrm.find(qn('a:chOff')), xfrm.find(qn('a:chExt')) # Calculate how much the child rect is transformed from the group rect tsx = sx * ext.cx / chext.cx tsy = sy * ext.cy / chext.cy tx = x + sx * (off.x - choff.x * ext.cx / chext.cx) ty = y + sy * (off.y - choff.y * ext.cy / chext.cy) # Transform child shapes using (x, y, sx, sy) for subshape in shape.shapes: flatten_group_transforms(subshape, tx, ty, tsx, tsy) # Transform shape positions using (x, y, sx, sy). But get all values before setting. # Placeholders need this: https://stackoverflow.com/a/49306814/100904 left, top = int(shape.left * sx + x + 0.5), int(shape.top * sy + y + 0.5) width, height = int(shape.width * sx + 0.5), int(shape.height * sy + 0.5) shape.left, shape.top, shape.width, shape.height = left, top, width, height # Set child rect coords same as group rect (AFTER scaling the group) if isinstance(shape, pptx.shapes.group.GroupShape): choff.x, choff.y, chext.cx, chext.cy = off.x, off.y, ext.cx, ext.cy
def __getattr__(self, name): """ Override ``__getattr__`` defined in ObjectifiedElement super class to intercept messages intended for custom property setters. """ if name in ("b", "i"): return self._get_bool_attr(name) elif name == "eg_fillproperties": return self._eg_fillproperties() elif name == "hlinkClick": return self.find(qn("a:hlinkClick")) else: return super(CT_TextCharacterProperties, self).__getattr__(name)
def _get_date_prop(self, name): """Return datetime value of *name* property.""" # explicit class reference avoids another pass through getattribute tag = qn(CT_CoreProperties._date_tags[name]) # date properties return None when property element not present if not hasattr(self, tag): return None datetime_str = getattr(self, tag).text try: return self._parse_W3CDTF_to_datetime(datetime_str) except ValueError: # invalid datetime strings are ignored return None
def test_has_table_return_value(self): """CT_GraphicalObjectFrame.has_table property has correct value""" # setup ------------------------ id_, name = 9, 'Table 8' left, top, width, height = 111, 222, 333, 444 tbl_uri = 'http://schemas.openxmlformats.org/drawingml/2006/table' chart_uri = 'http://schemas.openxmlformats.org/drawingml/2006/chart' graphicFrame = CT_GraphicalObjectFrame.new_graphicFrame( id_, name, left, top, width, height) graphicData = graphicFrame[qn('a:graphic')].graphicData # verify ----------------------- graphicData.set('uri', tbl_uri) assert_that(graphicFrame.has_table, is_(equal_to(True))) graphicData.set('uri', chart_uri) assert_that(graphicFrame.has_table, is_(equal_to(False)))
def _get_revision(self): """Return integer value of revision property.""" tag = qn('cp:revision') # revision returns zero when element not present if not hasattr(self, tag): return 0 revision_str = getattr(self, tag).text try: revision = int(revision_str) except ValueError: # non-integer revision strings also resolve to 0 revision = 0 # as do negative integers if revision < 0: revision = 0 return revision
def new_table(id_, name, rows, cols, left, top, width, height): """ Return a ``<p:graphicFrame>`` element tree populated with a table element. """ graphicFrame = CT_GraphicalObjectFrame.new_graphicFrame( id_, name, left, top, width, height) # set type of contained graphic to table graphicData = graphicFrame[qn('a:graphic')].graphicData graphicData.set('uri', CT_GraphicalObjectFrame.DATATYPE_TABLE) # add tbl element tree tbl = CT_Table.new_tbl(rows, cols, width, height) graphicData.append(tbl) objectify.deannotate(graphicFrame, cleanup_namespaces=True) return graphicFrame
def recalculate_extents(self): """Adjust x, y, cx, and cy to incorporate all contained shapes. This would typically be called when a contained shape is added, removed, or its position or size updated. This method is recursive "upwards" since a change in a group shape can change the position and size of its containing group. """ if not self.tag == qn('p:grpSp'): return x, y, cx, cy = self._child_extents self.chOff.x = self.x = x self.chOff.y = self.y = y self.chExt.cx = self.cx = cx self.chExt.cy = self.cy = cy self.getparent().recalculate_extents()
def recalculate_extents(self): """Adjust x, y, cx, and cy to incorporate all contained shapes. This would typically be called when a contained shape is added, removed, or its position or size updated. This method is recursive "upwards" since a change in a group shape can change the position and size of its containing group. """ if not self.tag == qn("p:grpSp"): return x, y, cx, cy = self._child_extents self.chOff.x = self.x = x self.chOff.y = self.y = y self.chExt.cx = self.cx = cx self.chExt.cy = self.cy = cy self.getparent().recalculate_extents()
def _SeriesFactory(ser): """ Return an instance of the appropriate subclass of _BaseSeries based on the xChart element *ser* appears in. """ xChart_tag = ser.getparent().tag try: SeriesCls = { qn("c:areaChart"): AreaSeries, qn("c:barChart"): BarSeries, qn("c:bubbleChart"): BubbleSeries, qn("c:doughnutChart"): PieSeries, qn("c:lineChart"): LineSeries, qn("c:pieChart"): PieSeries, qn("c:radarChart"): RadarSeries, qn("c:scatterChart"): XySeries, }[xChart_tag] except KeyError: raise NotImplementedError("series class for %s not yet implemented" % xChart_tag) return SeriesCls(ser)
def add_hlinkClick(self, rId): """ Add an <a:hlinkClick> child element with r:id attribute set to *rId*. """ assert self.find(qn("a:hlinkClick")) is None hlinkClick = Element("a:hlinkClick", nsmap("a", "r")) hlinkClick.set(qn("r:id"), rId) # find right insertion spot, will go away once xmlchemy comes in if self.find(qn("a:hlinkMouseOver")): successor = self.find(qn("a:hlinkMouseOver")) successor.addprevious(hlinkClick) elif self.find(qn("a:rtl")): successor = self.find(qn("a:rtl")) successor.addprevious(hlinkClick) elif self.find(qn("a:extLst")): successor = self.find(qn("a:extLst")) successor.addprevious(hlinkClick) else: self.append(hlinkClick) return hlinkClick
def _SeriesFactory(ser): """ Return an instance of the appropriate subclass of _BaseSeries based on the xChart element *ser* appears in. """ xChart_tag = ser.getparent().tag try: SeriesCls = { qn("c:areaChart"): AreaSeries, qn("c:barChart"): BarSeries, qn("c:bubbleChart"): BubbleSeries, qn("c:doughnutChart"): PieSeries, qn("c:lineChart"): LineSeries, qn("c:pieChart"): PieSeries, qn("c:radarChart"): RadarSeries, qn("c:scatterChart"): XySeries, }[xChart_tag] except KeyError: raise NotImplementedError( "series class for %s not yet implemented" % xChart_tag ) return SeriesCls(ser)
def it_can_add_a_lumMod_child_element(self, schemeClr, schemeClr_with_lumMod_xml): lumMod = schemeClr.add_lumMod(0.75) assert schemeClr.xml == schemeClr_with_lumMod_xml assert schemeClr.find(qn("a:lumMod")) == lumMod
def it_can_add_a_lumMod_child_element( self, schemeClr, schemeClr_with_lumMod_xml): lumMod = schemeClr.add_lumMod(75000) assert schemeClr.xml == schemeClr_with_lumMod_xml assert schemeClr.find(qn('a:lumMod')) == lumMod
def it_can_add_a_lumOff_child_element( self, schemeClr, schemeClr_with_lumOff_xml): lumOff = schemeClr.add_lumOff(40000) assert schemeClr.xml == schemeClr_with_lumOff_xml assert schemeClr.find(qn('a:lumOff')) == lumOff
def bodyPr(self): return self[qn("a:bodyPr")]
def iter_fixture(self, request): tbl_cxml = request.param tbl = element(tbl_cxml) rows = _RowCollection(tbl, None) expected_row_lst = tbl.findall(qn('a:tr')) return rows, expected_row_lst
def it_can_add_a_alpha_child_element( self, schemeClr, schemeClr_with_alpha_xml): alpha = schemeClr.add_alpha(0.4) assert schemeClr.xml == schemeClr_with_alpha_xml assert schemeClr.find(qn('a:alpha')) == alpha
def it_creates_a_new_child_if_one_is_not_present(self, parent_elm): child_elm = get_or_add(parent_elm, 'p:baz') assert child_elm.tag == qn('p:baz') assert child_elm.getparent() is parent_elm
def it_returns_an_element_with_the_specified_tag(self, nsptag_str): elm = Element(nsptag_str) assert elm.tag == qn(nsptag_str)
def known_child_elm(parent_elm, known_child_nsptag_str): return parent_elm[qn(known_child_nsptag_str)]
def it_calculates_the_clark_name_for_an_ns_prefixed_tag_string( self, nsptag_str, clark_name): assert qn(nsptag_str) == clark_name
def iter_xCharts(self): """ Generate each xChart child element in document. """ plot_tags = ( qn("c:area3DChart"), qn("c:areaChart"), qn("c:bar3DChart"), qn("c:barChart"), qn("c:bubbleChart"), qn("c:doughnutChart"), qn("c:line3DChart"), qn("c:lineChart"), qn("c:ofPieChart"), qn("c:pie3DChart"), qn("c:pieChart"), qn("c:radarChart"), qn("c:scatterChart"), qn("c:stockChart"), qn("c:surface3DChart"), qn("c:surfaceChart"), ) for child in self.iterchildren(): if child.tag not in plot_tags: continue yield child
class CT_GroupShape(BaseShapeElement): """ Used for the shape tree (``<p:spTree>``) element as well as the group shape (``<p:grpSp>``) element. """ nvGrpSpPr = OneAndOnlyOne('p:nvGrpSpPr') grpSpPr = OneAndOnlyOne('p:grpSpPr') _shape_tags = (qn('p:sp'), qn('p:grpSp'), qn('p:graphicFrame'), qn('p:cxnSp'), qn('p:pic'), qn('p:contentPart')) def add_autoshape(self, id_, name, prst, x, y, cx, cy): """ Append a new ``<p:sp>`` shape to the group/shapetree having the properties specified in call. """ sp = CT_Shape.new_autoshape_sp(id_, name, prst, x, y, cx, cy) self.insert_element_before(sp, 'p:extLst') return sp def add_cxnSp(self, id_, name, type_member, x, y, cx, cy, flipH, flipV): """ Append a new ``<p:cxnSp>`` shape to the group/shapetree having the properties specified in call. """ prst = MSO_CONNECTOR_TYPE.to_xml(type_member) cxnSp = CT_Connector.new_cxnSp(id_, name, prst, x, y, cx, cy, flipH, flipV) self.insert_element_before(cxnSp, 'p:extLst') return cxnSp def add_freeform_sp(self, x, y, cx, cy): """Append a new freeform `p:sp` with specified position and size.""" shape_id = self._next_shape_id name = 'Freeform %d' % (shape_id - 1, ) sp = CT_Shape.new_freeform_sp(shape_id, name, x, y, cx, cy) self.insert_element_before(sp, 'p:extLst') return sp def add_grpSp(self): """Return `p:grpSp` element newly appended to this shape tree. The element contains no sub-shapes, is positioned at (0, 0), and has width and height of zero. """ shape_id = self._next_shape_id name = 'Group %d' % (shape_id - 1, ) grpSp = CT_GroupShape.new_grpSp(shape_id, name) self.insert_element_before(grpSp, 'p:extLst') return grpSp def add_pic(self, id_, name, desc, rId, x, y, cx, cy): """ Append a ``<p:pic>`` shape to the group/shapetree having properties as specified in call. """ pic = CT_Picture.new_pic(id_, name, desc, rId, x, y, cx, cy) self.insert_element_before(pic, 'p:extLst') return pic def add_placeholder(self, id_, name, ph_type, orient, sz, idx): """ Append a newly-created placeholder ``<p:sp>`` shape having the specified placeholder properties. """ sp = CT_Shape.new_placeholder_sp(id_, name, ph_type, orient, sz, idx) self.insert_element_before(sp, 'p:extLst') return sp def add_table(self, id_, name, rows, cols, x, y, cx, cy): """ Append a ``<p:graphicFrame>`` shape containing a table as specified in call. """ graphicFrame = CT_GraphicalObjectFrame.new_table_graphicFrame( id_, name, rows, cols, x, y, cx, cy) self.insert_element_before(graphicFrame, 'p:extLst') return graphicFrame def add_textbox(self, id_, name, x, y, cx, cy): """ Append a newly-created textbox ``<p:sp>`` shape having the specified position and size. """ sp = CT_Shape.new_textbox_sp(id_, name, x, y, cx, cy) self.insert_element_before(sp, 'p:extLst') return sp @property def chExt(self): """Descendent `p:grpSpPr/a:xfrm/a:chExt` element.""" return self.grpSpPr.get_or_add_xfrm().get_or_add_chExt() @property def chOff(self): """Descendent `p:grpSpPr/a:xfrm/a:chOff` element.""" return self.grpSpPr.get_or_add_xfrm().get_or_add_chOff() def get_or_add_xfrm(self): """ Return the ``<a:xfrm>`` grandchild element, newly-added if not present. """ return self.grpSpPr.get_or_add_xfrm() def iter_ph_elms(self): """ Generate each placeholder shape child element in document order. """ for e in self.iter_shape_elms(): if e.has_ph_elm: yield e def iter_shape_elms(self): """ Generate each child of this ``<p:spTree>`` element that corresponds to a shape, in the sequence they appear in the XML. """ for elm in self.iterchildren(): if elm.tag in self._shape_tags: yield elm @classmethod def new_grpSp(cls, id_, name): """Return new "loose" `p:grpSp` element having *id_* and *name*.""" xml = ('<p:grpSp %s>\n' ' <p:nvGrpSpPr>\n' ' <p:cNvPr id="%%d" name="%%s"/>\n' ' <p:cNvGrpSpPr/>\n' ' <p:nvPr/>\n' ' </p:nvGrpSpPr>\n' ' <p:grpSpPr>\n' ' <a:xfrm>\n' ' <a:off x="0" y="0"/>\n' ' <a:ext cx="0" cy="0"/>\n' ' <a:chOff x="0" y="0"/>\n' ' <a:chExt cx="0" cy="0"/>\n' ' </a:xfrm>\n' ' </p:grpSpPr>\n' '</p:grpSp>' % nsdecls('a', 'p', 'r')) % (id_, name) grpSp = parse_xml(xml) return grpSp def recalculate_extents(self): """Adjust x, y, cx, and cy to incorporate all contained shapes. This would typically be called when a contained shape is added, removed, or its position or size updated. This method is recursive "upwards" since a change in a group shape can change the position and size of its containing group. """ if not self.tag == qn('p:grpSp'): return x, y, cx, cy = self._child_extents self.chOff.x = self.x = x self.chOff.y = self.y = y self.chExt.cx = self.cx = cx self.chExt.cy = self.cy = cy self.getparent().recalculate_extents() @property def xfrm(self): """ The ``<a:xfrm>`` grandchild element or |None| if not found """ return self.grpSpPr.xfrm @property def _child_extents(self): """(x, y, cx, cy) tuple representing net position and size. The values are formed as a composite of the contained child shapes. """ child_shape_elms = list(self.iter_shape_elms()) if not child_shape_elms: return Emu(0), Emu(0), Emu(0), Emu(0) min_x = min([xSp.x for xSp in child_shape_elms]) min_y = min([xSp.y for xSp in child_shape_elms]) max_x = max([(xSp.x + xSp.cx) for xSp in child_shape_elms]) max_y = max([(xSp.y + xSp.cy) for xSp in child_shape_elms]) x = min_x y = min_y cx = max_x - min_x cy = max_y - min_y return x, y, cx, cy @property def _next_shape_id(self): """Return unique shape id suitable for use with a new shape element. The returned id is the next available positive integer drawing object id in shape tree, starting from 1 and making use of any gaps in numbering. In practice, the minimum id is 2 because the spTree element itself is always assigned id="1". """ id_str_lst = self.xpath('//@id') used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()] for n in range(1, len(used_ids) + 2): if n not in used_ids: return n
def iter_fixture(self, request): tr_cxml = request.param tr = element(tr_cxml) cells = _CellCollection(tr, None) expected_cell_lst = tr.findall(qn('a:tc')) return cells, expected_cell_lst