Applying style to paragraphs below found text until next style
- ScottinPollock
- Posts: 36
- Joined: 2017-09-11 08:16:47
Applying style to paragraphs below found text until next style
Hi everybody...
So I now need to grok the Macro thing in order to do some long document formatting, and I must admit I am finding it a little obtuse (at least compared to the Classic Mac days of Nisus).
I want to find each occurence of the string "Result Codes" in the document;
then change the style of the following line to a paragraph style "Result Codes";
continue doing this with each following line until the I run into a line with paragraph style "Header 2"
Any help greatly appreciated!
-SiP
So I now need to grok the Macro thing in order to do some long document formatting, and I must admit I am finding it a little obtuse (at least compared to the Classic Mac days of Nisus).
I want to find each occurence of the string "Result Codes" in the document;
then change the style of the following line to a paragraph style "Result Codes";
continue doing this with each following line until the I run into a line with paragraph style "Header 2"
Any help greatly appreciated!
-SiP
Re: Applying style to paragraphs below found text until next style
Hello Scott,
let me see if I understand what you are trying to do. Your document has the structure:
Heading 2
stuff
Result codes
result
result
Heading 2
stuff
…
And I'm guessing that there is at most one paragraph with the string "Result Codes" per Heading-2 section. You want to apply a style to the results which form the tail end of each "Heading 2" section.
Before worrying about the macro language, you'll really need to find a suitable algorithm. There are basically two approaches.
First, a procedural "Turing machine" style approach would be to check each paragraph in the document. If the document contains the string "Result Codes" kick into second gear, and keep applying the style until you reach a "Heading 2"-styled paragraph. Then go back into first gear.
Second, a more holistic approach is to find all "Result Code" strings and all "Heading 2" paragraphs, and then match them up in pairs to find the stretches that contain results.
I'll add a reply for each of the two approaches.
let me see if I understand what you are trying to do. Your document has the structure:
Heading 2
stuff
Result codes
result
result
Heading 2
stuff
…
And I'm guessing that there is at most one paragraph with the string "Result Codes" per Heading-2 section. You want to apply a style to the results which form the tail end of each "Heading 2" section.
Before worrying about the macro language, you'll really need to find a suitable algorithm. There are basically two approaches.
First, a procedural "Turing machine" style approach would be to check each paragraph in the document. If the document contains the string "Result Codes" kick into second gear, and keep applying the style until you reach a "Heading 2"-styled paragraph. Then go back into first gear.
Second, a more holistic approach is to find all "Result Code" strings and all "Heading 2" paragraphs, and then match them up in pairs to find the stretches that contain results.
I'll add a reply for each of the two approaches.
philip
Re: Applying style to paragraphs below found text until next style
So let's try the procedural approach.
In this approach you could apply the style 'as you go', but I recommend against it. Instead you should create a selection that selects all the result sections at the same time. Then you can apply style as you want. If your document has any kind of size this will almost certainly be faster. In the code below, I'm only going to solve the selection problem.
So first we need all the paragraphs so we can go through them one-by-one. There are several ways to do this, but the easiest is to use Find. In NWML this will look like this:
To loop through all the paras we do:
To represent the two working states (the 'gear') we can use a boolean variable:
To check if the current paragraph contains the string "Result Codes" you can use the following:
And to check if the paragraph has the style "Heading 2" you will need to get the text attributes and check them:
My completed code looks like this:
In this approach you could apply the style 'as you go', but I recommend against it. Instead you should create a selection that selects all the result sections at the same time. Then you can apply style as you want. If your document has any kind of size this will almost certainly be faster. In the code below, I'm only going to solve the selection problem.
So first we need all the paragraphs so we can go through them one-by-one. There are several ways to do this, but the easiest is to use Find. In NWML this will look like this:
Code: Select all
$doc = Document.active
$paras = $doc.text.find('^.+\n','Ea')
Code: Select all
foreach $para in $paras
# do stuff
end
Code: Select all
$gear2 = @false
…
if $gear2
# now we're in gear 2
else
# now we're back in gear 1
end
Code: Select all
if $para.substring.find('Result Codes')
Code: Select all
$attr = $para.text.attributesAtIndex $para.location
if $attr.paragraphStyleName == 'Heading 2'
Code: Select all
$doc = Document.active
$gear2 = @false
$resultSels = Array.new
foreach $para in $doc.text.find('^.+\n','Ea')
if ! $gear2
if $para.substring.find('Result Codes')
$gear2 = @true
end
else
$attr = $para.text.attributesAtIndex $para.location
if $attr.paragraphStyleName == 'Heading 2'
$gear2 = @false
else
$resultSels.push $para
end
end
end
$doc.setSelection $resultSels
Last edited by phspaelti on 2018-07-18 23:00:59, edited 1 time in total.
philip
Re: Applying style to paragraphs below found text until next style
Ok, and now for the 'holistic' version.
This time we can just find the stuff we are actually looking for:
So this gives us two batches of text selections. The part we want to select is from end of any of the 'Result Code' selections to the beginning of the next "Heading 2" selection. So the 'Result Code' selections will be the driver of the loop.
There is one little sticking point. Presumably the last "Heading 2" section will not have a "Heading 2" paragraph as the delimiter (at the end). This means that we could run out of "Heading 2" selections. The easiest way to fix this is to add a selection that will always be at the end. That of course would be the end of the document. So I'll add an end of the document selection to the array of "Heading 2" selections. This can be done like this:
So now the matching up procedure. The "Heading 2" selections are in document order. So we'll take the first one from the queue. If this precedes our current "Result Codes" selection, then we need to get the next one. Finally when we have a match, we create the desired selection from the end (the bound) of the "Result Codes" selection to the beginning (the location) of the "Heading 2" selection.
Completed code is as follows:
This time we can just find the stuff we are actually looking for:
Code: Select all
$doc = Document.active
$text = $doc.text
$h2Style = $doc.styleWithName 'Heading 2'
$resultCodeSels = $text.findAll '^.*Result Codes.*\n', 'Ea'
$h2Sels = $text.findAll $h2Style
There is one little sticking point. Presumably the last "Heading 2" section will not have a "Heading 2" paragraph as the delimiter (at the end). This means that we could run out of "Heading 2" selections. The easiest way to fix this is to add a selection that will always be at the end. That of course would be the end of the document. So I'll add an end of the document selection to the array of "Heading 2" selections. This can be done like this:
Code: Select all
$endOfDocSel = TextSelection.new $text, Range.new($text.length, 0)
$h2Sels.push $endOfDocSel
Code: Select all
$resultSels = Array.new
$nextH2Sel = $h2Sels.dequeue
foreach $sel in $resultCodeSels
while $nextH2Sel.location < $sel.location
$nextH2Sel = $h2Sels.dequeue
end
$resultSels.push TextSelection.newWithLocationAndBound($text, $sel.bound, $nextH2Sel.location)
end
Code: Select all
$doc = Document.active
$text = $doc.text
$h2Style = $doc.styleWithName 'Heading 2'
$resultCodeSels = $text.findAll '^.*Result Codes.*\n', 'Ea'
$h2Sels = $text.findAll $h2Style
$h2Sels.push TextSelection.new($text,Range.new($text.length,0))
$resultSels = Array.new
$nextH2Sel = $h2Sels.dequeue
foreach $sel in $resultCodeSels
while $nextH2Sel.location < $sel.location
$nextH2Sel = $h2Sels.dequeue
end
$resultSels.push TextSelection.newWithLocationAndBound($text, $sel.bound, $nextH2Sel.location)
end
$doc.setSelection $resultSels
philip
- ScottinPollock
- Posts: 36
- Joined: 2017-09-11 08:16:47
Re: Applying style to paragraphs below found text until next style
Philip...
First I want to thank you for your time and expertise!
It appears this is one very granular language. I am really surprised there is no simple function or property to return the lines of a document, but I see what your doing. It actually is putting range descriptors in the array.
This logic certainly works and is understood. However it was not what I set out to do which was:
So thank you again, you have taught me much about selections in NWPM, but one (possibly stupid) question remains: Your code makes the selections, and I can easily click on the style in the Tool Drawer to apply it, but how do I apply it in the macro? I see the .apply command, but have been through the manual twice and haven't come up with a clue on how to apply it.
First I want to thank you for your time and expertise!
Good... as it is the most readable (to me), even though the second method may be more efficient.
Code: Select all
$doc = Document.active
$paras = $doc.text.find('^.+\n','Ea')
Code: Select all
$gear2 = @false
$resultSels = Array.new
foreach $para in $doc.text.find('^.+\n','Ea')
if ! $gear2
if $para.substring.find('Result Codes')
$gear2 = @true
end
else
$attr = $para.text.attributesAtIndex $para.location
if $attr.paragraphStyleName == 'Heading 2'
$gear2 = @false
else
$resultSels.push $para
end
end
end
Code: Select all
loop through the doc looking for "Result Codes"
get the line number
then a nested loop to
add 1 to it
exit loop if the style of line it was "Header 2"
set the style of line it to "result codes"
end loop
end loop
Re: Applying style to paragraphs below found text until next style
I guess you're right in the sense that Nisus does not have any direct access to 'lines' (= paragraphs). Location in text is handled by indexes and ranges. There is a text command to get the range of a paragraph for any given location. But using that to retrieve paragraphs requires looping through the document as well.ScottinPollock wrote: ↑2018-07-19 06:22:33 It appears this is one very granular language. I am really surprised there is no simple function or property to return the lines of a document, but I see what your doing. It actually is putting range descriptors in the array.
But ultimately it's probably best to get away from the idea that files are processed line by line, and instead use Find (≈grep) which can directly return the ranges you need. If you prefer line-by-line processing you could always use Perl, but that doesn't work if you want to use styles.
Well there is still the simplest way which was already possible back in Nisus Classic: just write the name of the menu command on a lineSo thank you again, you have taught me much about selections in NWPM, but one (possibly stupid) question remains: Your code makes the selections, and I can easily click on the style in the Tool Drawer to apply it, but how do I apply it in the macro? I see the .apply command, but have been through the manual twice and haven't come up with a clue on how to apply it.
So assuming you have a style called "Result Style", you can write:
Code: Select all
Paragraph Style:Result Style
If you want to do this in macro language you have to first get the Style object. So that looks like this:
Code: Select all
$style = $doc.styleWithName 'Result Style'
$style.apply
Code: Select all
$doc.styleWithName('Result Style').apply
philip
- ScottinPollock
- Posts: 36
- Joined: 2017-09-11 08:16:47
Re: Applying style to paragraphs below found text until next style
Can I safely assume variables are released when the macro is finished?
And regarding the object model in play here, it would be nice to write that data to files on disk (to aid my learning curve here). As with:
Code: Select all
$doc.text.find('^.+\n','Ea')
Code: Select all
$thePranges = $doc.text.find('^.+\n','Ea')
File.writeDataToPath $thePranges,"/Users/ss/Desktop/ranges.txt"
Thanks again for all your help... this is a lot of fun and will take NWP to a whole new level for me.
Re: Applying style to paragraphs below found text until next style
Addendum:
I just remembered, Nisus does have a command to access paragraphs by number:
I rarely ever use or even look at those selection commands, but they do exist. The main problem with addressing paragraphs by number is that there is no real way to know the number of any given paragraph, e.g., the paragraph of the current selection. But there are also selection commands for Select Next Paragraph, Select Previous Paragraph and Select Relative Paragraph (using an offset).
So your original idea could be implemented like this:
I just remembered, Nisus does have a command to access paragraphs by number:
Code: Select all
Select Paragraph 5
So your original idea could be implemented like this:
Code: Select all
Select Document Start
While Find 'Result Codes', '-W'
while Select Next Paragraph
$sel = TextSelection.active
$attr = $sel.text.attributesAtIndex $sel.location
if $attr.paragraphStyleName == 'Heading 2'
break
end
Paragraph Style:Result Style
end
end
philip
- ScottinPollock
- Posts: 36
- Joined: 2017-09-11 08:16:47
Re: Applying style to paragraphs below found text until next style
Think I am seeing a bug here... not in your Macro but in NWP.
I realized I had some text after the result codes with a 'Heading 1' style as well. Each one was after an inserted page break. No problem I figure... so in your 'as you go' method I just changed:
to:
It missed every "Heading 1" after the page breaks. In fact, those paragraphs were not in the array created by $doc.text.find('^.+\n','Ea'). It wasn't until I put a CR in front of those header 1 paragraphs (after the page breaks) that text.find actually included them in the array. Weird! BTW I could easily reproduce this behavior in a new document.
On the other hand... The following works perfectly (although much slower):
I realized I had some text after the result codes with a 'Heading 1' style as well. Each one was after an inserted page break. No problem I figure... so in your 'as you go' method I just changed:
Code: Select all
if $attr.paragraphStyleName == "Heading 2"
$gear2 = @false
else
$resultSels.push $para
end
Code: Select all
if $attr.paragraphStyleName == "Heading 2"
$gear2 = @false
elseIf $attr.paragraphStyleName == "Heading 1"
$gear2 = @false
else
$resultSels.push $para
end
On the other hand... The following works perfectly (although much slower):
Code: Select all
Select Document Start
While Find 'Result Codes', '-W'
while Select Next Paragraph
$sel = TextSelection.active
$attr = $sel.text.attributesAtIndex $sel.location
if $attr.paragraphStyleName == 'Heading 2'
break
elseIf $attr.paragraphStyleName == 'Heading 1'
break
end
Paragraph Style:result codes it
end
end
Re: Applying style to paragraphs below found text until next style
I think this is a "feature", not a bug Page breaks are treated as text characters, and are not counted as start of paragraphs, though they allow different paragraph styles on either side. It's a bit weird, but, I believe, a result of Nisus' legacy treatment of page breaks.ScottinPollock wrote: ↑2018-07-19 10:13:47 Think I am seeing a bug here... not in your Macro but in NWP.
There even seems to be some confusion on Nisus Co.'s part about this, as the Find Wildcard "Any" is (?:.|\n|\f)+ where the '\f' seems to be superfluous.
But…
When you write:
Did you not have CRs before the page breaks? (That is where I would put them.)It missed every "Heading 1" after the page breaks. In fact, those paragraphs were not in the array created by $doc.text.find('^.+\n','Ea'). It wasn't until I put a CR in front of those header 1 paragraphs (after the page breaks) that text.find actually included them in the array.
So if you are in the habit of using page breaks without CRs you might want to use the find expression '(?<=^|\f)[^\f]+(?:\n|\f|$)' instead to locate your 'lines'.
I would suggest the following instead:I realized I had some text after the result codes with a 'Heading 1' style as well. Each one was after an inserted page break. No problem I figure... so in your 'as you go' method I just changed:
to:Code: Select all
if $attr.paragraphStyleName == "Heading 2" $gear2 = @false else $resultSels.push $para end
[snip]
Code: Select all
if $attr.paragraphStyleName.find("Heading \d",'E')
$gear2 = @false
else
$resultSels.push $para
end
NB: In this code too, you can use the .find('Heading') condition instead.On the other hand... The following works perfectly (although much slower):
Code: Select all
Select Document Start While Find 'Result Codes', '-W' while Select Next Paragraph $sel = TextSelection.active $attr = $sel.text.attributesAtIndex $sel.location if $attr.paragraphStyleName == 'Heading 2' break elseIf $attr.paragraphStyleName == 'Heading 1' break end Paragraph Style:result codes it end end
You're right. The Select Next Paragraph command treats text terminated by a PB as a paragraph. The above code is slow, because it does all the work via the visual interface (the "light show" of Classic days). It just doesn't look as flashy anymore, since computers are faster (and Nisus probably has some optimizations to suppress redrawing, or something). So in the end I would really recommend the 'holistic' method, though that too would require some adjustments for handling multiple Heading styles.
But just to summarize the break character ('\f') is treated as a text character in grep. So it's not a bug, and it isn't something Nisus can change.
philip
Re: Applying style to paragraphs below found text until next style
Anyhow here is the macro for that. The heading paragraph text selections are gathered one style at a time for each of the heading styles, and then all combined together. But since we need them in document order, the "trick" is to sort them.
Code: Select all
$doc = Document.active
$text = $doc.text
# Get all paragraphs containing the phrase 'Result Codes'
$resultCodeSels = $text.findAll '^.*Result Codes.*\n', 'Ea'
# Get all Heading paragraphs
$hSels = Array.new
foreach $style in $doc.paragraphStyles
if $style.name.find('Heading')
$hSels.appendValuesFromArray $text.findAll($style)
end
end
# Make sure they are in document order
$hSels.sort
# Add an end-of-doc delimiter
$hSels.push TextSelection.new($text,Range.new($text.length,0))
# Match each 'Result Codes' paragraph to a following heading
$resultSels = Array.new
$nextHSel = $hSels.dequeue
foreach $sel in $resultCodeSels
while $nextHSel.location < $sel.bound
$nextHSel = $hSels.dequeue
end
# Add a text selection from the end of the 'Result Codes' to the start of the next heading
$resultSels.push TextSelection.newWithLocationAndBound($text, $sel.bound, $nextHSel.location)
end
# Select all the bits
$doc.setSelection $resultSels
Last edited by phspaelti on 2018-07-20 18:48:14, edited 1 time in total.
philip
- ScottinPollock
- Posts: 36
- Joined: 2017-09-11 08:16:47
Re: Applying style to paragraphs below found text until next style
I insert the page breaks with the insertion point at the end of the line of the paragraph previous to the one that is to be the heading on the next page. Oddly enough, this inserts a blank paragraph above the heading on the next page (which I remove with a forward delete). This method shows a paragraph formatting icon next to the heading, where inserting the page break with the insertion point at the left of the heading text does not. I had assumed that if the paragraph formatting icon was displayed, I had a real paragraph there.phspaelti wrote: ↑2018-07-19 19:05:10 When you write:Did you not have CRs before the page breaks? (That is where I would put them.)It missed every "Heading 1" after the page breaks. In fact, those paragraphs were not in the array created by $doc.text.find('^.+\n','Ea'). It wasn't until I put a CR in front of those header 1 paragraphs (after the page breaks) that text.find actually included them in the array.
So while this may not technically be a bug, I find it pretty unexpected behavior. In a simple two page document with a page break, the only way $doc.text.find('^.+\n','Ea') will return ranges for all paragraphs with text is to include an empty CR before the PB, and another empty CR after the PB. There is just no way I can format my documents that way.
That throws an unexpected error when used in the form of $doc.text.find(?<=^|\f)[^\f]+(?:\n|\f|$). Obviously I am missing something.So if you are in the habit of using page breaks without CRs you might want to use the find expression '(?<=^|\f)[^\f]+(?:\n|\f|$)' instead to locate your 'lines'.
This aslo throws an error here on line 27 "The given bound (51492) is less than the range location (51505)."So in the end I would really recommend the 'holistic' method, though that too would require some adjustments for handling multiple Heading styles.
Anyhow here is the macro for that. The heading paragraph text selections are gathered one style at a time for each of the heading styles, and then all combined together. But since we need them in document order, the "trick" is to sort them.
Re: Applying style to paragraphs below found text until next style
What it seems you are doing is inserting the PB before the CR. Nisus is not 'inserting' a CR, it's just moving it to the next line. When you delete it, you end up with a single paragraph broken across two pages. Nisus, reasonably, but confusingly, allows you to apply separate paragraph styles to the two halves of the paragraph, and "Select Next Paragraph" treats them separately, but Find/grep does not.ScottinPollock wrote: ↑2018-07-20 09:48:17 I insert the page breaks with the insertion point at the end of the line of the paragraph previous to the one that is to be the heading on the next page. Oddly enough, this inserts a blank paragraph above the heading on the next page (which I remove with a forward delete). This method shows a paragraph formatting icon next to the heading, where inserting the page break with the insertion point at the left of the heading text does not. I had assumed that if the paragraph formatting icon was displayed, I had a real paragraph there.
That's what I would recommend…So while this may not technically be a bug, I find it pretty unexpected behavior. In a simple two page document with a page break, the only way $doc.text.find('^.+\n','Ea') will return ranges for all paragraphs with text is to include an empty CR before the PB, …
I don't see why you would need to do that. I would only use a CR after a PB if I needed extra space there (since Nisus 'gobbles up' the Space Before at the top of the page).…and another empty CR after the PB.
Don't get me wrong, I don't think this is great. This is simply the result of using ASCII 12 ("Form Feed") for PB, and it's a place where Nisus' way of doing things feels very 'legacy'. Already in the Classic days I remember having discussions about replacing this with CRs that can enforce a page break. Maybe someday…
Sorry if I was a bit unclear. The find expression will need to include the whole thing, including the single quotes. You can put this after the .find with or without parentheses:That throws an unexpected error when used in the form of $doc.text.find(?<=^|\f)[^\f]+(?:\n|\f|$). Obviously I am missing something.So if you are in the habit of using page breaks without CRs you might want to use the find expression '(?<=^|\f)[^\f]+(?:\n|\f|$)' instead to locate your 'lines'.
Code: Select all
$doc.text.find '(?<=^|\f)[^\f]+(?:\n|\f|$)', 'Ea'
Code: Select all
$doc.text.find('(?<=^|\f)[^\f]+(?:\n|\f|$)', 'Ea')
Clearly, there is something I am not understanding about the structure of your document. Apparently there is a place where the 'Code Results' paragraph and the 'Heading' style overlap. (By a total of 13 characters. I'm guessing that the "Results Code" paragraph has a Heading style applied.) You can get the macro to complete without error by changing the following code line:This aslo throws an error here on line 27 "The given bound (51492) is less than the range location (51505)."So in the end I would really recommend the 'holistic' method, though that too would require some adjustments for handling multiple Heading styles.
Anyhow here is the macro for that. The heading paragraph text selections are gathered one style at a time for each of the heading styles, and then all combined together. But since we need them in document order, the "trick" is to sort them.
Code: Select all
while $nextHSel.location < $sel.location
Code: Select all
while $nextHSel.location < $sel.bound
philip
- ScottinPollock
- Posts: 36
- Joined: 2017-09-11 08:16:47
Re: Applying style to paragraphs below found text until next style
Got it. So you're suggesting the PB be on its own line. Unfortunately I can't always do that (that extra CR can sometimes force it to the next page). It'd be nice to have a PB that didn't actually require any vertical space in the document.
Got it. Wasn't positive I needed the Ea (pretty sure I did), but didn't know how to pass it with this find string cause of two sets of parens... seems passing options is pretty flexible.Sorry if I was a bit unclear. The find expression will need to include the whole thing, including the single quotes. You can put this after the .find with or without parentheses:orCode: Select all
$doc.text.find '(?<=^|\f)[^\f]+(?:\n|\f|$)', 'Ea'
Code: Select all
$doc.text.find('(?<=^|\f)[^\f]+(?:\n|\f|$)', 'Ea')
Yup. The line with the string "Result Codes" has a style of "Heading 3".Clearly, there is something I am not understanding about the structure of your document. Apparently there is a place where the 'Code Results' paragraph and the 'Heading' style overlap. (By a total of 13 characters. I'm guessing that the "Results Code" paragraph has a Heading style applied.)
Thanks! It would be nice if the Macro guide had a few more real world syntax templates, but I think I have enough to cobble things together for any future needs. I don't need this kind of thing often, but with hundreds of pages documenting these routines that did not have that particular style applied, this was a real time saver (and a long overdue exercise).You can get the macro to complete without error by changing the following code
Thanks again for your help on this!
-SiP