Import From Paperless

Summary: Imports a database from the Mariner Paperless app (formerly ReceiptWallet).
Requires: EagleFiler
Install Location: ~/Library/Scripts/Applications/EagleFiler/
Last Modified: 2026-01-15

Description

The Paperless app from Mariner Software (formerly called ReceiptWallet) has been discontinued. This script lets you migrate the files and metadata from your Paperless database into EagleFiler. When you run the script, it will ask you to select the .paperless package for your database. It will then import all the files into the frontmost EagleFiler library window:

Installation Instructions · Download in Compiled Format · Download in Text Format

Script

use AppleScript version "2.4"
use
framework "Foundation"
use
scripting additions

property
_tagSeparator : "|"
set
_package to choose file of type {"paperless"} with prompt "Select the Mariner Paperless package to import into EagleFiler."
set
_databasePath to _package's POSIX path & "/DocumentWallet.documentwalletsql"
my
importPackage(_package)

on
importPackage(_package)
    set
_folderName to my getBasename(_package's POSIX path)
    tell
application "EagleFiler"
        tell
library document 1
            set
_folderRecord to add folder name _folderName
            set
_tagName to _folderRecord's name & " Collections"
            set
_topTag to tag _tagName
        end tell
    end tell
    set
_ids to getDocumentIDs()
    repeat with
_id in _ids
        my
importID(_id, _folderRecord, _topTag)
    end repeat
    tell
application "EagleFiler"
        repeat with
_tag in _topTag's tags
            my
removeCollectionIDs(_tag)
        end repeat
    end tell
end
importPackage

on
getDocumentIDs()
    
paragraphs of my sqlite("SELECT Z_PK FROM ZRECEIPT ORDER BY Z_PK")
end
getDocumentIDs

on
importID(_id, _topFolderRecord, _topTag)
    if my
isInTrash(_id) then
        set
_topFolderRecord to my getFolder(_topFolderRecord, "Trash")
    end if
    set
_category to my getCategoryName(_id)
    set
_subcategory to my getSubcategoryName(_id)
    set
_folderRecord to _topFolderRecord
    if
_category is not "" then
        set
_folderRecord to my getFolder(_topFolderRecord, _category)
    end if
    if
_subcategory is not "" then
        set
_folderRecord to my getFolder(_folderRecord, _subcategory)
    end if
    tell
application "EagleFiler"
        global
_package
        tell
_folderRecord's library document
            set
_path to _package's POSIX path & "/" & my getAttribute(_id, "ZPATH")
            try
                set
_tagNames to my getTagNames(_id) & my getCollectionNames(_id, _topTag)
                set {
_record} to import files {_path} container _folderRecord tag names _tagNames note my getNote(_id) with allowing duplicates
                set
_record's added date to my getDate(_id, "ZIMPORTDATE")
                set
_record's creation date to my getDate(_id, "ZDATE")
                set
_record's title to my getAttribute(_id, "ZMERCHANT")
                set
_record's basename to my getBasename(my getAttribute(_id, "ZORIGINALFILENAME"))
            on error
_error
                with timeout of 60 * 60 * 24
seconds
                    
display dialog "Error importing: " & _path & return & return & _error
                end timeout
            end try
        end tell
    end tell
end
importID

on
isInTrash(_id)
    set
_value to my getAttribute(_id, "ZINTRASHVALUE")
    if
_value is "" then return false
    return
_value as number as boolean
end
isInTrash

on
getAttribute(_id, _name)
    my
sqlite("SELECT " & _name & " FROM ZRECEIPT WHERE Z_PK=" & _id)
end
getAttribute

on
getCategoryName(_id)
    my
sqlite("SELECT ZNAME FROM ZCATEGORY INNER JOIN ZRECEIPT ON ZCATEGORY.Z_PK = ZRECEIPT.ZCATEGORY WHERE ZRECEIPT.Z_PK=" & _id)
end
getCategoryName

on
getSubcategoryName(_id)
    my
sqlite("SELECT ZNAME FROM ZSUBCATEGORY INNER JOIN ZRECEIPT ON ZSUBCATEGORY.Z_PK = ZRECEIPT.ZSUBCATEGORY WHERE ZRECEIPT.Z_PK=" & _id)
end
getSubcategoryName

on
getDate(_id, _name)
    set
_timeInterval to my getAttribute(_id, _name) as number
    set
_nsDate to current application's NSDate's dateWithTimeIntervalSinceReferenceDate:_timeInterval
    return
_nsDate as date
end
getDate

on
getTagNames(_id)
    set
_sql to "SELECT ZNAME FROM ZTAG WHERE Z_PK IN (SELECT Z_18TAGS FROM Z_14TAGS INNER JOIN ZRECEIPT ON Z_14TAGS.Z_14RECEIPTS1 = ZRECEIPT.Z_PK WHERE ZRECEIPT.Z_PK=" & _id & ")"
    return
paragraphs of my sqlite(_sql)
end
getTagNames

on
getCollectionIDs(_id)
    set
_sql to "SELECT Z_3COLLECTIONS FROM Z_3RECEIPTS INNER JOIN ZRECEIPT ON Z_3RECEIPTS.Z_14RECEIPTS = ZRECEIPT.Z_PK WHERE ZRECEIPT.Z_PK=" & _id
    return
paragraphs of my sqlite(_sql)
end
getCollectionIDs

on
isBasicCollection(_id)
    my
sqlite("SELECT ZTYPE=1 FROM ZCOLLECTION WHERE Z_PK=" & _id) as number as boolean
end
isBasicCollection

on
getCollectionName(_id)
    my
sqlite("SELECT ZNAME FROM ZCOLLECTION WHERE Z_PK=" & _id)
end
getCollectionName

on
getCollectionParent(_id)
    my
sqlite("SELECT ZPARENT FROM ZCOLLECTION WHERE Z_PK=" & _id)
end
getCollectionParent

on
getCollectionNames(_id, _topTag)
    set
_result to {}
    repeat with
_id in my getCollectionIDs(_id)
        if my
isBasicCollection(_id) then
            set
_tagName to my tagNameForCollection(_id, _topTag)
            copy
_tagName to end of _result
        end if
    end repeat
    return
_result
end
getCollectionNames

on
tagNameForCollection(_id, _topTag)
    tell
application "EagleFiler"
        tell
_topTag's library document
            set
_parentCollection to my getCollectionParent(_id)
            if
_parentCollection is "" then
                set
_parentTag to _topTag
            else
                set
_parentTag to tag (my tagNameForCollection(_parentCollection, _topTag))
            end if
            set
_tagName to my getCollectionName(_id) & _tagSeparator & _id
            set
_tag to tag _tagName
            set
_tag's container to _parentTag
            return
_tag's name
        end tell
    end tell
end
tagNameForCollection

on
removeCollectionIDs(_tag)
    tell
application "EagleFiler"
        set {
_newName, _prefix} to my split(_tag's name, _tagSeparator)
        try
            set
_tag's name to _newName
        on error
            
-- This requires EagleFiler 1.9.20 or later.
            
-- If there's a duplicate name, it won't be changed and the ID number will remain appended.
        end try
        repeat with
_child in _tag's tags
            my
removeCollectionIDs(_child)
        end repeat
    end tell
end
removeCollectionIDs

on
getNote(_id)
    set
_note to ""
    set
_note to my appendField(_note, "Amount", _id, "ZAMOUNTASSTRING")
    set
_note to my appendField(_note, "Shipping", _id, "ZSHIPPINGAMOUNTASSTRING")
    set
_note to my appendField(_note, "Shipping", _id, "ZSHIPPINGAMOUNTSTRING")
    set
_note to my appendField(_note, "Tax", _id, "ZTAXAMOUNT2ASSTRING")
    set
_note to my appendField(_note, "Tax", _id, "ZTAXAMOUNTASSTRING")
    set
_note to my appendField(_note, "Tax", _id, "ZTAXAMOUNTSTRING2")
    set
_note to my appendField(_note, "Tip", _id, "ZTIPAMOUNTASSTRING")
    set
_note to my appendField(_note, "Tip", _id, "ZTIPAMOUNTSTRING")
    set
_note to my appendField(_note, "Custom 1", _id, "ZCUSTOM1")
    set
_note to my appendField(_note, "Custom 2", _id, "ZCUSTOM2")
    set
_note to my appendField(_note, "Custom 3", _id, "ZCUSTOM3")
    set
_note to my appendField(_note, "Custom 4", _id, "ZCUSTOM4")
    set
_note to my appendField(_note, "Custom 5", _id, "ZCUSTOM5")
    set
_note to my appendField(_note, "Custom 6", _id, "ZCUSTOM6")
    set
_note to my appendField(_note, "Notes", _id, "ZNOTES")
    set
_note to my appendField(_note, "OCR", _id, "ZOCRRESULT")
end
getNote

on
appendField(_string, _title, _id, _column)
    set
_value to my getAttribute(_id, _column)
    if
_value is "" then return _string
    if
_value is "$0.00" and _column contains "AMOUNT" then return _string
    if
_title is not "" then
        set
_string to _string & _title & ": "
    end if
    set
_string to _string & _value
    set
_string to _string & return
    return
_string
end
appendField

on
sqlite(_sql)
    global
_databasePath
    set
_script to "sqlite3 " & _databasePath's quoted form & " " & _sql's quoted form
    try
        return
do shell script _script
    on error
_error
        error
_error & return & "SQL:" & _sql
    end try
end
sqlite

on
getFolder(_folderRecord, _name)
    tell
application "EagleFiler"
        set
_name to my makeFileNameValid(_name)
        try
            return
library record _name of _folderRecord
        on error
            tell
_folderRecord's library document
                return
add folder name _name container _folderRecord
            end tell
        end try
    end tell
end
getFolder

on
makeFileNameValid(_name)
    set
_name to my replace(_name, ":", ":") -- "FULLWIDTH COLON" U+FF1A
    set
_name to my replace(_name, "/", ":")
    set
_limit to 255
    set
_extension to my pathExtension(_name)
    set
_basename to my getBasename(_name)
    set
_basenameLimit to _limit - ((length of _extension) + 1)
    set
_basename to my prefix(_basename, _basenameLimit)
    if
_extension is "" then return _basename
    set
_result to _basename & "." & _extension
    set
_result to my replace(_name, ":", "/")
    return
_result
end
makeFileNameValid

on
getBasename(_path) -- Terminology conflict without the "get"
    set
_nsString to current application's NSString's stringWithString:_path
    return
_nsString's lastPathComponent's stringByDeletingPathExtension as Unicode text
end
getBasename

on
pathExtension(_path)
    set
_nsString to current application's NSString's stringWithString:_path
    return
_nsString's lastPathComponent's pathExtension as Unicode text
end
pathExtension

on
replace(_string, _source, _replacement)
    return my
join(my split(_string, _source), _replacement)
end
replace

on
join(_list, _sep)
    set
_temp to AppleScript's text item delimiters
    set
AppleScript's text item delimiters to _sep
    set
_result to _list as string
    set
AppleScript's text item delimiters to _temp
    return
_result
end
join

on
split(_string, _sep)
    set
_temp to AppleScript's text item delimiters
    set
AppleScript's text item delimiters to _sep
    set
_result to text items of _string
    set
AppleScript's text item delimiters to _temp
    return
_result
end
split

on
prefix(_string, _length)
    if
length of _string_length then return _string
    my
join(characters 1 thru _string of _basename, "")
end
prefix