2017-04-01 105 views
8

我想使用Python添加和從Office/Excel文檔中提取文件。到目前爲止,添加事物很容易,但對於提取我還沒有找到一個乾淨的解決方案Python:無需剪貼板,從Office/Excel文檔訪問嵌入式OLE

爲了清楚我已經得到了什麼,以及我沒有寫下這個小例子test.py下面並進一步解釋。

test.py

import win32com.client as win32 
import os 
from tkinter import messagebox 
import win32clipboard 

# (0) Setup 
dir_path = os.path.dirname(os.path.realpath(__file__)) 
print(dir_path) 
excel = win32.gencache.EnsureDispatch('Excel.Application') 
wb = excel.Workbooks.Open(dir_path + "\\" + "test_excel.xlsx") 
ws = wb.Worksheets.Item(1) 
objs = ws.OLEObjects() 

# (1) Embed file 
f = dir_path + "\\" + "test_txt.txt" 
name = "test_txt_ole.txt" 
objs.Add(Filename=f, IconLabel=name) 

# (2) Access embedded file 
obj = objs.Item(1) # Get single OLE from OLE list 
obj.Copy() 
win32clipboard.OpenClipboard() 
data = win32clipboard.GetClipboardData(0xC004) # Binary access 
win32clipboard.EmptyClipboard() 
win32clipboard.CloseClipboard() 
messagebox.showinfo(title="test_txt_ole.txt", message=str(data)) 

# (3) Press don't save here to keep 
# wb.Close() # Will close excel document and leave excel opened. 
excel.Application.Quit() # Will close excel with all opened documents 

爲了製備(步驟0)它打開與一個工作表給定的excel文件,這是通過使用Excel中新文檔按鈕建立之前。 在步驟(1)它使用API​​將給定的文本文件嵌入到Excel文檔中。該文本文件之前使用文本編輯器創建了內容「TEST123」。然後在步驟(2)它嘗試使用剪貼板從嵌入的OLE中讀回內容,並打開一個消息框,顯示剪貼板中OLE的內容。最後(3)程序關閉打開的文件。要保持不變的設置,請按這裏。

這個解決方案的一個很大的缺點是使用了剪貼板,它可以在剪貼板中破壞任何用戶內容,這在生產環境中是不好的風格。此外,它使用剪貼板的未公開選項。

更好的解決方案是安全的OLE或OLE嵌入文件到Python數據容器或我選擇的文件。在我的示例中,我使用了TXT文件來輕鬆識別文件數據。最後,我將使用ZIP進行一體化解決方案,但對於base64數據,TXT文件解決方案已足夠。 0xC004的

源= 49156:https://danny.fyi/embedding-and-accessing-a-file-in-excel-with-vba-and-ole-objects-4d4e7863cfff

這VBA例子看起來有趣,但我不知道VBA線索:Saving embedded OLE Object (Excel doc) to file in Excel 2010 vs 2013

+0

所以,我發現這個問題令人困惑。看起來你想從excel文件中提取某些東西,但是我發現* something *有點含糊。 –

+0

我對python並不熟悉,但事實上,你已經在做很多「VBA」了。 'Workbooks.Open','Worksheets.Item'等等都是VBA命令(技術上它們是IDispatch調用)。爲什麼不直接嘗試在Python代碼中直接調用VBA示例中的'oEmbFile.Object.SaveAs fName' –

+0

@Simon:Pyhton就像VBA一樣使用COM接口。我可以使用下面的代碼行來查看對象API,但沒有SaveAs只有Activate和Copy之類的東西。 messagebox.showinfo(title =「packBootstrap」,message =「Item obj:\ n」+ str(type(obj))+ str(dir(obj)) messagebox.showinfo(title =「packBootstrap」,message =「_ oleobj _:\ n」+ str(type(obj._oleobj_))+ str(dir(obj._oleobj_))) 但沒有任何東西讓我進一步也「obj._oleobj_」是一個「PylDispatch」對象。 – BREMI

回答

1

考慮使用,將臨時存儲OLE對象的文件源Windows臨時目錄中嵌入時在工作簿中。此解決方案中不使用剪貼板,而是物理文件。使用這種方法,您將需要檢索當前用戶的名稱並遍歷臨時目錄中的所有文件:C:\ Documents and Settings \ {用戶名} \ Local Settings \ Temp(標準Excel轉儲文件夾Windows Vista/7/8/10)。此外,與in條件類似名稱的搜索使用含有原始文件的基本名稱爲多個版本的後綴(1),(2),(3),...可能取決於有多少次腳本運行存在。嘗試在這裏甚至正則表達式搜索。

最後,下面例程使用try...except...finally塊乾淨地存在Excel對象無論錯誤的,但將輸出任何異常消息。請注意,這只是使用文本文件的Windows解決方案。

import win32com.client as win32 
import os, shutil 
from tkinter import messagebox 

# (0) Setup 
dir_path = cd = os.path.dirname(os.path.abspath(__file__)) 
print(dir_path) 

try: 
    excel = win32.gencache.EnsureDispatch('Excel.Application')  
    wb = excel.Workbooks.Open(os.path.join(dir_path, "test_excel.xlsx")) 
    ws = wb.Worksheets(1) 
    objs = ws.OLEObjects() 

    # (1) Embed file 
    f = os.path.join(dir_path, "test_txt.txt")  
    name = "test_txt_ole.txt" 
    objs.Add(Filename=f, IconLabel=name).Name = 'Test' 

    # (2) Open file from temporary folder 
    ole = ws.OLEObjects(1)   
    ole.Activate() 

    # (3) Grab the recent like-named file 
    user = os.environ.get('USERNAME') 
    outfile = os.path.join(dir_path, "test_txt_out.txt") 

    tempfolder = r"C:\Documents and Settings\{}\Local Settings\Temp".format(user) 

    for subdir, dirs, files in os.walk(tempfolder): 
     for file in sorted(files, reverse=True): 
      if 'test_txt' in file:     
       tempfile = os.path.join(tempfolder, file) 
       break 

    shutil.copyfile(tempfile, outfile) 

    # (4) Read text content 
    with open(outfile, 'r') as f:   
     content = f.readlines() 

    # (5) Output message with content 
    messagebox.showinfo(title="test_txt_ole.txt", message="".join(content)) 

except Exception as e: 
    print(e) 

finally: 
    wb.Close(True)  # CLOSES AND SAVES WORKBOOK 
    excel.Quit   # QUITS EXCEL APP 

    # RELEASES COM RESOURCES 
    ws = None; wb = None; objs = None; ole = None; excel = None 

Tkinter的消息框

Message Output

+0

今天我缺乏時間。所以一個簡短的愚蠢問題。行「ole = ws.OLEObjects(1)」在%TEMP%中創建OLE的臨時文件? – BREMI

+0

對不起,但據我所知,BREMI想要在他的Excel文件中檢索OLE對象的內容。您的代碼獲取原始文件的內容,但是,如果用戶修改它們,Excel中的OLE對象可能會有所不同。 – z32a7ul

+0

不,'ole = ws.OLEObject(1)'將工作表中的第一個OLEObject賦值給該變量。 – Parfait

3

嗯,我覺得凍糕的解決方案有點hackish(在壞的意義上),因爲

  • 它假定Excel將保存嵌入作爲臨時文件,
  • 它假定pat這個臨時文件的h始終是用戶的默認臨時路徑
  • 它假定您將有權在其中打開文件,它假定您使用命名約定來標識您的對象(例如, 'test_txt'總是出現在名字中,你不能 插入一個對象'account_data'),
  • 它假定這個約定不受操作系統干擾(例如它不會將其改變爲'〜test_tx 1)'保存字符長度 ,
  • 它假定此約定是已知的並且被計算機上的所有其他程序接受(其他人不會使用包含'test_txt'的名稱)。

因此,我寫了一個替代解決方案。這樣做的實質是thef如下:

  1. 解壓縮.xlsx文件(或在新的基於XML的格式 ,這是沒有密碼保護的任何其他Office文件)到一個臨時路徑。

  2. 迭代通過 '/ XXX /的嵌入' 內的所有.bin文件( 'XXX'= 'XL' 或 '單詞' 或 'PPT'),並創建包含的.bin 文件的字典作爲鍵的臨時路徑和從步驟3返回的詞典作爲值返回步驟3。

  3. 根據(不是很有 有據可查的)Ole Packager格式從.bin文件中提取信息,並將信息返回爲 字典。 (檢索原始二進制數據作爲「內容」,而不是從.txt,但任何類型的文件,如巴紐只有 )

我還在學習Python的,所以這並不是完美的(沒有錯誤檢查,沒有性能優化),但你可以從中得到它的想法。我用幾個例子對它進行了測試。 這裏是我的代碼:

import tempfile 
import os 
import shutil 
import zipfile 
import glob 
import pythoncom 
import win32com.storagecon 


def read_zipped_xml_bin_embeddings(path_zipped_xml): 
    temp_dir = tempfile.mkdtemp() 

    zip_file = zipfile.ZipFile(path_zipped_xml) 
    zip_file.extractall(temp_dir) 
    zip_file.close() 

    subdir = { 
      '.xlsx': 'xl', 
      '.xlsm': 'xl', 
      '.xltx': 'xl', 
      '.xltm': 'xl', 
      '.docx': 'word', 
      '.dotx': 'word', 
      '.docm': 'word', 
      '.dotm': 'word', 
      '.pptx': 'ppt', 
      '.pptm': 'ppt', 
      '.potx': 'ppt', 
      '.potm': 'ppt', 
     }[ os.path.splitext(path_zipped_xml)[ 1 ] ] 
    embeddings_dir = temp_dir + '\\' + subdir + '\\embeddings\\*.bin' 

    result = {} 
    for bin_file in list(glob.glob(embeddings_dir)): 
     result[ bin_file ] = bin_embedding_to_dictionary(bin_file) 

    shutil.rmtree(temp_dir) 

    return result 


def bin_embedding_to_dictionary(bin_file): 
    storage = pythoncom.StgOpenStorage(bin_file, None, win32com.storagecon.STGM_READ | win32com.storagecon.STGM_SHARE_EXCLUSIVE) 
    for stastg in storage.EnumElements(): 
     if stastg[ 0 ] == '\1Ole10Native': 
      stream = storage.OpenStream(stastg[ 0 ], None, win32com.storagecon.STGM_READ | win32com.storagecon.STGM_SHARE_EXCLUSIVE) 

      result = {} 
      result[ 'original_filename' ] = '' # original filename in ANSI starts at byte 7 and is null terminated 
      stream.Seek(6, 0) 
      while True: 
       ch = stream.Read(1) 
       if ch == '\0': 
        break 
       result[ 'original_filename' ] += ch 

      result[ 'original_filepath' ] = '' # original filepath in ANSI is next and is null terminated 
      while True: 
       ch = stream.Read(1) 
       if ch == '\0': 
        break 
       result[ 'original_filepath' ] += ch 

      stream.Seek(4, 1) # next 4 bytes is unused 

      temporary_filepath_size = 0 # size of the temporary file path in ANSI in little endian 
      temporary_filepath_size |= ord(stream.Read(1)) << 0 
      temporary_filepath_size |= ord(stream.Read(1)) << 8 
      temporary_filepath_size |= ord(stream.Read(1)) << 16 
      temporary_filepath_size |= ord(stream.Read(1)) << 24 

      result[ 'temporary_filepath' ] = stream.Read(temporary_filepath_size) # temporary file path in ANSI 

      result[ 'size' ] = 0 # size of the contents in little endian 
      result[ 'size' ] |= ord(stream.Read(1)) << 0 
      result[ 'size' ] |= ord(stream.Read(1)) << 8 
      result[ 'size' ] |= ord(stream.Read(1)) << 16 
      result[ 'size' ] |= ord(stream.Read(1)) << 24 

      result[ 'contents' ] = stream.Read(result[ 'size' ]) # contents 

      return result 

您可以使用它像這樣:

objects = read_zipped_xml_bin_embeddings(dir_path + '\\test_excel.xlsx') 
obj = objects.values()[ 0 ] # Get first element, or iterate somehow, the keys are the temporary paths 
print('Original filename: ' + obj[ 'original_filename' ]) 
print('Original filepath: ' + obj[ 'original_filepath' ]) 
print('Original filepath: ' + obj[ 'temporary_filepath' ]) 
print('Contents: ' + obj[ 'contents' ])