【问题标题】:Python: Access embedded OLE from Office/Excel document without clipboardPython:在没有剪贴板的情况下从 Office/Excel 文档访问嵌入式 OLE
【发布时间】:2017-08-26 12:54:07
【问题描述】:

我想使用 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 中的 new document 按钮创建的工作表。

步骤 (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 workbook) to file in Excel 2010

【问题讨论】:

  • 所以,我发现这个问题令人困惑。看起来您想从 excel 文件中提取某些内容,但我发现 something 有点含糊。
  • 我不熟悉python,但其实你已经在做很多“VBA”了。 Workbooks.OpenWorksheets.Item 等是一种 VBA 命令(从技术上讲,它们是 IDispatch 调用)。你为什么不直接从你的python代码中尝试在VBA示例中调用oEmbFile.Object.SaveAs fName之类的东西
  • @Simon: Pyhton 和 VBA 一样使用 COM 接口。我可以使用以下代码行来查看对象 API,但没有 SaveAs,只有诸如激活和复制之类的东西。 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 " 对象。
  • @Stephen:我的问题故意含糊不清,因为我想在没有深度协议的情况下将二进制数据添加到 Excel。要求是: 使用 Python。使用类似 COM 的策略 (win32com)。要专业(=没有“黑客”)。我还考虑过 Excel 单元格中的 base64 数据,但这里的大小限制为 32k。分块多细胞解决方案并不专业,因为它需要自制协议。
  • 什么是对象 CLSID?打印(obj.CLSID)

标签: python excel com ms-office ole


【解决方案1】:

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

使用这种方法,您将需要检索当前用户的名称并遍历临时目录的所有文件: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 消息框

【讨论】:

  • 我今天没时间。所以一个简短的愚蠢问题。 "ole = ws.OLEObjects(1)" 行在 %TEMP% 中创建 OLE 的临时文件?
  • 抱歉,据我了解,BREMI 想要检索其 Excel 文件中的 OLE 对象的内容。您的代码获取原始文件的内容,但是,如果用户修改它们,Excel 中的 OLE 对象可能会有所不同。
  • 否,ole=ws.OLEObject(1) 将工作表中的第一个 OLEObject 分配给该变量。
  • @z32a7ul - 好点。我添加了一个ole.Activate(),它启动了嵌入的 OLE 对象,并在此行为中创建了复制到当前目录以在 python 中读取的临时文件。为了测试,我使用 Hamlet 引用修改保存的对象,并在脚本中注释掉 Add 以不向工作簿添加任何新的 OLE 对象。我的新文本显示在 Tkinter 消息中:Test123 To be or not to be.
【解决方案2】:

嗯,我觉得 Parfait 的解决方案有点骇人听闻(在不好的意义上),因为

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

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

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

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

  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

你可以这样使用它:

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' ] )

【讨论】:

    【解决方案3】:

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

    安装库后使用以下代码 sn -p 代码:

     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.
    

    【讨论】:

      【解决方案4】:

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

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

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

      这是一个执行上述操作的 sn-p:

      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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-02
        • 1970-01-01
        • 1970-01-01
        • 2012-03-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多