ScriptForge (FileSystem) Manage ZIP archives

The FileSystem service provides simple
methods to manage compressed zip archives.

As an example, .odf and .oxt files being
formatted as such archives, massive or
punctual changes of file contents
with smart scripts can be facilitated.

New methods:
- IsZipFile(filename)
    returns True when filename is a valid
    zip file
- FilesInZip(filename, pattern)
    returns a list of archive members,
    optionally filtered with a Like pattern,
    stored in filename
- ExtractFromZip(filename, destination, source)
    stores the source file(s) found in the
    given ZIP file into a destination folder
- CompressToZip(filename, foldername, rootfolder)
    stores the content of folder, including
    its subfolders, into a zip archive,
    optionally under a root folder

The implementation is based on the python
zipfile standard library.

The methods area available both for Basic and
Python user scripts. For Python scripts, the
user is also free to use the native zipfile
library instead.

The user documentation shouid be updated
according to this commit.

Change-Id: I506a8583dc3fa38c7e05094562ade33f84a24747
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211891
Tested-by: Jenkins
Reviewed-by: Jean-Pierre Ledure <jp@ledure.be>
This commit is contained in:
Jean-Pierre Ledure
2026-10-07 14:26:54 +02:00
parent 72d2a706a3
commit 2821d29a87
4 changed files with 499 additions and 43 deletions
@@ -54,6 +54,14 @@ Option Explicit
&apos;&apos;&apos; The file manifest.xml is managed automatically. &apos;&apos;&apos; The file manifest.xml is managed automatically.
&apos;&apos;&apos; The FileNaming setting is ignored. &apos;&apos;&apos; The FileNaming setting is ignored.
&apos;&apos;&apos; &apos;&apos;&apos;
&apos;&apos;&apos; The FileSystem service provides also simple methods to extract files from or load files
&apos;&apos;&apos; into archive (zip) files.
&apos;&apos;&apos; .odf and .oxt files being compressed files, direct vreation or update of such files (with care !) is made easy.
&apos;&apos;&apos; Note that multipart and encrypted zip files are not supported.
&apos;&apos;&apos;
&apos;&apos;&apos; The service provides symbolic names for a number of system folders (home, LibreOffice install, ...)
&apos;&apos;&apos; that contribute to improving the portability of user scripts.
&apos;&apos;&apos;
&apos;&apos;&apos; Service invocation example: &apos;&apos;&apos; Service invocation example:
&apos;&apos;&apos; Dim FSO As Variant &apos;&apos;&apos; Dim FSO As Variant
&apos;&apos;&apos; Set FSO = CreateScriptService(&quot;FileSystem&quot;) &apos;&apos;&apos; Set FSO = CreateScriptService(&quot;FileSystem&quot;)
@@ -377,6 +385,96 @@ CatchNotSupported:
GoTo Finally GoTo Finally
End Function &apos; ScriptForge.SF_FileSystem.CompareFiles End Function &apos; ScriptForge.SF_FileSystem.CompareFiles
REM -----------------------------------------------------------------------------
Public Function CompressToZip(Optional ByVal FileName As Variant _
, Optional ByVal FolderName As Variant _
, Optional ByVal RootFolder As Variant _
) As Variant
&apos;&apos;&apos; Stores the content of Folder, including its subfolders, into a zip archive.
&apos;&apos;&apos; Encrypted archives are not supported.
&apos;&apos;&apos; The compressed files are stored optionally under a RootFolder.
&apos;&apos;&apos; Files existing already in the archive are not overwritten.
&apos;&apos;&apos; The method is not supported for document&apos;s internal file systems.
&apos;&apos;&apos; Args:
&apos;&apos;&apos; FileName: A string representing the zip file.
&apos;&apos;&apos; If it does not exist, the archive is created, oncluding its parent folders.
&apos;&apos;&apos; All files present in the archive are kept untouched.
&apos;&apos;&apos; FolderName: The folder to compress, as a string.
&apos;&apos;&apos; All files and subfolders are included. The Folder itself is not.
&apos;&apos;&apos; RootFolder: When absent, files and folders are loaded in the archive from the root (&quot;/&quot;).
&apos;&apos;&apos; When a RootFolder is given, as a string without path separator, they are stored below that folder.
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; True when successful.
&apos;&apos;&apos; When False, an error message might be found in the console.
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; UNKNOWNFOLDERERROR Source folder does not exist
&apos;&apos;&apos; NOTAFOLDERERROR The folder is a file, not a folder
&apos;&apos;&apos; FILESYSTEMERROR The method is not applicable on document&apos;s file systems
&apos;&apos;&apos; Example:
&apos;&apos;&apos; a = SF_FileSystem.CompressToZip(&quot;C:\archive.zip&quot;, Folder := &quot;C:\Downloads&quot;)
Dim bCompress As Boolean &apos; Return value
Dim vCompress As Variant &apos; Returned from Python (True/False, error message)
Dim sFile As String &apos; Alias of FileName
Dim sMode As String &apos; a = append, file exists - x = exclusive read/write
Dim sParentFolder As String &apos; Parent folder of FileName
Dim vRootFolder As Variant &apos; Alias of RootFolder
Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_FileSystem__CompressToZip&quot;
Const cstThisSub = &quot;FileSystem.CompressToZip&quot;
Const cstSubArgs = &quot;FileName, FolderName, [RootFolder=&quot;&quot;&quot;&quot;]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
bCompress = False
Check:
If IsMissing(RootFolder) Or IsEmpty(RootFolder) Then RootFolder = &quot;&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
If Not SF_Utils._ValidateFile(FolderName, &quot;FolderName&quot;) Then GoTo Finally
If Not SF_Utils._Validate(RootFolder, &quot;RootFolder&quot;, V_STRING) Then GoTo Finally
End If
If SF_FileSystem._IsDocFileSystem(FileName) Then GoTo CatchNotSupported
If SF_FileSystem.FileExists(FolderName) Then GoTo CatchFileNotFolder
If Not SF_FileSystem.FolderExists(FolderName) Then GoTo CatchFolderNotExists
Try:
With ScriptForge.SF_FileSystem
&apos; Append (a) or exclusive create (x) mode ? Eventually, create parent folder
If .FileExists(FileName) Then
sMode = &quot;a&quot;
Else
sParentFolder = .GetParentFolderName(FileName)
If Not .FolderExists(sParentFolder) Then .CreateFolder(sParentFolder)
sMode = &quot;x&quot;
End If
If Len(RootFolder) &gt; 0 Then vRootFolder = RootFolder Else vRootFolder = Null
With ScriptForge.SF_Session
&apos; Python requires OS notation for files and folders
vCompress = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper _
, _ConvertFromUrl(FileName), sMode, ConvertFromUrl(FolderName), vRootFolder)
End With
bCompress = vCompress(0)
If Not bCompress Then SF_Exception.DebugPrint(&quot;CompressToZip&quot;, vCompress(1))
End With
Finally:
CompressToZip = bCompress
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchFolderNotExists:
SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, &quot;FolderName&quot;, FolderName)
GoTo Finally
CatchFileNotFolder:
SF_Exception.RaiseFatal(NOTAFOLDERERROR, &quot;FolderName&quot;, FolderName)
GoTo Finally
CatchNotSupported:
SF_Exception.RaiseFatal(FILESYSTEMERROR, &quot;FileName&quot;, Split(cstThisSub, &quot;.&quot;)(1), FileName)
GoTo Finally
End Function &apos; ScriptForge.SF_FileSystem.CompressToZip
REM ----------------------------------------------------------------------------- REM -----------------------------------------------------------------------------
Public Function CopyFile(Optional ByVal Source As Variant _ Public Function CopyFile(Optional ByVal Source As Variant _
, Optional ByVal Destination As Variant _ , Optional ByVal Destination As Variant _
@@ -725,6 +823,85 @@ Catch:
GoTo Finally GoTo Finally
End Function &apos; ScriptForge.SF_FileSystem.ExtensionFolder End Function &apos; ScriptForge.SF_FileSystem.ExtensionFolder
REM -----------------------------------------------------------------------------
Public Function ExtractFromZip(Optional ByVal FileName As Variant _
, Optional ByVal Destination As Variant _
, Optional ByVal Source As Variant _
) As Variant
&apos;&apos;&apos; Extracts a list of files contained in a zip container and stores them in a folder.
&apos;&apos;&apos; The process ends at the first error. Existing files are overwritten without warning.
&apos;&apos;&apos; The method is not supported for document&apos;s internal file systems.
&apos;&apos;&apos; Args:
&apos;&apos;&apos; FileName: A string representing a zip file
&apos;&apos;&apos; Destination: The folder where to extract the compressed files to.
&apos;&apos;&apos; Existing files with the same names are overwritten. Other files stay untouched.
&apos;&apos;&apos; When the folder does not exist, it is created, as are subfolders.
&apos;&apos;&apos; Source: A single string or an array of strings with valid names of files contained in the zip file
&apos;&apos;&apos; When Source is omitted, is an empty array or an empty string, all the files and folders are extracted.
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; True when successful.
&apos;&apos;&apos; When False, an error message might be found in the console.
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; UNKNOWNFILEERROR The file does not exist or is a folder
&apos;&apos;&apos; FILESYSTEMERROR The method is not applicable on document&apos;s file systems
&apos;&apos;&apos; Example:
&apos;&apos;&apos; a = SF_FileSystem.ExtractFromZip(&quot;C:\writerdoc.odt&quot;, Source := Array(&quot;content.xml&quot;, &quot;meta.xml&quot;), Desctination := &quot;C:\TEMP&quot;)
Dim bExtract As Boolean &apos; Return value
Dim vExtract As Variant &apos; Returned from Python (True/False, error message)
Dim vSource As Variant &apos; Alias of Source
Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_FileSystem__ExtractFromZip&quot;
Const cstThisSub = &quot;FileSystem.ExtractFromZip&quot;
Const cstSubArgs = &quot;FileName, Destination, [Source=&quot;&quot;&quot;&quot;]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
bExtract = False
Check:
If IsMissing(Source) Or IsEmpty(Source) Then Source = &quot;&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
If Not SF_Utils._ValidateFile(Destination, &quot;Destination&quot;) Then GoTo Finally
If IsArray(Source) Then
If Not SF_Utils._ValidateArray(Source, &quot;Source&quot;, 1, V_STRING) Then GoTo Finally
Else
If Not SF_Utils._Validate(Source, &quot;Source&quot;, V_STRING) Then GoTo Finally
End If
End If
If SF_FileSystem._IsDocFileSystem(FileName) Then GoTo CatchNotSupported
Try:
If SF_FileSystem.FileExists(FileName) Then
With ScriptForge.SF_Session
&apos; Be sure to pass an array to the Python script
If Not IsArray(Source) Then
If Len(Source) = 0 Then vSource = Array() Else vSource = Array(Source)
Else
vSource = Source
End If
vExtract = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper _
, _ConvertFromUrl(FileName), Destination, vSource)
bExtract = vExtract(0)
If Not bExtract Then SF_Exception.DebugPrint(&quot;ExtractFromZip&quot;, vExtract(1))
End With
Else
GoTo CatchNotExists
End If
Finally:
ExtractFromZip = bExtract
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchNotExists:
SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot;, FileName)
GoTo Finally
CatchNotSupported:
SF_Exception.RaiseFatal(FILESYSTEMERROR, &quot;FileName&quot;, Split(cstThisSub, &quot;.&quot;)(1), FileName)
GoTo Finally
End Function &apos; ScriptForge.SF_FileSystem.ExtractFromZip
REM ----------------------------------------------------------------------------- REM -----------------------------------------------------------------------------
Public Function FileExists(Optional ByVal FileName As Variant) As Boolean Public Function FileExists(Optional ByVal FileName As Variant) As Boolean
&apos;&apos;&apos; Return True if the given file exists &apos;&apos;&apos; Return True if the given file exists
@@ -829,6 +1006,74 @@ CatchFolder:
GoTo Finally GoTo Finally
End Function &apos; ScriptForge.SF_FileSystem.Files End Function &apos; ScriptForge.SF_FileSystem.Files
REM -----------------------------------------------------------------------------
Public Function FilesInZip(Optional ByVal FileName As Variant _
, Optional ByVal Pattern As Variant _
) As Variant
&apos;&apos;&apos; Returns The list of the files contained in a zip container.
&apos;&apos;&apos; The list of names may optionally be filtered.
&apos;&apos;&apos; The method is not supported for document&apos;s internal file systems.
&apos;&apos;&apos; Args:
&apos;&apos;&apos; FileName: A string representing a zip file
&apos;&apos;&apos; Pattern: A pattern as a not case-sensitive string containing 0 or more wildcards
&apos;&apos;&apos; Admitted wildcard are: the &quot;?&quot; represents any single character
&apos;&apos;&apos; the &quot;*&quot; represents zero, one, or multiple characters
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; An unordered array of file names without a heading slash.
&apos;&apos;&apos; The path separator is always the slash (&quot;/&quot;), also on Windows.
&apos;&apos;&apos; The list may be filtered with a name pattern. Without pattern, all files are returned.
&apos;&apos;&apos; The resulting array may be empty.
&apos;&apos;&apos; An empty array is returned when the file is not a valid zip file.
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; UNKNOWNFILEERROR The file does not exist or is a folder
&apos;&apos;&apos; FILESYSTEMERROR The method is not applicable on document&apos;s file systems
&apos;&apos;&apos; Example:
&apos;&apos;&apos; a = SF_FileSystem.FilesInZip(&quot;C:\writerdoc.odt&quot;, Pattern := &quot;*.xml&quot;)
Dim vFiles As Variant &apos; Return value
Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_FileSystem__FilesInZip&quot;
Const cstThisSub = &quot;FileSystem.FilesInZip&quot;
Const cstSubArgs = &quot;FileName, [Pattern=&quot;&quot;&quot;&quot;]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vFiles = Array()
Check:
If IsMissing(Pattern) Or IsEmpty(Pattern) Then Pattern = &quot;&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
If Not SF_Utils._Validate(Pattern, &quot;Pattern&quot;, V_STRING) Then GoTo Finally
End If
If SF_FileSystem._IsDocFileSystem(FileName) Then GoTo CatchNotSupported
Try:
If SF_FileSystem.FileExists(FileName) Then
With ScriptForge.SF_Session
vFiles = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper _
, _ConvertFromUrl(FileName))
End With
&apos; Filter files based on pattern
If Len(Pattern) &gt; 0 And UBound(vFiles) &gt; -1 Then
vFiles = ScriptForge.SF_Array.Filter(ScriptForge.SF_String, &quot;IsLike&quot;, vFiles, Pattern, False)
End If
Else
GoTo CatchNotExists
End If
Finally:
FilesInZip = vFiles
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchNotExists:
SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot;, FileName)
GoTo Finally
CatchNotSupported:
SF_Exception.RaiseFatal(FILESYSTEMERROR, &quot;FileName&quot;, Split(cstThisSub, &quot;.&quot;)(1), FileName)
GoTo Finally
End Function &apos; ScriptForge.SF_FileSystem.FilesInZip
REM ----------------------------------------------------------------------------- REM -----------------------------------------------------------------------------
Public Function FolderExists(Optional ByVal FolderName As Variant) As Boolean Public Function FolderExists(Optional ByVal FolderName As Variant) As Boolean
&apos;&apos;&apos; Return True if the given folder name exists &apos;&apos;&apos; Return True if the given folder name exists
@@ -1293,12 +1538,65 @@ CatchNotSupported:
GoTo Finally GoTo Finally
End Function &apos; ScriptForge.SF_FileSystem.HashFile End Function &apos; ScriptForge.SF_FileSystem.HashFile
REM -----------------------------------------------------------------------------
Public Function IsZipFile(Optional ByVal FileName As Variant) As Boolean
&apos;&apos;&apos; Returns True if filename is a valid ZIP file based on its magic number.
&apos;&apos;&apos; The method is not supported for document&apos;s internal file systems.
&apos;&apos;&apos; Args:
&apos;&apos;&apos; FileName: a string representing a file
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; True when FileName is a valid zip file. Otherwise False.
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; UNKNOWNFILEERROR The file does not exist or is a folder
&apos;&apos;&apos; FILESYSTEMERROR The method is not applicable on document&apos;s file systems
&apos;&apos;&apos; Example:
&apos;&apos;&apos; MsgBox SF_FileSystem.IsZipFile(&quot;C:\writerdoc.odt&quot;)
Dim bIsZip As Boolean &apos; Return value
Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_FileSystem__IsZipFile&quot;
Const cstThisSub = &quot;FileSystem.IsZipFile&quot;
Const cstSubArgs = &quot;FileName&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
bIsZip = False
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
End If
If SF_FileSystem._IsDocFileSystem(FileName) Then GoTo CatchNotSupported
Try:
If SF_FileSystem.FileExists(FileName) Then
With ScriptForge.SF_Session
bIsZip = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper _
, _ConvertFromUrl(FileName))
End With
Else
GoTo CatchNotExists
End If
Finally:
IsZipFile = bIsZip
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchNotExists:
SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot;, FileName)
GoTo Finally
CatchNotSupported:
SF_Exception.RaiseFatal(FILESYSTEMERROR, &quot;FileName&quot;, Split(cstThisSub, &quot;.&quot;)(1), FileName)
GoTo Finally
End Function &apos; ScriptForge.SF_FileSystem.IsZipFile
REM ----------------------------------------------------------------------------- REM -----------------------------------------------------------------------------
Public Function Methods() As Variant Public Function Methods() As Variant
&apos;&apos;&apos; Return the list or methods of the FileSystem service as an array &apos;&apos;&apos; Return the list or methods of the FileSystem service as an array
Methods = Array(&quot;BuildPath&quot; _ Methods = Array(&quot;BuildPath&quot; _
, &quot;CompareFiles&quot; _ , &quot;CompareFiles&quot; _
, &quot;CompressToZip&quot; _
, &quot;CopyFile&quot; _ , &quot;CopyFile&quot; _
, &quot;CopyFolder&quot; _ , &quot;CopyFolder&quot; _
, &quot;CreateFolder&quot; _ , &quot;CreateFolder&quot; _
@@ -1306,8 +1604,10 @@ Public Function Methods() As Variant
, &quot;DeleteFile&quot; _ , &quot;DeleteFile&quot; _
, &quot;DeleteFolder&quot; _ , &quot;DeleteFolder&quot; _
, &quot;ExtensionFolder&quot; _ , &quot;ExtensionFolder&quot; _
, &quot;ExtractFromZip&quot; _
, &quot;FileExists&quot; _ , &quot;FileExists&quot; _
, &quot;Files&quot; _ , &quot;Files&quot; _
, &quot;FilesInZip&quot; _
, &quot;FolderExists&quot; _ , &quot;FolderExists&quot; _
, &quot;GetBaseName&quot; _ , &quot;GetBaseName&quot; _
, &quot;GetExtension&quot; _ , &quot;GetExtension&quot; _
@@ -1317,6 +1617,7 @@ Public Function Methods() As Variant
, &quot;GetParentFolderName&quot; _ , &quot;GetParentFolderName&quot; _
, &quot;GetTempName&quot; _ , &quot;GetTempName&quot; _
, &quot;HashFile&quot; _ , &quot;HashFile&quot; _
, &quot;IsZipFile&quot; _
, &quot;MoveFile&quot; _ , &quot;MoveFile&quot; _
, &quot;MoveFolder&quot; _ , &quot;MoveFolder&quot; _
, &quot;Normalize&quot; _ , &quot;Normalize&quot; _
@@ -22,17 +22,19 @@
# distributed with this file, see http://www.gnu.org/licenses/ . # distributed with this file, see http://www.gnu.org/licenses/ .
""" """
Collection of Python helper functions called from the ScriptForge Basic libraries Collection of Python helper functions called from the ScriptForge Basic libraries
to execute specific services that are not or not easily available from Basic directly. to execute specific services that are not or not easily available from Basic directly.
When relevant, the methods present in the ScriptForge Python module (scriptforge.py) might call the When relevant, the methods present in the ScriptForge Python module (scriptforge.py) may call the
functions below for compatibility reasons. functions below directly to bypass the Python-Basic bridge.
""" """
import getpass import getpass
import os import os
import platform import platform
import hashlib import hashlib
import filecmp import filecmp
import pathlib
import zipfile
import webbrowser import webbrowser
import json import json
@@ -66,7 +68,8 @@ def _SF_Dictionary__ConvertToJson(propval, indent = None) -> str:
return json.dumps(pvDict, indent=indent, skipkeys=True) return json.dumps(pvDict, indent=indent, skipkeys=True)
def _SF_Dictionary__ImportFromJson(jsonstr: str): # used by Dictionary.ImportFromJson() Basic method def _SF_Dictionary__ImportFromJson(jsonstr: str) -> list:
# used by Dictionary.ImportFromJson() Basic method
""" """
Given a JSON string as argument, convert it to a list of tuples (name, value) Given a JSON string as argument, convert it to a list of tuples (name, value)
The value must not be a (sub)dict. This doesn't pass the python-basic bridge. The value must not be a (sub)dict. This doesn't pass the python-basic bridge.
@@ -118,11 +121,68 @@ def _SF_FileSystem__CompareFiles(filename1: str, filename2: str, comparecontents
return False return False
def _SF_FileSystem__GetFilelen(systemfilepath: str) -> str: # used by SF_FileSystem.GetFilelen() Basic method def _SF_FileSystem__CompressToZip(filename: str, mode: str, folder: str, rootfolder: str = None) -> tuple[bool, str]:
# used by SF_FileSystem.CompressToZip() Basic method
"""
Stores the content of folder, including its subfolders, into a zip archive.
The mode argument determines if the zip file is created (e'x'clusive create) or not ('a'ppend).
The compressed files are stored optionally under a root folder.
"""
try:
if mode == 'a': # existence of filename is checked in the calling routine
if not zipfile.is_zipfile(filename):
return False, "File is not a zip archive: '%s'" % filename
directory = pathlib.Path(folder)
# Write compressed files under root or under the given root folder. Normal slashes are required, also in Windows..
topfolder = (None if rootfolder is None else pathlib.PurePosixPath(rootfolder + '/'))
with zipfile.ZipFile(filename, mode, strict_timestamps = False) as archive:
for filepath in directory.rglob('*'):
name = (filepath.relative_to(directory) if topfolder is None
else topfolder.joinpath(filepath.relative_to(directory)))
archive.write(filepath, arcname = name, compress_type = zipfile.ZIP_DEFLATED)
return True, ''
except Exception as e:
return False, str(e)
def _SF_FileSystem__ExtractFromZip(filename: str, destination: str, source: tuple = ()) -> tuple[bool, str]:
# used by SF_FileSystem.ExtractFromZip() Basic method
"""
Stores the source file(s) found in the given ZIP file into a destination folder.
"""
try:
if not zipfile.is_zipfile(filename):
return False, "File is not a zip archive: '%s'" % filename
with zipfile.ZipFile(filename, mode = 'r') as archive:
archive.extractall(destination, members = (None if len(source) == 0 else source))
return True, ''
except Exception as e:
return False, str(e)
def _SF_FileSystem__FilesInZip(filename: str) -> list:
# used by SF_FileSystem.FilesInZip() Basic method
"""
Returns a list of archive members by name. The objects are in the same order as their entries
in the given ZIP file.
"""
try:
if not zipfile.is_zipfile(filename):
return []
with zipfile.ZipFile(filename, mode = 'r') as archive:
namelist = archive.namelist()
return namelist
except Exception:
return []
def _SF_FileSystem__GetFilelen(systemfilepath: str) -> str:
# used by SF_FileSystem.GetFilelen() Basic method
return str(os.path.getsize(systemfilepath)) return str(os.path.getsize(systemfilepath))
def _SF_FileSystem__HashFile(filename: str, algorithm: str) -> str: # used by SF_FileSystem.HashFile() Basic method def _SF_FileSystem__HashFile(filename: str, algorithm: str) -> str:
# used by SF_FileSystem.HashFile() Basic method
""" """
Hash a given file with the given hashing algorithm Hash a given file with the given hashing algorithm
cfr. https://www.pythoncentral.io/hashing-files-with-python/ cfr. https://www.pythoncentral.io/hashing-files-with-python/
@@ -159,6 +219,14 @@ def _SF_FileSystem__HashFile(filename: str, algorithm: str) -> str: # used by S
return '' return ''
def _SF_FileSystem__IsZipFile(filename: str) -> bool:
# used by SF_FileSystem.IsZipFile() Basic method
"""
Returns True if filename is a valid ZIP file based on its magic number, otherwise returns False.
"""
return zipfile.is_zipfile(filename)
def _SF_FileSystem__Normalize(systemfilepath: str) -> str: def _SF_FileSystem__Normalize(systemfilepath: str) -> str:
# used by SF_FileSystem.Normalize() Basic method # used by SF_FileSystem.Normalize() Basic method
""" """
@@ -301,6 +369,7 @@ def _SF_String__HashStr(string: str, algorithm: str) -> str: # used by SF_Strin
g_exportedScripts = () g_exportedScripts = ()
if __name__ == "__main__": if __name__ == "__main__":
"""
print(_SF_Platform('Architecture')) print(_SF_Platform('Architecture'))
print(_SF_Platform('ComputerName')) print(_SF_Platform('ComputerName'))
print(_SF_Platform('CPUCount')) print(_SF_Platform('CPUCount'))
@@ -323,10 +392,13 @@ if __name__ == "__main__":
# _SF_Session__OpenURLInBrowser('https://docs.python.org/3/library/webbrowser.html') # _SF_Session__OpenURLInBrowser('https://docs.python.org/3/library/webbrowser.html')
# #
js = """ js = """
{"firstName": "John","lastName": "Smith","isAlive": true,"age": 27, """{"firstName": "John","lastName": "Smith","isAlive": true,"age": 27,
"address": {"streetAddress": "21 2nd Street","city": "New York","state": "NY","postalCode": "10021-3100"}, "address": {"streetAddress": "21 2nd Street","city": "New York","state": "NY","postalCode": "10021-3100"},
"phoneNumbers": [{"type": "home","number": "212 555-1234"},{"type": "office","number": "646 555-4567"}], "phoneNumbers": [{"type": "home","number": "212 555-1234"},{"type": "office","number": "646 555-4567"}],
"children": ["Q", "M", "G", "T"],"spouse": null} "children": ["Q", "M", "G", "T"],"spouse": null}
""" """
arr = _SF_Dictionary__ImportFromJson(js) """arr = _SF_Dictionary__ImportFromJson(js)
print(arr) print(arr)"""
a = "/tmp/SF_YYY/zipped.odt"
b = _SF_FileSystem__CompressToZip(a, "a", "/tmp/SF_XXX", "New")
print(b)
@@ -1220,6 +1220,9 @@ class SFScriptForge:
else: else:
return False return False
def CompressToZip(self, filename, foldername, rootfolder = ''):
return self.ExecMethod(self.vbMethod, 'CompressToZip', filename, foldername, rootfolder)
def CopyFile(self, source, destination, overwrite = True): def CopyFile(self, source, destination, overwrite = True):
return self.ExecMethod(self.vbMethod, 'CopyFile', source, destination, overwrite) return self.ExecMethod(self.vbMethod, 'CopyFile', source, destination, overwrite)
@@ -1241,12 +1244,18 @@ class SFScriptForge:
def ExtensionFolder(self, extension): def ExtensionFolder(self, extension):
return self.ExecMethod(self.vbMethod, 'ExtensionFolder', extension) return self.ExecMethod(self.vbMethod, 'ExtensionFolder', extension)
def ExtractFromZip(self, filename, destination, source = ''):
return self.ExecMethod(self.vbMethod, 'ExtractFromZip', filename, destination, source)
def FileExists(self, filename): def FileExists(self, filename):
return self.ExecMethod(self.vbMethod, 'FileExists', filename) return self.ExecMethod(self.vbMethod, 'FileExists', filename)
def Files(self, foldername, filter = '', includesubfolders = False): def Files(self, foldername, filter = '', includesubfolders = False):
return self.ExecMethod(self.vbMethod, 'Files', foldername, filter, includesubfolders) return self.ExecMethod(self.vbMethod, 'Files', foldername, filter, includesubfolders)
def FilesInZip(self, filename, pattern = ''):
return self.ExecMethod(self.vbMethod, 'FilesInZip', filename, pattern)
def FolderExists(self, foldername): def FolderExists(self, foldername):
return self.ExecMethod(self.vbMethod, 'FolderExists', foldername) return self.ExecMethod(self.vbMethod, 'FolderExists', foldername)
@@ -1284,6 +1293,14 @@ class SFScriptForge:
else: else:
return '' return ''
def IsZipFile(self, filename):
py = ScriptForge.pythonhelpermodule + '$' + '_SF_FileSystem__IsZipFile'
if self.FileExists(filename):
file = self._ConvertFromUrl(filename)
return self.SIMPLEEXEC(py, file)
else:
return False
def MoveFile(self, source, destination): def MoveFile(self, source, destination):
return self.ExecMethod(self.vbMethod, 'MoveFile', source, destination) return self.ExecMethod(self.vbMethod, 'MoveFile', source, destination)
@@ -975,6 +975,26 @@ class SFScriptForge:
""" """
... ...
def CompressToZip(self, filename: FILE, foldername: FILE, rootfolder: str = ...) -> bool:
"""
Stores the content of Folder, including its subfolders, into a zip archive.
Encrypted files are not supported. The compressed files are stored optionally under a root folder.
Files existing already in the archive are not overwritten.
Args
``filename``: a string representing the zip file.
If it does not exist, the archive is created, oncluding its parent folders.
All files present in the archive are kept untouched.
``foldername``: the folder to compress, as a string. All files and subfolders are included.
The folder itself is not.
``rootfolder``: when absent, files and folders are loaded in the archive from the root ("/").
When present, given as a string without path separator, they are stored below that folder.
Returns
``True`` when successful. When ``False``, the reason might be found in the console.
"""
...
def CopyFile(self, source: FILE, destination: FILE, overwrite: bool = ...) -> bool: def CopyFile(self, source: FILE, destination: FILE, overwrite: bool = ...) -> bool:
""" """
Copies one or more files from one location to another. Copies one or more files from one location to another.
@@ -1092,6 +1112,24 @@ class SFScriptForge:
""" """
... ...
def ExtractFromZip(self, filename: FILE, destination: FILE, source: str | tuple[str, ...] = ...) -> bool:
"""
Extracts a list of files contained in a zip archive and stores them in a folder.
Args
``filename``: a string representing the zip archive.
``destination``: the folder where to extract the compressed files to.
Existing files with the same names are overwritten without warning. Other files stay untouched.
When the folder or a subfolder does not exist, it is created.
``source``: a single string or a tuple of strings with valid names of files contained in
the zip file. When Source is omitted, is an empty tuple or an empty string,
all the files and folders are extracted.
Returns
``True`` when successful. When ``False``, the reason might be found in the console.
"""
...
def FileExists(self, filename: FILE) -> bool: def FileExists(self, filename: FILE) -> bool:
""" """
Return ``True`` if the given file exists. Return ``True`` if the given file exists.
@@ -1121,6 +1159,24 @@ class SFScriptForge:
""" """
... ...
def FilesInZip(self, filename: FILE, pattern: str = ...) -> tuple[str, ...]:
"""
Returns The list of the files contained in a zip container. The list of names may optionally
be filtered.
Args
``filename``: a string representing the zip archive.
``pattern``: a pattern as a not case-sensitive string containing 0 or more wildcards.
Admitted wildcard are: the "?" represents any single character,
the "*" represents zero, one, or multiple characters.
Returns
An unordered tuple of file names without a heading slash. The path separator is always
the slash ("/"), also on Windows. The list may be filtered with a name pattern.
Without pattern, all files are returned. The resulting tuple may be empty. An empty tuple
is returned when the file is not a valid zip file.
"""
...
def FolderExists(self, foldername: FILE) -> bool: def FolderExists(self, foldername: FILE) -> bool:
""" """
Returns ``True`` if the given folder name exists. Returns ``True`` if the given folder name exists.
@@ -1235,6 +1291,16 @@ class SFScriptForge:
""" """
... ...
def IsZipFile(self, filename: FILE) -> bool:
"""
Returns ``True`` if filename is a valid ZIP file based on its magic number.
Args
``filename``: a string representing the zip archive.
Returns
``True`` when ``filename`` is a valid zip file. Otherwise ``False``.
"""
...
def MoveFile(self, source: FILE, destination: FILE) -> bool: def MoveFile(self, source: FILE, destination: FILE) -> bool:
""" """
Moves one or more files from one location to another. Moves one or more files from one location to another.