问题提出原因、背景
在高可用云电脑的项目中,需要将云电脑中的所有文件同步到另一台云电脑中,这些文件中就有许多是Office文件。而在实际运行中发现,Office源文件的微小改动,都会使得整个Office文件都需要重新同步,例如,在Word文件的末尾中,添加一个逗号,整个Word文件都发生了变化(在磁盘层面观察)。这样同步传输时耗费的时间和网络资源都很大。
因此,需要调研Office的缓存机制,了解为何Office的微小改动会导致整个文件都发生变化,继而得出该问题的解决办法。
由上一篇“Office调研——Office的缓存机制”的文章中通过调研得出了Office的缓存、落盘机制,并且证明了是“由于Office软件在每次保存中,都将全量数据(包括增量)写入到新的文件中,所以从磁盘层面观察,无论是多微小的改动,都是产生了一个新文件”,导致Office的微小改动会导致整个文件都发生变化。
本篇继续调研上一篇“Office调研——Office的缓存机制”的文章中提出的获取Office增量的三种方法是否可行。
1.问题描述
- 方法1:将ppt解压,变成分散的文件,对比各个文件的差异,按照文件的方式去获取增量内容。是否可行?
- 方法2:利用python,读取ppt,在python内比较两个ppt之间的差异,将差异部分以某种文件的形式保存下来,得到保存增量数据的文件。是否可行?
- 方法3:利用VBA编程,将代码直接插入到Office里面,获取Office的增量数据。是否可行?
2.方法1:将ppt解压,变成分散的文件,对比各个文件的差异
2.1.对 word、excel、ppt解压后(zip 解压)的文件结构,如下:
Word | excel | ppt |
2.2.修改word内容,记录word解压后的文件的改变情况
修改前文件大小 | 修改后文件大小 |
text001.docx :570KB 解压后:604KB 图片:557KB 其他:43.9KB |
text001.docx :1.095MB 解压后:1.10MB 图片:1050KB 其他:45.7KB 其中新增图片:525KB |
若按照docx文件传输,则需要传输 1.095 MB
若按照解压后的文件传输,则至少需要传输 526.317KB ,最多需要传输 1.10MB
2.3.修改excel内容,记录excel解压后的文件的改变情况
修改前文件大小 | 修改后文件大小 |
若按照excel文件传输,则需要传输 9KB
若按照解压后的文件传输,则至少需要传输 0.2KB ,最多需要传输 28KB
2.4.修改ppt内容,记录ppt解压后的文件改变情况
修改前文件大小 | 修改后文件大小 |
若按照excel文件传输,则需要传输 346KB
若按照解压后的文件传输,则至少需要传输 11KB ,最多需要传输 407KB
2.5.使用 beyond compare 工具对比文件前后改变情况
Word:增加了两行文字,增加了一张图片
Excel:增加了两个单元格内容。
ppt:第一张幻灯片调整了文字框,没有增加文字。
新增了2,3,4,5,6幻灯片,幻灯片内容都为文字
解压后传输 26185 字节,约 25.57KB
pptx传输 353780 字节,约 345.49KB
传输量约为原本的 7.4 01%
2.6.结论
方法1可行。将ppt,word,excel文件解压,变成分散的文件,对比各个文件的差异,然后传输这些差异文件,为一个可行,且过程不太复杂的方法。
3.方法2:利用python,读取ppt,在python内比较两个ppt之间的差异,将差异部分以某种文件的形式保存下来,得到保存增量数据的文件
3.1.以ppt文件为例,利用python-pptx库实现获取ppt的增量数据,编写demo代码
demo代码:
import pathlib
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.shapes import MSO_SHAPE_TYPE
# 借用openpyxl的单位转换
from openpyxl.utils.units import EMU_to_pixels as e2p
def output_diff_dict(diff_dict):
print(f'========== print diff dict ==========')
for key, value in diff_dict.items():
print(f'key = {key} | value = {value} | type = {type(value)}')
path = list(pathlib.Path.cwd().parents)[1].joinpath('D:\\xxxxx')
img_out_path = path.joinpath('ppt_out_images')
txt_out_path = path.joinpath('ppt_notes.txt')
def create_new_ppt(origin_ppt, diff_dict):
for key,value in diff_dict.items():
key_temp = key.split(",")
key_len = len(key_temp)
# 添加页
# todo
if key_len == 1:
if value == 'NULL':
origin_ppt.slides._sldIdLst.remove[int(key_temp[0])]
else:
# todo
print('need add slides')
# 添加 shape
# todo
if key_len == 2:
shapes_temp = origin_ppt.slides[int(key_temp[0])].shapes
if value == 'NULL':
# todo
print('need delete shape')
else:
if (value.shape_type == MSO_SHAPE_TYPE.PICTURE):
#shapes_temp.add_picture(value, left = value.left, top = value.top)
# 获取二进制字符流
imdata = value.image.blob
# 判断文件后缀类型
imagetype = value.image.content_type
typekey = imagetype.find('/') + 1
imtype = imagetype[typekey:]
# 图片生成
image_file = ".\\pic001" + "." + imtype
file_str=open(image_file,'wb')
file_str.write(imdata)
file_str.close()
shapes_temp.add_picture(image_file, left = value.left, top = value.top, width=value.width, height=value.height)
elif (value.has_text_frame):
new_text = shapes_temp.add_textbox(left=value.left, top=value.top, width=value.width, height=value.height).text_frame
new_text.paragraphs[0].text = value.text_frame.text
else:
print('need add shape')
# 修改 shape
# todo
if key_len == 3:
slide_layouts_temp = origin_ppt.slide_master.slide_layouts
shape_temp = origin_ppt.slides[int(key_temp[0])].shapes[int(key_temp[1])]
# text 类型
if key_temp[2] == 'text':
if value == 'NULL':
#todo
print('need delete text')
else:
shape_temp.text_frame.text = value
# image 类型
if key_temp[2] == 'image':
if value == 'NULL':
print('need delete image')
else:
shape_temp.image = value
print('save ppt.')
origin_ppt.save("text003.pptx")
# 获取到对象
in_path1 = path.joinpath('test001.pptx')
prs1 = Presentation(in_path1)
in_path2 = path.joinpath('test002.pptx')
prs2 = Presentation(in_path2)
# 存储位置 + 内容
diff_dict = {}
# 比较页数,存储不同的整页
print(f'========== compare slide len ==========')
slide1_len = len(prs1.slides)
slide2_len = len(prs2.slides)
# EMU单位,1 Pixel = 9525 EMU
w, h = prs1.slide_width, prs1.slide_height
print(f'ppt1 总共 {slide1_len} 页,页宽 {e2p(w)} px,页高 {e2p(h)} px')
w, h = prs2.slide_width, prs2.slide_height
print(f'ppt2 总共 {slide2_len} 页,页宽 {e2p(w)} px,页高 {e2p(h)} px')
less_slide_len = slide1_len
if(slide1_len != slide2_len):
if(slide1_len < slide2_len):
for i in range(slide1_len, slide2_len):
diff_dict[f"{i}"] = prs2.slides[i]
else:
less_slide_len = slide2_len
for i in range(slide2_len, slide1_len):
diff_dict[f"{i}"] = 'NULL'
# 比较每页,存储每页不同
for i in range(less_slide_len):
print(f'========== compare slide[{i}] ==========')
slide1 = prs1.slides[i]
slide2 = prs2.slides[i]
slide_key = f"{i}"
# 比较每页里面的形状shape,shape包含ppt里面一切可视元素
shape1_len = len(slide1.shapes)
shape2_len = len(slide2.shapes)
less_shape_len = shape1_len
if(shape1_len != shape2_len):
if(shape1_len < shape2_len):
for j in range(shape1_len, shape2_len):
shape_key = slide_key + f",{j}"
diff_dict[shape_key] = slide2.shapes[j]
else:
less_shape_len = shape2_len
for j in range(shape2_len, shape1_len):
shape_key = slide_key + f",{j}"
diff_dict[shape_key] = 'NULL'
print(f'ppt1 总共 {shape1_len} 个shape')
print(f'ppt2 总共 {shape2_len} 个shape')
# 比较每个形状
for j in range(less_shape_len):
print(f'========== compare shape[{j}] ==========')
shape1 = slide1.shapes[j]
shape2 = slide2.shapes[j]
shape_key = slide_key + f",{j}"
# 比较shape里的文本
if (shape1.has_text_frame) :
text_key = shape_key + ",text"
if (shape2.has_text_frame):
text1 = shape1.text_frame.text
text2 = shape2.text_frame.text
print(f'ppt1 = {text1}')
print(f'ppt2 = {text2}')
if( text1 != text2):
print(f'text different.')
diff_dict[text_key] = text2
else:
print(f'ppt2 has not text.')
diff_dict[text_key] = 'NULL'
# 比较shape里的图片
if (shape1.shape_type == MSO_SHAPE_TYPE.PICTURE):
image_key = shape_key + ",image"
if (shape2.shape_type == MSO_SHAPE_TYPE.PICTURE):
img1 = shape1.image
img2 = shape2.image
if( img1 != img2):
print(f'image different.')
diff_dict[image_key] = img2
else:
print(f'ppt2 has not image.')
diff_dict[image_key] = 'NULL'
output_diff_dict(diff_dict)
create_new_ppt(prs1, diff_dict)
3.2.结论
方法2可行。但提取出增量数据需要原文件和新文件,将两个文件读取到python内存中比较,代码需要循环比较,过程复杂。
4.方法3:利用VBA编程,将代码直接插入到Office里面,获取Office的增量数据
4.1.以ppt文件为例,调研VBA编程可以获取到ppt对象模型中的全量数据,还是增量数据。
经过调研、代码实践可知,VBA 编程中获取的 ppt 对象数据为全量数据,证明如下:
1. 利用VBA代码访问打开的 ppt 中的对象,检查其提取该对象的文本内容。
在未保存时,提取的结果为该对象的所有文本(不区分原有和新增)
在按下 “CTRL+S” 保存时,提取的结果依然为该对象的所有文本(不区分原有和新增)
2. 对官方网站提供的API接口进行查询,未发现专门存储增量数据的对象。从API文档中可以看出,ppt在内存中是以一个个对象进行存储的,该对象包含了ppt的所有信息,用户在编辑ppt时,实际上就在动态地修改内存中的对象(对象中存储的数据总是原有+增量,即最新的全量数据),用户敲下CTRL+S时,系统对内存中的所有ppt对象进行落盘存储,并覆盖原来的ppt文件,形成新的ppt文件。
3. 即使ppt在缓存中把原有的数据和增量的数据分开存储了,在VBA层面也是不可知的,因为VBA是调用ppt的API进行访问,API中没有对增量数据的访问,则VBA无法获取增量数据。
4.1.1.结论
由以上3点可以推断,利用VBA工具也只能先提取出ppt的全量数据,再与原有的ppt文件进行对比,才能得出增量的数据。
4.2. ppt缓存对象结构
由以上获得的信息可得ppt的缓存对象模型结构,如下:
4.3.VBA 获取ppt增量数据考虑的实现方法
4.3.1.针对word、excel
- 使用录制宏,记录下用户操作,然后传输这个宏文件。(即自动生成的VBA代码)【不可行】
- 问题:录制宏能否实现自动化,使得用户无感知?
- 答:目前未发现录制宏的自动化功能。
- 问题:录制宏能否实现自动化,使得用户无感知?
- 使用 VBA 代码,动态获取 word、excel 的对象,跟原对象进行对比,找出差异进行传输。【过程复杂,繁琐】
- 问题:具体代码实现有哪些需要考虑的问题?
- 答:代码较为繁琐,但可以实现。需要评估获取差异所耗费的时间、资源和直接全量传输所耗费的时间、资源。
- 问题:具体代码实现有哪些需要考虑的问题?
4.3.2.针对ppt
- 使用 VBA 代码,动态获取ppt的对象,跟原对象进行对比,找出差异进行传输。
- 问题:与前面的一样。
4.4.从官方文档中寻找比较内容差异相关的API接口
- Application.CompareDocuments (word)【不可行】
- 能够返回有修订记录的文档,不符合要求。
- Windows.CompareSideBySideWith method (Excel)【不可行】
- 能够打开两个窗口,不符合要求。
4.5.结论
方法3可行。利用 VBA 编程考虑能够实现Office文件内容差异的提取,但是过程较为繁琐,复杂。
5.综合结论
1. 方法1:将ppt解压,变成分散的文件,对比各个文件的差异,按照文件的方式去获取增量内容。是否可行?
答:方法1可行。将ppt,word,excel文件解压,变成分散的文件,对比各个文件的差异,然后传输这些差异文件,为一个可行,且过程不太复杂的方法。
2. 方法2:利用python,读取ppt,在python内比较两个ppt之间的差异,将差异部分以某种文件的形式保存下来,得到保存增量数据的文件。是否可行?
答:方法2可行。但提取出增量数据需要原文件和新文件,将两个文件读取到python内存中比较,代码需要循环比较,过程复杂。
3. 方法3:利用VBA编程,将代码直接插入到Office里面,获取Office的增量数据。是否可行?
答:方法3可行。利用 VBA 编程考虑能够实现Office文件内容差异的提取,但是过程较为繁琐,复杂。