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

Python:从Office/Excel文档中访问嵌入式OLE而不使用剪贴板,python,excel,com,ms-office,ole,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.

我想使用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作为一体式解决方案,但对于base64数据,TXT文件解决方案就足够了

0xC004=49156的来源:


这个VBA示例看起来很有趣,但我对VBA一无所知:

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

使用这种方法,您需要检索当前用户名并遍历临时目录的所有文件:C:\Documents and Settings\{username}\Local Settings\temp(适用于Windows Vista/7/8/10的标准Excel转储文件夹)。此外,还使用了在中使用
的类似条件的名称搜索,该搜索将原始文件的基本名称包含为多个版本,并带有数字后缀(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消息框


好吧,我觉得冻糕的解决方案有点老土(不好的意思),因为

  • 它假定Excel将嵌入保存为临时文件
  • 它假定此临时文件的路径始终是用户的默认临时路径
  • 它假定您有权限在那里打开文件
  • 它假设您使用命名约定来标识您的对象(例如,“test_txt”总是在名称中找到,您不能 插入对象“帐户\数据”)
  • 它假定此约定不受操作系统的干扰(例如,它不会将其更改为“~test_tx(1)”以保存字符) 长度)
  • 它假定计算机上的所有其他程序都知道并接受此约定(其他程序不会使用包含“test_txt”的名称)
所以,我写了一个替代方案。其实质是:

  • 解压.xlsx文件(或基于XML的新版本中的任何其他Office文件) 格式(不受密码保护)设置为临时路径

  • 遍历“/xxx/embeddings”(“xxx”)中的所有.bin文件= “xl”或“word”或“ppt”),并创建包含.bin的字典 文件的临时路径作为键和从中返回的字典 第3步,作为值

  • 从.bin文件中根据 详细记录)Ole Packager格式,并按以下方式返回信息: 一本字典。(将原始二进制数据检索为“内容”,而不仅仅是 来自.txt,但不包括任何文件类型,例如.png)

  • 我仍在学习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
    

    我构建了一个python模块来完成这项工作,请在这里查看。该模块也可以在任何操作系统上运行

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


    我最近试图回答一个类似的问题:我能从excel文件中提取嵌入式word文档并将其保存到磁盘吗

    调整此页面上的答案(并利用excel文件是压缩的文件集合(主要是XML文件)的知识),这可以很容易地执行:

  • 创建临时文件
  • 将excel文件的所有内容提取到临时文件夹中
  • 查找所有嵌入的文件
  • 将嵌入的文件移动到您选择的永久文件夹中
  • 以下是执行上述操作的代码段:

    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)
    

    因此,我发现这个问题令人困惑。看起来你想从excel文件中提取一些东西,但我发现这些东西有点模糊。我不熟悉python,但事实上,你已经做了很多“VBA”<代码>工作簿。打开
    工作表。项
    等是一种VBA命令(技术上它们是IDispatch调用)。为什么不直接从python中调用VBA示例中的
    oEmbFile.Object.SaveAs fName
    code@Simon:Pyhton-like VBA使用COM接口。我可以使用下面的代码行查看对象API,但是没有SaveAs,只有Activate和Copy之类的东西。messagebox.showinfo(title=“packBootstrap”,message=“Item obj:\n”+str(type(obj))+str(dir(dir(obj)))messagebox.showinfo(title=“packBootstrap”,message=“oleobj:\n”+str(type(obj.\u oleobj))+str dir(dir(obj.\u oleobj))
     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.
    
    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)