我想使用python从Office/Excel文档中添加和提取文件。到目前为止,添加东西很容易,但是为了提取,我还没有找到一个干净的解决方案。
为了弄清楚我有什么和没有什么,我在下面编写了一个小示例test.py,并进一步解释。
测试.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 workbook) to file in Excel 2010

最佳答案

嗯,我觉得帕尔法特的解决方案有点老土(坏的意思),因为
假设Excel将嵌入保存为临时文件,
它假定此临时文件的路径始终是用户的默认临时路径,
它假定您有权在那里打开文件,
它假定您使用命名约定来标识对象(例如,“test_txt”总是在名称中找到,您不能
插入对象'account_data'),
它假定此约定不受操作系统的干扰(例如,它不会将其更改为“~ test_tx(1)”以保存字符
长度),
它假定计算机上的所有其他程序都知道并接受此约定(其他任何程序都不会使用包含“test-txt”的名称)。
所以,我写了一个替代方案。其实质是:
解压.xlsx文件(或新的基于XML的任何其他Office文件)
格式化(不受密码保护)到临时路径。
遍历'/xxx/embeddings'('xxx'中的所有.bin文件=
'xl'或'word'或'ppt'),并创建包含.bin的字典
文件作为键的临时路径和从返回的字典
步骤3作为值。
根据从.bin文件中提取信息
有据可查)OLE Packager格式,并将信息返回为
一本字典。(将原始二进制数据检索为“contents”,而不仅仅是
来自.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' ] )

07-24 09:45
查看更多