Applescript for QXP 8.5.1 Find and Replace w/ Placeholder

Hi everyone,

first of all, nice to be a part of this community :smiley:

It is as it is. I have a question concerning a applescript I wrote to access QuarkXpress-documents to process them in a batch with the find and replace function of QuarkXPress.

System: OS X 10.7.5
QuarkXPress: 8.5.1

Here is the applescript:


--choose directory containing quark files
tell application "Finder"
	activate
	set source_folder to choose folder with prompt "Select folder:"
	set source_files to (files of entire contents of source_folder whose file type is "XPRJ")
	set source_files to sort source_files by name
	set source_files to (sort source_files by name) as alias list
end tell

--loop through quark documents

set findString to "\\?\\?\\?,\\?\\? €"
set replacestring to "000,00 €"

tell application "QuarkXPress"
	activate
	repeat with i from 1 to length of source_files
		set quarkfile to item i of source_files
		open quarkfile as alias use doc prefs yes remap fonts no without reflow
		tell document 1
			set (text of every story whose it is findString) to replacestring
		end tell
		save document 1
		close document 1 saving no
	end repeat
end tell

This applescript works as long as findstring is a plain text, but the findstring need to be “???,?? €” (Explanation: in Quark ? is a placeholder for 1 character (roughly like in a regex pattern!)).

I have to escape the backslash, because applescript uses the backslash itself as a escape character.

If I escape the backslash as you can see above in the applescript, I get the following result:

Applescript Error message box:

žQuarkXPress" found an error:can’t set ževery text of every story of document 1 whose it = “\?\?\?,\?\? €”" to ž"000,00 €"".

Results pane message:

error “žQuarkXPress” found an error:can’t set ževery text of every story of document 1 whose it = "\\?\\?\\?,\\?\\? €"" to ž"000,00 €"“.” number -10006 from every text of every story of document 1 whose it = “\?\?\?,\?\? €”

I tried everything to escape the backslash even with “ASCII character (92)” but with the same result.

Does anybody know what I am doing wrong or how I can use the placeholder pattern in QuarkXpress 8.5.1 find and replace via applescript?

I appreciate any help anyone can give.

Thanks in advance.

Kind regards
helpit

Model: Mac Pro (Early 2009)
AppleScript: 2.2.1
Browser: Safari 537.36
Operating System: Mac OS X (10.7)

First of all welcome :slight_smile:

The problem is the euro sign. For some clients we update the prices in print as well, using a script. For more reliability I fetch the story as plain text, getting the ranges of all occurrences and then replace the text in QuarkXPress by range, not by content. Some of my clients also work with QuarkXPress 9.x and there we have the same issues so we replace text also by range there as well.

I’m using AppleScript Toolbox to get all occurrences and it’s range but you could also use satimage.osax or write your own handler using text item delimiters for instance.

Why It’s the euro sign I have not idea. It is a 3 byte character in UTF-8 but AFAIK QuarkXpress internals are UTF-16. So euro sign is an 2 byte character like most characters in UTF-16. When replacing normal ASCII characters I don’t have this problem.

Thanks DJ Bazzie Wazzie.

I don’t have any experience with Applescript Toolbox / satimage.osax, but I want to give it a try!

I added the toolbox to that system ScriptingAdditons. Do you know how I can check if I can use it in the Script Editor and it is working? Because the System where the script have to run is OS X 10.7.5 and referring to the Applescript Toolbox website it has to be 10.9, but it could be working with older versions the author says :wink:

I tried my applescript w/o the euro sign with the same result. Maybe I misunderstood you, but it seems not the problem. In my opinion it is the pattern (?) Quark uses to address characters w/o the need to use a specific plain text.

I could be wrong, so any advice would be great. But this gives me really a headache!

Thanks again for any help.

use the command, if it compiles it works, if it doesn’t it’s not loaded (you can check the console why not).

AST version

Well I wrote that because I don’t have older systems. Also the regex.h file clearly says that enhanced and other non-posix features are not supported by pre-mountain lion versions, the greedy option will compile and run in AppleScript but won’t work in 10.7 (tested and confirmed by one of the users). But due to many requests the version 1.3 should load in 10.6 and newer and also works when Script Editor runs both in 32-bit or 64-bit (old versions was only 64 bit). I leave the text on the main site as it is because I cannot give any guarantees on older systems and also no support.

Is the euro sign an real euro sign or an euro sign font? When I remove all the euro signes from the story and other non-ASCII characters the search and replace works. With try and error I narrowed the problem down that when I removed the euro sign it became stable, so I concluded that it had to be the euro sign. However there could be more characters or even combination of them that’s causing this problem.

I could be the one that is wrong :). But the command I used was using to narrow down the problem was:

tell application "QuarkXPress"
       tell document 1
           return text of story 1 whose it is "€"
       end tell
end tell

Which was tested on a new document with 1 text containing text with euro signs. The result was all kind of different characters and a few euro signs. When I removed the euro sign from the text and searched for another normal ASCII character it became stable. So therefore my conclusion became that it had to be the euro sign causing this issue.

AST version

This is working. :slight_smile:

1.3.2

The euro sign is a real euro sign. But it doesn’t matter, because i don’t need the Euro sign. The euro sign should only help to find the right text respectively numbers (price).

I will try to get the text by their character format. Do you know if and how I can address the character format in Quark via applescript to find a text with this format?

I try this way to achieve to get prices and replace them by zeros.

Thank you very much and big thanks to you DJ Bazzie Wazzie for you help so far. I really appreciate it!

Here a complete working solution, just open 1 document and that document contain the strings and run the script. The reason for the weird square brackets around the backslash and question mark is that in regex they are special characters and need to be escaped. I found this the most readable solution.


repeat with storyIndex from 1 to countStories()
	set theStory to getStoryContents(storyIndex)
	set priceOffsets to AST find regex "[\\][?][\\][?][\\][?],[\\][?][\\][?] €" in string theStory with returning offsets
	if priceOffsets is not missing value then
		-- reverse the list, edit from end to front so the offsets stays correct
		set priceOffsets to reverse of priceOffsets
		repeat with priceRange in priceOffsets
			set foundString to getRangeOfStory(storyIndex, rm_so of priceRange, rm_eo of priceRange)
			--additional security check
			if foundString is not "\\?\\?\\?,\\?\\? €" then
				error "Text or document has been changed while running this script. Cannot continue and will not replace the price." number -1
			end if
			setRangeOfStory(storyIndex, rm_so of priceRange, rm_eo of priceRange, "000,00 €")
		end repeat
	end if
end repeat

on countStories()
	tell application "QuarkXPress"
		tell document 1 to return count of stories
	end tell
end countStories

on getStoryContents(i)
	tell application "QuarkXPress"
		tell document 1 to return contents of story i
	end tell
end getStoryContents

on setRangeOfStory(i, s, e, t) -- index, start range, end range, text
	tell application "QuarkXPress"
		tell document 1 to set text of characters s thru e of story i to t
	end tell
end setRangeOfStory

on getRangeOfStory(i, s, e) -- index, start range, end range
	tell application "QuarkXPress"
		tell document 1 to return characters s thru e of story i
	end tell
end getRangeOfStory

Wow! Thanks a lot. Very thankful for that DJ Bazzie Wazzie

This is working with 1 open qxp document, but with a little change. As it is possible to use regex, I changed the line like below:

set priceOffsets to AST find regex "[0-9]{1,3},[0-9]{2} €" in string theStory with returning offsets

And I removed the security check, as the found string is something else.

Maybe I described it wrong before ? is a placeholder cmd in QuarkXPress find & replace for any single character. This means if you do a find in QuarkXPress with ???,?? it will find any sequence like 123,45 or abc,de or a1b,2c.

Now using regex is much better and preferred by me.

Now I tried to combine it with the ability to make it work with a couple qxp documents, which should be saved and closed afterwards like in my initial applescript (see first post).

No luck so far, I get the open folder dialogue, but then it should open 1 qxp document after another and change the prices, saving and closing the document.

My tries end with an syntax error:

“end” was expected, but “on” was found

I am surely do something wrong by combining those functions. :rolleyes:

Here is what i tried:

--choose directory containing quark files
tell application "Finder"
	activate
	set source_folder to choose folder with prompt "Select folder:"
	set source_files to (files of entire contents of source_folder whose file type is "XPRJ")
	set source_files to sort source_files by name
	set source_files to (sort source_files by name) as alias list
end tell

--loop through quark documents

repeat with storyIndex from 1 to countStories()
	set theStory to getStoryContents(storyIndex)
	set priceOffsets to AST find regex "[0-9]{1,3},[0-9]{2} €" in string theStory with returning offsets
	if priceOffsets is not missing value then
		-- reverse the list, edit from end to front so the offsets stays correct
		set priceOffsets to reverse of priceOffsets
		repeat with priceRange in priceOffsets
			set foundString to getRangeOfStory(storyIndex, rm_so of priceRange, rm_eo of priceRange)
			setRangeOfStory(storyIndex, rm_so of priceRange, rm_eo of priceRange, "000,00 €")
		end repeat
	end if
end repeat

tell application "QuarkXPress"
	activate
	repeat with i from 1 to length of source_files
		set quarkfile to item i of source_files
		open quarkfile as alias use doc prefs yes remap fonts no without reflow

on countStories()
	tell application "QuarkXPress"
		tell document 1 to return count of stories
	end tell
end countStories

on getStoryContents(i)
	tell application "QuarkXPress"
		tell document 1 to return contents of story i
	end tell
end getStoryContents

on setRangeOfStory(i, s, e, t) -- index, start range, end range, text
	tell application "QuarkXPress"
		tell document 1 to set text of characters s thru e of story i to t
	end tell
end setRangeOfStory

on getRangeOfStory(i, s, e) -- index, start range, end range
	tell application "QuarkXPress"
		tell document 1 to return characters s thru e of story i
	end tell
end getRangeOfStory

		save document 1
		close document 1 saving no
	end repeat
end tell

I can only thank for any help and especially for your help DJ Bazzie Wazzie. :smiley: :smiley:

You cannot put handlers in the middle of the script, things will go wrong. Routines should be stored seperately from the script so they can be called by the root (read: implicit run handler) code.

set theFolder to (choose folder) as string
set theFiles to paragraphs of (do shell script "ls " & quoted form of POSIX path of theFolder)
repeat with i from 1 to count theFiles
	repeat 1 times --to simulate continue
		set theFile to theFolder & item i of theFiles as string
		if theFile does not end with ".qxp" then
			exit repeat -- simulate a continue command
		end if
		openFile(theFile)
		-- if number of document = 0 the file isn't opened
		if nrOfOpenDocuments() is 0 then
			exit repeat -- simulate a continue command
		end if
		processOpenDocument()
		closeAndSaveDocument()
	end repeat
end repeat

on processOpenDocument()
	repeat with storyIndex from 1 to countStories()
		set theStory to getStoryContents(storyIndex)
		set priceOffsets to AST find regex "[\\][?][\\][?][\\][?],[\\][?][\\][?] €" in string theStory with returning offsets
		if priceOffsets is not missing value then
			-- reverse the list, edit from end to front so the offsets stays correct
			set priceOffsets to reverse of priceOffsets
			repeat with priceRange in priceOffsets
				set foundString to getRangeOfStory(storyIndex, rm_so of priceRange, rm_eo of priceRange)
				--additional security check
				if foundString is not "\\?\\?\\?,\\?\\? €" then
					error "Text or document has been changed while running this script. Cannot continue and will not replace the price." number -1
				end if
				setRangeOfStory(storyIndex, rm_so of priceRange, rm_eo of priceRange, "000,00 €")
			end repeat
		end if
	end repeat
end processOpenDocument


-------------------------------------------------------------
-- QXP HANDLERS
-------------------------------------------------------------

on nrOfOpenDocuments()
	tell application "QuarkXPress" to return count of documents
end nrOfOpenDocuments

on openFile(thePath)
	tell application "QuarkXPress" to open alias thePath with Suppress All Warnings
end openFile

on closeAndSaveDocument()
	tell application "QuarkXPress" to close document 1 with saving
end closeAndSaveDocument

on countStories()
	tell application "QuarkXPress"
		tell document 1 to return count of stories
	end tell
end countStories

on getStoryContents(i)
	tell application "QuarkXPress"
		tell document 1 to return contents of story i
	end tell
end getStoryContents

on setRangeOfStory(i, s, e, t) -- index, start range, end range, text
	tell application "QuarkXPress"
		tell document 1 to set text of characters s thru e of story i to t
	end tell
end setRangeOfStory

on getRangeOfStory(i, s, e) -- index, start range, end range
	tell application "QuarkXPress"
		tell document 1 to return characters s thru e of story i
	end tell
end getRangeOfStory

-------------------------------------------------------------
-- MISCELLANEOUS HANDLERS
-------------------------------------------------------------
on getPathsFromFolder()
	set rawData to do shell script "ls " & quoted form of POSIX path of inPath
	set theFileNames to AST find regex "([^" & return & "]+)($|" & return & ")" in string rawData regex group 2
	
	repeat with x from 1 to count theFileNames
		set item x of theFileNames to inPath & item x of theFileNames
	end repeat
	return theFileNames
end getPathsFromFolder

I get it … sorry for that. Functions and Routines called by the main should be declared outside :rolleyes:

I get an error message:

error “QuarkXPress” … Invalid filename for “some object” number -37


on openFile(thePath)
	tell application "QuarkXPress" to open alias thePath with Suppress All Warnings
end openFile

The Path looks like this:

Macintosh HD:Users:Shared:Test QXP:TEST.qxd/Users/Shared/Test QXP/TEST.qxd

theFolder = Macintosh HD:Users:Shared:Test QXP:TEST.qxd
theFiles = /Users/Shared/Test QXP/TEST.qxd

I can choose only a file not the folder at the beginning of the script. And QuarkXPress will only open the file if the path is like of “theFolder”. The raeson could be the missing volume name in the “theFiles” variable.

If I change this:


set theFile to theFolder & item i of theFiles as string

to this


set theFile to theFolder as string

QuarkXpress will open the document, but nothing happens. No replacement to “000,00 €” will be processed.

This worked without the file handler perfectly, but now it doesn’t. And the file handler doesn’t process a folder instead it process a single file.

I appreciate your help so far DJ Bazzie Wazzie and I have a lot to learn and learned already some things :wink:

But I don’t want to try your patience more than I already have done. As you already recognized I am not really used to and experienced in programming applescript, therefore I can’t express my gratitude enough for your help you are giving me.

Thanks a lot DJ Bazzie Wazzie.

I try to figure out how I can batch a folder with “.qxd” files and get your script running on them like it works before with an already opened document.

You are mixing the Hfs and the Unix versions of the same path.

Macintosh HD:Users:Shared:Test QXP:TEST.qxd is an Hfs path
/Users/Shared/Test QXP/TEST.qxd is the same path in Unix/Posix format

If you run :


POSIX Path of "Macintosh HD:Users:Shared:Test QXP:TEST.qxd"

you will get :
/Users/Shared/Test QXP/TEST.qxd

I don’t know how you built the variable thePath but it’s this piece of code which must be corrected.

Yvan KOENIG (VALLAURIS, France) vendredi 24 juillet 2015 20:58:05

Hi Yvan Koenig

here is how the variable thePath is built:


set theFolder to (choose file) as string
set theFiles to paragraphs of (do shell script "ls " & quoted form of POSIX path of theFolder)
repeat with i from 1 to count theFiles
   repeat 1 times --to simulate continue
      set theFile to theFolder & item i of theFiles as string
      if theFile does not end with ".qxd" then
         exit repeat -- simulate a continue command
      end if
      openFile(theFile)
      -- if number of document = 0 the file isn't opened
      if nrOfOpenDocuments() is not 0 then
         exit repeat -- simulate a continue command
      end if
        processOpenDocument()
        closeAndSaveDocument()
   end repeat
end repeat

Yes, the paths are mixing up. I need to figure out how I can achieve that I can open a folder and every .qxd file in that folder will be processed by the script to find & replace prices in QuarkXPress.

The Script is mostly what DJ Bazzie Wazzie posted. Some minor changes are made by me, but not something essential.

Thanks for your help and any help everyone can give!

I must apologize, I missed the fact that the caller code was available in your original message.

You have two problems:

Here is a subset of your code .

set theFolder to (choose file) as string # define the Hfs+ path of a FILE, not a folder
log result --> (*SSD 500:Users:<userAccount>:Desktop:Import:LEVELS.csv*)
set theFiles to paragraphs of (do shell script "ls " & quoted form of POSIX path of theFolder)
log result --> (*/Users/<userAccount>/Desktop/Import/LEVELS.csv*)
repeat with i from 1 to count theFiles
	repeat 1 times --to simulate continue
		set theFile to theFolder & item i of theFiles as string # concatenate the Hfs+ path with an Unix one.
		log result --> (*SSD 500:Users:<userAccount>:Desktop:Import:LEVELS.csv/Users/<userAccount>/Desktop/Import/
	end repeat
end repeat

As you see, the first instruction supposed to define a folder is calling choose file which select a file, not a folder.
When you trigger theFolder to list its content, as it’s not a folder but a file, you get its own Unix/Posix version

Then, the instruction supposed to concatenate an Hfs+ folder and a file name does exactly what you really ask it to do and concatenate the Hfs+ path and its Unix version.

Edit the first instruction as :

set theFolder to (choose folder) as string # define the Hfs+ path of a FOLDER
log result --> (*SSD 500:Users:<userAccount>:Desktop:Import:*)
set theFiles to paragraphs of (do shell script "ls " & quoted form of POSIX path of theFolder)
log result --> (*CHANNELS.csv, FIXTURES.csv, LEVELS.csv, SHOWCONTROL.csv, TARGETS.csv*)
repeat with i from 1 to count theFiles
	repeat 1 times --to simulate continue
		set theFile to theFolder & item i of theFiles as string # concatenate the Hfs+ path with a filename.
		log result --> (*SSD 500:Users:<userAccount>:Desktop:Import:CHANNELS.csv*)
		(*SSD 500:Users:<userAccount>:Desktop:Import:FIXTURES.csv*)
		(*SSD 500:Users:<userAccount>:Desktop:Import:LEVELS.csv*)
		(*SSD 500:Users:<userAccount>:Desktop:Import:SHOWCONTROL.csv*)
		(*SSD 500:Users:<userAccount>:Desktop:Import:TARGETS.csv*)
	end repeat
end repeat

Now it works.

Yvan KOENIG running Yosemite 10.10.4 (VALLAURIS, France) samedi 25 juillet 2015 10:08:55

Thanks Yvan,

Update my code above changed choose file to choose folder , the only part of the code that I haven’t tested before posting :facepalm:

I’ll hope everything works fine now.

Sorry for the late response.

Thanks to both of you… really big thank you! :smiley:

It now works to open all .qxd-files in a chosen folder, but for some reason the routines processOpenDocument & closeAndSaveDocument will be not processed.

You may correct me, but if I understand the script so far it should open a quark document replace the prices and finally save and close it. Then it should process the next document in the chosen folder.

Is that right so far?

If yes, than it really seems that the above mentioned routines are not processed as it opens one after the other document right away.

I tried to delay it, but it doesn’t matter as for some reason the 2 routines are not processed at all. I don’t understand it, because the replace routine is working, we already tested it before.

Can you try it again?

I tried it. Now it seems that the replace routine is working but when it comes to the closeAndSaveDocument routine I get this error:

error “ž{}” doesn’t match the parameters ž{thePath}" for žcloseAndSaveDocument"." number -1721 partial result «handler closeAndSaveDocument» from {}

Found the problem… see my last post!!!

@ DJ Bazzie Wazzie

Hello

Some times ago I received a script from somebody asking for help.

I was really puzzled because it was built this way :



my handler1()

on handler1()
# some code
end handler1

my handler2()

on handler2()
# some code
end handler2

my handler3()

on handler3()
# some code
end handler3


My first thought was: it’s foolish, it can’t work.
But, as you know I’m curious and pig-headed so I opened the script in the editor and clicked the Run button.

What a surprise, everything worked well.

Of course, as new features were asked, I rebuilt the code the more standard way but I’m always wondering how this odd structure was able to work.

Yvan KOENIG running Yosemite 10.10.4 in French (VALLAURIS, France) lundi 27 juillet 2015 17:34:02

Sorry my fault :frowning: I had to look into it a bit further so it catches my eye.


closeAndSaveDocument()

Correct:


closeAndSaveDocument(theFile)

Here is the fully working script:


set theFolder to (choose folder) as string
set theFiles to paragraphs of (do shell script "ls " & quoted form of POSIX path of theFolder)
repeat with i from 1 to count theFiles
   repeat 1 times --to simulate continue
       set theFile to theFolder & item i of theFiles as string
       if theFile does not end with ".qxd" then
           exit repeat -- simulate a continue command
       end if
       openFile(theFile)
       -- if number of document = 0 the file isn't opened
       if nrOfOpenDocuments() is 0 then
           exit repeat -- simulate a continue command
       end if
       processOpenDocument()
       closeAndSaveDocument(theFile)
   end repeat
end repeat

on processOpenDocument()
   repeat with storyIndex from 1 to countStories()
       set theStory to getStoryContents(storyIndex)
       set priceOffsets to AST find regex "[0-9]{1,3},[0-9]{2} €" in string theStory with returning offsets
       if priceOffsets is not missing value then
           -- reverse the list, edit from end to front so the offsets stays correct
           set priceOffsets to reverse of priceOffsets
           repeat with priceRange in priceOffsets
               set foundString to getRangeOfStory(storyIndex, rm_so of priceRange, rm_eo of priceRange)
               setRangeOfStory(storyIndex, rm_so of priceRange, rm_eo of priceRange, "000,00 €")
           end repeat
       end if
   end repeat
end processOpenDocument


-------------------------------------------------------------
-- QXP HANDLERS
-------------------------------------------------------------

on nrOfOpenDocuments()
   tell application "QuarkXPress" to return count of documents
end nrOfOpenDocuments

on openFile(thePath)
   tell application "QuarkXPress" to open alias thePath with Suppress All Warnings
end openFile

on closeAndSaveDocument(thePath)
   tell application "QuarkXPress" to close document 1 with saving
end closeAndSaveDocument

on countStories()
   tell application "QuarkXPress"
       tell document 1 to return count of stories
   end tell
end countStories

on getStoryContents(i)
   tell application "QuarkXPress"
       tell document 1 to return contents of story i
   end tell
end getStoryContents

on setRangeOfStory(i, s, e, t) -- index, start range, end range, text
   tell application "QuarkXPress"
       tell document 1 to set text of characters s thru e of story i to t
   end tell
end setRangeOfStory

on getRangeOfStory(i, s, e) -- index, start range, end range
   tell application "QuarkXPress"
       tell document 1 to return characters s thru e of story i
   end tell
end getRangeOfStory

-------------------------------------------------------------
-- MISCELLANEOUS HANDLERS
-------------------------------------------------------------
on getPathsFromFolder()
   set rawData to do shell script "ls " & quoted form of POSIX path of inPath
   set theFileNames to AST find regex "([^" & return & "]+)($|" & return & ")" in string rawData regex group 2
   
   repeat with x from 1 to count theFileNames
       set item x of theFileNames to inPath & item x of theFileNames
   end repeat
   return theFileNames
end getPathsFromFolder

Both of you a really BIG THANKS :smiley: :smiley: :smiley:

I hopefully get the time and more opportunities to do some applescript programming to get more experience to give back the help I received here!

I really, really appreciate your help.

THANK YOU! Your Great! :slight_smile:

Thanks for the feedback, it was a pleasure.

Yvan KOENIG running Yosemite 10.10.4 in French (VALLAURIS, France) lundi 27 juillet 2015 21:18:55

I am so sorry, but I need your help or hint (again).

I saw that in few of the QuarkXPress documents prices are not in text boxes instead the texts are in tables. The script we put together works perfectly with text boxes but not all with tables. :frowning:

I used a script to get sure that these all tables in the document:


tell application "QuarkXPress"
	get every table box of document 1
	--> {}  
end tell

Here is an excerpt out of it:

I need either a second script for tables or I need to expand the script with the ability to find and replace the prices in tables.

Could you please help me again by this? I take what ever help you are willing to give me!

Thanks in advance.