Python:从Office / Excel文档访问嵌入式OLE而不使用剪贴板

时间:2017-04-01 08:14:38

标签: python excel com ms-office ole

我想使用Python从Office / Excel文档中添加和提取文件。到目前为止添加东西很容易,但是为了提取我还没有找到一个干净的解决方案。

要清楚我已经得到了什么,以及我在下面写了一些小例子 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作为一体化解决方案,但TXT文件解决方案足以支持base64数据。

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

4 个答案:

答案 0 :(得分:4)

好吧,我发现Parfait的解决方案有点hackish(在坏的意义上)因为

  • 它假定Excel将嵌入保存为临时文件,
  • 它假定此临时文件的路径始终是用户的默认临时路径
  • 它假定您有权在那里打开文件,
  • 它假定您使用命名约定来标识您的对象(例如,' test_txt'总是在名称中找到,您可以' t 插入一个对象' account_data'),
  • 它假设此约定不受操作系统的干扰(例如,它不会将其更改为' ~test_tx(1)'以保存字符 长度),
  • 它假定此约定已知并被计算机上的所有其他程序接受(其他任何人都不会使用包含' test_txt'的名称)。

所以,我写了一个替代解决方案。其实质如下:

  1. 解压缩.xlsx文件(或新XML中的任何其他Office文件 格式,不受密码保护)到临时路径。

  2. 遍历' / xxx / embeddings'中的所有.bin文件。 (' xxx' =     ' XL'或者'字'或者' ppt'),并创建一个包含.bin的字典     文件'临时路径作为键和返回的字典     第3步作为值。

  3. 根据(不是非常)从.bin文件中提取信息     记录良好)Ole Packager格式,并返回信息为     一本字典。 (不仅仅将原始二进制数据检索为'内容'     来自.txt,但任何文件类型,例如.PNG)

  4. 我还在学习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' ] )
    

答案 1 :(得分:2)

考虑使用Windows临时目录,该目录将在嵌入工作簿时临时存储OLE对象的文件源。此解决方案中没有使用剪贴板,而是使用物理文件。

使用这种方法,您需要检索当前用户的名称并遍历temp目录的所有文件: C:\ Documents and Settings \ {username} \ Local Settings \ Temp (Windows Vista / 7/8/10的标准Excel转储文件夹)。此外,使用带有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 Messagebox

Message Output

答案 2 :(得分:2)

我构建了一个python模块来完成此检查。 https://pypi.org/project/AttachmentsExtractor/该模块也可以在任何操作系统上运行。

在安装库之后,使用以下代码段 代码:

 from AttachmentsExtractor import extractor
            
 abs_path_to_file='Please provide absolute path here '
 path_to_destination_directory = 'Please provide path of the directory where the extracted attachments should be stored'
 extractor.extract(abs_path_to_file,path_to_destination_directory) # returns true if one or more attachments are found else returns false.

答案 3 :(得分:0)

我最近试图回答一个类似的问题:我可以从 excel 文件中提取嵌入的 Word 文档并将它们保存到磁盘吗?

调整此页面上的答案(并利用 Excel 文件是压缩文件集合(主要是 XML 文件)这一知识)可以轻松执行:

  1. 创建临时文件
  2. 将excel文件的所有内容解压到临时文件夹中。
  3. 查找所有嵌入的文件
  4. 将嵌入的文件移动到您选择的永久文件夹中。

这是一个执行上述操作的片段:

import zipfile
import tempfile
import os
import glob
import shutil
import sys

def extract_embedded_files(file_path,
                           save_path,
                           sub_dir='xl'):
    """
    Extracts embedded files from Excel documents, it takes advantage of
    excel being a zipped collection of files. It creates a temporary folder,
    extracts all the contents of the excel folder there and then moves the
    embedded files to the requested save_path.

    Parameters:
    ----------
    file_path : str, 
        The path to the excel file to extract embedded files from.
    
    save_path : str,
        Path to save the extracted files to.

    sub_dir : str,
        one of 'xl' (for excel), 'word' , or 'ppt'. 
    """

    # make a temporary directory 
    temp_dir = tempfile.mkdtemp()

    # extract contents excel file to temporary dir
    zip_file = zipfile.ZipFile(file_path)
    zip_file.extractall(temp_dir)
    zip_file.close()

    # find all embedded files and copy to save_path
    embeddings_dir = f'{temp_dir}/{sub_dir}/embeddings/'
    embedded_files = list(glob.glob(embeddings_dir+'*'))
    for file in embedded_files:
        shutil.copy(file, save_path)