This Lingo script will read the directory contents of a .ZIP archive including file names, folder paths, file sizes, file dates and times.
This file was originally hosted on the InvisibleThreads website until 2008. It has now been archived. The original layout and formatting for this document is missing from the archives — sorry!
This code uses the BinaryIO Xtra to directly read the directory structure of a common ZIP file. The resulting list of files contained within the ZIP file is returned as a list of property lists (with one property list per entry inside the ZIP file).
The ZIP file format was found on Wotsit's Format.
I consider this script a work in progress. Additional functionality can easily be added, and there may be some platform-specific issues to be resolved (this was written and tested on a Windows-platform machine). Please feel free to offer suggestions. If you make any enhancements to this script, I would appreciate hearing about them.
This document describes the public methods and usage examples for the Zip Parent Script.
Handler and Arguments | Return Value | |
mGetArchiveContents object me, string filename | list or error number | |
mInterface object me | 0 | |
mGetErrorMsg integer ErrorNumber | string | |
List of Error Codes |
mGetArchiveContents | ||||||||||||||||||||
Syntax | mGetArchiveContents object me, string filename | |||||||||||||||||||
Returns | A list of property lists if successful. If not successful, returns an error code (use mGetErrorMsg to determine the error message) e.g., returns [ [ proplist for file1 ], [ proplist for file 2], etc ] The property list for each file contains the following properties:
| |||||||||||||||||||
Description | Creates a new instance of the script and assigns it to the specified file. The specified is opened with the given accessMode.
| |||||||||||||||||||
Example | Read the directory contents of the ZIP file located in c:\temp\myfile.zip. |
sFile = "c:\temp\myfile.zip" |
mInterface | ||
Syntax | mInterface object me | |
Returns | 0 | |
Description | Outputs a list of public handlers for this script to the messagewindow. |
mGetErrorMsg | ||||||||
Syntax | mGetErrorMsg object me, integer ErrorNumber | |||||||
Returns | String of the error message for the given error number. If no error number is supplied, a list of all error numbers and messages is returned. | |||||||
Description | If an error code is returned by mGetArchiveContents, use this method to determine the specific error which occurred.
| |||||||
Example | Determine the error message for error number -997. |
put script("Zip Archive Parent").mGetErrorMsg(-997) |
Error Number | Error Message | |
-1 | Error opening file | |
-2 | Error getting filesize | |
-3 | Error setting file position | |
-4 | Error reading from file | |
-5 | Couldn't find end of central directory record | |
-6 | Error seeking to start of central directory | |
-7 | Error matching central directory signature | |
-997 | ConvertData Xtra not present | |
-998 | BinaryIO Xtra not present | |
-999 | Could not get valid instance of BinaryIO Xtra |
This is the Macromedia Director Lingo source code for a parent script which returns the internal directory list of a .ZIP archive.
Copy and paste the source code below into a parent script.
-- Zip Archive Parent
-- Copyright (c) 2002 Kendall Anderson. All rights reserved.
-- http://invisiblethreads.com
-- This script was originally located at:
-- http://invisiblethreads.com/lingo/zipparent/index.html
-- OVERVIEW:
-- This parent script will read the directory contents of a .ZIP archive.
-- This process is quite fast - far faster than extracting the zip contents
-- to a text file and then parsing the text file.
-- This is a tool script, and there is no need to make an instance of the script.
-- Rather, execute the script as:
-- lData = script("Zip Archive Parent").mGetArchiveContents(sFilename)
-- You should run your BinaryIO registration code BEFORE calling this script.
-- Current Limitations:
-- We do not deal with .zip files which are parts of a set
-- (ie: zip files spanned across many volumes).
-- Not all elements of a file's directory entry are returned, but this code
-- could very easily be modified such that other elements (compressed size,
-- version needed to extract, etc) would also be returned.
-- Requirements:
-- Director 7.0+ (dot syntax)
-- BinaryIO Xtra (www.updatestage.com/xtras/binaryio.html)
-- ConvertData Xtra (free with BinaryIO Xtra - www.updatestage.com/xtras/convertdata.html)
-- Projector (BinaryIO Xtra does not work in ShockWave)
-- History:
-- Feb 14 2002, Kendall Anderson: initial design and coding
-- May 31 2002, Kendall Anderson: revised to use BinaryIO and read .zip archive directly
-- Jun 07 2002, Kendall Anderson: cleaned up code, added error-messaging
-- Public Methods:
-- mGetArchiveContents object me, string sFilename
-- returns a list of property lists in the format:
-- [ #arc_filesize: <integer>, -- ie: 147968
-- #arc_filedate: <string>, -- ie: "25-05-2002" (dd-mm-yyyy)
-- #arc_filetime: <string>, -- ie: "14:35:05" (hh:mm:ss)
-- #arc_folder: <string>, -- ie: "folder1/folder2/"
-- #arc_filename: <string> ] -- ie: "myfilename.ext"
-- There will be 1 property list for each file in the archive.
-- mInterface object me
-- mGetErrorMsg object me, integer iErrorNum
-- Error Handling:
-- If a non-zero integer is returned, it is an error-number.
-- Use obj.mGetErrorMsg(iErrorNumber) for the corresponding error message.
-- Example:
-- lDirectory = script("Zip Archive Parent").mGetArchiveContents("c:\myzipfile.zip")
-- if (lDirectory.ilk = #list) then
-- repeat with i = 1 to lDirectory.count
-- put i && lDirectory[i][#arc_filename]
-- end repeat
-- else
-- put "Did not successfully read archive contents."
-- end if
-------------------
-- Public Methods
-- given a .zip filename, read the directory list contents and
-- return as a list of property lists.
on mGetArchiveContents me, sArchiveFilename
if NOT (me._IsXtraPresent("binaryio")) then -- if BinaryIO Xtra not installed,
return -998 -- return with error
end if
if NOT (me._IsXtraPresent("convertdata")) then -- if ConvData Xtra not installed,
return -997 -- return with error
end if
lDirectory = me._ReadZipDirectory(sArchiveFilename)
if (lDirectory.ilk <> #list) then
put "mGetArchiveContents:didn't read contents from (" & sArchiveFilename & "):" & lDirectory
end if
return lDirectory
end
-----------------------------------------
-- Interface Usage and Error Handling methods
-----------------------------------------
-- show the interface for this object
on mInterface me
sMsg = RETURN & "Zip Archive Parent"
sMsg = sMsg & RETURN & "-------------------"
sMsg = sMsg & RETURN & "This object reads the directory contents of a .ZIP archive."
sMsg = sMsg & RETURN & "The object requires the BinaryIO and ConvertData xtras."
sMsg = sMsg & RETURN
sMsg = sMsg & RETURN & "Public Methods:"
sMsg = sMsg & RETURN & "---------------"
sMsg = sMsg & RETURN & "mGetArchiveContents object me, string sFilename"
sMsg = sMsg & RETURN
sMsg = sMsg & RETURN & "mInterface object me"
sMsg = sMsg & RETURN & "mGetErrorMsg object me, integer iErrorNum"
sMsg = sMsg & RETURN
put sMsg
return 0
end
-- given an error number, return the corresponding message
on mGetErrorMsg me, iErrorNum
return (me._GetErrorMessage(iErrorNum))
end
-------------------
-- Private Methods
-- given a .zip filename, read the directory structure and return as property list
-- do this by directly reading the file through BinaryIO
on _ReadZipDirectory me, sFilename
oFile = new(xtra "binaryio") -- get xtra reference
if NOT (objectP(oFile)) then -- if we didn't get a valid object
put "_ReadZipDirectory:error getting xtra reference"
return -999 -- return with error
end if
sError = oFile.OpenFile(1, sFilename) -- open the zip file
if (sError.word[1] = "Error") then -- if didn't open successfully
put "_ReadZipDirectory:error opening file (" & sFilename & "):" & sError
oFile.CloseFile() -- close the file
oFile = void -- clear the binaryio reference
return -1 -- return with error
end if
-- try to read the End of Central Directory record
lEndOfCentralDir = me._ReadEndOfCentralDirectory(oFile) -- read the end of central dir data
if (lEndOfCentralDir.ilk <> #proplist) then -- if we didn't read it successfully
put "_ReadZipDirectory:error reading end of central dir (" & sFilename & "):" & lEndOfCentralDir
oFile.CloseFile() -- close the file
oFile = void -- clear the binaryio reference
return lEndOfCentralDir -- return the error
end if
-- try to read the Central Directory Structure
lDirectory = me._ReadCentralDirectory(oFile, lEndOfCentralDir)
if (lDirectory.ilk <> #list) then -- if we didn't read central dir successfully
put "_ReadZipDirectory:error reading central dir (" & sFilename & "):" & lDirectory
oFile.CloseFile() -- close the file
oFile = void -- clear the binaryio reference
return lDirectory -- return the error
end if
oFile.CloseFile() -- after reading data, close the zip file
oFile = void -- and clear the binaryio reference
return lDirectory -- return the directory data
end
-- scan the .zip file and try to find/read the End of Central Directory record.
-- if we successfully read it, we return a property list of the values
-- if NOT, we return an integer error number.
on _ReadEndOfCentralDirectory me, oFile
iFileSize = oFile.GetFileSize() -- get the file length
if (iFileSize.ilk <> #integer) then -- if we didn't read it successfully,
put "_ReadCentralDirectory:error getting filesize:" & iFileSize
return -2 -- return error
end if
-- we have now opened the file
-- we want to find the end of central directory signature
iOffset = 4 -- offset to where we look for signature
bFound = FALSE
sSignature = me._GetZipSignature(#endofcentraldir) -- get signature for 'end of central directory'
repeat while NOT (bFound) -- starting at end of file
sError = oFile.SetFilePosition(iFileSize - iOffset) -- and working backwards
if (sError.word[1] = "Error") then
put "_ReadCentralDirectory:error setting file position:" & sError
return -3
end if
sBytes = oFile.ReadBytes(4) -- read 4 bytes
if (sBytes.ilk = #symbol) then -- if error reading bytes
put "_ReadCentralDirectory:error reading bytes:" & sBytes
return -4 -- return with error
end if
if (sBytes = sSignature) then -- if we matched the signature
bFound = TRUE
exit repeat -- exit our scanning search
else -- if we didn't find signature,
iOffset = iOffset + 1 -- increase offset
if (iOffset > iFileSize) then -- if we are now past start of file
exit repeat -- stop our search
end if
end if
end repeat
if NOT (bFound) then -- if at end of search, and didn't find signature
put "_ReadCentralDirectory:couldn't find central directory record!"
return -5 -- return error
end if
-- otherwise, we should now be positioned right after the end of central
-- dir signature, so we start reading data at 'number of this disk'
iDiskNum = oFile.ReadUnsignedShort() -- number of this disk
iDiskNumWithDir = oFile.ReadUnsignedShort() -- number of disk with start of central dir
iNumEntriesThisDisk = oFile.ReadUnsignedShort() -- # of entries in the central dir on this disk
iNumEntries = oFile.ReadUnsignedShort() -- # of entries in central directory
iSizeOfCentralDir = oFile.ReadUnsignedLong() -- size of central directory
iOffsetToCentralDir = oFile.ReadUnsignedLong() -- offset to start of central directory
iCommentLength = oFile.ReadUnsignedShort() -- length of the comment
if (iCommentLength > 0) then -- if we have a zip comment,
sComment = oFile.ReadBytes(iCommentLength) -- get the comment text
else
sComment = "" -- otherwise assume blank
end if
lData = [:] -- construct end of central dir data
lData.addProp(#disknum, iDiskNum)
lData.addProp(#disknumwithdir, iDiskNumWithDir)
lData.addProp(#numentriesthisdisk, iNumEntriesThisDisk)
lData.addProp(#numentries, iNumEntries)
lData.addProp(#sizeofcentraldir, iSizeOfCentralDir)
lData.addProp(#offsettocentraldir, iOffsetToCentralDir)
lData.addProp(#commentlength, iCommentLength)
lData.addProp(#comment, sComment)
return lData
end
-- given file pointer and data for end of central dir,
-- read the Central Dir
on _ReadCentralDirectory me, oFile, lEndOfCentralDir
-- seek to beginning of central directory
sError = oFile.SetFilePosition(lEndOfCentralDir[#offsettocentraldir])
if (sError.word[1] = "Error") then -- if couldn't seek to central dir
put "_ReadCentralDirectory:error seeking to start of central dir:" & sError
return -6 -- return error
end if
sSignature = me._GetZipSignature(#centralfileheader) -- get signature of central file header
iNumFiles = lEndOfCentralDir[#numentries] -- get # entries in directory
lDirectory = []
repeat with i = 1 to iNumFiles -- scan through all files in the directory
sSig = oFile.ReadBytes(4) -- read the file signature
if (sSig <> sSignature) then -- if it doesn't match,
--put i && "_ReadCentralDirectory:error matching signature:" & sSig
--put "...actually read..."
--repeat with j = 1 to sSig.length
-- put j && chartonum(sSig.char[j])
--end repeat
return -7 -- return error code
--return sSig
end if
iVersionMadeBy = oFile.ReadUnsignedShort() -- read version created by
iVersionNeeded = oFile.ReadUnsignedShort() -- read version required
iGeneralFlag = oFile.ReadUnsignedShort() -- read general flag data
iCompressionMethod = oFile.ReadUnsignedShort() -- read compression method of this file
iTime = oFile.ReadUnsignedShort() -- read time of this file
iDate = oFile.ReadUnsignedShort() -- read date of this file
iCRC = oFile.ReadUnsignedLong() -- read CRC-32 of this file
iCompressedSize = oFile.ReadUnsignedLong() -- compressed size of this file
iUncompressedSize = oFile.ReadUnsignedLong() -- uncompressed size of this file
iFilenameLength = oFile.ReadUnsignedShort() -- length of the path/filename
iDiskNumberStart = oFile.ReadUnsignedShort() -- disk number start (?)
iExtraFieldLength = oFile.ReadUnsignedShort() -- extra field length (?)
iCommentLength = oFile.ReadUnsignedShort() -- length of the file comment
iInternalAttributes = oFile.ReadUnsignedShort() -- internal file attributes
iExternalAttributes = oFile.ReadUnsignedLong() -- external file attributes
iLocalHeaderOffset = oFile.ReadUnsignedLong() -- offset of local header
sFilename = oFile.ReadBytes(iFilenameLength) -- path/filename
if (iExtraFieldLength > 0) then -- if we have extra field length,
sExtraField = oFile.ReadBytes(iExtraFieldLength) -- we extra field length
else
sExtraField = ""
end if
if (iCommentLength > 0) then -- if we have a file comment,
sComment = oFile.ReadBytes(iCommentLength) -- read the file comment
else
sComment = ""
end if
sTime = me._ConvertTime(iTime) -- convert time bits to a string
sDate = me._ConvertDate(iDate) -- convert date bits to a string
-- now add this entry to our list
lRecord = [:]
lRecord.addProp(#arc_filesize, iUncompressedSize)
lRecord.addProp(#arc_filedate, sDate)
lRecord.addProp(#arc_filetime, sTime)
lRecord.addProp(#arc_folder, me._SplitPathFile(sFilename, #path, "/"))
lRecord.addProp(#arc_filename, me._SplitPathFile(sFilename, #file, "/"))
lDirectory.add(lRecord)
end repeat
return lDirectory
end
-- return a string for the requested type of ZIP signature
-- we use this to scan/verify locations in the zip file
on _GetZipSignature me, sType
case (sType) of
#endofcentraldir: -- end of central directory signature
lSig = [ 80, 75, 05, 06 ] -- 50/4b/05/06 = 0x06054b50
#centralfileheader: -- central file header signature
lSig = [ 80, 75, 01, 02 ] -- 50/4b/01/02 = 0x02014b50
otherwise:
lSig = []
end case
sSig = ""
repeat with i = 1 to lSig.count
sSig = sSig & numToChar(lSig[i])
end repeat
return sSig
end
-- use ConvertData xtra (free, for use with BinaryIO Xtra)
-- to do bitshifting to get values
on _ConvertTime me, iTime
iHour = cdatBitShiftRight(iTime, 11) -- shift right to remove min/sec
iMin = iTime
repeat with i = 11 to 15 -- clear out 'hour' bits
iMin = cdatSetBit(iMin, i, 0)
end repeat
iMin = cdatBitShiftRight(iMin, 5) -- shift to clear 'second' bits
iSec = iMin
repeat with i = 5 to 10 -- clear out 'minute' bits
iSec = cdatSetBit(iSec, i, 0)
end repeat
iSec = iSec * 2 -- because only half-seconds are recorded in MS-DOS format
sTime = "" -- construct the time string
lItems = [ iHour, iMin, iSec ]
repeat with i = 1 to lItems.count
sTemp = string(lItems[i])
if (sTemp.length < 2) then -- ensure leading 0's if req'd
sTemp = "0" & sTemp
end if
sTime = sTime & sTemp
if (i < lItems.count) then
sTime = sTime & ":"
end if
end repeat
return sTime
end
-- use ConvertData xtra to convert the numerical date
-- to a string (by bitshifting the number)
on _ConvertDate me, iDate
iYear = cdatBitShiftRight(iDate, 9) -- shift right to remove month/day bits
iYear = iYear + 1980
iMonth = iDate
repeat with i = 9 to 15 -- clear out 'year' bits
iMonth = cdatSetBit(iMonth, i, 0)
end repeat
iMonth = cdatBitShiftRight(iMonth, 5)
iDay = iMonth
repeat with i = 5 to 8 -- clear out 'month' bits
iDay = cdatSetBit(iDay, i, 0)
end repeat
sDay = string(iDay)
if (sDay.length < 2) then
sDay = "0" & sDay
end if
sMonth = string(iMonth)
if (sMonth.length < 2) then
sMonth = "0" & sMonth
end if
sDate = sDay & "-" & sMonth & "-" & iYear
return sDate
end
-- return TRUE if the given xtra is installed, FALSE if not
on _IsXtraPresent me, sXtraName
bFound = FALSE
dNumXtras = the number of xtras
repeat with i = 1 to dNumXtras
if (xtra(i).name = sXtraName) then
bFound = TRUE
exit repeat
end if
end repeat
return bFound
end
-- Given an error number, return the corresponding message.
-- If an error number isn't specified, construct a list
-- of all the error messages.
on _GetErrorMessage me, iErrorNum
if (voidP(iErrorNum)) then
-- compile a list of all error messages
lErrors = [ -1, -2, -3, -4, -5, -6, -7, -997, -998, -999 ]
sMsg = ""
repeat with i = 1 to lErrors.count
iError = lErrors[i]
sMsg = sMsg & me._GetErrorMessage(iError)
if (iError < lErrors.count) then
sMsg = sMsg & RETURN
end if
end repeat
else
-- if I create this as a prop list, how do I return just a specific item??
case (iErrorNum) of
-1: sMsg = "( -1) Error opening file"
-2: sMsg = "( -2) Error getting filesize"
-3: sMsg = "( -3) Error setting file position"
-4: sMsg = "( -4) Error reading from file"
-5: sMsg = "( -5) Couldn't find end of central directory record"
-6: sMsg = "( -6) Error seeking to start of central directory"
-7: sMsg = "( -7) Error matching central directory signature"
-997: sMsg = "(-997) ConvertData Xtra not present"
-998: sMsg = "(-998) BinaryIO Xtra not present"
-999: sMsg = "(-999) Could not get valid instance of BinaryIO Xtra"
otherwise:
sMsg = "unknown error number:" && iErrorNum
end case
end if
return sMsg
end
-- Handlers taken from KA Toolkit Scripts
-- given a full path and filename, return either the path or file component only
-- sType is either #path or #file, or #both and we then return prop list
-- we can supply the optional item delimiter cDelim, if none supplied we use
-- the last character of the moviepath (\ on PC, : on MAC)
on _SplitPathFile me, sText, sType, cDelim
if (voidP(cDelim)) then
the itemdelimiter = the last char of the moviepath
else
the itemdelimiter = cDelim
end if
if (sText.items.count = 1) then
sFile = sText
sPath = ""
else
sFile = the last item of sText
iLen = sText.length - sFile.length
sPath = char 1 to iLen of sText
end if
case (sType) of
#path: return sPath
#file: return sFile
#both: return [ #path: sPath, #filename: sFile ]
end case
end
If there are any questions about this document or how to use this, please feel free to contact me.
Copyright © 2002 Kendall Anderson | Invisible Threads