Page 1 of 1

Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-09-29 16:29:26
by jb
Hi,
I have a simple macro like this:

Code: Select all

Go to Next Comment
Copy
Go to Marked Text
Paste
This works fine, but of course it works for only one Comment at a time.

Edit: I had asked how to make this work for all Comments at once.

By stealing from http://www.nisus.com/forum/viewtopic.php?f=17&t=3955 I got what I was after.

This is what I ended up with:

Code: Select all

Select Document Start
$count = 0

While true
   # move to the next comment, detecting if no more comments
   $previousSelection = TextSelection.active
   Menu 'Go to Next Comment'
   $selection = TextSelection.active
   If $selection.range == $previousSelection.range
      Break
   End
   
Menu 'Copy'
Menu 'Go to Marked Text’
Menu ‘Paste’
End
It does what I wanted, but perhaps it's stupid. If so, do let me know.

Thanks.

Re: Move Comments to Marked Text (in one swell foop)

Posted: 2013-09-29 19:00:34
by phspaelti
jb wrote:Hi,
I’ve got a simple macro like this:

Code: Select all

Go to Next Comment
Copy
Go to Marked Text
Paste
This works fine, but of course it works for only one Comment at a time.
How can I turn this into a macro that will replace each instance of marked text with the contents of the Comment linked to it?
I guess it has to do with ‘While’ or ‘For’ or something, but I’ve never quite grasped how those work.

I suppose it's pretty simple ;-)
I guess, it depends on how you define simple…

One way to make a loop is indeed to use:

Code: Select all

while (condition)
…
end
So you need a condition that will be true until you are finished. Looking in the Nisus Macro Reference (in the Help menu) you will find the following under Comment Object.
Document.allComments which gives you an array with all the comments in the document. You could use this like this:

Code: Select all

$doc = Document.active
while $doc.allComments.count  > 0
…
end
So combining this with your code you get:

Code: Select all

$doc = Document.active
while $doc.allComments.count > 0
Go to Next Comment
Copy
Go to Marked Text
Paste
end
And this works.

There are more "elegant" ways to solve this problem, but elegance is in the eye of the beholder.

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-09-29 19:03:53
by phspaelti
For what it's worth here is my more terse version:

Code: Select all

$doc = Document.active
$comments = $doc.allComments

$docText = $doc.text
foreach $comment in reversed $comments
$docText.replaceInRange $comment.markedTextRange, $comment.text
end

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-09-30 03:46:57
by jb
Thank you two times, Philip.

It was this that I was trying to arrive at:

while $doc.allComments.count > 0

I'll see if I can figure out your more terse version, particularly 'in reversed'

Thanks again for your help.

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-09-30 07:08:42
by phspaelti
jb wrote:I'll see if I can figure out your more terse version, particularly 'in reversed'
A selection is basically a pair of numbers measuring the offset from the beginning of the file. If you edit an array of selections, then editing a previous one brings with it the danger of changing the location of following selections. To avoid this problem, it's best to work "back-to-front", so in reversed order.

Hope that helps.

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-09-30 15:19:44
by jb
Hi Philip,
Thanks for the explanation.
‘Reversed’ is very clever.

Not so long ago someone asked on the forum how to learn the Nisus macro language, and Martin replied that there’s not really a good way, aside from asking (!).
So I'm hoping that you—or Martin—will indulge me in asking a couple of basic questions.

As for the terse macro, I’m wondering about two things.

1) Are these two assignments necessary or do they just make things easier to read? Perhaps one can have too many periods?

$comments = $doc.allComments
$docText = $doc.text


2) It seems that there is a convention for the use of singular and plural, and I’m wondering just how far it goes. Your terse macro defines ‘$comments’ (pl) but then uses ‘$comment’ (sg) in the foreach statement. Of course this is perfectly understandable in English and it's not hard to see that we do want to distinguish singular and plural. But I didn't realize that the macro language was so intelligent. Can the same be done with, say, Notes, Bookmarks, CrossReferences, etc.? Perhaps there’s something about this in the Macro Reference, but I don’t find it.

Enough for now.
Thanks again.

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-09-30 17:01:02
by phspaelti
jb wrote: 1) Are these two assignments necessary or do they just make things easier to read? Perhaps one can have too many periods?

$comments = $doc.allComments
$docText = $doc.text
Yes, you could make the terse version even terser:

Code: Select all

$doc = Document.active

foreach $comment in reversed $doc.allComments
$doc.text.replaceInRange $comment.markedTextRange, $comment.text
end
The basic rule seems to be that the first assignment--the one that instantiates the main object--is necessary, but other assignments are for convenience. That's why all macros start with:

Code: Select all

$doc = Document.active
I tend to like to create as few variables as possible (and use periods instead), while the example macros from Nisussoft often have lots of assignments. There is something to be said for either approach.

jb wrote:2) It seems that there is a convention for the use of singular and plural, and I’m wondering just how far it goes. Your terse macro defines ‘$comments’ (pl) but then uses ‘$comment’ (sg) in the foreach statement. Of course this is perfectly understandable in English and it's not hard to see that we do want to distinguish singular and plural. But I didn't realize that the macro language was so intelligent. Can the same be done with, say, Notes, Bookmarks, CrossReferences, etc.? Perhaps there’s something about this in the Macro Reference, but I don’t find it.
Well in fact the macro language is not that intelligent. I could have written:

Code: Select all

$doc = Document.active
$comment = $doc.allComments
foreach $comments in reversed $comment
$doc.text.replaceInRange $comments.markedTextRange, $comments.text
end
and that would work just as well. But it would be harder to read.

The crucial thing about foreach is that the thing after in be an array variable. The name before the in is a loop variable; it exists only inside the loop construct, and you can use any name you like, but sensibly you want to use something that makes clear you are using one item from the array. The singular/plural scheme (and generally using plurals for array variables) is a pretty common set up, that is also used by Java programmers, etc.

And you can use this construct with any of the many array (of object) variables that the Nisus macro language has. So

Code: Select all

$doc = Document.active
$notes = $doc.allNotes
foreach $note in $notes
…

Code: Select all

$doc = Document.active
$bookmarks = $doc.allBookmarks
foreach $bookmark in $bookmarks
…
And in fact you can use the period method if you prefer:

Code: Select all

$doc = Document.active
foreach $bookmark in $doc.allBookmarks
…
Best
Philip

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-10-01 05:01:12
by jb
Philip,
This is really helpful.
I made a silly experiment concerning sing/pl to see that, as you wrote, you can use any name you like for the loop variable.

I changed ‘$comment’ to ‘$pear’ and it works just as well:

Code: Select all

$doc = Document.active
$comments = $doc.allComments
$docText = $doc.text
foreach $pear in reversed $comments
$docText.replaceInRange $pear.markedTextRange, $pear.text
end
It’s hard (for me) to switch off the familiar semantics (which is why I really shouldn’t use ‘pear’)—especially since sometimes (Document.active, allComments, text) ordinary meanings are used.

Next I’ll see if I can understand Range.

Thanks again.
James

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-10-01 14:14:22
by martin
jb wrote:It’s hard (for me) to switch off the familiar semantics (which is why I really shouldn’t use ‘pear’)—especially since sometimes (Document.active, allComments, text) ordinary meanings are used.
I think using appropriately named variables is very important, even if the macro language itself is oblivious to those you choose. It's hard to read and understand code that uses poor or illogical variable names.
Next I’ll see if I can understand Range.
It's a pretty simple object. It really only keeps track of two things: a location and a length, both stored as regular numbers. These numbers behave just as you'd expect, holding whatever value they were assigned upon creation, regardless of what happens in the future to the elements being described.

For example, in the text "Hi James" let's say we want to describe where the word "James" resides using a Range. If we take the location (index) of the first character "H" to be zero, then the letter "J" has a location of 3. The word "James" is 5 character long, so the range is: location=3, length=5, abbreviated for convenience as (3,5). You can get such a range in the macro language in a variety of ways:

Code: Select all

# construct range explicitly, using hard-coded literal values
$range = Range.new(3, 5)

# make the same range, but using a command that searches text:
$text = "Hi James!"
$range = $text.rangeOfString("James")
Either of those give you a range (3, 5).

The thing to remember is that the range's values do not change unless you explicitly tell them to. So even if we replace the word in the text, the range is still the same, eg:

Code: Select all

$text = "Hi James!"
$range = $text.rangeOfString("James")   # range is (3,5)
$text.findAndReplace("Hi", "Bye")       # word "James" is now at location of 4
# the variable $range is still (3,5) even though the word "James" has shifted location
So we can see that a Range is just a pair of plain-old numbers understood to have a specific meaning. Remember that ranges can describe any kind of elements: characters in text, rows in a table, people in a line, etc. From the macro language reference:
A Range object describes a contiguous block of elements in some larger context. For example, a range of characters is a block of characters that are all adjacent to one another in the same document. However, such a range only describes the location of these character, it does not contain the actual characters.

A range can describe any kind of element, such as indexes in an array or rows in a table. The type of element being described by a range is irrelevant, the only properties a range maintains are the indexes (integers) of the elements in the larger context. This means that you can use the exact same range object to access different types of elements if you like.
Hopefully that makes some sense.

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-10-02 15:22:59
by jb
Thank you, Martin. That’s very helpful. And yes it makes sense!

I saw the .markedText property for Comment Objects, so I thought that I should be able simply to ‘replace’ $comment.markedText with $comment.text. But apparently the only way to do that is with the .replaceInRange command, since there is no .replace command available for a Text object. I’m sure there’s a very good reason for that.
Also, I didn’t realize that there are no commands for Comment Objects.
Which objects do take commands, I wonder. I’ll go see if I can figure it out.
Thanks,
James

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-10-02 15:44:18
by martin
jb wrote:But apparently the only way to do that is with the .replaceInRange command, since there is no .replace command available for a Text object. I’m sure there’s a very good reason for that.
Not necessarily! I mean, which commands are available isn't arbitrary, but they are added based on priority/need. Most of the time with text you're going to be replacing some part of a text object, hence the "replaceInRange" command. This hypothetical "replace" command would (I assume) replace the contents of the entire text object, which is needed much less often. That's still possible using the "replaceInRange" command, just a little more work, so the need for the wholesale replacement command is low.

If you ever feel there's a gap, or something that's too inconvenient to code, please let us know and we can evaluate the need for an additional macro command(s) to make some task easier for macro authors.
Also, I didn’t realize that there are no commands for Comment Objects.
Which objects do take commands, I wonder. I’ll go see if I can figure it out.
While it's true that the Comment object does not technically have any commands, there are plenty of properties to play with that allow a macro to manipulate the comment. What commands would you want a comment object to respond to? Or perhaps the better question: what would you want a macro to be able to do with comments that is currently not possible?

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-10-02 17:26:41
by phspaelti
jb wrote:I saw the .markedText property for Comment Objects, so I thought that I should be able simply to ‘replace’ $comment.markedText with $comment.text.
The thing here is you have to understand what is meant by a text object. $comment.markedText refers to the text object to which the text is attached. And text object means the whole text object. Try this:

Code: Select all

$doc = Document.active
$comment = $doc.allComments[0]
prompt $comment.markedText
As you'll see, what you get is the whole text of the entire document. In fact it's really the whole object--all the footnotes, tables, styles, comments, bookmarks, etc. If you replaced that with $comment.text you wouldn't have much of a document left.

This takes some getting used to, but a selection object in Nisus macro language consists of two things (1) a range and (2) a text object. The text object is always the whole thing and the range is in relation to that object and describes the part out of it that is selected.
Anyhow, that is why you have to "replaceInRange".

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-10-02 17:35:36
by phspaelti
And something Martin said earlier:
martin wrote:
jb wrote:It’s hard (for me) to switch off the familiar semantics (which is why I really shouldn’t use ‘pear’)—especially since sometimes (Document.active, allComments, text) ordinary meanings are used.
I think using appropriately named variables is very important, even if the macro language itself is oblivious to those you choose. It's hard to read and understand code that uses poor or illogical variable names.
I completely agree. But it is also a worthwhile experiment to sometimes use different variable names, just to at least confirm to yourself what is crucial. The object types and property names are determined by Nisus and have to be used as given, but the variable names are determined by you. They should be chosen in a sensible manner that helps you keep track of what you are doing.

Re: Move Comments to Marked Text (in one swell foop)—Solved

Posted: 2013-10-03 06:07:40
by jb
@Martin
Thanks for this, too.
But you give me too much credit ;-)
I intended to indicate only my ignorance. My presumption that I could invoke a command to act on the Comment Object ‘directly’ was just a sign of how little I understand. Approaching the Comment Object as part of the text makes perfect sense. And at the moment there’s nothing I want/need that the macro language can’t do.
Now that I see that — as Philip’s macro makes clear — the text object is the whole text, it’s easy to see that the ‘replace’ command would replace the entire text. Not so handy.
I’m looking into which objects take commands and which don’t. It's not so simple (for me) to figure out/understand. More on that later.

@Philip
Thanks a lot for this macro. It's great for a simpleton like me to have this kind of demonstration.

James