Python-pptx Добавление пользовательских свойств в файл pptx

Я пытаюсь добавить пользовательские свойства в файл pptx, используя библиотеку python-pptx. Мне удалось добавить, но не удалось сохранить файл.

Использование блоков данных Python 3.10.12. Среда выполнения: 13.3 LTS.

from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.opc.constants import RELATIONSHIP_TYPE AS RT
from pptx.oxml.ns import nsdecls
from pptx.oxml import parse_xml

pptx = Presentation()
first_slide_layout = pptx.slide_layouts[0]

slide = pptx.slides.add_slide(first_slide_layout)

slide.shapes.title.text = "Created by Python-pptx"

pptx.save("databricks/driver/test.pptx")
####-----> until here code works.

## Below part of adding custom properties does not work.

def add_custom_properties(ppt, name, value):
        custom_property_xml = f'<properties xmlns = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">'\
                     f'<property name = "{name}" type = "string">{value}</property>'\
                      '</properties>'
        custom_property = parse_xml(custom_property_xml)

        ppt.part.package.relate_to(custom_property, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties')

# Below command does not return any error.
add_custom_properties(pptx, "test_key", "test_key_value")

# while saving throws an error.
pptx.save("/databricks/driver/aaa.pptx")

Ошибка: file может быть либо путем к файлу, либо файловоподобным объектом, открытым для записи байтов.

Может ли кто-нибудь помочь мне понять, что не так с этим кодом?


50
1

Ответ:

Решено

Если оставить в стороне все простые синтаксические ошибки и неправильный путь к файлу, этот код выглядит так, как будто любой ИИ догадался об этом по каким-то другим фрагментам кода. Нет, ИИ до сих пор не умеет программировать.

Package.relate_to нужна часть пакета, а не lxml.etree как parse_xml возвращается.

И отношения – это еще не все. Сначала необходимо создать часть пакета. Python-pptx до сих пор не поддерживает часть пользовательских свойств. Таким образом, в шаблоне pptx по умолчанию такой части пакета нет. Поэтому его необходимо создать с помощью PartFactory.

Кроме того, XML элемента Properties тоже неправильный. См. пример и описания в разделе Установка пользовательского свойства в текстовом документе. Это относится к текстовому документу, но то же самое относится и к слайд-шоу презентаций.

На связанной странице Learn-Microsoft.com описано:

  • Каждое свойство в содержимом XML состоит из элемента XML, который включает имя и стоимость имущества.
  • Для каждого свойства содержимое XML включает атрибут fmtid, который всегда установлено одно и то же строковое значение: {D5CDD505-2E9C-101B-9397-08002B2CF9AE}.
  • Каждое свойство в содержимом XML включает атрибут pid, который должен включите целое число, начинающееся с 2, для первого свойства и увеличивается для каждого последующего свойства.
  • Каждое свойство отслеживает свой тип (на рисунке vt:lpwstr и vt:filetime имена элементов определяют типы каждого свойства).

Поскольку это исходит от самой Microsoft, вы не найдете более достоверного описания. Но для типов свойств я могу сказать вам по собственному сроку действия, что есть lpwstr, filetime и bool, а также i4 для целых чисел и r8 для чисел с плавающей запятой.

Следующий код работает для меня и создает aaa.pptx с тремя наборами пользовательских свойств.

from pptx import Presentation

presentation = Presentation()
first_slide_layout = presentation.slide_layouts[0]

slide = presentation.slides.add_slide(first_slide_layout)

slide.shapes.title.text = 'Created by Python-pptx'

presentation.save('./test.pptx')
####-----> until here code works.

## Below part of adding custom properties works too now.

from pptx.opc.constants import (
    RELATIONSHIP_TYPE,
    CONTENT_TYPE,
)
from pptx.opc.packuri import PackURI
from pptx.opc.package import PartFactory

def add_custom_properties(presentation, name, value, value_type):
    contains_custom_property = False
    for part in presentation.part.package.iter_parts():
        if part.partname == '/docProps/custom.xml':
            custom_property_part = part
            contains_custom_property = True
            
    pid = 2    
    if not contains_custom_property:
        custom_property_xml = '<Properties xmlns = "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">'
        custom_property_xml += f'<property fmtid = "{{D5CDD505-2E9C-101B-9397-08002B2CF9AE}}" pid = "{pid}" name = "{name}">'
        custom_property_xml += f'<vt:{value_type}>{value}</vt:{value_type}>'
        custom_property_xml += '</property>'
        custom_property_xml += '</Properties>'
        
        custom_property_pack_uri = PackURI('/docProps/custom.xml')
        custom_property_content_type = CONTENT_TYPE.OFC_CUSTOM_PROPERTIES
        custom_property_part = PartFactory(custom_property_pack_uri, custom_property_content_type, presentation.part.package, custom_property_xml.encode('utf-8'))

        presentation.part.package.relate_to(custom_property_part, RELATIONSHIP_TYPE.CUSTOM_PROPERTIES)
    else:
        custom_property_xml = custom_property_part.blob.decode('utf-8')
        custom_property_xml = custom_property_xml[:-13]
        
        pid = 2 + custom_property_xml.count('<property')
        
        custom_property_xml += f'<property fmtid = "{{D5CDD505-2E9C-101B-9397-08002B2CF9AE}}" pid = "{pid}" name = "{name}">'
        custom_property_xml += f'<vt:{value_type}>{value}</vt:{value_type}>'
        custom_property_xml += '</property>'    
        custom_property_xml += '</Properties>'
        custom_property_part.blob = custom_property_xml.encode('utf-8')

add_custom_properties(presentation, "test_key", "test_key_value", "lpwstr")
add_custom_properties(presentation, "number", "1234", "i4")
add_custom_properties(presentation, "date", "2024-08-06", "filetime")

# while saving not throws any error.
presentation.save('./aaa.pptx')

Интересные вопросы для изучения

Как выполнить горячее кодирование столбца, который существует в нескольких кадрах данных pandas?Печать выходных строк стандартного вывода подпроцессаКак гарантировать, что вставки принимают только значения, определенные в Enum?Невозможно проанализировать результаты веб-страницы с помощью модуля запросовНевозможно очистить все данные из таблицы с отложенной загрузкой с помощью SeleniumИсточник данных databricks_metastores — я пытаюсь использовать его в Azure, чтобы узнать идентификатор хранилища метаданных. Получение ошибки: неверная конфигурация учетной записи блоков данныхНевозможно выполнить блокнот Databricks из фабрики данных AzureАвтоматическое создание соединителя доступа к единству-каталогуНевозможно создать таблицу на подключенном диске в Databricks: для пути не найдено родительское внешнее местоположениеData Factory Parquet неправильно принимает десятичные дроби