programing

Python: 클립보드 없이 Office/Excel 문서에서 내장된 OLE 액세스

magicmemo 2023. 8. 9. 20:38
반응형

Python: 클립보드 없이 Office/Excel 문서에서 내장된 OLE 액세스

파이썬을 사용하여 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) 프로그램이 열린 문서를 닫습니다.변경되지 않은 설정을 유지하려면 여기서 no를 누릅니다.

이 솔루션의 가장 큰 단점은 생산적인 환경에서 나쁜 스타일인 클립보드의 모든 사용자 콘텐츠를 박살내는 클립보드를 사용한다는 것입니다.또한 클립보드에 대해 문서화되지 않은 옵션을 사용합니다.

더 나은 해결책은 OLE 또는 OLE 내장 파일을 파이썬 데이터 컨테이너나 내가 선택한 파일에 안전하게 보관하는 것입니다.이 예에서는 파일 데이터를 쉽게 식별하기 위해 TXT 파일을 사용했습니다.마지막으로 일체형 솔루션에는 ZIP을 사용하지만 Base64 데이터에는 TXT 파일 솔루션으로 충분합니다.

0xC004 = 49156의 소스: https://https.fyi/https-and-a-file-in-vba-and-ole-le-dll-4d4e7863cff

이 VBA 예제는 흥미로워 보이지만 VBA에 대해 전혀 알지 못합니다. 내장 OLE 개체(Excel 워크북)를 Excel 2010에 파일로 저장합니다.

글쎄요, 저는 파르페의 해결책이 (나쁜 의미에서) 약간 진부하다고 생각합니다. 왜냐하면

  • 그것은 엑셀이 임베딩을 임시 파일로 저장할 것이라고 가정합니다.
  • 이 임시 파일의 경로는 항상 사용자의 기본 임시 경로라고 가정합니다.
  • 거기서 파일을 열 수 있는 권한이 있다고 가정합니다.
  • 이름 지정 규칙을 사용하여 개체를 식별한다고 가정합니다(예: 'test_txt'는 항상 이름에 있으며 'account_data' 개체를 삽입할 수 없습니다).
  • 이 규칙은 운영 체제에 의해 방해를 받지 않는다고 가정합니다(예: 문자 길이를 저장하기 위해 '~test_proxy(1)'로 변경하지 않습니다).
  • 이 규칙은 컴퓨터의 다른 모든 프로그램에서 이 규칙을 알고 수락한다고 가정합니다(다른 누구도 'test_txt'를 포함하는 이름을 사용하지 않습니다).

그래서 저는 다른 해결책을 썼습니다.그 본질은 다음과 같습니다.

  1. 임시 경로에 .xlsx 파일(또는 암호로 보호되지 않는 새 XML 기반 형식의 다른 Office 파일)의 압축을 풉니다.

  2. '/xxx/dreadings'('xxx' = 'word' 또는 'dreadings') 내의 모든 .bin 파일을 반복하고 .bin 파일의 임시 경로를 키로 포함하고 3단계에서 반환된 사전을 값으로 포함하는 사전을 만듭니다.

  3. 에 따라 .bin 파일에서 정보를 추출합니다(문서화되어 있지 않음).Ole Packager 형식을 지정하고 정보를 사전으로 반환합니다. (원시 이진 데이터를 .txt뿐만 아니라 모든 파일 형식(예: .png)에서 '내용'으로 검색합니다.

저는 여전히 파이썬을 배우고 있기 때문에 이것은 완벽하지 않습니다(오류 확인, 성능 최적화 없음). 하지만 당신은 그것에서 아이디어를 얻을 수 있습니다.저는 몇 가지 예를 들어 그것을 테스트했습니다.내 코드는 다음과 같습니다.

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

워크북에 포함될 때 OLE 오브젝트의 파일 소스를 임시로 저장할 Windows 임시 디렉토리를 사용하는 것이 좋습니다.이 솔루션에는 클립보드가 사용되지 않고 실제 파일만 사용됩니다.

이 방법을 사용하려면 현재 사용자의 이름을 검색하고 임시 디렉토리의 모든 파일을 반복해야 합니다.C:\문서 및 설정\{username}\로컬 설정\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 메시지 상자

Message Output

저는 파이썬 모듈을 만들었습니다. 바로 여기서 확인하기 위해서 말이죠.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.

저는 최근에 비슷한 질문에 답하려고 했습니다: 내장된 단어 문서를 엑셀 파일에서 추출하여 디스크에 저장할 수 있습니까?

이 페이지의 답변을 적용하고(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)

언급URL : https://stackoverflow.com/questions/43154439/python-access-embedded-ole-from-office-excel-document-without-clipboard

반응형