我有两个PPT(File1.pptx和File2.pptx),其中有以下两行

XX NOV 2021, Time: xx:xx – xx:xx hrs (90mins)
FY21/22 / FY22/23

我希望如下所示更换

(A)NOV 2021NOV 2022.

(B)FY21/22 / FY22/23FY21/22 or FY22/23.

但问题是,我的替代品在File1.pptx可以工作,但在File2.pptx不能工作.

当我打印运行文本时,我能够看到它们在两张幻灯片中以不同的方式表示.

def replace_text(replacements:dict,shapes:list):
    for shape in shapes:
        for match, replacement in replacements.items():
            if shape.has_text_frame:
                if (shape.text.find(match)) != -1:
                    text_frame = shape.text_frame
                    for paragraph in text_frame.paragraphs:
                        for run in paragraph.runs:
                            cur_text = run.text
                            print(cur_text)
                            print("---")
                            new_text = cur_text.replace(str(match), str(replacement))
                            run.text = new_text

在File1.pptx中,cur_text如下所示(表示第一个关键字).所以,我的替换起作用了(因为它包含了我正在寻找的关键字)

enter image description here

但在File2.pptx中,cur_text如下所示(表示第一个关键字).因此,替换不起作用(因为cur_text与我的搜索词不匹配)

enter image description here

同样的问题也发生在第二个关键字FY21/22 / FY22/23.

问题是Split关键字可能在当前运行的上一次运行或下一次运行中(没有模式).因此,我们应该能够将搜索项与之前的运行项(以及当前项)进行比较.然后可以找到匹配项(如2021年11月)并进行替换.

这个问题只发生在10%的搜索词(并不是我所有的搜索词),但这个问题的存在是可怕的,因为如果百分比增加,我们可能不得不做大量的手动工作.我们如何避免这种情况并正确编码呢?

如何获取/提取/查找/识别我们正在多次运行(如果确实存在)的单词,如CTRL+F并将其替换为所需的关键字?

有什么需要帮忙的吗?

推荐答案

正如您可以在https://python-pptx.readthedocs.io/en/latest/api/text.html%的python-pptx文档中找到的那样

  1. 文本框架由段落和
  2. 段落由游程组成,并指定用作其游程默认设置的字体配置.
  3. 运行指定具有特定字体配置的段落文本的一部分-可能与段落中的默认字体配置不同

这三个字段都有一个名为text的字段:

  1. 文本框架的text包含它的所有段落中的所有文本,它们连接在一起,并在段落之间使用适当的换行符.
  2. 段落的text包含它的所有运行的所有文本,这些文本连接成一个长字符串,在运行的任何文本中出现所谓的软换行符(软换行符)的地方都放置一个垂直制表符(\v)(软换行符类似于换行符,但不会结束段落).
  3. 游程text包含要用特定字体配置(字体系列、字体大小、斜体/粗体/下划线、 colored颜色 等)呈现的文本.它是任何文本的字体配置的最低级别.

现在,如果您在PowerPoint演示文稿的文本框架中指定一行文本,则此文本框架很可能只有一个段落,该段落将只有一个运行.

假设这一行写着:Hi there! How are you? What is your name?,并且都是正常的(既不斜体也不粗体),大小为10.

现在,如果你继续使用PowerPoint,并通过将问题How are you? What is your name?设置为斜体来突出它们,你将在我们的段落中得到2个连续的结果:

  1. Hello there! ,使用段落中的默认字体配置
  2. How are you? What is you name?,字体配置指定附加的italic属性.

现在想象一下,我们想让How are you?更突出,把它变成粗体和斜体.我们最终得了3分:

  1. Hello there! ,使用段落中的默认字体配置.
  2. How are you?,字体配置指定BOLDITALIC属性
  3. What is your name?,其中字体配置指定ITALIC属性.

更进一步,使How are you?人中的are人更大.我们得到5分:

  1. Hello there! ,使用段落中的默认字体配置.
  2. How ,字体配置指定BOLDITALIC属性
  3. are,字体配置指定BOLDITALIC属性以及字体大小16
  4. you?,字体配置指定BOLDITALIC属性
  5. What is your name?,其中字体配置指定ITALIC属性.

因此,如果您试图用您问题中的代码I'm fine!来替换How are you?,您将不会成功,因为文本How are you?实际上分布在3个运行中.

您可以再往上一层查看段落的文本,仍然是Hello there! How are you? What is your name?,因为它是所有运行的文本的串联.

但如果您继续替换段落的文本,它将擦除所有运行,并始终使用文本Hello there! I'm fine! What is your name?创建一个新运行,同时删除我们在What is your name?上设置的所有格式.

因此,在不影响段落中其他文本格式的情况下更改段落中的文本是相当复杂的.即使您要查找的文本具有所有相同的格式,也不能保证它在一次运行中.因为如果你--在上面的例子中--再把are变小,5个游程很可能会保留下来,2到4个游程现在只有相同的字体配置.

下面是生成一个测试演示文稿的代码,其中包含一个文本框,其中包含与我上面的示例中给出的完全相同的段落:

from pptx import Presentation
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE,XL_LABEL_POSITION
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.dml import MSO_THEME_COLOR

# create presentation with 1 slide ------
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[5])
textbox_shape = slide.shapes.add_textbox(Pt(200),Pt(200),Pt(30),Pt(240))
text_frame = textbox_shape.text_frame
p = text_frame.paragraphs[0]
font = p.font
font.name = 'Arial'
font.size = Pt(10)
font.bold = False
font.italic = False
font.color.rgb = RGBColor(0,0,0)

run = p.add_run()
run.text = 'Hello there! '

run = p.add_run()
run.text = 'How '
font = run.font
font.italic = True
font.bold = True

run = p.add_run()
run.text = 'are'
font = run.font
font.italic = True
font.bold = True
font.size = Pt(16)

run = p.add_run()
run.text = ' you?'
font = run.font
font.italic = True
font.bold = True

run = p.add_run()
run.text = ' What is your name?'
run.font.italic = True

prs.save('text-01.pptx')

如果你在PowerPoint中打开它,它看起来是这样的:

The created presentation slide

现在,如果您在其上使用此代码:

from pptx import Presentation
from pptx.chart.data import CategoryChartData
from pptx.shapes.graphfrm import GraphicFrame
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Inches

def replace_text(replacements, shapes):
    for shape in shapes:
        if shape.has_text_frame:
            text_frame = shape.text_frame
            for (match, replacement) in replacements.items():
                if text_frame.text.find(match)>=0:
                    for paragraph in text_frame.paragraphs:
                        pos = paragraph.text.find(match)
                        while pos>=0:
                            replace_runs_text(paragraph.runs, pos, len(match), replacement)
                            pos = paragraph.text.find(match)

def replace_runs_text(runs, pos, match_len, replacement):
    cnt = len(runs)
    i = 0
    while i<cnt:
        olen = len(runs[i].text)
        if pos<olen:
            # we found the run, where the match starts!
            to_replace = replacement
            repl_len = len(to_replace)

            while i<cnt:
                run = runs[i]
                otext = run.text
                olen = len(otext)
                if pos+match_len < olen:
                    # our match ends before the end of the text of this run therefore
                    # we put the rest of our replacement string here and we are done!
                    run.text = otext[0:pos]+to_replace+otext[pos+match_len:]
                    return
                if pos+match_len == olen:
                    # our match ends together with the text of this run therefore
                    # we put the rest of our replacement string here and we are done!
                    run.text = otext[0:pos]+to_replace
                    return
                # we still haven't found all of our original match string
                # so we process what we have here and go on to the next run
                part_match_len = olen-pos
                ntext = otext[0:pos]
                if repl_len <= part_match_len:
                    # we now found at least as many characters for our match string
                    # as we have replacement characters for it. Thus we use up the
                    # the rest of our replacement string here and will replace the
                    # remainder of the match with an empty string (which happens
                    # to happen in this exact same spot for the next run ;-))
                    ntext += to_replace
                    repl_len = 0
                    to_replace = ''
                else:
                    # we have got some more match characters but still more
                    # replacement characters than match characters found 
                    ntext += to_replace[0:part_match_len]
                    to_replace = to_replace[part_match_len:]
                    repl_len -= part_match_len
                run.text = ntext            # save the new text to the run
                match_len -= part_match_len # this is what is left to match
                pos = 0                     # in the next run, we start at pos 0 with our match
                i += 1                      # and off to the next run
        else:
            pos -= olen # the relative position of our match in the next run's text
            i += 1      # and off to the next run
            

# create presentation with 1 slide ------
prs = Presentation('text-01.pptx')

# what is to be replaced
replacements = { 'How are you?': "I'm fine!" }

# loop through all slides and replace text in all their shapes
for slide in prs.slides:
    replace_text(replacements, slide.shapes)

# save changed presentation
prs.save('text-02.pptx')

生成的演示文稿如下所示:

The changed presentation

正如您所看到的,它将替换字符串精确地映射到现有的字体配置上,因此,如果您的匹配项和它的替换项具有相同的长度,则替换字符串将保留匹配的确切格式.

但是--作为一个重要的旁注--如果文本框架打开了自动调整大小或适合框架,即使所有的工作也不能避免你搞砸格式,如果替换后的文本需要或多或少的空间!

Python相关问答推荐

Pystata:从Python并行运行stata实例

将特定列信息移动到当前行下的新行

运行Python脚本时,用作命令行参数的SON文本

图像 pyramid .难以创建所需的合成图像

如何从在虚拟Python环境中运行的脚本中运行需要宿主Python环境的Shell脚本?

Polars:用氨纶的其他部分替换氨纶的部分

运输问题分支定界法&

Pandas:将多级列名改为一级

从spaCy的句子中提取日期

为什么if2/if3会提供两种不同的输出?

matplotlib + python foor loop

python中csv. Dictreader. fieldname的类型是什么?'

Flask Jinja2如果语句总是计算为false&

python sklearn ValueError:使用序列设置数组元素

如何在Gekko中使用分层条件约束

Pandas在rame中在组内洗牌行,保持相对组的顺序不变,

使用xlsxWriter在EXCEL中为数据帧的各行上色

如何获取给定列中包含特定值的行号?

对包含JSON列的DataFrame进行分组

对列中的数字进行迭代,得到n次重复开始的第一个行号