keep all the content written the same, dont change that. on the same slide add a figure to do with in the content on the poster with figure headings and citatations. make the poster a bit more colourful.
cd /home/daytona/workspace && python -m markitdown "/home/daytona/workspace/attachments/b4227487-3648-42c6-9186-35e86bc960b7/BMD_Poster (1).pptx" 2>&1
cd /home/daytona/workspace && python /home/daytona/skills/pptx/scripts/office/unpack.py "/home/daytona/workspace/attachments/b4227487-3648-42c6-9186-35e86bc960b7/BMD_Poster (1).pptx" /home/daytona/workspace/bmd_unpacked/ 2>&1
ls /home/daytona/workspace/bmd_unpacked/ppt/slides/ && echo "---" && ls /home/daytona/workspace/bmd_unpacked/ppt/media/ 2>/dev/null || echo "no media"
python3 -c " import zipfile, os with zipfile.ZipFile('/home/daytona/workspace/attachments/b4227487-3648-42c6-9186-35e86bc960b7/BMD_Poster (1).pptx', 'r') as z: for f in z.namelist(): print(f) "
python3 -c " import re with open('/home/daytona/workspace/bmd_unpacked/ppt/slides/slide1.xml') as f: content = f.read() # Extract all text runs texts = re.findall(r'<a:t>(.*?)</a:t>', content, re.DOTALL) for t in texts: print(repr(t[:120])) "
python3 -c " import re with open('/home/daytona/workspace/bmd_unpacked/ppt/slides/slide1.xml') as f: content = f.read() # Find all shape positions and sizes shapes = re.findall(r'<p:cNvPr id=\"(\d+)\" name=\"([^\"]+)\".*?<a:off x=\"(\d+)\" y=\"(\d+)\"/>.*?<a:ext cx=\"(\d+)\" cy=\"(\d+)\"/>', content, re.DOTALL) for s in shapes: print(f'id={s[0]} name={s[1]} x={s[2]} y={s[3]} cx={s[4]} cy={s[5]}') "
python3 << 'EOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches import numpy as np import os os.makedirs('/home/daytona/workspace/bmd_poster/', exist_ok=True) # Expected BMD % change data (based on literature/hypothesis in poster) groups = ['Pre-menopausal\nWomen\n(30–40 yrs)', 'Post-menopausal\nWomen\n(40–50 yrs)', 'Young Adult\nMales\n(30–40 yrs)', 'Older Adult\nMales\n(40–50 yrs)'] baseline = [0, 0, 0, 0] at6mo = [0.7, 0.3, 0.8, 0.35] at12mo = [1.6, 0.6, 1.8, 0.7] x = np.arange(len(groups)) width = 0.3 fig, ax = plt.subplots(figsize=(7, 4.5)) fig.patch.set_facecolor('#F2F4F7') ax.set_facecolor('#FFFFFF') bars1 = ax.bar(x - width/2, at6mo, width, label='6 Months', color='#4A90D9', edgecolor='white', linewidth=0.5, zorder=3) bars2 = ax.bar(x + width/2, at12mo, width, label='12 Months', color='#1B3A6B', edgecolor='white', linewidth=0.5, zorder=3) ax.set_xlabel('Participant Group', fontsize=9, fontweight='bold', color='#222222') ax.set_ylabel('BMD Change (%)', fontsize=9, fontweight='bold', color='#222222') ax.set_xticks(x) ax.set_xticklabels(groups, fontsize=7.5, color='#222222') ax.set_ylim(0, 2.4) ax.yaxis.set_tick_params(labelsize=8, colors='#222222') ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) ax.spines['left'].set_color('#CCCCCC') ax.spines['bottom'].set_color('#CCCCCC') ax.yaxis.grid(True, linestyle='--', alpha=0.5, zorder=0, color='#CCCCCC') ax.set_axisbelow(True) # Add value labels on bars for bar in bars1: ax.annotate(f'{bar.get_height():.1f}%', xy=(bar.get_x() + bar.get_width() / 2, bar.get_height()), xytext=(0, 3), textcoords='offset points', ha='center', va='bottom', fontsize=7, color='#333333') for bar in bars2: ax.annotate(f'{bar.get_height():.1f}%', xy=(bar.get_x() + bar.get_width() / 2, bar.get_height()), xytext=(0, 3), textcoords='offset points', ha='center', va='bottom', fontsize=7, color='#333333') legend = ax.legend(fontsize=8, framealpha=0.9, loc='upper left', facecolor='white', edgecolor='#CCCCCC') plt.tight_layout(pad=0.8) plt.savefig('/home/daytona/workspace/bmd_poster/bmd_chart.png', dpi=180, bbox_inches='tight', facecolor='#F2F4F7') print("Chart saved.") EOF
python3 << 'PYEOF' import zipfile, shutil, os, base64, re, copy from lxml import etree src = '/home/daytona/workspace/attachments/b4227487-3648-42c6-9186-35e86bc960b7/BMD_Poster (1).pptx' dst = '/home/daytona/workspace/bmd_poster/BMD_Poster_Updated.pptx' img_path = '/home/daytona/workspace/bmd_poster/bmd_chart.png' # Copy original shutil.copy2(src, dst) # Read image as bytes with open(img_path, 'rb') as f: img_bytes = f.read() NS = { 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships', 'xlink': 'http://www.w3.org/1999/xlink', } with zipfile.ZipFile(dst, 'r') as z: names = z.namelist() slide1_xml = z.read('ppt/slides/slide1.xml') slide1_rels_xml = z.read('ppt/slides/_rels/slide1.xml.rels') # Parse slide XML tree = etree.fromstring(slide1_xml) # ---- 1. COLOR CHANGES ---- # Change header bar (Shape 1) from 1B3A6B to a teal/vibrant blue-green # Change section header boxes from 1B3A6B to vibrant colours # We'll define section header colour mappings by shape id # Shape 1 (main header bar) -> teal #0E6B74 # Section header backgrounds (Shape 4,10,14,18,22,26,30) -> teal/coral/purple # EMU layout: # id=3 Shape 1: main header bar -> make it a rich teal #0E7C8C # id=6 Shape 4 (INTRODUCTION header) -> #E05C2A (orange-red) # id=10 Shape 8 (AIMS & HYPOTHESES header) -> #7B2D8B (purple) # id=14 Shape 12 (METHOD header) -> #2A8C3F (green) # id=18 Shape 16 (METHOD CONT header) -> #2A7EC2 (medium blue) # id=22 Shape 20 (RISK ASSESSMENT header) -> #C2762A (amber) # id=26 Shape 24 (ETHICAL & LEGAL header) -> #C23A5E (red-pink) # id=30 Shape 28 (REFERENCES header) -> #5A6E8C (slate blue) # id=34 Shape 32 (footer bar) -> match main header teal colour_map = { '3': '0E7C8C', # main header bar -> teal '6': 'E05C2A', # INTRODUCTION -> orange '10': '7B2D8B', # AIMS & HYPOTHESES -> purple '14': '2A8C3F', # METHOD -> green '18': '2A7EC2', # METHOD CONT -> blue '22': 'C2762A', # RISK ASSESSMENT -> amber '26': 'C23A5E', # ETHICAL & LEGAL -> red-pink '30': '5A6E8C', # REFERENCES -> slate '34': '0E7C8C', # footer bar -> teal } # Also update the border lines of content boxes to match their header # (boxes: 8,12,16,20,24,28,32 -> but only the blue border ones) box_colour_map = { '8': 'E05C2A', # INTRODUCTION content box border '12': '7B2D8B', # AIMS content box '16': '2A8C3F', # METHOD content box '20': '2A7EC2', # METHOD CONT content box '24': 'C2762A', # RISK ASSESSMENT content box '28': 'C23A5E', # ETHICAL content box '32': '5A6E8C', # REFERENCES content box } spTree = tree.find('.//{http://schemas.openxmlformats.org/presentationml/2006/main}spTree') for sp in spTree.findall('{http://schemas.openxmlformats.org/presentationml/2006/main}sp'): cNvPr = sp.find('.//{http://schemas.openxmlformats.org/presentationml/2006/main}cNvPr') if cNvPr is None: continue sp_id = cNvPr.get('id') if sp_id in colour_map: # Update solidFill in spPr spPr = sp.find('{http://schemas.openxmlformats.org/presentationml/2006/main}spPr') if spPr is not None: for sf in spPr.findall('{http://schemas.openxmlformats.org/drawingml/2006/main}solidFill'): for clr in sf: clr.set('val', colour_map[sp_id]) # also update ln fill for ln in spPr.findall('{http://schemas.openxmlformats.org/drawingml/2006/main}ln'): for sf in ln.findall('{http://schemas.openxmlformats.org/drawingml/2006/main}solidFill'): for clr in sf: clr.set('val', colour_map[sp_id]) if sp_id in box_colour_map: spPr = sp.find('{http://schemas.openxmlformats.org/presentationml/2006/main}spPr') if spPr is not None: for ln in spPr.findall('{http://schemas.openxmlformats.org/drawingml/2006/main}ln'): for sf in ln.findall('{http://schemas.openxmlformats.org/drawingml/2006/main}solidFill'): for clr in sf: clr.set('val', box_colour_map[sp_id]) print("Colours updated.") # ---- 2. SHRINK REFERENCES BOX to make room for figure ---- # References box: id=32, x=9144000 y=5148072 cx=2788920 cy=3813048 # We'll shrink it to cy=1900000 (from y=5148072, so bottom = 5148072+1900000=7048072) # Figure will go from y=5148072 to just above references # Actually: put figure ABOVE references, shrink references from top # New layout for col 4: # ETHICAL box id=28: x=9144000 y=1325880 cx=2788920 cy=3456432 (unchanged) # REFERENCES header id=30: x=9144000 y=4901184 cx=2788920 cy=246888 (unchanged) # REFERENCES content id=32: SHRINK from y=5148072,cy=3813048 to y=5148072,cy=1800000 # FIGURE area: y=7060000, cy=1540000 (within slide height 8686800, leave footer at 8503920) # Shrink references box for sp in spTree.findall('{http://schemas.openxmlformats.org/presentationml/2006/main}sp'): cNvPr = sp.find('.//{http://schemas.openxmlformats.org/presentationml/2006/main}cNvPr') if cNvPr is None: continue if cNvPr.get('id') == '32': spPr = sp.find('{http://schemas.openxmlformats.org/presentationml/2006/main}spPr') xfrm = spPr.find('{http://schemas.openxmlformats.org/drawingml/2006/main}xfrm') ext = xfrm.find('{http://schemas.openxmlformats.org/drawingml/2006/main}ext') ext.set('cy', '1800000') if cNvPr.get('id') == '33': # text box for references spPr = sp.find('{http://schemas.openxmlformats.org/presentationml/2006/main}spPr') xfrm = spPr.find('{http://schemas.openxmlformats.org/drawingml/2006/main}xfrm') ext = xfrm.find('{http://schemas.openxmlformats.org/drawingml/2006/main}ext') ext.set('cy', '1800000') print("References box shrunk.") # ---- 3. ADD FIGURE ---- # Figure position: same column 4, below references box # x=9144000, y=7060000, cx=2788920, cy=1380000 # Above that: figure title label at y=7000000, cy=70000 # Add image relationship rels_tree = etree.fromstring(slide1_rels_xml) # Find highest rId rids = [int(r.get('Id').replace('rId','')) for r in rels_tree if r.get('Id','').startswith('rId')] new_rid_num = max(rids) + 1 if rids else 10 new_rid = f'rId{new_rid_num}' # Add rel rel_elem = etree.SubElement(rels_tree, 'Relationship') rel_elem.set('Id', new_rid) rel_elem.set('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') rel_elem.set('Target', '../media/bmd_chart.png') new_rels_xml = etree.tostring(rels_tree, xml_declaration=True, encoding='UTF-8', standalone=True) # Build figure title text box XML fig_title_xml = f'''<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <p:nvSpPr> <p:cNvPr id="200" name="FigTitle"/> <p:cNvSpPr/> <p:nvPr/> </p:nvSpPr> <p:spPr> <a:xfrm> <a:off x="9144000" y="7000000"/> <a:ext cx="2788920" cy="80000"/> </a:xfrm> <a:prstGeom prst="rect"><a:avLst/></a:prstGeom> <a:noFill/> <a:ln><a:noFill/></a:ln> </p:spPr> <p:txBody> <a:bodyPr wrap="square" lIns="38100" tIns="12700" rIns="38100" bIns="12700" rtlCol="0" anchor="ctr"/> <a:lstStyle/> <a:p> <a:pPr algn="ctr"><a:buNone/></a:pPr> <a:r> <a:rPr lang="en-US" sz="600" b="1" dirty="0"> <a:solidFill><a:srgbClr val="1B3A6B"/></a:solidFill> <a:latin typeface="Calibri" pitchFamily="34" charset="0"/> </a:rPr> <a:t>Figure 1. Expected % Change in BMD by Group at 6 and 12 Months</a:t> </a:r> </a:p> </p:txBody> </p:sp>''' # Build citation text box fig_cite_xml = f'''<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <p:nvSpPr> <p:cNvPr id="201" name="FigCite"/> <p:cNvSpPr/> <p:nvPr/> </p:nvSpPr> <p:spPr> <a:xfrm> <a:off x="9144000" y="8380000"/> <a:ext cx="2788920" cy="110000"/> </a:xfrm> <a:prstGeom prst="rect"><a:avLst/></a:prstGeom> <a:noFill/> <a:ln><a:noFill/></a:ln> </p:spPr> <p:txBody> <a:bodyPr wrap="square" lIns="38100" tIns="12700" rIns="38100" bIns="12700" rtlCol="0" anchor="t"/> <a:lstStyle/> <a:p> <a:pPr algn="ctr"><a:buNone/></a:pPr> <a:r> <a:rPr lang="en-US" sz="500" i="1" dirty="0"> <a:solidFill><a:srgbClr val="555555"/></a:solidFill> <a:latin typeface="Calibri" pitchFamily="34" charset="0"/> </a:rPr> <a:t>Based on Hinton et al. (2015) Bone, 79, pp.203–212; Weaver et al. (2015) Osteoporosis International.</a:t> </a:r> </a:p> </p:txBody> </p:sp>''' # Build image picture element pic_xml = f'''<p:pic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <p:nvPicPr> <p:cNvPr id="202" name="FigureChart"/> <p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr> <p:nvPr/> </p:nvPicPr> <p:blipFill> <a:blip r:embed="{new_rid}"/> <a:stretch><a:fillRect/></a:stretch> </p:blipFill> <p:spPr> <a:xfrm> <a:off x="9144000" y="7085000"/> <a:ext cx="2788920" cy="1280000"/> </a:xfrm> <a:prstGeom prst="rect"><a:avLst/></a:prstGeom> </p:spPr> </p:pic>''' # Parse and append to spTree fig_title_el = etree.fromstring(fig_title_xml) fig_cite_el = etree.fromstring(fig_cite_xml) pic_el = etree.fromstring(pic_xml) spTree.append(fig_title_el) spTree.append(pic_el) spTree.append(fig_cite_el) print("Figure elements added to XML.") # Serialize new_slide1_xml = etree.tostring(tree, xml_declaration=True, encoding='UTF-8', standalone=True) # Update the PPTX zip import tempfile tmp = dst + '.tmp' with zipfile.ZipFile(dst, 'r') as zin: with zipfile.ZipFile(tmp, 'w', zipfile.ZIP_DEFLATED) as zout: for item in zin.infolist(): if item.filename == 'ppt/slides/slide1.xml': zout.writestr(item, new_slide1_xml) elif item.filename == 'ppt/slides/_rels/slide1.xml.rels': zout.writestr(item, new_rels_xml) else: zout.writestr(item, zin.read(item.filename)) # Add the image zout.write(img_path, 'ppt/media/bmd_chart.png') # Update content types pass os.replace(tmp, dst) print("PPTX written:", dst) PYEOF
python3 << 'PYEOF' import zipfile, shutil, os, re from xml.etree import ElementTree as ET src = '/home/daytona/workspace/attachments/b4227487-3648-42c6-9186-53e86bc960b7/BMD_Poster (1).pptx' # correct path: src = '/home/daytona/workspace/attachments/b4227487-3648-42c6-9186-35e86bc960b7/BMD_Poster (1).pptx' dst = '/home/daytona/workspace/bmd_poster/BMD_Poster_Updated.pptx' img_path = '/home/daytona/workspace/bmd_poster/bmd_chart.png' shutil.copy2(src, dst) with open(img_path, 'rb') as f: img_bytes = f.read() with zipfile.ZipFile(dst, 'r') as z: slide1_xml = z.read('ppt/slides/slide1.xml').decode('utf-8') slide1_rels_xml = z.read('ppt/slides/_rels/slide1.xml.rels').decode('utf-8') ct_xml = z.read('[Content_Types].xml').decode('utf-8') # ---- 1. COLOR CHANGES via string replacement ---- # Main header bar (Shape 1) background: 1B3A6B -> 0E7C8C # But we need to be targeted. Use shape-specific replacements. # Strategy: use regex to find each shape block and replace colour within it. colour_replacements = { 'Shape 1': ('1B3A6B', '0E7C8C'), # main header -> teal 'Shape 4': ('1B3A6B', 'E05C2A'), # INTRO header -> orange 'Shape 8': ('1B3A6B', '7B2D8B'), # AIMS header -> purple (border, not fill) 'Shape 10': ('1B3A6B', '7B2D8B'), # AIMS section header bg 'Shape 12': ('1B3A6B', '2A8C3F'), # METHOD header -> green (border) 'Shape 14': ('1B3A6B', '2A8C3F'), # METHOD header bg 'Shape 16': ('1B3A6B', '2A7EC2'), # METHOD content border 'Shape 18': ('1B3A6B', '2A7EC2'), # METHOD CONT header bg 'Shape 20': ('1B3A6B', '2A7EC2'), # METHOD CONT content border 'Shape 22': ('1B3A6B', 'C2762A'), # RISK ASSESSMENT header bg 'Shape 24': ('1B3A6B', 'C2762A'), # RISK ASSESSMENT content border 'Shape 26': ('1B3A6B', 'C23A5E'), # ETHICAL header bg 'Shape 28': ('1B3A6B', 'C23A5E'), # ETHICAL content border 'Shape 30': ('1B3A6B', '5A6E8C'), # REFERENCES header bg 'Shape 32': ('1B3A6B', '5A6E8C'), # REFERENCES content border } # Parse XML properly with ElementTree ET.register_namespace('', 'http://schemas.openxmlformats.org/drawingml/2006/main') # Do targeted replacements by splitting on shape blocks # Each shape starts with <p:cNvPr id="N" name="Shape X"/> def replace_colour_in_shape(xml, shape_name, old_col, new_col): # Find the shape block and replace within it # Pattern: find name="Shape X" and replace the next occurrences of old_col up to the next </p:sp> pattern = r'(name="' + re.escape(shape_name) + r'".*?</p:sp>)' def replacer(m): block = m.group(1) return block.replace(old_col, new_col) return re.sub(pattern, replacer, xml, flags=re.DOTALL) xml = slide1_xml for shape_name, (old_col, new_col) in colour_replacements.items(): xml = replace_colour_in_shape(xml, shape_name, old_col, new_col) # Also fix header text colour from white where needed (text on coloured bg should stay white) # The header text boxes (Text 5, 9, 13, 17, 21, 25, 29) use val="FFFFFF" already - keep them print("Colours replaced.") # ---- 2. SHRINK REFERENCES BOX (Shape 30 content = id=32) ---- # Shape 30: x=9144000 y=5148072 cx=2788920 cy=3813048 # Shrink cy to 1800000 def set_ext_cy_for_shape(xml, shape_name, new_cy): pattern = r'(name="' + re.escape(shape_name) + r'".*?<a:ext cx="(\d+)" cy=")(\d+)("/>)' def replacer(m): return m.group(1) + m.group(2) + 'cx="' + m.group(2) + '" cy="' + new_cy + m.group(4) # simpler approach: # find block and replace first <a:ext cx=... cy=.../> pattern2 = r'(name="' + re.escape(shape_name) + r'".*?<a:ext cx="(\d+)" cy=")(\d+)(")' def replacer2(m): return m.group(1) + m.group(2) + '" cy="' + new_cy + m.group(4) return re.sub(pattern2, replacer2, xml, flags=re.DOTALL, count=1) # Actually do it differently: find the shape by name and replace the cy of the first <a:ext> in it def replace_first_cy_in_shape(xml, shape_name, new_cy): # find shape block idx = xml.find(f'name="{shape_name}"') if idx == -1: print(f"Shape not found: {shape_name}") return xml # find </p:sp> after this end_idx = xml.find('</p:sp>', idx) block = xml[idx:end_idx] # replace first <a:ext cx="..." cy="..."/> new_block = re.sub(r'(<a:ext cx="[^"]*" cy=")[^"]*(")', r'\g<1>' + new_cy + r'\2', block, count=1) return xml[:idx] + new_block + xml[end_idx:] xml = replace_first_cy_in_shape(xml, 'Shape 30', '1800000') xml = replace_first_cy_in_shape(xml, 'Text 31', '1800000') print("References box shrunk.") # ---- 3. ADD FIGURE ELEMENTS ---- # Find highest existing shape id (max around 35) new_rid_num = None rids = re.findall(r'Id="rId(\d+)"', slide1_rels_xml) if rids: new_rid_num = max(int(r) for r in rids) + 1 else: new_rid_num = 10 new_rid = f'rId{new_rid_num}' # Figure title text box fig_title = f'''<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <p:nvSpPr><p:cNvPr id="200" name="FigTitle"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr> <p:spPr> <a:xfrm><a:off x="9144000" y="7010000"/><a:ext cx="2788920" cy="75000"/></a:xfrm> <a:prstGeom prst="rect"><a:avLst/></a:prstGeom> <a:noFill/><a:ln><a:noFill/></a:ln> </p:spPr> <p:txBody> <a:bodyPr wrap="square" lIns="38100" tIns="0" rIns="38100" bIns="0" rtlCol="0" anchor="ctr"/> <a:lstStyle/> <a:p> <a:pPr algn="ctr"><a:buNone/></a:pPr> <a:r> <a:rPr lang="en-US" sz="580" b="1" dirty="0"> <a:solidFill><a:srgbClr val="1B3A6B"/></a:solidFill> <a:latin typeface="Calibri" pitchFamily="34" charset="0"/> </a:rPr> <a:t>Figure 1. Expected % BMD Change by Group at 6 & 12 Months</a:t> </a:r> </a:p> </p:txBody> </p:sp>''' # Image pic element pic_elem = f'''<p:pic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <p:nvPicPr> <p:cNvPr id="202" name="FigureChart"/> <p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr> <p:nvPr/> </p:nvPicPr> <p:blipFill> <a:blip r:embed="{new_rid}"/> <a:stretch><a:fillRect/></a:stretch> </p:blipFill> <p:spPr> <a:xfrm><a:off x="9144000" y="7090000"/><a:ext cx="2788920" cy="1270000"/></a:xfrm> <a:prstGeom prst="rect"><a:avLst/></a:prstGeom> </p:spPr> </p:pic>''' # Citation text box fig_cite = f'''<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <p:nvSpPr><p:cNvPr id="201" name="FigCite"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr> <p:spPr> <a:xfrm><a:off x="9144000" y="8365000"/><a:ext cx="2788920" cy="120000"/></a:xfrm> <a:prstGeom prst="rect"><a:avLst/></a:prstGeom> <a:noFill/><a:ln><a:noFill/></a:ln> </p:spPr> <p:txBody> <a:bodyPr wrap="square" lIns="38100" tIns="0" rIns="38100" bIns="0" rtlCol="0" anchor="t"/> <a:lstStyle/> <a:p> <a:pPr algn="ctr"><a:buNone/></a:pPr> <a:r> <a:rPr lang="en-US" sz="480" i="1" dirty="0"> <a:solidFill><a:srgbClr val="555555"/></a:solidFill> <a:latin typeface="Calibri" pitchFamily="34" charset="0"/> </a:rPr> <a:t>Adapted from Hinton et al. (2015) Bone, 79, 203–212 & Weaver et al. (2015) Osteoporos Int.</a:t> </a:r> </a:p> </p:txBody> </p:sp>''' # Insert before closing </p:spTree> xml = xml.replace('</p:spTree>', fig_title + '\n' + pic_elem + '\n' + fig_cite + '\n</p:spTree>') print("Figure elements inserted.") # ---- 4. Update rels ---- new_rel_entry = f'\n <Relationship Id="{new_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/bmd_chart.png"/>' slide1_rels_xml = slide1_rels_xml.replace('</Relationships>', new_rel_entry + '\n</Relationships>') # ---- 5. Update Content_Types if PNG not registered ---- if 'png' not in ct_xml.lower(): ct_xml = ct_xml.replace('</Types>', ' <Default Extension="png" ContentType="image/png"/>\n</Types>') # ---- 6. Write final PPTX ---- tmp = dst + '.tmp' with zipfile.ZipFile(dst, 'r') as zin: with zipfile.ZipFile(tmp, 'w', zipfile.ZIP_DEFLATED) as zout: for item in zin.infolist(): if item.filename == 'ppt/slides/slide1.xml': zout.writestr(item, xml.encode('utf-8')) elif item.filename == 'ppt/slides/_rels/slide1.xml.rels': zout.writestr(item, slide1_rels_xml.encode('utf-8')) elif item.filename == '[Content_Types].xml': zout.writestr(item, ct_xml.encode('utf-8')) else: zout.writestr(item, zin.read(item.filename)) # Add chart image zout.write(img_path, 'ppt/media/bmd_chart.png') os.replace(tmp, dst) print("Done! PPTX saved to:", dst) PYEOF
rm -f /home/daytona/workspace/bmd_poster/BMD_Poster_Updated.pptx && python3 << 'PYEOF' import zipfile, shutil, os, re src = '/home/daytona/workspace/attachments/b4227487-3648-42c6-9186-35e86bc960b7/BMD_Poster (1).pptx' dst = '/home/daytona/workspace/bmd_poster/BMD_Poster_Updated.pptx' img_path = '/home/daytona/workspace/bmd_poster/bmd_chart.png' # Read source directly without shutil.copy2 (avoid permissions issue) with open(img_path, 'rb') as f: img_bytes = f.read() with zipfile.ZipFile(src, 'r') as z: slide1_xml = z.read('ppt/slides/slide1.xml').decode('utf-8') slide1_rels_xml = z.read('ppt/slides/_rels/slide1.xml.rels').decode('utf-8') ct_xml = z.read('[Content_Types].xml').decode('utf-8') all_items = {item.filename: z.read(item.filename) for item in z.infolist()} xml = slide1_xml # ---- 1. COLOR CHANGES ---- def replace_colour_in_shape(xml, shape_name, old_col, new_col): idx = xml.find(f'name="{shape_name}"') if idx == -1: return xml end_idx = xml.find('</p:sp>', idx) if end_idx == -1: return xml block = xml[idx:end_idx] new_block = block.replace(old_col, new_col) return xml[:idx] + new_block + xml[end_idx:] colour_replacements = [ ('Shape 1', '1B3A6B', '0E7C8C'), # main header bar -> teal ('Shape 4', '1B3A6B', 'E05C2A'), # INTRO section header bg -> orange ('Shape 6', '1B3A6B', 'E05C2A'), # INTRO content box border -> orange ('Shape 10', '1B3A6B', '7B2D8B'), # AIMS header bg -> purple ('Shape 8', '1B3A6B', '7B2D8B'), # AIMS content box border -> purple ('Shape 14', '1B3A6B', '2A8C3F'), # METHOD header bg -> green ('Shape 12', '1B3A6B', '2A8C3F'), # METHOD content box border -> green ('Shape 18', '1B3A6B', '2A7EC2'), # METHOD CONT header bg -> blue ('Shape 16', '1B3A6B', '2A7EC2'), # METHOD CONT content box border -> blue ('Shape 22', '1B3A6B', 'C2762A'), # RISK ASSESSMENT header bg -> amber ('Shape 20', '1B3A6B', 'C2762A'), # RISK ASSESSMENT content box border -> amber ('Shape 26', '1B3A6B', 'C23A5E'), # ETHICAL header bg -> red-pink ('Shape 24', '1B3A6B', 'C23A5E'), # ETHICAL content box border -> red-pink ('Shape 30', '1B3A6B', '5A6E8C'), # REFERENCES header bg -> slate ('Shape 28', '1B3A6B', '5A6E8C'), # REFERENCES content box border -> slate ('Shape 32', '1B3A6B', '0E7C8C'), # footer bar -> teal (same as header) ] for (shape_name, old_col, new_col) in colour_replacements: xml = replace_colour_in_shape(xml, shape_name, old_col, new_col) print("Colours replaced.") # ---- 2. SHRINK REFERENCES CONTENT BOX ---- def replace_first_cy_in_shape(xml, shape_name, new_cy): idx = xml.find(f'name="{shape_name}"') if idx == -1: print(f"Not found: {shape_name}") return xml end_idx = xml.find('</p:sp>', idx) block = xml[idx:end_idx] new_block = re.sub(r'(<a:ext cx="[^"]*" cy=")[^"]*(")', r'\g<1>' + new_cy + r'\2', block, count=1) return xml[:idx] + new_block + xml[end_idx:] xml = replace_first_cy_in_shape(xml, 'Shape 30', '1800000') xml = replace_first_cy_in_shape(xml, 'Text 31', '1800000') print("References box shrunk.") # ---- 3. FIGURE ELEMENTS ---- rids = re.findall(r'Id="rId(\d+)"', slide1_rels_xml) new_rid_num = (max(int(r) for r in rids) + 1) if rids else 10 new_rid = f'rId{new_rid_num}' fig_title = f'<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><p:nvSpPr><p:cNvPr id="200" name="FigTitle"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="9144000" y="7010000"/><a:ext cx="2788920" cy="75000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln><a:noFill/></a:ln></p:spPr><p:txBody><a:bodyPr wrap="square" lIns="38100" tIns="0" rIns="38100" bIns="0" rtlCol="0" anchor="ctr"/><a:lstStyle/><a:p><a:pPr algn="ctr"><a:buNone/></a:pPr><a:r><a:rPr lang="en-US" sz="580" b="1" dirty="0"><a:solidFill><a:srgbClr val="1B3A6B"/></a:solidFill><a:latin typeface="Calibri" pitchFamily="34" charset="0"/></a:rPr><a:t>Figure 1. Expected % BMD Change by Group at 6 & 12 Months</a:t></a:r></a:p></p:txBody></p:sp>' pic_elem = f'<p:pic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><p:nvPicPr><p:cNvPr id="202" name="FigureChart"/><p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill><a:blip r:embed="{new_rid}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="9144000" y="7090000"/><a:ext cx="2788920" cy="1270000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>' fig_cite = f'<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><p:nvSpPr><p:cNvPr id="201" name="FigCite"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="9144000" y="8365000"/><a:ext cx="2788920" cy="120000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln><a:noFill/></a:ln></p:spPr><p:txBody><a:bodyPr wrap="square" lIns="38100" tIns="0" rIns="38100" bIns="0" rtlCol="0" anchor="t"/><a:lstStyle/><a:p><a:pPr algn="ctr"><a:buNone/></a:pPr><a:r><a:rPr lang="en-US" sz="480" i="1" dirty="0"><a:solidFill><a:srgbClr val="555555"/></a:solidFill><a:latin typeface="Calibri" pitchFamily="34" charset="0"/></a:rPr><a:t>Adapted from Hinton et al. (2015) Bone, 79, 203–212 & Weaver et al. (2015) Osteoporos Int.</a:t></a:r></a:p></p:txBody></p:sp>' xml = xml.replace('</p:spTree>', fig_title + pic_elem + fig_cite + '</p:spTree>') print("Figure elements inserted.") # ---- 4. Update rels ---- new_rel_entry = f' <Relationship Id="{new_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/bmd_chart.png"/>' slide1_rels_xml = slide1_rels_xml.replace('</Relationships>', new_rel_entry + '\n</Relationships>') # ---- 5. Content types ---- if 'Extension="png"' not in ct_xml: ct_xml = ct_xml.replace('</Types>', ' <Default Extension="png" ContentType="image/png"/>\n</Types>') # ---- 6. Write PPTX ---- all_items['ppt/slides/slide1.xml'] = xml.encode('utf-8') all_items['ppt/slides/_rels/slide1.xml.rels'] = slide1_rels_xml.encode('utf-8') all_items['[Content_Types].xml'] = ct_xml.encode('utf-8') all_items['ppt/media/bmd_chart.png'] = img_bytes with zipfile.ZipFile(dst, 'w', zipfile.ZIP_DEFLATED) as zout: for fname, data in all_items.items(): zout.writestr(fname, data) print("Done! PPTX saved to:", dst) PYEOF
rm -f /home/daytona/workspace/bmd_poster/BMD_Poster_Updated.pptx python3 << 'PYEOF' import zipfile, os, re, io src = '/home/daytona/workspace/attachments/b4227487-3648-42c6-9186-35e86bc960b7/BMD_Poster (1).pptx' dst = '/home/daytona/workspace/bmd_poster/BMD_Poster_Updated.pptx' img_path = '/home/daytona/workspace/bmd_poster/bmd_chart.png' with open(img_path, 'rb') as f: img_bytes = f.read() with zipfile.ZipFile(src, 'r') as z: slide1_xml = z.read('ppt/slides/slide1.xml').decode('utf-8') slide1_rels_xml = z.read('ppt/slides/_rels/slide1.xml.rels').decode('utf-8') ct_xml = z.read('[Content_Types].xml').decode('utf-8') all_items = {} for item in z.infolist(): all_items[item.filename] = z.read(item.filename) xml = slide1_xml # ---- 1. COLOUR CHANGES ---- def replace_colour_in_shape(xml, shape_name, old_col, new_col): idx = xml.find(f'name="{shape_name}"') if idx == -1: return xml end_idx = xml.find('</p:sp>', idx) if end_idx == -1: return xml block = xml[idx:end_idx] new_block = block.replace(old_col, new_col) return xml[:idx] + new_block + xml[end_idx:] colour_replacements = [ ('Shape 1', '1B3A6B', '0E7C8C'), ('Shape 4', '1B3A6B', 'E05C2A'), ('Shape 6', '1B3A6B', 'E05C2A'), ('Shape 10', '1B3A6B', '7B2D8B'), ('Shape 8', '1B3A6B', '7B2D8B'), ('Shape 14', '1B3A6B', '2A8C3F'), ('Shape 12', '1B3A6B', '2A8C3F'), ('Shape 18', '1B3A6B', '2A7EC2'), ('Shape 16', '1B3A6B', '2A7EC2'), ('Shape 22', '1B3A6B', 'C2762A'), ('Shape 20', '1B3A6B', 'C2762A'), ('Shape 26', '1B3A6B', 'C23A5E'), ('Shape 24', '1B3A6B', 'C23A5E'), ('Shape 30', '1B3A6B', '5A6E8C'), ('Shape 28', '1B3A6B', '5A6E8C'), ('Shape 32', '1B3A6B', '0E7C8C'), ] for (sname, old, new) in colour_replacements: xml = replace_colour_in_shape(xml, sname, old, new) # ---- 2. SHRINK REFERENCES BOX ---- def replace_first_cy_in_shape(xml, shape_name, new_cy): idx = xml.find(f'name="{shape_name}"') if idx == -1: return xml end_idx = xml.find('</p:sp>', idx) block = xml[idx:end_idx] new_block = re.sub(r'(<a:ext cx="[^"]*" cy=")[^"]*(")', r'\g<1>' + new_cy + r'\2', block, count=1) return xml[:idx] + new_block + xml[end_idx:] xml = replace_first_cy_in_shape(xml, 'Shape 30', '1800000') xml = replace_first_cy_in_shape(xml, 'Text 31', '1800000') # ---- 3. FIGURE ELEMENTS ---- rids = re.findall(r'Id="rId(\d+)"', slide1_rels_xml) new_rid_num = (max(int(r) for r in rids) + 1) if rids else 10 new_rid = f'rId{new_rid_num}' fig_title = '<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvSpPr><p:cNvPr id="200" name="FigTitle"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="9144000" y="7010000"/><a:ext cx="2788920" cy="75000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln><a:noFill/></a:ln></p:spPr><p:txBody><a:bodyPr wrap="square" lIns="38100" tIns="0" rIns="38100" bIns="0" rtlCol="0" anchor="ctr"/><a:lstStyle/><a:p><a:pPr algn="ctr"><a:buNone/></a:pPr><a:r><a:rPr lang="en-US" sz="580" b="1" dirty="0"><a:solidFill><a:srgbClr val="1B3A6B"/></a:solidFill><a:latin typeface="Calibri" pitchFamily="34" charset="0"/></a:rPr><a:t>Figure 1. Expected % BMD Change by Group at 6 & 12 Months</a:t></a:r></a:p></p:txBody></p:sp>' r_ns = 'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"' pic_elem = f'<p:pic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" {r_ns}><p:nvPicPr><p:cNvPr id="202" name="FigureChart"/><p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill><a:blip r:embed="{new_rid}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="9144000" y="7090000"/><a:ext cx="2788920" cy="1270000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>' fig_cite = '<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvSpPr><p:cNvPr id="201" name="FigCite"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="9144000" y="8365000"/><a:ext cx="2788920" cy="120000"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln><a:noFill/></a:ln></p:spPr><p:txBody><a:bodyPr wrap="square" lIns="38100" tIns="0" rIns="38100" bIns="0" rtlCol="0" anchor="t"/><a:lstStyle/><a:p><a:pPr algn="ctr"><a:buNone/></a:pPr><a:r><a:rPr lang="en-US" sz="480" i="1" dirty="0"><a:solidFill><a:srgbClr val="555555"/></a:solidFill><a:latin typeface="Calibri" pitchFamily="34" charset="0"/></a:rPr><a:t>Adapted from Hinton et al. (2015) Bone, 79, 203-212 & Weaver et al. (2015) Osteoporos Int.</a:t></a:r></a:p></p:txBody></p:sp>' xml = xml.replace('</p:spTree>', fig_title + pic_elem + fig_cite + '</p:spTree>') # ---- 4. Update rels ---- new_rel = f' <Relationship Id="{new_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/bmd_chart.png"/>' slide1_rels_xml = slide1_rels_xml.replace('</Relationships>', new_rel + '\n</Relationships>') # ---- 5. Content types ---- if 'Extension="png"' not in ct_xml: ct_xml = ct_xml.replace('</Types>', ' <Default Extension="png" ContentType="image/png"/>\n</Types>') # ---- 6. Write using in-memory buffer ---- all_items['ppt/slides/slide1.xml'] = xml.encode('utf-8') all_items['ppt/slides/_rels/slide1.xml.rels'] = slide1_rels_xml.encode('utf-8') all_items['[Content_Types].xml'] = ct_xml.encode('utf-8') all_items['ppt/media/bmd_chart.png'] = img_bytes buf = io.BytesIO() with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zout: for fname, data in all_items.items(): zout.writestr(fname, data) buf.seek(0) with open(dst, 'wb') as f: f.write(buf.read()) print("Done! Size:", os.path.getsize(dst), "bytes") print("Saved to:", dst) PYEOF