問題提出原因、背景
在(zai)(zai)(zai)高可用云(yun)電(dian)腦的項目中(zhong),需(xu)要(yao)將云(yun)電(dian)腦中(zhong)的所有文(wen)件(jian)(jian)(jian)同(tong)(tong)步到另一臺云(yun)電(dian)腦中(zhong),這些文(wen)件(jian)(jian)(jian)中(zhong)就有許(xu)多是(shi)Office文(wen)件(jian)(jian)(jian)。而在(zai)(zai)(zai)實際(ji)運(yun)行(xing)中(zhong)發(fa)現,Office源文(wen)件(jian)(jian)(jian)的微小改(gai)動,都(dou)會使得整個Office文(wen)件(jian)(jian)(jian)都(dou)需(xu)要(yao)重(zhong)新同(tong)(tong)步,例如,在(zai)(zai)(zai)Word文(wen)件(jian)(jian)(jian)的末尾中(zhong),添加一個逗(dou)號,整個Word文(wen)件(jian)(jian)(jian)都(dou)發(fa)生了(le)變化(在(zai)(zai)(zai)磁(ci)盤層面觀(guan)察(cha))。這樣同(tong)(tong)步傳(chuan)輸(shu)時(shi)耗費的時(shi)間(jian)和網絡資源都(dou)很(hen)大。
因此,需要(yao)調研Office的緩存機制,了解為何Office的微小改(gai)動會(hui)導致整個文件(jian)都(dou)發生變化,繼(ji)而得出該(gai)問(wen)題的解決辦(ban)法。
由上一(yi)篇“Office調研——Office的緩存機制”的文(wen)章中通過調研得出了(le)Office的緩存、落盤機制,并且(qie)證明了(le)是“由于Office軟件在每次保存中,都將全量(liang)數據(包括(kuo)增(zeng)量(liang))寫入到新的文(wen)件中,所(suo)以從磁(ci)盤層面觀察(cha),無(wu)論是多微(wei)小(xiao)(xiao)的改動,都是產生了(le)一(yi)個新文(wen)件”,導致(zhi)Office的微(wei)小(xiao)(xiao)改動會導致(zhi)整個文(wen)件都發(fa)生變化。
本篇繼(ji)續調研上一篇“Office調研——Office的(de)緩(huan)存機(ji)制”的(de)文章(zhang)中提(ti)出的(de)獲(huo)取Office增量的(de)三(san)種方法是否可行。
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 |
若按(an)照docx文件傳輸,則需要傳輸 1.095 MB
若按照解(jie)壓(ya)后(hou)的(de)文件傳輸(shu),則至(zhi)少需要傳輸(shu) 526.317KB ,最(zui)多需要傳輸(shu) 1.10MB
2.3.修改excel內容,記錄excel解壓后的文件的改變情況
| 修改前文件大小 | 修改后文件大小 |
![]() |
![]() |
若按照excel文件傳輸,則需(xu)要(yao)傳輸 9KB
若按(an)照解壓后的文(wen)件傳輸,則(ze)至少需要傳輸 0.2KB ,最多需要傳輸 28KB
2.4.修改ppt內容,記錄ppt解壓后的文件改變情況
| 修改前文件大小 | 修改后文件大小 |
![]() |
![]() |
若(ruo)按(an)照excel文件傳輸,則(ze)需(xu)要傳輸 346KB
若按照解壓后的文(wen)件(jian)傳(chuan)輸,則至少需要傳(chuan)輸 11KB ,最多需要傳(chuan)輸 407KB
2.5.使用 beyond compare 工具對比文件前后改變情況
Word:增加了兩行文字,增加了一(yi)張圖片(pian)

Excel:增加了兩個(ge)單元(yuan)格內容。

ppt:第一張(zhang)幻燈片(pian)調整了文字框,沒有增(zeng)加文字。
新增了(le)2,3,4,5,6幻(huan)燈(deng)片,幻(huan)燈(deng)片內容都為文(wen)字

解壓后傳輸(shu) 26185 字節,約 25.57KB
pptx傳輸 353780 字(zi)節(jie),約 345.49KB
傳(chuan)輸量約為原本的 7.4 01%
2.6.結論
方法1可(ke)(ke)行(xing)。將ppt,word,excel文件解(jie)壓,變成(cheng)分散的文件,對比各個(ge)文件的差異(yi),然后傳輸這些差異(yi)文件,為一個(ge)可(ke)(ke)行(xing),且過程不太復雜的方法。
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可行。但提(ti)取(qu)出增量(liang)數據需要原文(wen)件(jian)和新文(wen)件(jian),將兩個文(wen)件(jian)讀取(qu)到python內存中比較,代碼需要循環比較,過程(cheng)復雜(za)。
4.方法3:利用VBA編程,將代碼直接插入到Office里面,獲取Office的增量數據
4.1.以ppt文件為例,調研VBA編程可以獲取到ppt對象模型中的全量數據,還是增量數據。
經過調研、代碼實踐可知,VBA 編(bian)程(cheng)中獲取的 ppt 對象數(shu)據為全量數(shu)據,證明如下:
1. 利用VBA代碼訪問(wen)打開的 ppt 中(zhong)的對象(xiang),檢查其提取該對象(xiang)的文本內容。
在未保存時,提取的結果為該對象的所(suo)有(you)文(wen)本(不區(qu)分原有(you)和新(xin)增)

在按下(xia) “CTRL+S” 保存時,提取的結果依然為該對象的所有(you)文本(不區(qu)分原有(you)和新(xin)增)

2. 對官(guan)方(fang)網站提供的(de)(de)API接口進(jin)(jin)行(xing)查詢,未發現專(zhuan)門存(cun)儲(chu)增(zeng)量數據的(de)(de)對象(xiang)。從API文(wen)檔中(zhong)(zhong)可以(yi)看出,ppt在(zai)(zai)內存(cun)中(zhong)(zhong)是以(yi)一個個對象(xiang)進(jin)(jin)行(xing)存(cun)儲(chu)的(de)(de),該(gai)對象(xiang)包含(han)了ppt的(de)(de)所有(you)信息,用戶在(zai)(zai)編輯ppt時,實際上就在(zai)(zai)動態(tai)地修改內存(cun)中(zhong)(zhong)的(de)(de)對象(xiang)(對象(xiang)中(zhong)(zhong)存(cun)儲(chu)的(de)(de)數據總是原有(you)+增(zeng)量,即最(zui)新(xin)的(de)(de)全量數據),用戶敲(qiao)下CTRL+S時,系統對內存(cun)中(zhong)(zhong)的(de)(de)所有(you)ppt對象(xiang)進(jin)(jin)行(xing)落盤存(cun)儲(chu),并覆蓋原來(lai)的(de)(de)ppt文(wen)件(jian),形成新(xin)的(de)(de)ppt文(wen)件(jian)。

3. 即使ppt在緩存中把原有的數(shu)據(ju)和(he)增量的數(shu)據(ju)分開(kai)存儲(chu)了,在VBA層面(mian)也是不可(ke)知的,因(yin)為VBA是調(diao)用ppt的API進(jin)行(xing)訪問(wen),API中沒有對增量數(shu)據(ju)的訪問(wen),則(ze)VBA無法(fa)獲(huo)取增量數(shu)據(ju)。
4.1.1.結論
由以上3點可以推斷,利用VBA工(gong)具也只能先提取(qu)出(chu)ppt的全量(liang)數(shu)(shu)據(ju),再與原有(you)的ppt文件進(jin)行(xing)對比,才能得出(chu)增量(liang)的數(shu)(shu)據(ju)。
4.2. ppt緩存對象結構
由(you)以上獲得的(de)信息(xi)可得ppt的(de)緩存對象模型結(jie)構(gou),如下:

4.3.VBA 獲取ppt增量數據考慮的實現方法
4.3.1.針對word、excel
- 使用錄制宏,記錄下用戶操作,然后傳輸這個宏文件。(即自動生成的VBA代碼)【不可行】
- 問題:錄制宏能否實現自動化,使得用戶無感知?
- 答:目前未發現錄制宏的自動化功能。
- 問題:錄制宏能否實現自動化,使得用戶無感知?
- 使用 VBA 代碼,動態獲取 word、excel 的對象,跟原對象進行對比,找出差異進行傳輸。【過程復(fu)雜,繁瑣】
- 問題:具體代碼實現有哪些需要考慮的問題?
- 答:代碼較為繁瑣,但可以實現。需要評估獲取差異所耗費的時間、資源和直接全量傳輸所耗費的時間、資源。
- 問題:具體代碼實現有哪些需要考慮的問題?
4.3.2.針對ppt
- 使用 VBA 代碼,動態獲取ppt的對象,跟原對象進行對比,找出差異進行傳輸。
- 問題:與前面的一樣。
4.4.從官方文檔中尋找比較內容差異相關的API接口
- Application.CompareDocuments (word)【不可行(xing)】
- 能夠返回有修訂記錄的文檔,不符合要求。
- Windows.CompareSideBySideWith method (Excel)【不可(ke)行】
- 能夠打開兩個窗口,不符合要求。
4.5.結論
方法3可行(xing)。利用 VBA 編程考(kao)慮能夠(gou)實現Office文件內(nei)容差(cha)異的提取,但是(shi)過程較(jiao)為繁瑣,復雜(za)。
5.綜合結論
1. 方法1:將ppt解壓,變成分散的(de)文(wen)件(jian),對比(bi)各個文(wen)件(jian)的(de)差異,按照文(wen)件(jian)的(de)方式去獲取增量內容。是否可行(xing)?
答:方法1可行(xing)(xing)。將ppt,word,excel文件(jian)解壓(ya),變成分散的文件(jian),對比各個(ge)(ge)文件(jian)的差異(yi),然(ran)后(hou)傳輸這些差異(yi)文件(jian),為(wei)一個(ge)(ge)可行(xing)(xing),且過程不(bu)太復雜(za)的方法。
2. 方法2:利用python,讀取ppt,在python內比較兩個ppt之間的差(cha)異,將差(cha)異部分以某種文(wen)件的形式保存(cun)下來,得到(dao)保存(cun)增(zeng)量數據的文(wen)件。是否可行?
答:方法2可行。但提取(qu)出增量數(shu)據需要(yao)原(yuan)文件和新文件,將兩個文件讀取(qu)到python內存中比較(jiao),代碼需要(yao)循環比較(jiao),過程(cheng)復(fu)雜。
3. 方法3:利用VBA編程,將代碼直(zhi)接插(cha)入到(dao)Office里(li)面,獲取Office的增量數(shu)據。是否可行?
答:方(fang)法(fa)3可(ke)行。利(li)用 VBA 編程考慮能夠(gou)實(shi)現Office文件內(nei)容差異的提取,但是過程較為繁瑣,復雜。








