AppleScript: ¿Cómo modificar los datos HTML y RTF en el portapapeles?

0

Estoy escribiendo un archivo .scpt de AppleScript, activado en todo el sistema mediante una combinación de teclas asignada en FastScripts.app, que agrega paréntesis alrededor del texto editable seleccionado.

Si el texto seleccionado ya está envuelto entre paréntesis, quiero que mi script elimine efectivamente los paréntesis de la selección. Aquí es donde necesito ayuda. No quiero eliminar ningún formato del texto formateado.

Mi script funciona si la selección con paréntesis son datos de texto sin formato, pero no si son datos RTF o HTML.

Aquí está mi código completo:

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

(*
Get the selected text into an AppleScript, while preserving the original clipboard:
From: http://apple.stackexchange.com/questions/271161/how-to-get-the-selected-text-into-an-applescript-without-copying-the-text-to-th/
*)

-- Back up the original clipboard contents:
set savedClipboard to my fetchStorableClipboard()
set thePasteboard to current application's NSPasteboard's generalPasteboard()
set theCount to thePasteboard's changeCount()

-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}

-- Check for changed clipboard:
repeat 20 times
    if thePasteboard's changeCount() is not theCount then exit repeat
    delay 0.1
end repeat

set firstCharacter to (character 1 of (the clipboard))
set lastCharacter to (character (length of (the clipboard))) of (the clipboard)

-- Remove the parentheses from the selection, if the selection is wrapped in parentheses:
if (firstCharacter is "(") and (lastCharacter is ")") then
    -- The selection already has parentheses.
    -- I must discern what class types are available for the clipboard content:
    tell current application
        set cbInfo to get (clipboard info) as string
        if cbInfo contains "RTF" then
            -- I need help here.
            -- Remove the first and last characters of the rich text, while retaining the rich text formatting.
        else if cbInfo contains "HTML" then
            -- I need help here.
            -- Remove the first and last characters of the HTML, while retaining formatting data.
        else
            -- The clipboard contains plain text.

            -- Remove the first and last character of a plain text string:
            set theSelectionWithoutParentheses to (text 2 thru -2 of (the clipboard))
            set the clipboard to theSelectionWithoutParentheses
            tell application "System Events" to keystroke "v" using {command down}
        end if
    end tell

else
    -- The selection needs parentheses.
    tell application "System Events" to keystroke "("
    delay 0.1
    tell application "System Events" to keystroke "v" using {command down}
    delay 0.1
    tell application "System Events" to keystroke ")"
end if

delay 1 -- Without this delay, may restore clipboard before pasting.

-- Restore clipboard:
my putOnClipboard:savedClipboard

on fetchStorableClipboard()
    set aMutableArray to current application's NSMutableArray's array() -- used to store contents
    -- get the pasteboard and then its pasteboard items
    set thePasteboard to current application's NSPasteboard's generalPasteboard()
    -- loop through pasteboard items
    repeat with anItem in thePasteboard's pasteboardItems()
        -- make a new pasteboard item to store existing item's stuff
        set newPBItem to current application's NSPasteboardItem's alloc()'s init()
        -- get the types of data stored on the pasteboard item
        set theTypes to anItem's types()
        -- for each type, get the corresponding data and store it all in the new pasteboard item
        repeat with aType in theTypes
            set theData to (anItem's dataForType:aType)'s mutableCopy()
            if theData is not missing value then
                (newPBItem's setData:theData forType:aType)
            end if
        end repeat
        -- add new pasteboard item to array
        (aMutableArray's addObject:newPBItem)
    end repeat
    return aMutableArray
end fetchStorableClipboard

on putOnClipboard:theArray
    -- get pasteboard
    set thePasteboard to current application's NSPasteboard's generalPasteboard()
    -- clear it, then write new contents
    thePasteboard's clearContents()
    thePasteboard's writeObjects:theArray
end putOnClipboard:
    
pregunta rubik's sphere 16.05.2017 - 07:03

1 respuesta

1

Con un poco de ayuda de

He ideado una solución para modificar el contenido del portapapeles HTML o RTF, y luego poner este contenido modificado en el portapapeles.

Mientras estamos en ello, aquí hay otro método para eliminar paréntesis externos de texto sin formato datos del portapapeles:

do shell script "pbpaste | tr -d '()' | pbcopy"

Para eliminar paréntesis externos de HTML datos del portapapeles (al tiempo que se conservan los datos de formato):

do shell script "osascript -e 'try' -e 'get the clipboard as «class HTML»' -e 'end try' | awk '{sub(/«data HTML/, \"3C68746D6C3E\") sub(/»/, \"3C2F68746D6C3E\")} {print}' | xxd -r -p | textutil -convert rtf -stdin -stdout | tr -d '()' | pbcopy"

Tanto Método 1 como Método 2 para eliminar paréntesis externos de RTF datos (manteniendo los datos de formato), manipular el portapapeles RTF contenido al analizar estos datos como código hexadecimal:

Método 1:

try
    -- Get the RTF clipboard data in hexadecimal form:
    set theOriginalHexData to do shell script "osascript -e 'try' -e 'get the clipboard as «class RTF »' -e 'end try'"
on error eStr number eNum
    display dialog eStr & " number " & eNum buttons {"OK"} default button 1 with icon caution
    error number -128 (* user cancelled *)
end try

-- I don't want any parentheses inside of the outer parentheses to be removed; they must be preserved. So...

-- Check to see if there is more than 1 instance of "(":    
set originalDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to "28" -- The hex code for the character: "("
set theHexInAListSeparatedByOpeningParentheses to text items of theOriginalHexData
set numberOfOpeningParentheses to ((count theHexInAListSeparatedByOpeningParentheses) - 1)
set AppleScript's text item delimiters to originalDelimiters

if numberOfOpeningParentheses is 1 then
-- There are zero inner opening-parentheses.
    set theModifiedHexData to (item 1 of theHexInAListSeparatedByOpeningParentheses) & (item 2 of theHexInAListSeparatedByOpeningParentheses)

else if numberOfOpeningParentheses is greater than 1 then
-- There is at least one inner opening-parenthesis.
    set theModifiedHexData to (item 1 of theHexInAListSeparatedByOpeningParentheses) & (item 2 of theHexInAListSeparatedByOpeningParentheses)
    set counter to 2
    repeat until (counter is greater than numberOfOpeningParentheses)
        -- Add the desired inner opening-parentheses back into the string:
        set theModifiedHexData to (theModifiedHexData & "28" & (item (counter + 1) of theHexInAListSeparatedByOpeningParentheses))
        set counter to counter + 1
    end repeat
end if


-- Check to see if there is more than 1 instance of ")":
set originalDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to "29" -- The hex code for the character: ")"
set theHexInAListSeparatedByClosingParentheses to text items of theModifiedHexData
set numberOfClosingParentheses to ((count theHexInAListSeparatedByClosingParentheses) - 1)
set AppleScript's text item delimiters to originalDelimiters

if numberOfClosingParentheses is 1 then
-- There are zero inner closing-parentheses.
    set theModifiedHexData to (item 1 of theHexInAListSeparatedByClosingParentheses) & (item 2 of theHexInAListSeparatedByClosingParentheses)

else if numberOfClosingParentheses is greater than 1 then
-- There is at least one inner closing-parenthesis.
    set theModifiedHexData to (item 1 of theHexInAListSeparatedByClosingParentheses)
    set counter to 2
    repeat until ((counter) is greater than numberOfClosingParentheses)
        -- Add the desired inner closing-parentheses back into the string:
        set theModifiedHexData to (theModifiedHexData & "29" & (item (counter) of theHexInAListSeparatedByClosingParentheses))
        set counter to counter + 1
    end repeat
    set theModifiedHexData to (theModifiedHexData & (item (counter) of theHexInAListSeparatedByClosingParentheses))
end if

-- Put the modified hex code onto the clipboard, as RTF data:
try
    -- Get the RTF clipboard data in hexadecimal form:
    do shell script "osascript -e 'set the clipboard to " & theModifiedHexData & "'"
on error eStr number eNum
    display dialog eStr & " number " & eNum buttons {"OK"} default button 1 with icon caution
    error number -128 (* user cancelled *)
end try

El método 1 para modificar los datos del portapapeles RTF es posiblemente susceptible a falsos positivos, si el código hexadecimal deseado se divide entre el segundo carácter de un par de hexes y el primer carácter del siguiente par de hexes.

Por ejemplo, 32 y 85 , cuando están lado a lado en el código hexadecimal, también se interpreta como 28 , a los ojos del Método 1. Claramente, esto no es deseable.

Método 2:

El método 2 resuelve el problema, haciendo imposible un falso positivo como este. Esto se debe a que, antes de que el Método 2 analice el código hexadecimal, primero organiza el objeto hexadecimal text en un objeto list de base binaria.

A diferencia del Método 1, el Método 2 interpreta el código hexadecimal en pares. Por lo tanto, el Método 2 es técnicamente mejor:

global hexCodeInBinaryList

set firstCharacterHEX to "28"
set lastCharacterHEX to "29"


try
    -- Get the RTF clipboard data in hexadecimal form:
    set theOriginalHexData to do shell script "osascript -e 'try' -e 'get the clipboard as «class RTF »' -e 'end try'"
on error eStr number eNum
    display dialog eStr & " number " & eNum buttons {"OK"} default button 1 with icon caution
    error number -128 (* user cancelled *)
end try


-- I don't want any parentheses inside of the outer parentheses to be removed; they must be preserved. So...

-- Make sure that any parentheses that come after the first parenthesis is preseved:
putStringIntoBinaryList(theOriginalHexData)
set counter to 1
set listContainingItemNumbers to {}

repeat until (counter is greater than (count of hexCodeInBinaryList))
    if ((item counter) of hexCodeInBinaryList) is firstCharacterHEX then
        set listContainingItemNumbers to listContainingItemNumbers & counter
    end if
    set counter to counter + 1
end repeat
set numberOfOpeningParentheses to (count of listContainingItemNumbers)

set theNewLocation to item 1 of listContainingItemNumbers
set theModifiedHexData to ((items 1 thru (theNewLocation - 1) of hexCodeInBinaryList) as text)
set theModifiedHexData to theModifiedHexData & ((items (theNewLocation + 1) thru (count of hexCodeInBinaryList) of hexCodeInBinaryList) as text)


-- Make sure that any parentheses that come before the last parenthesis is preseved:
putStringIntoBinaryList(theModifiedHexData)

set counter to 1
set listContainingItemNumbers to {}

repeat until (counter is greater than (count of hexCodeInBinaryList))
    if ((item counter) of hexCodeInBinaryList) is lastCharacterHEX then
        set listContainingItemNumbers to listContainingItemNumbers & counter
    end if
    set counter to counter + 1
end repeat
set numberOfClosingParentheses to (count of listContainingItemNumbers)

set theNewLocation to (item numberOfClosingParentheses of listContainingItemNumbers)
set theModifiedHexData to ((items 1 thru (theNewLocation - 1) of hexCodeInBinaryList) as text)
set theModifiedHexData to theModifiedHexData & ((items (theNewLocation + 1) thru (length of hexCodeInBinaryList) of hexCodeInBinaryList) as text)



try
    -- Put the modified hex code onto the clipboard, as RTF data:
    do shell script "osascript -e 'set the clipboard to " & theModifiedHexData & "'"
on error eStr number eNum
    display dialog eStr & " number " & eNum buttons {"OK"} default button 1 with icon caution
    error number -128 (* user cancelled *)
end try




on putStringIntoBinaryList(theStringToConvertToList)

    set totalNumberOfChracters to length of theStringToConvertToList

    set startPoint to 1
    set endPoint to 2
    set hexCodeInBinaryList to {}

    repeat until (endPoint is totalNumberOfChracters) or ((endPoint - 1) is totalNumberOfChracters)
        set hexCodeInBinaryList to hexCodeInBinaryList & (text startPoint thru endPoint of (the theStringToConvertToList))

        set startPoint to (startPoint + 2)
        set endPoint to (endPoint + 2)
    end repeat

    if ((endPoint - 1) is totalNumberOfChracters) then
        set hexCodeInBinaryList to hexCodeInBinaryList & (character (endPoint - 1) of (the theStringToConvertToList))
    end if

end putStringIntoBinaryList

Sin embargo, tenga en cuenta que el Método 2 se diseñó en base a la suposición de que está trabajando con un delimitador de código hex de dos caracteres .

Si su delimitador de código hexadecimal supera los 2 caracteres (como el delimitador de código hexadecimal de 8 caracteres para una comilla doble de apertura , que es 5C273933 ), tendría que volver a escribir el putStringIntoBinaryList() subrutine en consecuencia (o use el Método 1 en su lugar, que probablemente sea seguro para usar en un delimitador de código hexadecimal de 8 caracteres largo).

respondido por el rubik's sphere 18.05.2017 - 03:41

Lea otras preguntas en las etiquetas