Skip to main content

⚠️ Please note that this topic or post has been archived. The information contained here may no longer be accurate or up-to-date. ⚠️

SemiAutomatic Image Sequencer

Comments

3 comments

  • Eric Valk
    Here is the second part of the script


    ###############################
    ## Logging Handlers Version 2019/02/23

    on setupLoqqing5()
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## Handler to setup the variables used by the loqqing system at the start of the script

    global debugLogEnable, Loqqing, loqResultDocRef, loqResultMethod, loqDialogTextList
    ## loqResultDocRef is kept separate from Loqqing to allow easy listing of Loqqing

    ## Default values are entered
    set {loqResultDocRef, loqResultMethod} to {false, "not Initialized"}
    set Loqqing to {stateResultDoc:false, gateResultsFile:false, enableResultsFile:false, initResultDoc:false, ResultsFileMaxDebug:0, debugLogLevel:0}
    set Loqqing to Loqqing & {stateResultsByClipboard:true, gateResultsByClipboard:true, enableResultsByClipboard:true}
    set Loqqing to Loqqing & {stateResultsByDialog:false, gateResultsDialog:false, enableResultsByDialog:false, maxDialogPercent:50, maxDialogLines:25, maxDialogChar:1000}
    set Loqqing to Loqqing & {stateResultsByNotification:false, gateResultsNotification:false, enableResultsByNotifications:false, enableNotifications:true, notificationsMaxDebug:0}
    set Loqqing to Loqqing & {stateResultsByLoq:true, gateParentLoqqing:true, enableResultsByLoq:true, initLoqqing:false, gateLoqqing:true}

    return {}
    end setupLoqqing5

    on InitializeLoqqing5(DocName_Ext, sourceTitle)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## Handler to initialize logging of results
    ## Do use loq_Results() until the end of the handler and initialising is completed
    ## Doesn't set loqResultMethod, this is set by caller of this handler

    global parent_name, Script_Title, Loqqing, gateScriptProgress, loqDialogTextList, loqResultDocRef

    local LogMethods, LogHeader, date_string
    tell current application to set date_string to (current date) as text
    set LogMethods to {}
    set LogHeader to (sourceTitle & " results on " & date_string)

    local targetFileWasCreated, TextEditlist, createdLine
    if Loqqing's gateResultsFile and Loqqing's enableResultsFile then
    set end of LogMethods to DocName_Ext
    if not Loqqing's initResultDoc then
    set targetFileWasCreated to false
    set Loqqing's stateResultDoc to false
    set Loqqing's initResultDoc to true

    ## If TextEdit is already open and has the document open then add the headerline
    tell application "System Events" to set TextEditlist to get background only of every application process whose name is "TextEdit"
    if (0 < (count of TextEditlist)) and not item 1 of TextEditlist then
    if (DocName_Ext is in (get name of documents of application "TextEdit")) then
    tell application "TextEdit" to tell document DocName_Ext
    set loqResultDocRef to it
    tell its text to set paragraph (1 + (count paragraphs)) to return & LogHeader & return
    set Loqqing's stateResultDoc to true
    end tell
    end if
    end if
    end if

    local targetFolderParent_a, targetFolderParent_p, targetFolderName, targetFolder_a, targetFolder_p, ResultDocPath_a, ResultDocPath_p, newFolderRef, newFileRef
    if not Loqqing's stateResultDoc or (false = loqResultDocRef) then
    -- create the document and the folder if necessary
    -- Do not use finder to test for the file existence because it has a bug that ignores leading 0's
    -- https://www.macscripter.net/viewtopic.php?id=45178
    set targetFolderParent_a to alias (get path to desktop folder as text)
    set targetFolderParent_p to get POSIX path of targetFolderParent_a
    set targetFolderName to "ScriptReports"
    set targetFolder_p to (targetFolderParent_p & targetFolderName)
    set ResultDocPath_p to targetFolder_p & "/" & DocName_Ext

    try
    set ResultDocPath_a to (get alias POSIX file ResultDocPath_p)
    on error
    try
    set targetFolder_a to (get alias POSIX file targetFolder_p) --x1
    on error
    tell application "Finder" to set newFolderRef to make new folder at targetFolderParent_a with properties {name:targetFolderName}
    set targetFolder_a to newFolderRef as alias
    end try
    tell application "Finder" to set newFileRef to make new file at targetFolder_a with properties {name:DocName_Ext}
    set ResultDocPath_a to newFileRef as alias
    set targetFileWasCreated to true
    end try

    set createdLine to ("Created by " & Script_Title & " on " & date_string)
    tell application "TextEdit" -- open the document and add the first line if empty
    activate
    set loqResultDocRef to open ResultDocPath_a
    tell text of loqResultDocRef
    if targetFileWasCreated then
    set paragraph 1 to createdLine & return & return
    else
    if (0 = (count of paragraphs)) then set paragraph 1 to createdLine & return & return
    end if
    end tell
    if 2 ≤ Loqqing's debugLogLevel then tell me to log ResultDocPath_p & ": " & createdLine
    end tell

    set Loqqing's stateResultDoc to true -- prevents initialisation from repeating
    tell application "TextEdit" to tell text of loqResultDocRef to ¬
    set paragraph (1 + (count paragraphs)) to return & LogHeader & return
    end if
    end if
    if Loqqing's gateResultsFile and Loqqing's stateResultDoc and not Loqqing's enableResultsFile then
    set Loqqing's stateResultDoc to false
    tell application "TextEdit" to tell text of loqResultDocRef to ¬
    set paragraph (1 + (count paragraphs)) to return & parent_name & "Results reporting disabled for: " & Script_Title & return
    end if

    local screenWidthO, screenHeightO, screenWidth, screenHeight, fontSize_pts, charactersPerLine, dotsPer_Point, linesPerScreen, borderLines
    if Loqqing's gateResultsDialog and Loqqing's enableResultsByDialog then
    set end of LogMethods to "Dialogs"
    if not Loqqing's stateResultsByDialog then
    set loqDialogTextList to (get LogHeader & return)
    set Loqqing's stateResultsByDialog to true
    end if
    tell application "Finder" to set {screenWidthO, screenHeightO, screenWidth, screenHeight} to bounds of window of desktop
    set fontSize_pts to 12 -- estimated font size for display dialog, including line spacing
    set charactersPerLine to 58 -- estimated characters per line fo display dialog, if there are no "return" characters
    set dotsPer_Point to 1.5 -- similar for 5K 27" iMac and 11" MBA - similar for others - retina independent
    set linesPerScreen to (screenHeight - screenHeightO) / (fontSize_pts * dotsPer_Point)
    set borderLines to 5
    set Loqqing's maxDialogLines to (get ((Loqqing's maxDialogPercent) / 100 * (linesPerScreen - borderLines)) as integer)
    set Loqqing's maxDialogChar to (get ((Loqqing's maxDialogPercent) / 100 * (linesPerScreen - borderLines) * charactersPerLine) as integer)
    if 2 ≤ Loqqing's debugLogLevel then log {"maxDialogPercent", Loqqing's maxDialogPercent, "screenHeight", screenHeight, "linesPerScreen", linesPerScreen, "maxDialogLines", Loqqing's maxDialogLines, "maxDialogChar", Loqqing's maxDialogChar}
    end if
    if Loqqing's gateResultsDialog and Loqqing's stateResultsByDialog and not Loqqing's enableResultsByDialog then
    set Loqqing's stateResultsByDialog to false
    set loqDialogTextList to ""
    end if

    if Loqqing's gateResultsByClipboard and Loqqing's enableResultsByClipboard then
    set end of LogMethods to "Clipboard"
    if not Loqqing's stateResultsByClipboard then
    set the clipboard to LogHeader
    set Loqqing's stateResultsByClipboard to true
    end if
    end if
    if Loqqing's gateResultsByClipboard and Loqqing's stateResultsByClipboard and not Loqqing's enableResultsByClipboard then
    set the clipboard to {}
    set Loqqing's stateResultsByClipboard to false
    end if

    global gateScriptProgress
    if not Loqqing's initLoqqing then
    set Loqqing's initLoqqing to true
    if ("Script Editor" = parent_name) then
    set Loqqing's gateParentLoqqing to true
    set Loqqing's gateLoqqing to true
    set gateScriptProgress to true
    try -- Open the Log History window
    tell application "System Events" to tell application process "Script Editor"
    if (get name of windows) does not contain "log History" then ¬
    click menu item "Log History" of menu "Window" of menu bar 1
    end tell
    end try
    else if ("Script Debugger" = parent_name) then
    set Loqqing's gateParentLoqqing to false
    set Loqqing's gateLoqqing to true
    set gateScriptProgress to true
    ## Avoids compiler errors when Script Debugger is not present
    run script "tell application "Script Debugger" to tell first document to set event log visible to true"
    run script "tell application "Script Debugger" to tell first document to set event log scope bar visible to true"
    else
    set Loqqing's gateParentLoqqing to false
    set Loqqing's gateLoqqing to false
    set gateScriptProgress to false
    end if
    end if

    if Loqqing's gateParentLoqqing and Loqqing's enableResultsByLoq then
    set end of LogMethods to " " & parent_name & " Log"
    if not Loqqing's stateResultsByLoq then
    set Loqqing's stateResultsByLoq to true
    log "Results Logging enabled for: " & Script_Title
    end if
    end if
    if Loqqing's gateParentLoqqing and Loqqing's stateResultsByLoq and not Loqqing's enableResultsByLoq then
    set Loqqing's stateResultsByLoq to false
    log "Results Logging disabled for: " & Script_Title
    end if

    if (Loqqing's gateResultsNotification and Loqqing's enableResultsByNotifications) or Loqqing's enableNotifications then
    set end of LogMethods to "Notifications"
    if not Loqqing's stateResultsByNotification then
    --display notification "Notifications enabled for: " & Script_Title
    set Loqqing's stateResultsByNotification to true
    end if
    else if Loqqing's stateResultsByNotification then
    display notification "Notifications disabled for: " & Script_Title
    set Loqqing's stateResultsByNotification to false
    end if

    set LogMethods_S to U's joinListToString(LogMethods, ", ")
    if 2 ≤ Loqqing's debugLogLevel then loqThis(2, false, ("Result Reported by " & LogMethods_S))
    return LogMethods_S
    end InitializeLoqqing5

    on loqThis(thisLogDebugLevel, MakeFront, log_Text)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose handler for logging results
    ## log results if the debug level of the message is below the the threshold set by debugLogLevel
    ## log the results by whatever mechanism is ebabled - {Script Editor Log, Text Editor Log, Display Dialog}

    global parent_name, Loqqing, loqDialogTextList, loqResultDocRef
    local log_Text_S

    if thisLogDebugLevel ≤ Loqqing's debugLogLevel then
    set log_Text_S to U's joinListToString(log_Text, "; ")
    set triggeredDialog to false

    if (thisLogDebugLevel = 0) then -- Critical errors
    if Loqqing's gateLoqqing then log (log_Text_S)
    if Loqqing's enableResultsByClipboard then set the clipboard to ((get the clipboard) & return & (log_Text_S as text))
    if Loqqing's enableResultsFile then ¬
    tell application "TextEdit" to tell text of loqResultDocRef to ¬
    set paragraph (1 + (count paragraphs)) to (log_Text_S & return)
    if Loqqing's enableResultsByDialog then display alert log_Text_S giving up after 10
    (Loqqing's enableResultsByNotifications and (thisLogDebugLevel ≤ 0))
    else if (thisLogDebugLevel ≤ 0) then -- Expected Results
    if Loqqing's enableResultsByLoq then log (log_Text_S)
    if Loqqing's enableResultsByClipboard then set the clipboard to ((get the clipboard) & return & (log_Text_S as text))

    if Loqqing's enableResultsFile then ¬
    tell application "TextEdit" to tell text of loqResultDocRef to ¬
    set paragraph (1 + (count paragraphs)) to (log_Text_S & return)

    if Loqqing's enableResultsByDialog then -- process the dialog last
    set loqDialogTextList to (get loqDialogTextList & return & log_Text_S)
    if MakeFront or (0 ≥ Loqqing's maxDialogLines) or ¬
    (Loqqing's maxDialogLines < (get count of paragraphs of loqDialogTextList)) or ¬
    (Loqqing's maxDialogChar < (get length of loqDialogTextList)) then
    --tell application "System Events" to set frontmost of process parent_name to true
    display dialog loqDialogTextList
    set triggeredDialog to true
    set loqDialogTextList to ""
    end if
    end if

    else -- debugging information
    if Loqqing's gateLoqqing then log (log_Text_S)
    if Loqqing's enableResultsFile and ((thisLogDebugLevel ≤ Loqqing's ResultsFileMaxDebug) or not Loqqing's gateLoqqing) then ¬
    tell application "TextEdit" to tell text of loqResultDocRef to ¬
    set paragraph (1 + (count paragraphs)) to ((log_Text_S as text) & return)
    end if

    if (Loqqing's enableNotifications and (thisLogDebugLevel ≤ Loqqing's notificationsMaxDebug)) or ¬
    (Loqqing's enableResultsByNotifications and (thisLogDebugLevel ≤ 0)) then
    set paraCtr to 0
    set notString to ""
    set lineCnt to 39
    set paramax to 3
    copy (get paramax * lineCnt) to remChar
    repeat with thePara in (get paragraphs of log_Text_S)
    set thisCount to (get count of (get contents of thePara))
    if thisCount > 0 then
    set paraCtr to paraCtr + 1
    if thisCount > remChar then copy remChar to thisCount
    set notString to notString & (get text 1 thru thisCount of thePara)
    set remChar to remChar - lineCnt * (thisCount div lineCnt)
    if (0 < (thisCount mod lineCnt)) then set remChar to remChar - lineCnt
    if (paramax ≤ paraCtr) or (0 ≥ remChar) then exit repeat
    set notString to notString & return
    end if
    end repeat
    display notification notString
    end if

    if (not triggeredDialog) and (MakeFront or (0 = thisLogDebugLevel)) then --
    if Loqqing's gateLoqqing then tell application "System Events" to set frontmost of process parent_name to true
    if Loqqing's enableResultsFile then tell application "System Events" to set frontmost of process "TextEdit" to true
    end if

    else
    set log_Text_S to ""
    end if
    return log_Text_S
    end loqThis

    on loqqed_Error_Halt5(errorText)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose handler for logging during script termination
    global debugLogEnable, Script_Title
    try
    M's finalCleanup()
    end try
    tell current application to set date_string to (current date) as text
    return (get loqThis(0, true, ("Script "" & Script_Title & "" is exitting at " & date_string & "Reason: " & errorText & return)))
    end loqqed_Error_Halt5

    ####################################
    ## General Utility Handlers Version 2019/01/12
    ## No Dependencies on other Libraries

    on compareVersion(testVersion_S, minVersion_S, maxVersion_S)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose handler for comparing versions
    --local digitMult, testVersionNumber, minVersionNumber, maxVersionNumber, testVersion_LS, minVersion_LS, maxVersion_LS, groupsCount, group_ctr
    --local testGroupsCount, minGroupsCount, maxGroupsCount, hasTestGroup, hasMinGroup, hasMaxGroup, testGroupVersion, minGroupVersion, maxGroupVersion
    --local allBoundsPass, lowerBoundPass, upperBoundPass, lowerBoundFail, upperBoundFail

    if (text ≠ (get class of testVersion_S)) or (text ≠ (get class of minVersion_S)) or (text ≠ (get class of maxVersion_S)) then error "Text inputs only"

    set testVersion_LS to (splitStringToList(removeLeadingTrailingSpaces(testVersion_S), "."))
    set minVersion_LS to (splitStringToList(removeLeadingTrailingSpaces(minVersion_S), "."))
    set maxVersion_LS to (splitStringToList(removeLeadingTrailingSpaces(maxVersion_S), "."))

    set testGroupsCount to get count of testVersion_LS
    set minGroupsCount to get count of minVersion_LS
    set maxGroupsCount to get count of maxVersion_LS

    set groupsCount to testGroupsCount
    if groupsCount < minGroupsCount then set groupsCount to minGroupsCount
    if groupsCount < maxGroupsCount then set groupsCount to maxGroupsCount

    set lowerBoundPass to false
    set lowerBoundFail to false
    set upperBoundPass to false
    set upperBoundFail to false

    repeat with group_ctr from 1 to groupsCount

    set hasTestGroup to (get group_ctr ≤ testGroupsCount)
    set hasMinGroup to (get group_ctr ≤ minGroupsCount)
    set hasMaxGroup to (get group_ctr ≤ maxGroupsCount)

    if hasTestGroup then set testGroupVersion to (get (item group_ctr of testVersion_LS) as integer)
    if hasMinGroup then set minGroupVersion to (get (item group_ctr of minVersion_LS) as integer)
    if hasMaxGroup then set maxGroupVersion to (get (item group_ctr of maxVersion_LS) as integer)

    if not (lowerBoundPass or lowerBoundFail) then
    if hasMinGroup and hasTestGroup then
    if testGroupVersion > minGroupVersion then set lowerBoundPass to true
    if testGroupVersion < minGroupVersion then set lowerBoundFail to true
    else if hasMinGroup then -- (and no testGroup) e.g. test 11 , min 11.1
    set lowerBoundFail to true -- not symetric with upper bound behaviour
    else if hasTestGroup then -- (and no minGroup) e.g. test 11.1, min 11 --> OK
    set lowerBoundPass to true
    end if
    end if

    if not (upperBoundPass or upperBoundFail) then
    if hasMaxGroup and hasTestGroup then
    if testGroupVersion < maxGroupVersion then set upperBoundPass to true
    if testGroupVersion > maxGroupVersion then set upperBoundFail to true
    else if hasMaxGroup then -- (and no testGroup) e.g. test 11.1 , max 11.1.2 or test 11 , max 11.1
    set upperBoundPass to true -- not symetric with lower bound behaviour
    else if hasTestGroup then --(and no maxGroup) e.g. test 11.1, max 11 --> pass
    set upperBoundPass to true
    end if
    end if

    end repeat

    return {maxVersionPass:(not upperBoundFail), minVersionPass:(not lowerBoundFail)}
    end compareVersion

    on deReference(theItem, theclassName)
    ## General purpose handler for removing references from a variable
    ## reusult is a value, or list of values, of the specified class
    ## Enables data handling of multiple classes of items in the same code

    if class = (get class of theclassName) then set theclassName to (get theclassName as text)

    if list = (get class of (get theItem)) then
    set theResult to {}
    set cntItems to length of theItem
    if (text = (get class of theclassName)) then
    set hasClassList to false
    else if (list = (get class of theclassName)) then
    if (1 = (get length of theclassName)) then
    set hasClassList to false
    else if (cntItems = (get length of theclassName)) then
    set hasClassList to true
    else
    error "deReference() can't handle a mismatch between number of items and number of classes"
    end if
    end if
    if not hasClassList then copy (get theclassName as text) to thisclassName
    repeat with item_ctr from 1 to cntItems
    if hasClassList then copy (get (theclassName's item item_ctr) as text) to thisclassName
    copy (get theItem's item item_ctr) to thisItem
    if "boolean" = thisclassName then
    set the end of theResult to false or (get thisItem as boolean)
    else if "integer" = thisclassName then
    set the end of theResult to 0 + (get thisItem as integer)
    else if "text" = thisclassName then
    set the end of theResult to "" & (get thisItem as text)
    else if "real" = thisclassName then
    set the end of theResult to 0.0 + (get thisItem as real)
    else if "date" = thisclassName then
    set the end of theResult to (get thisItem as date) + 0
    else
    error "deReference() can't handle class "" & thisclassName & """
    end if
    end repeat

    else
    if "boolean" = theclassName then
    set theResult to false or (get theItem as boolean)
    else if "integer" = theclassName then
    set theResult to 0 + (get theItem as integer)
    else if "text" = theclassName then
    set theResult to "" & (get theItem as text)
    else if "real" = theclassName then
    set theResult to 0.0 + (get theItem as real)
    else if "date" = theclassName then
    set theResult to (get theItem as date) + 0
    else
    error "deReference() can't handle class "" & theclassName & "" as " & (get class of theclassName)
    end if
    end if
    return theResult
    end deReference

    on makeList(listLength, theElement)
    -- Note that the theElement can even be a List
    if listLength = 0 then return {}
    if listLength = 1 then return {theElement}

    set theList to {theElement}
    repeat while (count of theList) < listLength / 2
    copy contents of theList to ListB
    copy theList & ListB to theList
    end repeat
    copy contents of theList to ListB
    return (theList & items 1 thru (listLength - (count of ListB)) of ListB)
    end makeList

    on splitStringToList(theString, theDelim)
    ## Public Domain
    set theList to {}
    set astid to AppleScript's text item delimiters
    try
    set AppleScript's text item delimiters to theDelim
    set theList to text items of theString
    on error
    set AppleScript's text item delimiters to astid
    end try
    set AppleScript's text item delimiters to astid
    return theList
    end splitStringToList

    on joinListToString(theList, theDelim)
    ## Public Domain
    set theString to ""
    set astid to AppleScript's text item delimiters
    try
    set AppleScript's text item delimiters to theDelim
    set theString to theList as string
    end try
    set AppleScript's text item delimiters to astid
    return theString
    end joinListToString

    on removeLeadingTrailingSpaces(theListString)
    ## 40% faster than a version which trims the string 1 space at a time
    ## handles both string and list input correctly
    -- local theListString, input_is_list, cleanList, thecount, hasTriggered, indexLow, indexHigh

    set input_is_list to ("list" = (get (class of theListString) as text))
    if not input_is_list then set theListString to {theListString}

    set cleanList to {}
    repeat with theString in theListString
    if ("text" = (get (class of theString) as text)) then
    set thecount to (get count of theString)
    set hasTriggered to false
    repeat with indexLow from 1 to thecount
    if " " ≠ (get text indexLow of theString as text) then
    set hasTriggered to true
    exit repeat
    end if
    end repeat
    if not hasTriggered then
    set theString to ""
    else
    repeat with indexHigh from -1 to (-thecount) by -1
    if " " ≠ (get text indexHigh of theString as text) then exit repeat
    end repeat
    set theString to text indexLow thru indexHigh of theString
    end if
    end if

    set the end of cleanList to theString
    end repeat

    if input_is_list then return cleanList

    return (get contents of theString)
    end removeLeadingTrailingSpaces

    on replaceText(this_text, search_string, replacement_string)
    set astid to AppleScript's text item delimiters
    try
    set AppleScript's text item delimiters to the search_string
    set the item_list to every text item of this_text
    set AppleScript's text item delimiters to the replacement_string
    set this_text to the item_list as string
    end try
    set AppleScript's text item delimiters to astid
    return this_text
    end replaceText
    0
  • Eric Valk
    Heree is an update which is better documented and has more functions.

    With this version you can:
    • have a counter in the sequence name instead of the image numbers

    • the sequence name can be copied into the variant's metadata (selectable)

    • Making albums for each sequence is selectable

    • Making the Unsorted smart album is selectable


    Also a couple of bug fixes.

    This is the first part, the second part has not changed.
    Copy and paste this part into Script Editor's script window, and then copy and paste the second part from thee previous posting.

    [color=#BF0040:14fwk4ll]Code Editted once[/color:14fwk4ll]

    ## Applescript to sequence images in a COP 12 Catalog
    ## Version 12.22 !! NO GUARANTEE OF SUPPORT !! Best effort
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.

    -- *** Use and Function
    -- select a Project, an album or smart album which is under a Project or Group (or both)
    -- Select some variants and run this script
    -- If not already present, a group "Stacks" and a smart album "Unsorted" will be created
    -- the smart album rules will hide all images with blue or magenta color tag
    -- an album with a name starting with SEQ will be created in the group Stacks
    -- the album name will contain the lowest and highest numbers sequences from the selected variants
    -- the selected variants will be color tagged Blue and copied to the album
    -- the Sequence name will be copied into the IPTC Image Scenes metadata of the Variants
    -- Once "Unsorted" Exists, continue creating sequences from there, only the unsequenced images will be shown.
    -- ***To Setu

    -- Start Script Editor, open a new (blank) file, copy and paste both parts into one Script Editor Document, compile (hammer symbol) and save.
    -- Best if you make "Scripts" folder somewhere in your Documents or Desktop
    -- This file is suitable to use as an application in Capture One Pro's Script Menu

    -- *** Operation in Script Editor
    -- Open the compiled and saved document
    -- Open the Script Editor log window, and select the messages tab
    -- The user may elect to set defaults for enabling or disabling results in Notifications, TextEdit and Script Editor by setting the "enable" variables at beginning of the script
    -- The user may change the default amount of reporting by setting the "debugLogLevel" and "ResultsFileMaxDebug" variables at beginning of the script
    -- If you are having some issues, then set debugLogLevel to 3 and send me the results from Script Editors log window, or Text Edit.

    use AppleScript version "2.5"
    use scripting additions

    property M : missing value
    set M to me
    global C, L, U
    set C to me
    set L to me
    set U to me

    set loqGUIsettings_L to L's setupLoqqing5() -- setup the variables used for Loqqing system

    ## Values in this section are safe to change, within limits indicated. Support is likely but no commitment
    ## Default values

    ### Read Carefully
    set enableSeqImageName to true -- enable image based sequence name; disabling results in counter based sequence names

    set enableCreateCollections to true -- this enable creating of albums for each sequence, and creating of the "Stacks" group
    set enableCreateUnsorted to true -- this enable creating of of the "Unsorted" smart album
    set nameStackGroup to "Stacks" -- this is the name of group that holds the albums for each sequence
    set nameUnsortedAlbum to "Unsorted" -- this is the name of smart album that shows the not yet sequenced variants
    set enableSequenceNameEntry to false -- enables manual entry of sequence names
    set seqBaseImageName to "SEQ " -- the base used for image based sequence name
    set seqBaseCountedName to "SEQ-" -- the base used for counter based sequence name
    set seqCountDigits to 2 -- the number of counter digits in a counter based sequence name
    property sequenceCount : 1 -- the starting count in a counter based sequence name
    set enableSeqInMetaData to true -- shall the sequence name be written to the IPTC Image Scenes metadata
    set enableMetaDataMultiSeq to true -- shall the sequence name be added to other sequences in the IPTC Image Scenes metadata

    set Loqqing's debugLogLevel to 0 -- 0...6 Values >1 result in increasing amounts of debug data that takes longer to report
    set Loqqing's enableResultsFile to true -- (true/false)
    set Loqqing's enableResultsByClipboard to false -- (true/false)
    set Loqqing's enableNotifications to true -- (true/false) - enable notifications of errors and exceptions
    set Loqqing's notificationsMaxDebug to 1 -- 0...6 suggest not more than 1
    set Loqqing's ResultsFileMaxDebug to 3 -- 0...6 suggest not more than 2
    set Loqqing's enableResultsByNotifications to false
    set Loqqing's enableResultsByLoq to true


    set sortedColorTag1 to 5 -- for the stacked images
    set sortedColorTag2 to 6 -- for the picked stacked images
    --Color Tag
    --Criterion
    --0--> none
    --1-->red
    --2-->orange
    --3-->yellow
    --4-->green
    --5-->blue
    --6-->magenta
    --7-->purple


    ## ***** Not safe to change stuff below this line, unless you have some background in SW development.
    ## I generally won't help much if you change stuff below this line. I may explain the design intent.

    set inpGroup1 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them
    set inpGroup2 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character
    set inpMethod1 to {"SOOC Parse", {inpGroup1, inpGroup2}} -- parses the image file names typically assigned by a camera

    set inpGroup3 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}, {"-", "M", 0}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them. Start over next method if a "-" is found.
    set inpGroup4 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{{"-", "M", 0}}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character. Start over next method if a "-" is found.

    set inpGroup5 to {inpPref:"", inpAlp:false, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:4, inpAct:{{return, "O"}, {return, "D"}}} -- Take Alpha and Numbers, stop on Space or symbol or 4 characters. Then discard
    set inpGroup6 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:3, inpAct:{{"$", "G"}, {return, "O"}, {return, "D"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 3 characters. Then discard. Start over if a "$" is found
    set inpGroup7 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:5, inpAct:{{"$", "G"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 5 characters. Start over if a "$" is found
    set inpMethod2 to {"Universal Parse", {inpGroup3, inpGroup4}, {inpGroup5, inpGroup6, inpGroup7}} -- combination of Method 3 and Method 1
    set inpMethod3 to {"Eric's Detailed Parse", {inpGroup5, inpGroup6, inpGroup7}} -- parses Eric's image file names

    set imageNameParsingMethod to inpMethod1 -- OK to change this

    set debugLogEnable to true

    ## Result reporting methods which are valid for this script
    set Loqqing's gateResultsFile to true
    set Loqqing's gateResultsByClipboard to true
    set Loqqing's gateResultsNotification to true

    ## Reporting methods which are NOT valid for this script
    -- disable the reporting method
    set Loqqing's gateResultsDialog to false
    -- disable user control
    set Loqqing's enableResultsByDialog to false -- (true/false)
    set Loqqing's maxDialogPercent to 85 -- (0% to 100% of the monitor) The amount of data that triggers a dialog report
    ##


    tell application "System Events" to set parent_name to name of current application
    set Script_Title to (get name of me)
    set Result_DocName to "CO_Image_Sorting_Report.txt"

    set loqResultMethod to L's InitializeLoqqing5(Result_DocName, Script_Title) -- Initialize the results logging system

    set {minCOPversion, maxCOPversion} to {"12", "12"}
    tell C's validateCOP5(minCOPversion, maxCOPversion)
    if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
    set {theAppName, copVersion} to {its theAppName, its copVersion}
    end tell

    tell application "Capture One 12" to set COPDocRef to get current document
    tell C's validateCOPdoc5(COPDocRef, {"Catalog"})
    if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
    set {COPDocName, COPDocKind_s} to {its COPDocName, its COPDocKind_s}
    end tell

    tell C's validateCOPcollections5(COPDocRef) to set {selectedCollectionRef, kindSelectedCollection_s, nameSelectedCollection} to ¬
    {its selectedCollectionRef, its kindSelectedCollection_s, its nameSelectedCollection}

    tell application "Capture One 12" to set countSelectedVariants to get count of selected variants
    tell application "Capture One 12" to set countEveryVariant to count of variants

    set startSortRule to "<?xml version="1.0" encoding="UTF-8"?><MatchOperator Kind="AND"><MatchOperator Kind="AND"><Condition Enabled="YES"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"
    set endSortRule to "</Criterion></Condition></MatchOperator></MatchOperator>"
    set midSortRule to "</Criterion></Condition></MatchOperator><MatchOperator Kind="AND"><Condition Enabled="YES"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"

    set smartSortRule to startSortRule & sortedColorTag1 & midSortRule & sortedColorTag2 & endSortRule

    if enableCreateCollections then
    set {theStacksRef, foundStacks, refStacksParent} to findCollection(selectedCollectionRef, nameStackGroup, "group", true, true, {"group", "project"}, "")
    if not foundStacks then error L's loqqed_Error_Halt5("Can't find or create the "" & nameStackGroup & "" group")
    end if

    if enableCreateUnsorted then
    set {theUnsortedRef, foundUnsorted, refUnsortedParent} to findCollection(selectedCollectionRef, nameUnsortedAlbum, "smart album", true, true, {"group", "project"}, smartSortRule)
    if not foundUnsorted then error L's loqqed_Error_Halt5("Can't find or create the "" & nameUnsortedAlbum & "" smart album")
    end if

    tell application "Capture One 12" to tell (variants whose selected is true) to set {theVariantsRef, theVariantsName} to {it, its name}
    if (0 = (count of theVariantsName)) then
    L's loqThis(-1, true, "No variants selected")
    else

    if enableSeqImageName then
    set numdigits to -1
    repeat with aName in theVariantsName
    set thisCtr to findBaseName(aName, imageNameParsingMethod)
    set {thisLen, thisCtr} to {(count of thisCtr), (thisCtr as integer)}
    if -1 = numdigits then
    set {numdigits, minCtr, maxCtr} to {thisLen, (0 + (thisCtr as integer)), (0 + (thisCtr as integer))}
    else
    if thisLen > numdigits then set numdigits to thisLen
    if thisCtr < minCtr then set minCtr to thisCtr
    if thisCtr > maxCtr then set maxCtr to thisCtr
    end if
    end repeat
    set padZeros to U's makeList(numdigits, 0) as string
    set sequenceName to seqBaseImageName & (text (-numdigits) thru -1 of (padZeros & minCtr))
    if maxCtr > minCtr then set sequenceName to sequenceName & "-" & (text (-numdigits) thru -1 of (padZeros & maxCtr))
    else
    set padZeros to U's makeList(seqCountDigits, 0) as string
    set sequenceName to seqBaseCountedName & (text (-seqCountDigits) thru -1 of (padZeros & sequenceCount))
    set sequenceCount to sequenceCount + 1

    if enableCreateCollections then
    tell application "Capture One 12" to tell theStacksRef to set seqAlbumExists to (0 < (count of (every collection whose name begins with sequenceName)))
    if seqAlbumExists then
    tell application "Capture One 12" to tell theStacksRef to set seqAlbumName_L to name of every collection whose name begins with seqBaseCountedName
    set {ptrFirstNum, maxAlbumCount} to {(1 + (count of seqBaseCountedName)), 1}
    repeat with aName in seqAlbumName_L
    set {aNamesId_L, ptrChar, lenName} to {(id of aName), (ptrFirstNum + 0), (length of aName)}
    repeat while (48 ≤ aNamesId_L's item ptrChar) and (57 ≥ aNamesId_L's item ptrChar)
    set ptrChar to ptrChar + 1
    if ptrChar > lenName then exit repeat
    end repeat
    set ptrChar to ptrChar - 1 -- now ptrChar points to the last number character
    if ((ptrChar - ptrFirstNum + 1) ≤ seqCountDigits) and ((ptrChar - ptrFirstNum) > 0) then
    set albumsCount to (string id (items ptrFirstNum thru ptrChar of aNamesId_L)) as integer
    if albumsCount > maxAlbumCount then set maxAlbumCount to albumsCount
    end if
    end repeat
    set sequenceCount to maxAlbumCount + 2
    set sequenceName to seqBaseCountedName & (text (-seqCountDigits) thru -1 of (padZeros & (maxAlbumCount + 1)))
    end if
    end if
    end if

    set userCanceled to false
    if enableSequenceNameEntry then
    try
    set dialogResult to display dialog ¬
    "Sequence Name" buttons {"Cancel", "OK"} ¬
    default button "OK" cancel button "Cancel" default answer sequenceName
    on error number -128
    set userCanceled to true
    end try
    set sequenceName to dialogResult's text returned
    end if

    if not userCanceled then
    set gotSequenceAlbum to false
    if enableCreateCollections then
    set {refSequenceAlbum, gotSequenceAlbum, refSeqParent} to findCollection(theStacksRef, sequenceName, "album", true, false, {"group"}, "")
    if gotSequenceAlbum then
    tell application "Capture One 12" to add inside refSequenceAlbum variants theVariantsRef
    L's loqThis(-1, false, "Copied " & (count of theVariantsRef) & " variants to "" & sequenceName & """)
    else
    error L's loqqed_Error_Halt5("Can't create the "" & sequenceName & "" album")
    end if
    end if
    if enableSeqInMetaData then
    tell application "Capture One 12"
    if enableMetaDataMultiSeq then
    set oneByOne_L to get variants whose (image scenes is not "") and (selected is true)
    tell (variants whose selected is true and image scenes is "") to set its image scenes to sequenceName

    repeat with aVariant in oneByOne_L
    tell aVariant
    set the aVarSeq_L to its image scenes
    if aVarSeq_L does not contain sequenceName then set its image scenes to (aVarSeq_L & "," & sequenceName)
    end tell
    end repeat
    else
    tell (variants whose selected is true) to set its image scenes to sequenceName
    end if

    end tell
    end if
    try
    tell application "Capture One 12" to tell (variants whose selected is true) to set its color tag to sortedColorTag1 -- important to do this last to avoid the effect of color filtering
    on error
    if gotSequenceAlbum then
    tell application "Capture One 12" to tell refSequenceAlbum to tell its variants to set its color tag to sortedColorTag1
    else
    error L's loqqed_Error_Halt5("Unable to set the color tag because the variants have disappeared from the collection. Likely cause is a filter that has been set on the Image Scenes MetaData")
    end if
    end try
    end if
    end if

    ################ The End ###############


    on findCollection(thisCollRef, nameTargetColl, kindTargetColl, enableCreate, enableSearch, kindParentColl_L, saSortRule)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose handler to find and create a collection, starting from some arbitrary collection

    global debugLogEnable
    local thisCollKind, thisCollKind_S, thisCollName, subCollNames, subCollRef, refFoundColl, errorText, parentString, theParentRef, parentList, nextRef, refCtr, lastRef, hasFoundColl, cntColl, docMark, docName
    local saRules, saNoRules, debugText

    tell application "Capture One 12" to tell thisCollRef to set {thisCollKind, thisCollName, subCollNames, subCollRef, refParentColl} to {its kind, its name, name of its collections, its collections, it}
    set thisCollKind_S to C's convertKindList(thisCollKind)

    set debugText to kindTargetColl & " "" & nameTargetColl & "" in " & thisCollKind_S & " "" & thisCollName & """
    if debugLogEnable then L's loqThis(2, false, return & "Find Collection: " & debugText & " with Create: " & enableCreate & " Search: " & enableSearch & " Sort Rule: " & (count of saSortRule) & " characters")

    set isChevronForm to ("«" = text 1 of (thisCollKind as text))
    if debugLogEnable then
    L's loqThis(3, false, "SubCollections {" & U's joinListToString(subCollNames, "; ") & "}")
    L's loqThis(3, false, "Chevron Form: " & isChevronForm)
    end if

    set refFoundColl to {}
    if (kindParentColl_L contains thisCollKind_S) and (subCollNames contains nameTargetColl) then
    tell application "Capture One 12" to tell thisCollRef
    if ("project" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is project))
    if ("group" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is group))
    if ("album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is album))
    if ("smart album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is smart album))
    end tell
    end if

    set hasFoundColl to false
    if (1 = (count of refFoundColl)) then set {refFoundColl, hasFoundColl} to {(item 1 of refFoundColl), true}

    if hasFoundColl then
    if debugLogEnable then L's loqThis(1, false, "Found " & debugText)
    else if (not enableCreate) and (not enableSearch) then
    L's loqThis(0, false, "The " & kindTargetColl & " "" & nameTargetColl & "" is missing from " & thisCollKind_S & " "" & thisCollName & """)
    else
    if (not hasFoundColl) and enableCreate and (kindParentColl_L contains thisCollKind_S) then
    if debugLogEnable then L's loqThis(2, false, "Attempting to Create the collection")
    set collCreated to false
    tell application "Capture One 12" to tell thisCollRef
    set saNoRules to "<?xml version="1.0" encoding="UTF-8"?><MatchOperator Kind="AND"></MatchOperator>"
    if ("group" = kindTargetColl) then set {collCreated, refFoundColl} to {true, {make new collection with properties {kind:group, name:nameTargetColl}}}
    if ("album" = kindTargetColl) then set {collCreated, refFoundColl} to {true, {make new collection with properties {kind:album, name:nameTargetColl}}}
    if ("smart album" = kindTargetColl) then
    set saNoRules to "<?xml version="1.0" encoding="UTF-8"?><MatchOperator Kind="AND"></MatchOperator>"
    try
    if (text 1 thru 10 of saNoRules) = (text 1 thru 10 of saSortRule) then set saRules to saSortRule
    on error
    set saRules to saNoRules
    end try
    set ctrSaName to 1
    repeat until ({} = (every collection whose name is nameTargetColl and kind is album))
    set {nameTargetColl, ctrSaName} to {(nameTargetColl & ctrSaName), ctrSaName + 1}
    if 10 < ctrSaName then error
    end repeat
    set refFoundColl to (every collection whose name is nameTargetColl and kind is smart album)
    if (0 = (count of refFoundColl)) then set {collCreated, refFoundColl} to {true, {make new collection with properties {kind:smart album, name:nameTargetColl, rules:saRules}}}
    if (1 < ctrSaName) then set debugText to kindTargetColl & " "" & nameTargetColl & "" in " & thisCollKind_S & " "" & thisCollName & """
    end if
    try
    if ("project" = kindTargetColl) then set refFoundColl to {make new collection with properties {kind:project, name:nameTargetColl}}
    set collCreated to true
    on error errorText
    if debugLogEnable then L's loqThis(2, false, "Creation failed with: " & errorText)
    end try
    end tell
    if (1 = (count of refFoundColl)) then
    set {refFoundColl, hasFoundColl} to {(item 1 of refFoundColl), true}
    if collCreated then L's loqThis(1, false, "Created " & debugText)
    end if
    end if

    if (not hasFoundColl) and enableSearch then
    if debugLogEnable then L's loqThis(2, false, "Starting Search")
    try
    get || of {thisCollRef}
    on error errorText
    end try
    if isChevronForm then set errorText to U's replaceText(errorText, "«class COcl»", "collection")
    set parentList to U's splitStringToList(errorText, {"of", "{", "}"})
    repeat with refCtr from (count of parentList) to 0 by -1
    try
    if "document" = first word of item refCtr of parentList then exit repeat
    end try
    end repeat
    if 0 = (get (contents of refCtr) as integer) then error L's loqqed_Error_Halt5("Didn't find the document reference in " & errorText)
    set docMark to contents of refCtr
    set docName to U's removeLeadingTrailingSpaces((get item 2 of U's splitStringToList((U's removeLeadingTrailingSpaces((get item docMark of parentList))), """)))
    tell application "Capture One 12" to set lastRef to document docName
    set cntColl to 0
    repeat with refCtr from 3 to docMark - 1
    try
    if "collection" = (first word of item refCtr of parentList) then
    set cntColl to cntColl + 1
    if 2 ≤ cntColl then
    tell application "Capture One 12" to set nextRef to collection id (get last word of parentList's item refCtr) of lastRef
    copy nextRef to lastRef
    end if
    end if
    end try
    end repeat
    if (0 = cntColl) then
    L's loqThis(0, false, "Unable to identify parent collection")
    else
    set {refFoundColl, hasFoundColl, refParentColl} to my findCollection(lastRef, nameTargetColl, kindTargetColl, enableCreate, enableSearch, kindParentColl_L, saSortRule)
    end if
    end if
    end if

    if not hasFoundColl then set refFoundColl to missing value
    return {refFoundColl, hasFoundColl, refParentColl}
    end findCollection


    on findBaseName(theString, theMethod_L)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose handler to parse an image file name

    ## Looks long, but execution time is about 20 - 80 microseconds
    ## theMethod_L is a list of Methods. Each Method describes a parsing method (a particular way of parsing a string)
    ## Each Method is a list of Groups, each Group is a record that describes how to find the end of a group of characters.
    ## While a group runs each character is copied from theString to the output until the end is found.
    ## The algorithm starts with Group 1 of Method 1. When a Group ends the next one starts
    ## When there are no Groups left or no Characters left, the algorithm stops reading characters and returns the output characters.
    ## The Group record: {inpAlp, inpNum, inpSpa, inpSym, inpCnt, inpBck, inpAct}:
    ## (inpAlp, inpNum, inpSpa, inpSym, inpBck) boolean, (inpCnt) integer, inpAct a list of Actions {{X,Y,Z},{X,Y,Z},...}
    #3 Character Type Tests are evaluated first, then Actions
    ## A group ends upon finding Alphabetic characters (if inpAlp true), or Numbers (if inpNum true), or a Space(if inpSpa true),
    ## or Symbols (if inpSym true), or finding more than inpCnt characters (if inpCnt >0).
    ## If inpBck is true, when a group ends, the last character becomes part of the next group
    ## EG Group3: {inpAlp:false, inpNum:false, inpSpa:false, inpSym:true, inpBck:true, inpCnt:9, inpAct:aList}
    ## Means: stop if a symbol is found or after the 9th character, and the last character becomes the first character of the next group
    ## Thus up to 9 Numbers, Spaces and Alphabetic characters are accepted, and 9th character or symbol is part of the next group.
    ## if Group 3 is the last Group, then 9th or symbol character (and all subsequent characters) are dropped.

    ## inpAct contains Alists {X,Y,Z}, each describes an action to execute when finding some character. inpAct= {} results in no actions
    ## X is the character that triggers an action; Y is the Action; Z optionally specifies "how much"
    ## X= return triggers the action on a Group Increment from Character Type Tests
    ## Actions (1): T-Translate Character to "Z" ; G- Switch to Group Z ; M- Switch to Method Z ; I- Increment group by Z;
    ## B- Drop Back 1 character; J- Jump ahead Z characters, D- Drop trigger Characters, O - Drop Output
    ## Actions A,B,C,N,S and P set the value of inpAlp, inpBck, inpCnt, inpNum, inpSpa and inpSym
    ## If the Alist has no "z" value: for "I", z is taken as 1; for "J", z is taken as the length of the trigger; for "T" z is taken as "";
    ## for G, z is taken as "0"; for M, the method is incremented, output and trigger are dropped
    ## for all other commands z is taken as true when missing
    ## Selecting Group < 1 resets the parser to Group 1 and clears all output. If inpBck is true, the current character is dropped.
    ## Selecting Method 0 triggers a Method reset, output and input are reset, and then the previous method is incremented
    ## Switching to Method whose number does not exist causes the previous output to be cleared, the remaining input characters are accepted
    ## E.G. For inpAct:{{"$","M"},{"-","T"}} in Method 1 --> when an "$" is encountered, start over again with Group 1 of Method 2, also drop every "-"
    ## If there is no Method 2, all remaining characters of the input are taken as output.

    global debugLogEnable
    local theCharIDList, stringCount, thisCharsId, parsedNumList, charPointer, iterationCtr, SafetyLimit
    local Alpha, Numb, Blank, Symbol, MaxCnt, backOneOnGrpInc, ActionList, AlistTriggerId_L, anAlist, theAction, Prefix
    local thisMethod, MethodCount, thisGroup, GroupCount, hasMaxCnt, countChars, stringCount
    local triggerMethodInit, triggerGroupInit, triggerGroupInc, validChar, newCharIDs, hasNewChars, nextGroup, nextMethod, nextCharPointer
    local triggerBack1Char, hasPar3, valuePar3, endTriggerPointer, lenActTrigger, actionString, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop, lenActTrigger


    set {theCharIDList, stringCount, nextMethod, MethodCount, triggerMethodInit, parsedNumList} to ¬
    {(get id of theString), (get count of theString), 1, (count of lists of theMethod_L), true, {}}
    set {charPointer, iterationCtr, SafetyLimit} to {1, 0, (MethodCount * stringCount * 4)}
    if debugLogEnable then
    L's loqThis(2, false, "Parsing "" & theString & """)
    if (0 < (count of strings of theMethod_L)) then L's loqThis(2, false, ("Using Method " & (string 1 of theMethod_L)))
    L's loqThis(3, false, {"Initialising: stringCount " & stringCount & ", Method " & nextMethod & ", MethodCount " & MethodCount})
    end if

    repeat while (charPointer ≤ stringCount) and (iterationCtr < SafetyLimit)
    set iterationCtr to iterationCtr + 1

    if triggerMethodInit then set {nextGroup, GroupCount, triggerGroupInit, thisMethod} to ¬
    {1, (count of records of theMethod_L's list nextMethod), true, nextMethod}
    if debugLogEnable and triggerMethodInit then L's loqThis(3, false, "Start Method #" & nextMethod & ", Group Count: " & (count of theMethod_L's list nextMethod))

    if triggerGroupInit then
    tell theMethod_L's list thisMethod's record nextGroup to set {Prefix, Alpha, Numb, Blank, Symbol, MaxCnt, hasMaxCnt, backOneOnGrpInc, ActionList, countChars, thisGroup} to ¬
    {its inpPref, its inpAlp, its inpNum, its inpSpa, its inpSym, its inpCnt, (0 < its inpCnt), its inpBck, its inpAct, 0, nextGroup}
    if debugLogEnable then L's loqThis(3, false, "Start Group #" & thisGroup & " , Prefix "" & Prefix & "", Alpha " & Alpha & ", Number " & Numb & ", Blank " & Blank & ", Symbol " & Symbol & ", Max Count " & MaxCnt & ", has Max Count " & hasMaxCnt & ", Drop Back Last Character " & backOneOnGrpInc & " , ActionList : " & (count of ActionList) & " action lists")
    if (0 < (count of ActionList)) and ("list" ≠ ((class of ActionList's item 1) as text)) then error "Group " & thisGroup & " has an incorrectly formatted Action List"
    set parsedNumList to parsedNumList & (id of Prefix)
    end if

    set {triggerBack1Char, triggerGroupInc, triggerGroupInit, triggerMethodInit, validChar, newCharIDs, hasNewChars, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop} to ¬
    {false, false, false, false, true, {}, false, false, missing value, 0, false, false}

    set {thisCharsId, countChars, nextCharPointer, lenActTrigger} to {(theCharIDList's item charPointer), (countChars + 1), charPointer, 1}
    if debugLogEnable then L's loqThis(5, false, "Character " & charPointer & " : "" & (string id (get theCharIDList's item charPointer)) & """)

    if hasMaxCnt and (countChars ≥ MaxCnt) then -- if the counter has triggered, don't check the character groups
    set triggerGroupInc to true
    if debugLogEnable then L's loqThis(3, false, "Count triggers Group Increment")
    else -- check the character type
    if debugLogEnable then L's loqThis(3, false, "Character Type Check")
    if ((thisCharsId ≥ 65) and (thisCharsId ≤ 90)) or ((thisCharsId ≥ 97) and (thisCharsId ≤ 122)) or (thisCharsId ≥ 192) then -- a latin character
    if Alpha then set triggerGroupInc to true
    if debugLogEnable and Alpha then L's loqThis(4, false, "Alpha triggers Group Increment")
    else if ((thisCharsId ≥ 48) and (thisCharsId ≤ 57)) then -- a number
    if Numb then set triggerGroupInc to true
    if debugLogEnable and Numb then L's loqThis(4, false, "Number triggers Group Increment")
    else if thisCharsId = 32 then --- its a space
    if Blank then set triggerGroupInc to true
    if debugLogEnable and Blank then L's loqThis(4, false, "Space triggers Group Increment")
    else if thisCharsId > 32 then -- its a symbol
    if Symbol then set triggerGroupInc to true
    if debugLogEnable and Symbol then L's loqThis(4, false, "Symbol triggers Group Increment")
    end if
    end if

    repeat with anAlist in ActionList
    set {AlistTriggerId_L, endTriggerPointer} to {((id of contents of anAlist's item 1) as list), (charPointer + (count of anAlist's item 1) - 1)}

    if (endTriggerPointer ≤ stringCount) and ((not hasActionTrigger) or (theActionTrigger = AlistTriggerId_L)) and ¬
    ((triggerGroupInc and ({13} = AlistTriggerId_L)) or (AlistTriggerId_L = (theCharIDList's items charPointer thru endTriggerPointer))) ¬
    then

    if debugLogEnable and not hasActionTrigger then L's loqThis(4, false, "Action List is triggered by "" & (anAlist's item 1) & """)
    if not hasActionTrigger then set {hasActionTrigger, theActionTrigger, lenActTrigger} to {true, AlistTriggerId_L, (count of AlistTriggerId_L)}

    set {theAction, hasPar3, valuePar3} to {(item 2 of anAlist), (3 ≤ (count of anAlist)), true}
    if hasPar3 then set valuePar3 to (contents of item 3 of anAlist)

    if debugLogEnable then
    if hasPar3 then
    set actionString to "Action "" & (contents of item 2 of anAlist) & "" ; "" & (contents of item 3 of anAlist) & """
    else
    set actionString to "Action "" & (contents of item 2 of anAlist) & """
    end if
    end if

    ## Group 1 - Character Control - Does Not clear triggerGroupInc

    if ("A" = theAction) then
    set Alpha to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpAlp to " & valuePar3)

    else if "B" = theAction then --
    set backOneOnGrpInc to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpBck to " & valuePar3)
    else if ("C" = theAction) then
    if (not hasPar3) or (0 = valuePar3) then
    set {MaxCnt, hasMaxCnt} to {0, false}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to 0")
    else if (0 < valuePar3) then
    set {MaxCnt, hasMaxCnt} to {valuePar3, true}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to " & valuePar3)
    else if (0 > valuePar3) then
    set {MaxCnt, hasMaxCnt} to {(MaxCnt - valuePar3), true}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Increment inpCnt by " & (-valuePar3))
    end if

    else if ("N" = theAction) then
    if hasPar3 then set Numb to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpNum to " & valuePar3)

    else if ("P" = theAction) then
    set Symbol to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & "- Set inpSym to " & valuePar3)
    else if ("Q" = theAction) then
    if debugLogEnable then L's loqThis(4, false, actionString & "- Quit Actions")
    exit repeat

    else if "S" = theAction then
    set Blank to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpSpa to " & valuePar3)

    ## Group 2 - Character Control - clears triggerGroupInc on multi character trigger

    else if "D" = theAction then --
    set validChar to not valuePar3
    if 1 < lenActTrigger then set triggerGroupInc to false
    if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Action Trigger: " & valuePar3)

    else if "O" = theAction then --
    set triggerOutputDrop to valuePar3
    if 1 < lenActTrigger then set triggerGroupInc to false
    if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Output characters")

    else if "J" = theAction then --
    if not hasPar3 then set valuePar3 to lenActTrigger
    set {nextCharJump, hasJump} to {valuePar3, true}
    if 1 < lenActTrigger then set triggerGroupInc to false
    if debugLogEnable then L's loqThis(5, false, actionString & " - Jump forward " & valuePar3 & " Characters")

    else if "T" = theAction then -- translate/drop this character
    if not hasPar3 then set valuePar3 to ""
    set {newCharIDs, hasNewChars} to {(id of valuePar3), true}
    if 1 < lenActTrigger then set triggerGroupInc to false
    if debugLogEnable then L's loqThis(5, false, actionString & " - Translate characters to "" & valuePar3 & """)

    ## Group 3 - Group & Method control, clears triggerGroupInc - later
    else if "G" = theAction then -- new group
    if not hasPar3 then set valuePar3 to 0
    set {nextGroup, triggerGroupInit} to {valuePar3, true}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Group " & valuePar3)

    else if "I" = theAction then -- increment group
    if not hasPar3 then set valuePar3 to 1
    set {nextGroup, triggerGroupInit} to {(thisGroup + valuePar3), true}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Increment Group to " & nextGroup)

    else if "M" = theAction then -- set method
    if not hasPar3 then set valuePar3 to (thisMethod + 1)
    set {nextMethod, triggerMethodInit, triggerOutputDrop, validChar} to {valuePar3, true, true, false}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Method " & valuePar3 & " and drop output and trigger")

    else
    if debugLogEnable then L's loqThis(3, false, actionString & " - Command not found ")
    end if
    end if
    end repeat

    if (lenActTrigger > 1) then set countChars to countChars - 1 + lenActTrigger
    if hasMaxCnt and (countChars ≥ MaxCnt) then set {triggerGroupInc} to {true, L's loqThis(3, false, "Character count triggers Group Increment")}

    if (triggerGroupInit or triggerMethodInit) then set triggerGroupInc to false
    if (triggerGroupInc or triggerGroupInit or triggerMethodInit) and backOneOnGrpInc then set triggerBack1Char to true

    if (0 ≥ nextMethod) then
    if debugLogEnable then L's loqThis(3, false, "Executing Method Reset - Increment Method, Clear output, Reset Input Queue")
    set {parsedNumList, nextMethod, triggerMethodInit, nextCharPointer} to {{}, (thisMethod + 1), true, 1, false}
    set {triggerBack1Char, triggerGroupInc, hasJump, hasNewChars, hasMaxCnt} to {false, false, false, false, false}
    else
    if (0 ≥ nextGroup) then
    set {triggerBack1Char, validChar, triggerGroupInc, hasJump, hasNewChars} to {false, false, false, false, false}
    if debugLogEnable then L's loqThis(3, false, "Executing Group Reset - Switch to Group 1, Clear output , dropping back " & (-nextGroup) & " characters")
    if (-1 ≥ nextGroup) then set {nextCharJump, hasJump} to {(nextGroup), true}
    set {parsedNumList, nextGroup, triggerGroupInit} to {{}, 1, true}
    end if

    if triggerGroupInc then set {nextGroup, triggerGroupInit} to {thisGroup + 1, true, L's loqThis(3, false, "Executing Group Increment - Switch to Group " & (thisGroup + 1))}

    if debugLogEnable and triggerOutputDrop then L's loqThis(4, false, "Dropped Output")
    if triggerOutputDrop then set parsedNumList to {}
    if triggerBack1Char then
    set {validChar, nextCharPointer} to {false, charPointer}
    if debugLogEnable then L's loqThis(3, false, "Drop Back 1 Character")
    else if (hasJump or hasNewChars) then
    set validChar to false
    if (hasJump and (((thisGroup ≠ nextGroup) or (thisMethod ≠ nextMethod) or (0 < nextCharJump)))) then
    set nextCharPointer to (charPointer + nextCharJump)
    if nextCharPointer < 1 then set nextCharPointer to 1
    if hasNewChars and (0 < nextCharJump) and (lenActTrigger > nextCharJump) then set nextCharPointer to (charPointer + lenActTrigger)
    else if hasJump then
    ## do nothing - can't move charPointer back
    if debugLogEnable then L's loqThis(3, false, "Unable to move back " & (-nextCharJump) & " characters - infinite loop")
    else if hasNewChars then -- and not hasJump
    set nextCharPointer to (charPointer + lenActTrigger)
    end if
    if debugLogEnable then L's loqThis(3, false, "Input Moved Forward " & (nextCharPointer - charPointer) & " characters")
    else
    set nextCharPointer to (charPointer + 1)
    if debugLogEnable then L's loqThis(5, false, "Input Moved Forward 1 character")
    end if
    if hasNewChars then set {parsedNumList} to {(parsedNumList & newCharIDs), L's loqThis(4, false, "Added new Characters to the output")}
    if validChar then set end of parsedNumList to thisCharsId
    if debugLogEnable and not validChar then L's loqThis(3, false, "The input character has been dropped")
    end if
    if debugLogEnable then L's loqThis(3, false, "The output is: "" & (string id parsedNumList) & """)

    if nextMethod > MethodCount then -- replace the output with every remaining character
    if debugLogEnable then L's loqThis(3, false, "Got Method " & nextMethod & " Return remaining characters")
    if charPointer > 1 then set parsedNumList to (theCharIDList's items charPointer thru stringCount)
    if charPointer ≤ 1 then copy theCharIDList to parsedNumList
    exit repeat
    else if (nextGroup > GroupCount) then
    if debugLogEnable then L's loqThis(3, false, "Finished all groups, exitting")
    exit repeat
    end if
    set charPointer to nextCharPointer -- must be last statement in the the loop
    end repeat
    if debugLogEnable and (charPointer > stringCount) then L's loqThis(3, false, "Finished all characters")

    return (string id parsedNumList)
    end findBaseName

    ##########################################
    ## Capture One General Handlers Version 2019/03/11

    on validateCOP5(minCOPversionstr, maxCOPversionstr)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose initialisation handler for scripts using Capture One Pro
    ## Extract and check basic information about the Capture One application

    global debugLogEnable
    local COPProcList, theAppRef, numCOPversion, minCOPversion, maxCOPversion
    local digit_mult, Version_digit, min_digit, max_digit, copVersionStr
    local theAppName, copVersion

    tell application "System Events"
    if debugLogEnable then
    L's loqThis(2, false, ("COP Processes:" & (get U's joinListToString((get name of every process whose name begins with "Capture One" and background only is false), ", "))))
    L's loqThis(3, false, ("All Processes: " & (get U's joinListToString((get name of every process whose background only is false), ", "))))
    end if
    set COPProcList to every process whose name begins with "Capture One" and background only is false
    if (count of COPProcList) = 0 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("COP is not running"))}
    if (count of COPProcList) ≠ 1 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("Unexpected: >1 COP instances"))}
    set theAppRef to item 1 of COPProcList
    set theAppName to ((get name of theAppRef) as text)
    set copDetailedVersion to get version of my application theAppName
    end tell

    tell application "Capture One 12" to set copVersionStr to (get app version)
    set copVersion to (word -1 of copVersionStr)

    if debugLogEnable then
    L's loqThis(2, false, ("theAppName: " & theAppName))
    L's loqThis(1, false, copVersionStr)
    L's loqThis(2, false, ("Capture One full Version " & copDetailedVersion))
    end if
    tell U's compareVersion(copVersion, minCOPversionstr, maxCOPversionstr) to set {minVersionPass, maxVersionPass} to {its minVersionPass, its maxVersionPass}
    if not minVersionPass then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5(("This Script does not support Capture One " & copDetailedVersion & " - supported versions are from " & minCOPversionstr & " to " & maxCOPversionstr)))}
    if not maxVersionPass then L's loqThis(0, true, ("Caution: Capture One " & copDetailedVersion & " has not been verified yet"))
    return {hasErrors:false, theAppName:theAppName, copVersion:copVersion}
    end validateCOP5

    on validateCOPdoc5(theDocRef, validDocKindList)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose initialisation handler for scripts using Capture One Pro
    ## Extract and check basic information about a document

    global debugLogEnable
    local COPDocKind_s, COPDocKind_p, COPDocName

    if "text" = (get class of theDocRef as text) and (0 = (get count of theDocRef)) then tell application "Capture One 12" to set theDocRef to get current document

    try
    tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
    on error errorText number errorNumber
    return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("The Script could not retrieve Capture One document info. Error " & errorNumber & ": "" & errorText & """))}
    end try
    set COPDocKind_s to convertKindList(COPDocKind_p)

    if validDocKindList does not contain COPDocKind_s then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5((COPDocName & " is a " & COPDocKind_s & " -- not supported by this script")))}
    return {hasErrors:false, COPDocName:COPDocName, COPDocKind_s:COPDocKind_s}
    end validateCOPdoc5

    on validateCOPcollections5(theDocRef)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose initialisation handler for scripts using Capture One Pro
    ## Extract basic information regarding the current collection, and the top level collections
    global debugLogEnable
    local namesTopCollections, kindsTopCollections_s, countTopCollections, selectedCollectionRef, selectedCollectionIndex, kindSelectedCollection_s, nameSelectedCollection

    tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
    set COPDocKind_s to convertKindList(COPDocKind_p)

    tell application "Capture One 12" to tell theDocRef
    set selectedCollectionRef to get current collection
    if (missing value = selectedCollectionRef) then
    try
    set current collection to collection "All Images"
    on error
    set current collection to last collection
    end try
    set selectedCollectionRef to get current collection
    end if
    tell selectedCollectionRef to set {nameSelectedCollection, kindSelectedCollection_s} to {name, my convertKindList(kind)}
    set {namesTopCollections, kindsTopCollections_s} to {every collection's name, my convertKindList(every collection's kind)}
    end tell
    set countTopCollections to count of namesTopCollections

    repeat with collectionCounter from 1 to countTopCollections
    if (nameSelectedCollection = item collectionCounter of namesTopCollections) and ¬
    (kindSelectedCollection_s = item collectionCounter of kindsTopCollections_s) then
    set selectedCollectionIndex to collectionCounter
    exit repeat
    end if
    end repeat

    local selectedCollectionMirroredAtTopLast, bottomUserCollectionIndex, topUserCollectionIndex, countFavoriteCollections, namesFavoriteCollections

    if COPDocKind_s = "catalog" then
    repeat with collectionCounter from countTopCollections to 1 by -1
    if ("in Catalog" = item collectionCounter of namesTopCollections) and ¬
    ("smart album" = item collectionCounter of kindsTopCollections_s) then
    set topUserCollectionIndex to collectionCounter - 1
    exit repeat
    end if
    end repeat
    repeat with collectionCounter from 1 to countTopCollections
    if ("Trash" = item collectionCounter of namesTopCollections) and ¬
    ("smart album" = item collectionCounter of kindsTopCollections_s) then
    set bottomUserCollectionIndex to collectionCounter + 1
    exit repeat
    end if
    end repeat

    set selectedCollectionMirroredAtTopLast to ¬
    (selectedCollectionIndex = countTopCollections) and ¬
    ({"catalog folder", "favorite"} does not contain last item of kindsTopCollections_s)

    set {countFavoriteCollections, namesFavoriteCollections} to {missing value, missing value}

    else if COPDocKind_s = "session" then
    repeat with collectionCounter from countTopCollections to 1 by -1
    if ("favorite" ≠ item collectionCounter of kindsTopCollections_s) then
    set topUserCollectionIndex to collectionCounter
    exit repeat
    end if
    end repeat
    repeat with collectionCounter from 1 to countTopCollections
    if ("Trash" = item collectionCounter of namesTopCollections) and ¬
    ("favorite" = item collectionCounter of kindsTopCollections_s) then
    set bottomUserCollectionIndex to collectionCounter + 1
    exit repeat
    end if
    end repeat

    set countFavoriteCollections to countTopCollections - topUserCollectionIndex
    if 0 = countFavoriteCollections then
    set namesFavoriteCollections to {}
    else
    set namesFavoriteCollections to (get items (topUserCollectionIndex + 1) thru countTopCollections of namesTopCollections)
    end if

    set selectedCollectionMirroredAtTopLast to false
    end if

    local selectedCollectionIsUser, namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections
    set selectedCollectionIsUser to ¬
    (selectedCollectionMirroredAtTopLast or ¬
    ((selectedCollectionIndex ≥ bottomUserCollectionIndex) and (selectedCollectionIndex ≤ topUserCollectionIndex)))

    if topUserCollectionIndex < bottomUserCollectionIndex then
    set {topUserCollectionIndex, bottomUserCollectionIndex} to {missing value, missing value}
    set {namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections} to {{}, {}, 0}
    else
    set {namesTopUserCollections, kindsTopUserCollections_s} to {(get items bottomUserCollectionIndex thru topUserCollectionIndex of namesTopCollections), (get items bottomUserCollectionIndex thru topUserCollectionIndex of kindsTopCollections_s)}
    set countTopUserCollections to count of namesTopUserCollections
    end if

    return {hasErrors:false, namesTopUserCollections:namesTopUserCollections, kindsTopUserCollections_s:kindsTopUserCollections_s, countTopUserCollections:countTopUserCollections, selectedCollectionRef:selectedCollectionRef, selectedCollectionIndex:selectedCollectionIndex, kindSelectedCollection_s:kindSelectedCollection_s, nameSelectedCollection:nameSelectedCollection, selectedCollectionMirroredAtTopLast:selectedCollectionMirroredAtTopLast, selectedCollectionIsUser:selectedCollectionIsUser, bottomUserCollectionIndex:bottomUserCollectionIndex, topUserCollectionIndex:topUserCollectionIndex, countFavoriteCollections:countFavoriteCollections, namesFavoriteCollections:namesFavoriteCollections}

    end validateCOPcollections5

    on convertKindList(kind_list)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General Purpose Handler for scripts using Capture One Pro
    ## Capture One returns the chevron form of the "kind" property when AppleScript is run as an Application
    ## Unless care is taken to avoid text conversion of this property, this bug breaks script decisions based on "kind"
    ## This script converts text strings with the chevron form to strings with the expected text form
    ## The input may be a single string, a single enum, a list of strings or a list of enums
    ## The code is not compact but runs very fast, between 60us and 210us per item

    local kind_s_list, input_is_list, theItem, kind_s1, fail_flag, code_start, kind_list, Kind_s_item, kind_code, kind_type

    set kind_s_list to {}
    set input_is_list to ("list" = (get (class of kind_list) as text))
    if input_is_list then
    if ("text" = (get (class of item 1 of kind_list) as text)) and ¬
    ("«" ≠ (get text 1 of item 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
    repeat with theItem in kind_list
    tell application "Capture One 12" to set the end of kind_s_list to (get theItem as text)
    end repeat
    if ("«" ≠ (get text 1 of item 1 of kind_s_list)) then return kind_s_list
    else
    if ("text" = (get (class of kind_list) as text)) and ¬
    ("«" ≠ (get text 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
    tell application "Capture One 12" to set kind_s1 to (get kind_list as text)
    if "«" ≠ (get text 1 of kind_s1) then return kind_s1 -- quick pass through if input is OK
    set kind_s_list to {kind_s1}
    end if

    set fail_flag to false
    set code_start to -5

    set kind_list to {}
    repeat with Kind_s_item in kind_s_list
    if ("«" ≠ (get text 1 of Kind_s_item)) or (16 > (count of Kind_s_item)) then ¬
    error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))

    set kind_code to get (text code_start thru (code_start + 3) of Kind_s_item)
    set kind_type to get (text code_start thru (code_start + 1) of Kind_s_item)

    if kind_type = "CC" then ## Collection Kinds
    if kind_code = "CCpj" then
    set the end of kind_list to "project"
    else if kind_code = "CCgp" then
    set the end of kind_list to "group"
    else if kind_code = "CCal" then
    set the end of kind_list to "album"
    else if kind_code = "CCsm" then
    set the end of kind_list to "smart album"
    else if kind_code = "CCfv" then
    set the end of kind_list to "favorite"
    else if kind_code = "CCff" then
    set the end of kind_list to "catalog folder"
    else
    set fail_flag to true
    end if

    else if kind_type = "CL" then ## Layer Kinds
    if kind_code = "CLbg" then
    set the end of kind_list to "background"
    else if kind_code = "CLnm" then
    set the end of kind_list to "adjustment"
    else if kind_code = "CLcl" then
    set the end of kind_list to "clone"
    else if kind_code = "CLhl" then
    set the end of kind_list to "heal"
    else
    set fail_flag to true
    end if

    else if kind_type = "CR" then ## Watermark Kinds
    if kind_code = "CRWn" then
    set the end of kind_list to "none"
    else if kind_code = "CRWt" then
    set the end of kind_list to "textual"
    else if kind_code = "CRWi" then
    set the end of kind_list to "imagery"
    else
    set fail_flag to true
    end if

    else if kind_type = "CO" then ## Document Kinds
    if kind_code = "COct" then
    set the end of kind_list to "catalog"
    else if kind_code = "COsd" then
    set the end of kind_list to "session"
    else
    set fail_flag to true
    end if
    else
    set fail_flag to true
    end if

    if fail_flag then ¬
    error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))

    end repeat

    if input_is_list then
    return kind_list
    else
    return item 1 of kind_list
    end if

    end convertKindList
    0
  • Eric Valk
    Bug Fix. Complete rewrite of the findCollection() handler.

    This is the first part of the script, a complete replacement for the script from postings 1 or 3. The second part of the script has not changed.
    Copy and paste the script below into Script Editor's script window, and then copy and paste the script from posting 2.


    ## Applescript to sequence images in a COP 12 Catalog
    ## Version 12.23 !! NO GUARANTEE OF SUPPORT !! Best effort
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.

    -- *** Use and Function
    -- select a Project, an album or smart album which is under a Project or Group (or both)
    -- Select some variants and run this script
    -- If not already present, a group "Stacks" and a smart album "Unsorted" will be created
    -- the smart album rules will hide all images with blue or magenta color tag
    -- an album with a name starting with SEQ will be created in the group Stacks
    -- the album name will contain the lowest and highest numbers sequences from the selected variants
    -- the selected variants will be color tagged Blue and copied to the album
    -- the Sequence name will be copied into the IPTC Image Scenes metadata of the Variants
    -- Once "Unsorted" Exists, continue creating sequences from there, only the unsequenced images will be shown.
    -- ***To Setu

    -- Start Script Editor, open a new (blank) file, copy and paste both parts into one Script Editor Document, compile (hammer symbol) and save.
    -- Best if you make "Scripts" folder somewhere in your Documents or Desktop
    -- This file is suitable to use as an application in Capture One Pro's Script Menu

    -- *** Operation in Script Editor
    -- Open the compiled and saved document
    -- Open the Script Editor log window, and select the messages tab
    -- The user may elect to set defaults for enabling or disabling results in Notifications, TextEdit and Script Editor by setting the "enable" variables at beginning of the script
    -- The user may change the default amount of reporting by setting the "debugLogLevel" and "ResultsFileMaxDebug" variables at beginning of the script
    -- If you are having some issues, then set debugLogLevel to 3 and send me the results from Script Editors log window, or Text Edit.

    use AppleScript version "2.5"
    use scripting additions

    property M : missing value
    set M to me
    global C, L, U
    set C to me
    set L to me
    set U to me

    set loqGUIsettings_L to L's setupLoqqing5() -- setup the variables used for Loqqing system

    ## Values in this section are safe to change, within limits indicated. Support is likely but no commitment
    ## Default values

    ### Read Carefully
    set enableSeqImageName to true -- enable image based sequence name; disabling results in counter based sequence names

    set enableCreateCollections to true -- this enable creating of albums for each sequence, and creating of the "Stacks" group
    set enableCreateUnsorted to true -- this enable creating of of the "Unsorted" smart album
    set nameStackGroup to "Stacks" -- this is the name of group that holds the albums for each sequence
    set nameUnsortedAlbum to "Unsorted" -- this is the name of smart album that shows the not yet sequenced variants
    set enableSequenceNameEntry to false -- enables manual entry of sequence names
    set seqBaseImageName to "SEQ " -- the base used for image based sequence name
    set seqBaseCountedName to "SEQ-" -- the base used for counter based sequence name
    set seqCountDigits to 2 -- the number of counter digits in a counter based sequence name
    property sequenceCount : 1 -- the starting count in a counter based sequence name
    set enableSeqInMetaData to true -- shall the sequence name be written to the IPTC Image Scenes metadata
    set enableMetaDataMultiSeq to true -- shall the sequence name be added to other sequences in the IPTC Image Scenes metadata

    set Loqqing's debugLogLevel to 0 -- 0...6 Values >1 result in increasing amounts of debug data that takes longer to report
    set Loqqing's enableResultsFile to true -- (true/false)
    set Loqqing's enableResultsByClipboard to false -- (true/false)
    set Loqqing's enableNotifications to true -- (true/false) - enable notifications of errors and exceptions
    set Loqqing's notificationsMaxDebug to 1 -- 0...6 suggest not more than 1
    set Loqqing's ResultsFileMaxDebug to 3 -- 0...6 suggest not more than 2
    set Loqqing's enableResultsByNotifications to false
    set Loqqing's enableResultsByLoq to true


    set sortedColorTag1 to 5 -- for the stacked images
    set sortedColorTag2 to 6 -- for the picked stacked images
    --Color Tag
    --Criterion
    --0--> none
    --1-->red
    --2-->orange
    --3-->yellow
    --4-->green
    --5-->blue
    --6-->magenta
    --7-->purple


    ## ***** Not safe to change stuff below this line, unless you have some background in SW development.
    ## I generally won't help much if you change stuff below this line. I may explain the design intent.

    set inpGroup1 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them
    set inpGroup2 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character
    set inpMethod1 to {"SOOC Parse", {inpGroup1, inpGroup2}} -- parses the image file names typically assigned by a camera

    set inpGroup3 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}, {"-", "M", 0}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them. Start over next method if a "-" is found.
    set inpGroup4 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{{"-", "M", 0}}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character. Start over next method if a "-" is found.

    set inpGroup5 to {inpPref:"", inpAlp:false, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:4, inpAct:{{return, "O"}, {return, "D"}}} -- Take Alpha and Numbers, stop on Space or symbol or 4 characters. Then discard
    set inpGroup6 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:3, inpAct:{{"$", "G"}, {return, "O"}, {return, "D"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 3 characters. Then discard. Start over if a "$" is found
    set inpGroup7 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:5, inpAct:{{"$", "G"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 5 characters. Start over if a "$" is found
    set inpMethod2 to {"Universal Parse", {inpGroup3, inpGroup4}, {inpGroup5, inpGroup6, inpGroup7}} -- combination of Method 3 and Method 1
    set inpMethod3 to {"Eric's Detailed Parse", {inpGroup5, inpGroup6, inpGroup7}} -- parses Eric's image file names

    set imageNameParsingMethod to inpMethod1 -- OK to change this

    set debugLogEnable to true

    ## Result reporting methods which are valid for this script
    set Loqqing's gateResultsFile to true
    set Loqqing's gateResultsByClipboard to true
    set Loqqing's gateResultsNotification to true

    ## Reporting methods which are NOT valid for this script
    -- disable the reporting method
    set Loqqing's gateResultsDialog to false
    -- disable user control
    set Loqqing's enableResultsByDialog to false -- (true/false)
    set Loqqing's maxDialogPercent to 85 -- (0% to 100% of the monitor) The amount of data that triggers a dialog report
    ##


    tell application "System Events" to set parent_name to name of current application
    set Script_Title to (get name of me)
    set Result_DocName to "CO_Image_Sorting_Report.txt"

    set loqResultMethod to L's InitializeLoqqing5(Result_DocName, Script_Title) -- Initialize the results logging system

    set {minCOPversion, maxCOPversion} to {"12", "12"}
    tell C's validateCOP5(minCOPversion, maxCOPversion)
    if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
    set {theAppName, copVersion} to {its theAppName, its copVersion}
    end tell

    tell application "Capture One 12" to set COPDocRef to get current document
    tell C's validateCOPdoc5(COPDocRef, {"Catalog"})
    if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
    set {COPDocName, COPDocKind_s} to {its COPDocName, its COPDocKind_s}
    end tell

    tell C's validateCOPcollections5(COPDocRef) to set {selectedCollectionRef, kindSelectedCollection_s, nameSelectedCollection} to ¬
    {its selectedCollectionRef, its kindSelectedCollection_s, its nameSelectedCollection}

    tell application "Capture One 12" to set countSelectedVariants to get count of selected variants
    tell application "Capture One 12" to set countEveryVariant to count of variants

    set startSortRule to "<?xml version="1.0" encoding="UTF-8"?><MatchOperator Kind="AND"><MatchOperator Kind="AND"><Condition Enabled="YES"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"
    set endSortRule to "</Criterion></Condition></MatchOperator></MatchOperator>"
    set midSortRule to "</Criterion></Condition></MatchOperator><MatchOperator Kind="AND"><Condition Enabled="YES"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"

    set smartSortRule to startSortRule & sortedColorTag1 & midSortRule & sortedColorTag2 & endSortRule

    if enableCreateCollections then
    set {theStacksRef, foundStacks, refStacksParent} to findCollection({selectedCollectionRef}, nameStackGroup, "group", true, true, 1, {"group", "project"}, "")
    if not foundStacks then error L's loqqed_Error_Halt5("Can't find or create the "" & nameStackGroup & "" group")
    end if

    if enableCreateUnsorted then
    set {theUnsortedRef, foundUnsorted, refUnsortedParent} to findCollection({selectedCollectionRef}, nameUnsortedAlbum, "smart album", true, true, 50, {"group", "project"}, smartSortRule)
    if not foundUnsorted then error L's loqqed_Error_Halt5("Can't find or create the "" & nameUnsortedAlbum & "" smart album")
    end if

    tell application "Capture One 12" to tell (variants whose selected is true) to set {theVariantsRef, theVariantsName} to {it, its name}
    if (0 = (count of theVariantsName)) then
    L's loqThis(-1, true, "No variants selected")
    else

    if enableSeqImageName then
    set numdigits to -1
    repeat with aName in theVariantsName
    set thisCtr to findBaseName(aName, imageNameParsingMethod)
    set {thisLen, thisCtr} to {(count of thisCtr), (thisCtr as integer)}
    if -1 = numdigits then
    set {numdigits, minCtr, maxCtr} to {thisLen, (0 + (thisCtr as integer)), (0 + (thisCtr as integer))}
    else
    if thisLen > numdigits then set numdigits to thisLen
    if thisCtr < minCtr then set minCtr to thisCtr
    if thisCtr > maxCtr then set maxCtr to thisCtr
    end if
    end repeat
    set padZeros to U's makeList(numdigits, 0) as string
    set sequenceName to seqBaseImageName & (text (-numdigits) thru -1 of (padZeros & minCtr))
    if maxCtr > minCtr then set sequenceName to sequenceName & "-" & (text (-numdigits) thru -1 of (padZeros & maxCtr))
    else
    set padZeros to U's makeList(seqCountDigits, 0) as string
    set sequenceName to seqBaseCountedName & (text (-seqCountDigits) thru -1 of (padZeros & sequenceCount))
    set sequenceCount to sequenceCount + 1

    if enableCreateCollections then
    tell application "Capture One 12" to tell theStacksRef to set seqAlbumExists to (0 < (count of (every collection whose name begins with sequenceName)))
    if seqAlbumExists then
    tell application "Capture One 12" to tell theStacksRef to set seqAlbumName_L to name of every collection whose name begins with seqBaseCountedName
    set {ptrFirstNum, maxAlbumCount} to {(1 + (count of seqBaseCountedName)), 1}
    repeat with aName in seqAlbumName_L
    set {aNamesId_L, ptrChar, lenName} to {(id of aName), (ptrFirstNum + 0), (length of aName)}
    repeat while (48 ≤ aNamesId_L's item ptrChar) and (57 ≥ aNamesId_L's item ptrChar)
    set ptrChar to ptrChar + 1
    if ptrChar > lenName then exit repeat
    end repeat
    set ptrChar to ptrChar - 1 -- now ptrChar points to the last number character
    if ((ptrChar - ptrFirstNum + 1) ≤ seqCountDigits) and ((ptrChar - ptrFirstNum) > 0) then
    set albumsCount to (string id (items ptrFirstNum thru ptrChar of aNamesId_L)) as integer
    if albumsCount > maxAlbumCount then set maxAlbumCount to albumsCount
    end if
    end repeat
    set sequenceCount to maxAlbumCount + 2
    set sequenceName to seqBaseCountedName & (text (-seqCountDigits) thru -1 of (padZeros & (maxAlbumCount + 1)))
    end if
    end if
    end if

    set userCanceled to false
    if enableSequenceNameEntry then
    try
    set dialogResult to display dialog ¬
    "Sequence Name" buttons {"Cancel", "OK"} ¬
    default button "OK" cancel button "Cancel" default answer sequenceName
    on error number -128
    set userCanceled to true
    end try
    set sequenceName to dialogResult's text returned
    end if

    if not userCanceled then
    set gotSequenceAlbum to false
    if enableCreateCollections then
    set {refSequenceAlbum, gotSequenceAlbum, refSeqParent} to findCollection({theStacksRef}, sequenceName, "album", true, false, 0, {"group"}, "")
    if gotSequenceAlbum then
    tell application "Capture One 12" to add inside refSequenceAlbum variants theVariantsRef
    L's loqThis(-1, false, "Copied " & (count of theVariantsRef) & " variants to "" & sequenceName & """)
    else
    error L's loqqed_Error_Halt5("Can't create the "" & sequenceName & "" album")
    end if
    end if
    if enableSeqInMetaData then
    tell application "Capture One 12"
    if enableMetaDataMultiSeq then
    set oneByOne_L to get variants whose (image scenes is not "") and (selected is true)
    tell (variants whose selected is true and image scenes is "") to set its image scenes to sequenceName

    repeat with aVariant in oneByOne_L
    tell aVariant
    set the aVarSeq_L to its image scenes
    if aVarSeq_L does not contain sequenceName then set its image scenes to (aVarSeq_L & "," & sequenceName)
    end tell
    end repeat
    else
    tell (variants whose selected is true) to set its image scenes to sequenceName
    end if

    end tell
    end if
    try
    tell application "Capture One 12" to tell (variants whose selected is true) to set its color tag to sortedColorTag1 -- important to do this last to avoid the effect of color filtering
    on error
    if gotSequenceAlbum then
    tell application "Capture One 12" to tell refSequenceAlbum to tell its variants to set its color tag to sortedColorTag1
    else
    error L's loqqed_Error_Halt5("Unable to set the color tag because the variants have disappeared from the collection. Likely cause is a filter that has been set on the Image Scenes MetaData")
    end if
    end try
    end if
    end if

    ################ The End ###############

    on findCollection(parentRefList, nameTargetColl, kindTargetColl, enableCreate, enableFindParents, MaxCount, kindParentColl_L, saSortRule)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose handler to find and create a collection, starting from some arbitrary collection
    ## Version March 17 2019

    global debugLogEnable
    local refParentColl, ptrCollRef, thisCollRef, thisCollKind, thisCollKind_S, thisCollName, subCollNames, subCollRef, refFoundColl, hasFoundColl, saRules, debugText, collSearchFail

    copy (contents of item 1 of parentRefList) to thisCollRef
    if 1 < (count of parentRefList) then set enableFindParents to false

    set saRules to "<?xml version="1.0" encoding="UTF-8"?><MatchOperator Kind="AND"></MatchOperator>" -- No rules string -- length 80, first part is 38 long
    if (80 < (get length of saSortRule)) and (text 1 thru 38 of saRules) = (text 1 thru 38 of saSortRule) then set saRules to saSortRule
    set {refFoundColl, hasFoundColl, collCreated, refParentColl} to {{}, false, false, missing value}

    repeat with ptrCollRef in parentRefList
    set thisCollRef to contents of ptrCollRef

    tell application "Capture One 12" to tell thisCollRef to set {thisCollKind, thisCollName, subCollNames, subCollRef, refParentColl} to {its kind, its name, name of its collections, its collections, it}
    set thisCollKind_S to C's convertKindList(thisCollKind)

    set debugText to kindTargetColl & " "" & nameTargetColl & "" in " & thisCollKind_S & " "" & thisCollName & """
    if debugLogEnable then L's loqThis(2, false, return & "Find Collection: " & debugText & " with Create: " & enableCreate & " Search: " & enableFindParents & " Sort Rule: " & (count of saSortRule) & " characters")
    if debugLogEnable then L's loqThis(3, false, "SubCollections {" & U's joinListToString(subCollNames, "; ") & "}")

    if (kindParentColl_L contains thisCollKind_S) and (subCollNames contains nameTargetColl) then
    tell application "Capture One 12" to tell thisCollRef
    if ("project" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is project))
    if ("group" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is group))
    if ("album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is album))
    if ("smart album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is smart album and rules is saRules))
    end tell
    end if

    if (1 ≠ (count of refFoundColl)) then
    if enableCreate and (kindParentColl_L contains thisCollKind_S) and ({"album", "smart album"} does not contain thisCollKind_S) then
    if debugLogEnable then L's loqThis(2, false, "Attempting to Create the collection")

    set collCreated to false
    tell application "Capture One 12" to tell thisCollRef
    set {ctrCollName, collSearchFail} to {1, false}
    copy (nameTargetColl as string) to nbrTargetCollName
    repeat until ({} = (every collection whose name is nbrTargetCollName)) or collSearchFail
    set refFoundColl to {}
    if ("project" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is project))
    if ("group" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is group))
    if ("album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is album))
    if ("smart album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is smart album and rules is saRules))
    if (1 = (count of refFoundColl)) then exit repeat -- found anumbered collection
    set {nbrTargetCollName, ctrCollName} to {(nameTargetColl & ctrCollName), ctrCollName + 1}
    if MaxCount < ctrCollName then set collSearchFail to true
    end repeat

    if not collSearchFail and (0 = (count of refFoundColl)) then
    if ("group" = kindTargetColl) then set {refFoundColl, collCreated} to {{make new collection with properties {kind:group, name:nbrTargetCollName}}, true}
    if ("album" = kindTargetColl) then set {refFoundColl, collCreated} to {{make new collection with properties {kind:album, name:nbrTargetCollName}}, true}
    if ("project" = kindTargetColl) then set {refFoundColl, collCreated} to {{make new collection with properties {kind:project, name:nbrTargetCollName}}, true}
    if ("smart album" = kindTargetColl) then set {refFoundColl, collCreated} to {{make new collection with properties {kind:smart album, name:nbrTargetCollName, rules:saRules}}, true}
    if (1 < ctrCollName) then set debugText to kindTargetColl & " "" & nbrTargetCollName & "" in " & thisCollKind_S & " "" & thisCollName & """
    end if
    end tell
    end if
    end if

    if (1 = (count of refFoundColl)) then
    set {refFoundColl, hasFoundColl, refParentColl} to {(item 1 of refFoundColl), true, thisCollRef}
    if collCreated then
    if debugLogEnable then L's loqThis(2, false, "Created " & debugText)
    else
    if debugLogEnable then L's loqThis(2, false, "Found " & debugText)
    end if
    exit repeat
    end if
    end repeat

    if (not hasFoundColl) and enableFindParents then set {refFoundColl, hasFoundColl, refParentColl} to my findCollection((my findParentColl(thisCollRef)), nameTargetColl, kindTargetColl, enableCreate, false, MaxCount, kindParentColl_L, saSortRule)

    if not hasFoundColl then L's loqThis(0, false, "The " & kindTargetColl & " "" & nameTargetColl & "" is missing")
    return {refFoundColl, hasFoundColl, refParentColl}

    end findCollection

    on findParentColl(thisCollRef)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose handler to find the parent of a collection
    ## March 23 2019
    global debugLogEnable
    local errorText, errorText1, parentStringList, refPtr, docPtr, docName, parentPtr, parentRefList, startPtr, stopPtr
    if debugLogEnable then L's loqThis(2, false, "Starting Search")
    try
    get || of {thisCollRef}
    on error errorText
    if debugLogEnable then L's loqThis(5, false, "Full error text "" & errorText & """)
    end try
    ## Extract the string between "{" and "}"
    repeat with startPtr from 1 to count of errorText
    if "{" = text startPtr of errorText then exit repeat
    end repeat
    repeat with stopPtr from -1 to -(count of errorText) by -1
    if "}" = text stopPtr of errorText then exit repeat
    end repeat
    set errorText to U's removeLeadingTrailingSpaces((text (startPtr + 1) thru (stopPtr - 1) of errorText))
    ## if script runs as an application "collection" is replaced by "«class COcl»", fix that
    if "«class COcl»" = text 1 thru 12 of errorText then set errorText to U's replaceText(errorText, "«class COcl»", "collection")
    set parentStringList to U's splitStringToList(errorText, "of") -- make a list of references
    if debugLogEnable then L's loqThis(4, false, "Processed error text "" & errorText & """)

    repeat with docPtr from (count of parentStringList) to 0 by -1
    try
    if "document" = first word of item docPtr of parentStringList then exit repeat
    end try
    end repeat
    set {docPtr, parentRefList} to {(docPtr + 0), {}}
    if 0 = docPtr then error L's loqqed_Error_Halt5("Didn't find "document" in the string "" & errorText & """)
    set docName to U's removeLeadingTrailingSpaces((get item 2 of U's splitStringToList((U's removeLeadingTrailingSpaces((get item docPtr of parentStringList))), """)))
    tell application "Capture One 12" to copy (document docName) to beginning of parentRefList
    if debugLogEnable then L's loqThis(5, false, ("Found Document Reference "" & (get parentStringList's item docPtr) & """))
    repeat with parentPtr from 1 to docPtr
    if "collection" = (first word of item parentPtr of parentStringList) then exit repeat
    end repeat
    set parentPtr to parentPtr + 1
    if (parentPtr > docPtr) then error L's loqqed_Error_Halt5("Unable to find starting collection")
    repeat with refPtr from docPtr - 1 to parentPtr by -1
    if debugLogEnable then L's loqThis(5, false, ("Collection Reference "" & (get parentStringList's item refPtr) & """))
    tell application "Capture One 12" to tell (first item of parentRefList) to copy (collection id (get last word of parentStringList's item refPtr)) to beginning of parentRefList
    end repeat
    return parentRefList
    end findParentColl


    on findBaseName(theString, theMethod_L)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose handler to parse an image file name

    ## Looks long, but execution time is about 20 - 80 microseconds
    ## theMethod_L is a list of Methods. Each Method describes a parsing method (a particular way of parsing a string)
    ## Each Method is a list of Groups, each Group is a record that describes how to find the end of a group of characters.
    ## While a group runs each character is copied from theString to the output until the end is found.
    ## The algorithm starts with Group 1 of Method 1. When a Group ends the next one starts
    ## When there are no Groups left or no Characters left, the algorithm stops reading characters and returns the output characters.
    ## The Group record: {inpAlp, inpNum, inpSpa, inpSym, inpCnt, inpBck, inpAct}:
    ## (inpAlp, inpNum, inpSpa, inpSym, inpBck) boolean, (inpCnt) integer, inpAct a list of Actions {{X,Y,Z},{X,Y,Z},...}
    #3 Character Type Tests are evaluated first, then Actions
    ## A group ends upon finding Alphabetic characters (if inpAlp true), or Numbers (if inpNum true), or a Space(if inpSpa true),
    ## or Symbols (if inpSym true), or finding more than inpCnt characters (if inpCnt >0).
    ## If inpBck is true, when a group ends, the last character becomes part of the next group
    ## EG Group3: {inpAlp:false, inpNum:false, inpSpa:false, inpSym:true, inpBck:true, inpCnt:9, inpAct:aList}
    ## Means: stop if a symbol is found or after the 9th character, and the last character becomes the first character of the next group
    ## Thus up to 9 Numbers, Spaces and Alphabetic characters are accepted, and 9th character or symbol is part of the next group.
    ## if Group 3 is the last Group, then 9th or symbol character (and all subsequent characters) are dropped.

    ## inpAct contains Alists {X,Y,Z}, each describes an action to execute when finding some character. inpAct= {} results in no actions
    ## X is the character that triggers an action; Y is the Action; Z optionally specifies "how much"
    ## X= return triggers the action on a Group Increment from Character Type Tests
    ## Actions (1): T-Translate Character to "Z" ; G- Switch to Group Z ; M- Switch to Method Z ; I- Increment group by Z;
    ## B- Drop Back 1 character; J- Jump ahead Z characters, D- Drop trigger Characters, O - Drop Output
    ## Actions A,B,C,N,S and P set the value of inpAlp, inpBck, inpCnt, inpNum, inpSpa and inpSym
    ## If the Alist has no "z" value: for "I", z is taken as 1; for "J", z is taken as the length of the trigger; for "T" z is taken as "";
    ## for G, z is taken as "0"; for M, the method is incremented, output and trigger are dropped
    ## for all other commands z is taken as true when missing
    ## Selecting Group < 1 resets the parser to Group 1 and clears all output. If inpBck is true, the current character is dropped.
    ## Selecting Method 0 triggers a Method reset, output and input are reset, and then the previous method is incremented
    ## Switching to Method whose number does not exist causes the previous output to be cleared, the remaining input characters are accepted
    ## E.G. For inpAct:{{"$","M"},{"-","T"}} in Method 1 --> when an "$" is encountered, start over again with Group 1 of Method 2, also drop every "-"
    ## If there is no Method 2, all remaining characters of the input are taken as output.

    global debugLogEnable
    local theCharIDList, stringCount, thisCharsId, parsedNumList, charPointer, iterationCtr, SafetyLimit
    local Alpha, Numb, Blank, Symbol, MaxCnt, backOneOnGrpInc, ActionList, AlistTriggerId_L, anAlist, theAction, Prefix
    local thisMethod, MethodCount, thisGroup, GroupCount, hasMaxCnt, countChars, stringCount
    local triggerMethodInit, triggerGroupInit, triggerGroupInc, validChar, newCharIDs, hasNewChars, nextGroup, nextMethod, nextCharPointer
    local triggerBack1Char, hasPar3, valuePar3, endTriggerPointer, lenActTrigger, actionString, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop, lenActTrigger


    set {theCharIDList, stringCount, nextMethod, MethodCount, triggerMethodInit, parsedNumList} to ¬
    {(get id of theString), (get count of theString), 1, (count of lists of theMethod_L), true, {}}
    set {charPointer, iterationCtr, SafetyLimit} to {1, 0, (MethodCount * stringCount * 4)}
    if debugLogEnable then
    L's loqThis(2, false, "Parsing "" & theString & """)
    if (0 < (count of strings of theMethod_L)) then L's loqThis(2, false, ("Using Method " & (string 1 of theMethod_L)))
    L's loqThis(3, false, {"Initialising: stringCount " & stringCount & ", Method " & nextMethod & ", MethodCount " & MethodCount})
    end if

    repeat while (charPointer ≤ stringCount) and (iterationCtr < SafetyLimit)
    set iterationCtr to iterationCtr + 1

    if triggerMethodInit then set {nextGroup, GroupCount, triggerGroupInit, thisMethod} to ¬
    {1, (count of records of theMethod_L's list nextMethod), true, nextMethod}
    if debugLogEnable and triggerMethodInit then L's loqThis(3, false, "Start Method #" & nextMethod & ", Group Count: " & (count of theMethod_L's list nextMethod))

    if triggerGroupInit then
    tell theMethod_L's list thisMethod's record nextGroup to set {Prefix, Alpha, Numb, Blank, Symbol, MaxCnt, hasMaxCnt, backOneOnGrpInc, ActionList, countChars, thisGroup} to ¬
    {its inpPref, its inpAlp, its inpNum, its inpSpa, its inpSym, its inpCnt, (0 < its inpCnt), its inpBck, its inpAct, 0, nextGroup}
    if debugLogEnable then L's loqThis(3, false, "Start Group #" & thisGroup & " , Prefix "" & Prefix & "", Alpha " & Alpha & ", Number " & Numb & ", Blank " & Blank & ", Symbol " & Symbol & ", Max Count " & MaxCnt & ", has Max Count " & hasMaxCnt & ", Drop Back Last Character " & backOneOnGrpInc & " , ActionList : " & (count of ActionList) & " action lists")
    if (0 < (count of ActionList)) and ("list" ≠ ((class of ActionList's item 1) as text)) then error "Group " & thisGroup & " has an incorrectly formatted Action List"
    set parsedNumList to parsedNumList & (id of Prefix)
    end if

    set {triggerBack1Char, triggerGroupInc, triggerGroupInit, triggerMethodInit, validChar, newCharIDs, hasNewChars, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop} to ¬
    {false, false, false, false, true, {}, false, false, missing value, 0, false, false}

    set {thisCharsId, countChars, nextCharPointer, lenActTrigger} to {(theCharIDList's item charPointer), (countChars + 1), charPointer, 1}
    if debugLogEnable then L's loqThis(5, false, "Character " & charPointer & " : "" & (string id (get theCharIDList's item charPointer)) & """)

    if hasMaxCnt and (countChars ≥ MaxCnt) then -- if the counter has triggered, don't check the character groups
    set triggerGroupInc to true
    if debugLogEnable then L's loqThis(3, false, "Count triggers Group Increment")
    else -- check the character type
    if debugLogEnable then L's loqThis(3, false, "Character Type Check")
    if ((thisCharsId ≥ 65) and (thisCharsId ≤ 90)) or ((thisCharsId ≥ 97) and (thisCharsId ≤ 122)) or (thisCharsId ≥ 192) then -- a latin character
    if Alpha then set triggerGroupInc to true
    if debugLogEnable and Alpha then L's loqThis(4, false, "Alpha triggers Group Increment")
    else if ((thisCharsId ≥ 48) and (thisCharsId ≤ 57)) then -- a number
    if Numb then set triggerGroupInc to true
    if debugLogEnable and Numb then L's loqThis(4, false, "Number triggers Group Increment")
    else if thisCharsId = 32 then --- its a space
    if Blank then set triggerGroupInc to true
    if debugLogEnable and Blank then L's loqThis(4, false, "Space triggers Group Increment")
    else if thisCharsId > 32 then -- its a symbol
    if Symbol then set triggerGroupInc to true
    if debugLogEnable and Symbol then L's loqThis(4, false, "Symbol triggers Group Increment")
    end if
    end if

    repeat with anAlist in ActionList
    set {AlistTriggerId_L, endTriggerPointer} to {((id of contents of anAlist's item 1) as list), (charPointer + (count of anAlist's item 1) - 1)}

    if (endTriggerPointer ≤ stringCount) and ((not hasActionTrigger) or (theActionTrigger = AlistTriggerId_L)) and ¬
    ((triggerGroupInc and ({13} = AlistTriggerId_L)) or (AlistTriggerId_L = (theCharIDList's items charPointer thru endTriggerPointer))) ¬
    then

    if debugLogEnable and not hasActionTrigger then L's loqThis(4, false, "Action List is triggered by "" & (anAlist's item 1) & """)
    if not hasActionTrigger then set {hasActionTrigger, theActionTrigger, lenActTrigger} to {true, AlistTriggerId_L, (count of AlistTriggerId_L)}

    set {theAction, hasPar3, valuePar3} to {(item 2 of anAlist), (3 ≤ (count of anAlist)), true}
    if hasPar3 then set valuePar3 to (contents of item 3 of anAlist)

    if debugLogEnable then
    if hasPar3 then
    set actionString to "Action "" & (contents of item 2 of anAlist) & "" ; "" & (contents of item 3 of anAlist) & """
    else
    set actionString to "Action "" & (contents of item 2 of anAlist) & """
    end if
    end if

    ## Group 1 - Character Control - Does Not clear triggerGroupInc

    if ("A" = theAction) then
    set Alpha to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpAlp to " & valuePar3)

    else if "B" = theAction then --
    set backOneOnGrpInc to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpBck to " & valuePar3)
    else if ("C" = theAction) then
    if (not hasPar3) or (0 = valuePar3) then
    set {MaxCnt, hasMaxCnt} to {0, false}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to 0")
    else if (0 < valuePar3) then
    set {MaxCnt, hasMaxCnt} to {valuePar3, true}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to " & valuePar3)
    else if (0 > valuePar3) then
    set {MaxCnt, hasMaxCnt} to {(MaxCnt - valuePar3), true}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Increment inpCnt by " & (-valuePar3))
    end if

    else if ("N" = theAction) then
    if hasPar3 then set Numb to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpNum to " & valuePar3)

    else if ("P" = theAction) then
    set Symbol to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & "- Set inpSym to " & valuePar3)
    else if ("Q" = theAction) then
    if debugLogEnable then L's loqThis(4, false, actionString & "- Quit Actions")
    exit repeat

    else if "S" = theAction then
    set Blank to valuePar3
    if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpSpa to " & valuePar3)

    ## Group 2 - Character Control - clears triggerGroupInc on multi character trigger

    else if "D" = theAction then --
    set validChar to not valuePar3
    if 1 < lenActTrigger then set triggerGroupInc to false
    if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Action Trigger: " & valuePar3)

    else if "O" = theAction then --
    set triggerOutputDrop to valuePar3
    if 1 < lenActTrigger then set triggerGroupInc to false
    if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Output characters")

    else if "J" = theAction then --
    if not hasPar3 then set valuePar3 to lenActTrigger
    set {nextCharJump, hasJump} to {valuePar3, true}
    if 1 < lenActTrigger then set triggerGroupInc to false
    if debugLogEnable then L's loqThis(5, false, actionString & " - Jump forward " & valuePar3 & " Characters")

    else if "T" = theAction then -- translate/drop this character
    if not hasPar3 then set valuePar3 to ""
    set {newCharIDs, hasNewChars} to {(id of valuePar3), true}
    if 1 < lenActTrigger then set triggerGroupInc to false
    if debugLogEnable then L's loqThis(5, false, actionString & " - Translate characters to "" & valuePar3 & """)

    ## Group 3 - Group & Method control, clears triggerGroupInc - later
    else if "G" = theAction then -- new group
    if not hasPar3 then set valuePar3 to 0
    set {nextGroup, triggerGroupInit} to {valuePar3, true}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Group " & valuePar3)

    else if "I" = theAction then -- increment group
    if not hasPar3 then set valuePar3 to 1
    set {nextGroup, triggerGroupInit} to {(thisGroup + valuePar3), true}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Increment Group to " & nextGroup)

    else if "M" = theAction then -- set method
    if not hasPar3 then set valuePar3 to (thisMethod + 1)
    set {nextMethod, triggerMethodInit, triggerOutputDrop, validChar} to {valuePar3, true, true, false}
    if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Method " & valuePar3 & " and drop output and trigger")

    else
    if debugLogEnable then L's loqThis(3, false, actionString & " - Command not found ")
    end if
    end if
    end repeat

    if (lenActTrigger > 1) then set countChars to countChars - 1 + lenActTrigger
    if hasMaxCnt and (countChars ≥ MaxCnt) then set {triggerGroupInc} to {true, L's loqThis(3, false, "Character count triggers Group Increment")}

    if (triggerGroupInit or triggerMethodInit) then set triggerGroupInc to false
    if (triggerGroupInc or triggerGroupInit or triggerMethodInit) and backOneOnGrpInc then set triggerBack1Char to true

    if (0 ≥ nextMethod) then
    if debugLogEnable then L's loqThis(3, false, "Executing Method Reset - Increment Method, Clear output, Reset Input Queue")
    set {parsedNumList, nextMethod, triggerMethodInit, nextCharPointer} to {{}, (thisMethod + 1), true, 1, false}
    set {triggerBack1Char, triggerGroupInc, hasJump, hasNewChars, hasMaxCnt} to {false, false, false, false, false}
    else
    if (0 ≥ nextGroup) then
    set {triggerBack1Char, validChar, triggerGroupInc, hasJump, hasNewChars} to {false, false, false, false, false}
    if debugLogEnable then L's loqThis(3, false, "Executing Group Reset - Switch to Group 1, Clear output , dropping back " & (-nextGroup) & " characters")
    if (-1 ≥ nextGroup) then set {nextCharJump, hasJump} to {(nextGroup), true}
    set {parsedNumList, nextGroup, triggerGroupInit} to {{}, 1, true}
    end if

    if triggerGroupInc then set {nextGroup, triggerGroupInit} to {thisGroup + 1, true, L's loqThis(3, false, "Executing Group Increment - Switch to Group " & (thisGroup + 1))}

    if debugLogEnable and triggerOutputDrop then L's loqThis(4, false, "Dropped Output")
    if triggerOutputDrop then set parsedNumList to {}
    if triggerBack1Char then
    set {validChar, nextCharPointer} to {false, charPointer}
    if debugLogEnable then L's loqThis(3, false, "Drop Back 1 Character")
    else if (hasJump or hasNewChars) then
    set validChar to false
    if (hasJump and (((thisGroup ≠ nextGroup) or (thisMethod ≠ nextMethod) or (0 < nextCharJump)))) then
    set nextCharPointer to (charPointer + nextCharJump)
    if nextCharPointer < 1 then set nextCharPointer to 1
    if hasNewChars and (0 < nextCharJump) and (lenActTrigger > nextCharJump) then set nextCharPointer to (charPointer + lenActTrigger)
    else if hasJump then
    ## do nothing - can't move charPointer back
    if debugLogEnable then L's loqThis(3, false, "Unable to move back " & (-nextCharJump) & " characters - infinite loop")
    else if hasNewChars then -- and not hasJump
    set nextCharPointer to (charPointer + lenActTrigger)
    end if
    if debugLogEnable then L's loqThis(3, false, "Input Moved Forward " & (nextCharPointer - charPointer) & " characters")
    else
    set nextCharPointer to (charPointer + 1)
    if debugLogEnable then L's loqThis(5, false, "Input Moved Forward 1 character")
    end if
    if hasNewChars then set {parsedNumList} to {(parsedNumList & newCharIDs), L's loqThis(4, false, "Added new Characters to the output")}
    if validChar then set end of parsedNumList to thisCharsId
    if debugLogEnable and not validChar then L's loqThis(3, false, "The input character has been dropped")
    end if
    if debugLogEnable then L's loqThis(3, false, "The output is: "" & (string id parsedNumList) & """)

    if nextMethod > MethodCount then -- replace the output with every remaining character
    if debugLogEnable then L's loqThis(3, false, "Got Method " & nextMethod & " Return remaining characters")
    if charPointer > 1 then set parsedNumList to (theCharIDList's items charPointer thru stringCount)
    if charPointer ≤ 1 then copy theCharIDList to parsedNumList
    exit repeat
    else if (nextGroup > GroupCount) then
    if debugLogEnable then L's loqThis(3, false, "Finished all groups, exitting")
    exit repeat
    end if
    set charPointer to nextCharPointer -- must be last statement in the the loop
    end repeat
    if debugLogEnable and (charPointer > stringCount) then L's loqThis(3, false, "Finished all characters")

    return (string id parsedNumList)
    end findBaseName

    ##########################################
    ## Capture One General Handlers Version 2019/03/11

    on validateCOP5(minCOPversionstr, maxCOPversionstr)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose initialisation handler for scripts using Capture One Pro
    ## Extract and check basic information about the Capture One application

    global debugLogEnable
    local COPProcList, theAppRef, numCOPversion, minCOPversion, maxCOPversion
    local digit_mult, Version_digit, min_digit, max_digit, copVersionStr
    local theAppName, copVersion

    tell application "System Events"
    if debugLogEnable then
    L's loqThis(2, false, ("COP Processes:" & (get U's joinListToString((get name of every process whose name begins with "Capture One" and background only is false), ", "))))
    L's loqThis(3, false, ("All Processes: " & (get U's joinListToString((get name of every process whose background only is false), ", "))))
    end if
    set COPProcList to every process whose name begins with "Capture One" and background only is false
    if (count of COPProcList) = 0 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("COP is not running"))}
    if (count of COPProcList) ≠ 1 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("Unexpected: >1 COP instances"))}
    set theAppRef to item 1 of COPProcList
    set theAppName to ((get name of theAppRef) as text)
    set copDetailedVersion to get version of my application theAppName
    end tell

    tell application "Capture One 12" to set copVersionStr to (get app version)
    set copVersion to (word -1 of copVersionStr)

    if debugLogEnable then
    L's loqThis(2, false, ("theAppName: " & theAppName))
    L's loqThis(1, false, copVersionStr)
    L's loqThis(2, false, ("Capture One full Version " & copDetailedVersion))
    end if
    tell U's compareVersion(copVersion, minCOPversionstr, maxCOPversionstr) to set {minVersionPass, maxVersionPass} to {its minVersionPass, its maxVersionPass}
    if not minVersionPass then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5(("This Script does not support Capture One " & copDetailedVersion & " - supported versions are from " & minCOPversionstr & " to " & maxCOPversionstr)))}
    if not maxVersionPass then L's loqThis(0, true, ("Caution: Capture One " & copDetailedVersion & " has not been verified yet"))
    return {hasErrors:false, theAppName:theAppName, copVersion:copVersion}
    end validateCOP5

    on validateCOPdoc5(theDocRef, validDocKindList)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose initialisation handler for scripts using Capture One Pro
    ## Extract and check basic information about a document

    global debugLogEnable
    local COPDocKind_s, COPDocKind_p, COPDocName

    if "text" = (get class of theDocRef as text) and (0 = (get count of theDocRef)) then tell application "Capture One 12" to set theDocRef to get current document

    try
    tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
    on error errorText number errorNumber
    return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("The Script could not retrieve Capture One document info. Error " & errorNumber & ": "" & errorText & """))}
    end try
    set COPDocKind_s to convertKindList(COPDocKind_p)

    if validDocKindList does not contain COPDocKind_s then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5((COPDocName & " is a " & COPDocKind_s & " -- not supported by this script")))}
    return {hasErrors:false, COPDocName:COPDocName, COPDocKind_s:COPDocKind_s}
    end validateCOPdoc5

    on validateCOPcollections5(theDocRef)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General purpose initialisation handler for scripts using Capture One Pro
    ## Extract basic information regarding the current collection, and the top level collections
    global debugLogEnable
    local namesTopCollections, kindsTopCollections_s, countTopCollections, selectedCollectionRef, selectedCollectionIndex, kindSelectedCollection_s, nameSelectedCollection

    tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
    set COPDocKind_s to convertKindList(COPDocKind_p)

    tell application "Capture One 12" to tell theDocRef
    set selectedCollectionRef to get current collection
    if (missing value = selectedCollectionRef) then
    try
    set current collection to collection "All Images"
    on error
    set current collection to last collection
    end try
    set selectedCollectionRef to get current collection
    end if
    tell selectedCollectionRef to set {nameSelectedCollection, kindSelectedCollection_s} to {name, my convertKindList(kind)}
    set {namesTopCollections, kindsTopCollections_s} to {every collection's name, my convertKindList(every collection's kind)}
    end tell
    set countTopCollections to count of namesTopCollections

    repeat with collectionCounter from 1 to countTopCollections
    if (nameSelectedCollection = item collectionCounter of namesTopCollections) and ¬
    (kindSelectedCollection_s = item collectionCounter of kindsTopCollections_s) then
    set selectedCollectionIndex to collectionCounter
    exit repeat
    end if
    end repeat

    local selectedCollectionMirroredAtTopLast, bottomUserCollectionIndex, topUserCollectionIndex, countFavoriteCollections, namesFavoriteCollections

    if COPDocKind_s = "catalog" then
    repeat with collectionCounter from countTopCollections to 1 by -1
    if ("in Catalog" = item collectionCounter of namesTopCollections) and ¬
    ("smart album" = item collectionCounter of kindsTopCollections_s) then
    set topUserCollectionIndex to collectionCounter - 1
    exit repeat
    end if
    end repeat
    repeat with collectionCounter from 1 to countTopCollections
    if ("Trash" = item collectionCounter of namesTopCollections) and ¬
    ("smart album" = item collectionCounter of kindsTopCollections_s) then
    set bottomUserCollectionIndex to collectionCounter + 1
    exit repeat
    end if
    end repeat

    set selectedCollectionMirroredAtTopLast to ¬
    (selectedCollectionIndex = countTopCollections) and ¬
    ({"catalog folder", "favorite"} does not contain last item of kindsTopCollections_s)

    set {countFavoriteCollections, namesFavoriteCollections} to {missing value, missing value}

    else if COPDocKind_s = "session" then
    repeat with collectionCounter from countTopCollections to 1 by -1
    if ("favorite" ≠ item collectionCounter of kindsTopCollections_s) then
    set topUserCollectionIndex to collectionCounter
    exit repeat
    end if
    end repeat
    repeat with collectionCounter from 1 to countTopCollections
    if ("Trash" = item collectionCounter of namesTopCollections) and ¬
    ("favorite" = item collectionCounter of kindsTopCollections_s) then
    set bottomUserCollectionIndex to collectionCounter + 1
    exit repeat
    end if
    end repeat

    set countFavoriteCollections to countTopCollections - topUserCollectionIndex
    if 0 = countFavoriteCollections then
    set namesFavoriteCollections to {}
    else
    set namesFavoriteCollections to (get items (topUserCollectionIndex + 1) thru countTopCollections of namesTopCollections)
    end if

    set selectedCollectionMirroredAtTopLast to false
    end if

    local selectedCollectionIsUser, namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections
    set selectedCollectionIsUser to ¬
    (selectedCollectionMirroredAtTopLast or ¬
    ((selectedCollectionIndex ≥ bottomUserCollectionIndex) and (selectedCollectionIndex ≤ topUserCollectionIndex)))

    if topUserCollectionIndex < bottomUserCollectionIndex then
    set {topUserCollectionIndex, bottomUserCollectionIndex} to {missing value, missing value}
    set {namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections} to {{}, {}, 0}
    else
    set {namesTopUserCollections, kindsTopUserCollections_s} to {(get items bottomUserCollectionIndex thru topUserCollectionIndex of namesTopCollections), (get items bottomUserCollectionIndex thru topUserCollectionIndex of kindsTopCollections_s)}
    set countTopUserCollections to count of namesTopUserCollections
    end if

    return {hasErrors:false, namesTopUserCollections:namesTopUserCollections, kindsTopUserCollections_s:kindsTopUserCollections_s, countTopUserCollections:countTopUserCollections, selectedCollectionRef:selectedCollectionRef, selectedCollectionIndex:selectedCollectionIndex, kindSelectedCollection_s:kindSelectedCollection_s, nameSelectedCollection:nameSelectedCollection, selectedCollectionMirroredAtTopLast:selectedCollectionMirroredAtTopLast, selectedCollectionIsUser:selectedCollectionIsUser, bottomUserCollectionIndex:bottomUserCollectionIndex, topUserCollectionIndex:topUserCollectionIndex, countFavoriteCollections:countFavoriteCollections, namesFavoriteCollections:namesFavoriteCollections}

    end validateCOPcollections5

    on convertKindList(kind_list)
    ## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
    ## General Purpose Handler for scripts using Capture One Pro
    ## Capture One returns the chevron form of the "kind" property when AppleScript is run as an Application
    ## Unless care is taken to avoid text conversion of this property, this bug breaks script decisions based on "kind"
    ## This script converts text strings with the chevron form to strings with the expected text form
    ## The input may be a single string, a single enum, a list of strings or a list of enums
    ## The code is not compact but runs very fast, between 60us and 210us per item

    local kind_s_list, input_is_list, theItem, kind_s1, fail_flag, code_start, kind_list, Kind_s_item, kind_code, kind_type

    set kind_s_list to {}
    set input_is_list to ("list" = (get (class of kind_list) as text))
    if input_is_list then
    if ("text" = (get (class of item 1 of kind_list) as text)) and ¬
    ("«" ≠ (get text 1 of item 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
    repeat with theItem in kind_list
    tell application "Capture One 12" to set the end of kind_s_list to (get theItem as text)
    end repeat
    if ("«" ≠ (get text 1 of item 1 of kind_s_list)) then return kind_s_list
    else
    if ("text" = (get (class of kind_list) as text)) and ¬
    ("«" ≠ (get text 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
    tell application "Capture One 12" to set kind_s1 to (get kind_list as text)
    if "«" ≠ (get text 1 of kind_s1) then return kind_s1 -- quick pass through if input is OK
    set kind_s_list to {kind_s1}
    end if

    set fail_flag to false
    set code_start to -5

    set kind_list to {}
    repeat with Kind_s_item in kind_s_list
    if ("«" ≠ (get text 1 of Kind_s_item)) or (16 > (count of Kind_s_item)) then ¬
    error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))

    set kind_code to get (text code_start thru (code_start + 3) of Kind_s_item)
    set kind_type to get (text code_start thru (code_start + 1) of Kind_s_item)

    if kind_type = "CC" then ## Collection Kinds
    if kind_code = "CCpj" then
    set the end of kind_list to "project"
    else if kind_code = "CCgp" then
    set the end of kind_list to "group"
    else if kind_code = "CCal" then
    set the end of kind_list to "album"
    else if kind_code = "CCsm" then
    set the end of kind_list to "smart album"
    else if kind_code = "CCfv" then
    set the end of kind_list to "favorite"
    else if kind_code = "CCff" then
    set the end of kind_list to "catalog folder"
    else
    set fail_flag to true
    end if

    else if kind_type = "CL" then ## Layer Kinds
    if kind_code = "CLbg" then
    set the end of kind_list to "background"
    else if kind_code = "CLnm" then
    set the end of kind_list to "adjustment"
    else if kind_code = "CLcl" then
    set the end of kind_list to "clone"
    else if kind_code = "CLhl" then
    set the end of kind_list to "heal"
    else
    set fail_flag to true
    end if

    else if kind_type = "CR" then ## Watermark Kinds
    if kind_code = "CRWn" then
    set the end of kind_list to "none"
    else if kind_code = "CRWt" then
    set the end of kind_list to "textual"
    else if kind_code = "CRWi" then
    set the end of kind_list to "imagery"
    else
    set fail_flag to true
    end if

    else if kind_type = "CO" then ## Document Kinds
    if kind_code = "COct" then
    set the end of kind_list to "catalog"
    else if kind_code = "COsd" then
    set the end of kind_list to "session"
    else
    set fail_flag to true
    end if
    else
    set fail_flag to true
    end if

    if fail_flag then ¬
    error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))

    end repeat

    if input_is_list then
    return kind_list
    else
    return item 1 of kind_list
    end if

    end convertKindList
    0

Post is closed for comments.