Changing content of paragraph "on the fly"

Yes! It won’t be 5 chars, it will be most likely 50.
It is part of larger project. I am using Ubersicht to display things on my monitor in a window-less and dynamic manner. A kind of personalised “Notification” system.
I have put some part (including my Calendar, To do…) together. See here:

https://funkyimg.com/i/326KH.jpg

I understand that, but your sample script will put the same five characters for every long paragraph. Is that what you wanted?

@ldicroce

I guess that Shane Stanley try to tell you that the correct code would be :

set mytext to "Luciano" & linefeed & "Lucyano_Luciano"
set allParagraphs to paragraphs of mytext
repeat with aParagraph in allParagraphs
	if length of aParagraph > 10 then
		set contents of aParagraph to text 1 thru 5 of aParagraph -- was wrongly mytext
	end if
end repeat
set {TID, text item delimiters} to {text item delimiters, linefeed}
set theResult to allParagraphs as text
set text item delimiters to TID
theResult

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) dimanche 9 février 2020 10:19:37

Absolutely correct. Thanks Yvan.
I want to replace the first “x” characters of each (long) paragraph.

In that case you could use a regex search/replace:

use AppleScript version "2.5" -- macOS 10.11 or later
use framework "Foundation"
use scripting additions

set myText to "Luciano" & linefeed & "Lucyano_Luciano"
set myText to current application's NSString's stringWithString:myText
set myText to (myText's stringByReplacingOccurrencesOfString:"(.{5}).{6,}" withString:"$1" options:(current application's NSRegularExpressionSearch) range:{0, myText's |length|()}) as text

Thanks, even tough I do get everything the code does. But I will search for info on stringByReplacingOccurrencesOfString command.
Thanks !
L

I assumed that I understood what it did so I tried a more general syntax:

set beg to 5
set myString to "(.{" & beg & "}).{" & (beg + 1) & ",}"
set myText to (myText's stringByReplacingOccurrencesOfString:(myString) withString:"$1" options:(current application's NSRegularExpressionSearch) range:{0, myText's |length|()}) as text

Alas, it works with beg from 2 thru 7 but doesn’t with beg greater than 7.
What is wrong in my attempt ?

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) dimanche 9 février 2020 18:51:01

You need to define two variables, something like:

set maxLen to 10 -- alowable length
set trimLen to 5 -- length to trim to
set myString to "(.{" & trimLen & "}).{" & (maxLen - trimLen + 1) & ",}"
set myText to (myText's stringByReplacingOccurrencesOfString:(myString) withString:"$1" options:(current application's NSRegularExpressionSearch) range:{0, myText's |length|()}) as text

Thanks to all. Learning more and more …
L.

Thank you Shane.
Now it’s clear.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 10 février 2020 08:44:24

I didn’t expect this!!!

1000 repetition of this, takes 5 seconds on my MacBook (2015).

use AppleScript version "2.5" -- macOS 10.11 or later
use framework "Foundation"
use scripting additions
repeat 1000 times
	set myText to "Luciano" & linefeed & "Luciano_Luciano"
	set myText to current application's NSString's stringWithString:myText
	set myText to (myText's stringByReplacingOccurrencesOfString:"(.{5}).{6,}" withString:"$1" options:(current application's NSRegularExpressionSearch) range:{0, myText's |length|()}) as text
end repeat

While 100000 repetition of this takes 3 seconds. So, 2 order of magnitude faster !!
Is that true or I am doing something wrong …

repeat 100000 times
	set mytext to "Luciano" & linefeed & "Luciano_Luciano"
	set allParagraphs to paragraphs of mytext
	repeat with aParagraph in allParagraphs
		if length of aParagraph > 10 then
			set contents of aParagraph to text 1 thru 5 of aParagraph -- was wrongly mytext
		end if
	end repeat
	set {TID, text item delimiters} to {text item delimiters, linefeed}
	set theResult to allParagraphs as text
	set text item delimiters to TID
	theResult
end repeat

That sounds reasonable.

You’re only dealing with two paragraphs. I suspect you’ll see different results with 19,000, or even just a couple of thousand.

I ran the two scripts without modification in Script Geek and the timings on my computer were:

ApplescriptObjC - 1.54 seconds

Basic AppleScript - 1.36 seconds

To test something longer, I created a text file with 19000 paragraphs alternating the following:

I then ran the following scripts in Script Geek with only 1 repetition:

use AppleScript version "2.5" -- macOS 10.11 or later
use framework "Foundation"
use scripting additions

tell current application to set myText to read file "Save:Temp:TestFile.txt"

set myText to current application's NSString's stringWithString:myText
set myText to (myText's stringByReplacingOccurrencesOfString:"(.{5}).{6,}" withString:"$1" options:(current application's NSRegularExpressionSearch) range:{0, myText's |length|()}) as text
set myText to read file "Save:Temp:TestFile.txt"

set allParagraphs to paragraphs of myText
repeat with aParagraph in allParagraphs
	if length of aParagraph > 10 then
		set contents of aParagraph to text 1 thru 5 of aParagraph -- was wrongly mytext
	end if
end repeat
set {TID, text item delimiters} to {text item delimiters, linefeed}
set theResult to allParagraphs as text
set text item delimiters to TID

The results reported by Script Geek were:

ApplescriptObjC - 0.27 seconds
Basic AppleScript - 11.84 seconds

This surprised me, so I ran the both scripts in Script Editor. They worked as expected and the times reported by Script Geek seemed ballpark correct.

BTW, the following line only took 4 milliseconds to run:

set myText to read file "Save:Temp:TestFile.txt"

Thanks !
I got similar results with my new iMac. The results I reported earlier were done my my laptop which is older.
L.

I suspect that latter figure would come down a lot if you used the script object technique, but probably not to near that of the regex approach.

Thanks Shane for the response. I’m not familiar with the script-object technique and google was of little help. I did try utilizing the a-reference-to operator as suggested in the ASLG:


set myText to (paragraphs of (read file "Save:Temp:TestFile.txt"))
set myTextRef to a reference to myText

repeat with aParagraph in myTextRef
	if length of aParagraph > 10 then
		set contents of aParagraph to text 1 thru 5 of aParagraph -- was wrongly mytext
	end if
end repeat
set {TID, text item delimiters} to {text item delimiters, linefeed}
set theResult to myTextRef as text
set text item delimiters to TID
theResult

Utilizing the same text file as before, this script took 0.31 seconds in Script Geek, which is certainly surprising. Just to check, I ran this script in Script Editor, and it worked as expected and took less than a second to complete.

They’re essentially the same technique: the list object being iterated over is passed into the repeat loop by reference, which confers two things: 1. the contents of the list is never evaluated in its entirety as it would be normally. Instead each item is evaluated only as and when its data is retrieved; and 2. an object’s data passed by value has had a copy of its data made, that serves as a snapshot of what that data looked like at that point in time. As it’s a copy, it means that any changes one makes to these data do not affect the original source. However, data passed by reference doesn’t copy the data; it passes the script a pointer to the address at which the data resides. Therefore, when we access these data, they’ll always reflect the data in its most current state; and any changes we make will be to the original source data.

On top of this, access times for list items passed by reference in AppleScript are–as you’ve seen–much, much quicker. Here’s another way to achieve the same thing:

set f to "/tmp/text.poo"
set sentences to the paragraphs of (read f)

repeat with sentence in a reference to my sentences
		tell the sentence to if its length > 10 then ¬
				set the contents to text 1 thru 5
end repeat

set text item delimiters to linefeed
set textContent to the sentences as text
set eof of f to 0
write the textContent to f

It’s practically identical to your script, but instead of creating a new variable to hold a reference to the list, I just created a reference to it in the declaration of the repeat loop. Here, the reference was created by the word my, which would have been sufficient for speed gains had we not needed to alter the contents of the original list. To make changes, though, a reference to is necessary, or one can use a script object:

script 
		property path: "/tmp/text.poo"
		property list: paragraphs of (read path)
end script

tell the result
		repeat with chunk in a reference to its list
				tell the chunk to if its length > 10 then set the contents to text 1 thru 5
		end repeat

		set my text item delimiters to linefeed
		set textContent to its list as text
		set eof of (its path) to 0
		write the textContent to its path
end tell

I got my powers of 2 wrong when creating my text.poo file, which accidentally ended up containing 256,000 lines. It only took about three seconds to read from and write it back to the file.

On a minor note, I’ve observed virtually everyone on this site religiously saving the old tids and then restoring them back afterwards, which I get the sense is something being doing because everyone else is seen to do it. It really isn’t necessary. I think Shane’s gone over this before, but it’s a left-over mantra from back when scripts used to all run within a single AppleScript instance. Now, at least for Script Editor, Script Debugger, and any program calling out to osascript (Alfred, BTT, Keyboard Maestro), this is not the case. The one program I’m aware of (because I use it) that does run successive scripts in a single instance is FastScripts.

Regardless, I would say that a better Good Practice habit to get into would be to forget about storing and resetting them, but always be conscientious to explicitly set the tids immediately before any line in a script that splits text using text items, or joins list items through coercion to text. It uses fewer lines of code; is more readable; it ensures one always stays aware of when these text transformations are taking place; and it alleviates any need to worry about someone else’s script, and whether or not they could have “forgotten” to reset their tids, leaving one free to focus on taking care of their own code in front of them.

We’ve been down this one lots of times before, but let me add my two cents’ worth: I disagree. I think doing both is a safer habit. It’s just too easy, IMO, to use TIDs without realising it.

Thanks CK for the sample scripts and explanations.

I tested your scripts in Script Geek and retested my modification of Yvan’s script, and they all completed in about 0.31 seconds. The second script in your post was fastest but only by a few milliseconds.

When testing your scripts, I did have to preface the variables f and path with file. Otherwise they wouldn’t run. I also removed the two lines that wrote to the source file, just to compare like with like. With these changes, the textContent variable did return the expected results in Script Editor.

The second script in your post is of greatest interest, as it’s new to me and will give me something to study.