Page 1 of 1

List with TOC entries

Posted: 2016-07-19 09:48:33
by js
I wonder if somebody can help with the following:
Think of a document made of a series of notes. Each note may have one or several paragraphs. Each note is preceded by a heading, and the headings are made TOC entries.
With "Show TOC in Navigator" on, clicking the main text at any location will highlight the corresponding heading in the navigator. Is it possible to obtain the text of the heading through a macro?
A related question: Clicking the navigator, then doing Select All and Copy puts a list of all headings on the clipboard. Can that list be created by a macro, even if the insertion is in the main text?

Re: List with TOC entries

Posted: 2016-07-19 15:56:23
by phspaelti
For the first problem you can get the text of the paragraph of your current location with code like this:

Code: Select all

$sel = TextSelection.active
$paraRange = $sel.text.rangeOfParagraphAtIndex $sel.location
$paraText = $sel.text.subtextInRange $paraRange
prompt $paraText
For the second problem, I'm going to assume that the headings are marked with a style called "Heading". In that case code like this will give you an array with all the heading texts.

Code: Select all

$doc = Document.active
$headingStyle = $doc.styleWithName "Heading"
$headingSels = $doc.text.findAll $headingStyle
$headingTexts = $headingSels.arrayByMakingValuesDoCommand "subtext"
prompt $headingTexts
If you don't have a heading style, it's still possible to collect all the text marked for TOC via macro, but things are a bit more complicated.

Re: List with TOC entries

Posted: 2016-07-20 01:32:22
by adryan
G'day, js, Philip et al

As long as the cursor is not in a note heading, there is a supplied macro that will take you from anywhere in a note to its heading:–

Tools > Table of Contents > Select Previous in TOC.

Being selected, the text of the heading can then be copied or otherwise manipulated as required.

If the cursor is already in a note heading, the macro will take you to the heading of the previous note.

Cheers,
Adrian

Re: List with TOC entries

Posted: 2016-07-20 05:58:54
by phspaelti
Upon seeing adryan's answer, I realised that I misunderstood the (first) question. To do that you have several possibilities. You could use the command adryan mentions and save and restore the selection. If you want to do this directly via the macro language, you have to use a (backwards) "Kino loop". The code for that would look something like this:

Code: Select all

$doc = Document.active
$loc = $doc.textSelection.location
while $loc >= 0
    $range = $doc.text.rangeOfParagraphContentAtIndex($loc)
    $loc = $range.location
    $attrs = $doc.text.displayAttributesAtIndex $loc
    if $attrs.tocLevelForStyleName($doc.activeTocStyleName) == @undefined
        $loc -= 1
    else
        break
    end
end
prompt $doc.text.subtextInRange($range)
If all the TOC marked bits have the same style applied you could write a simpler macro using "Find".

Re: List with TOC entries

Posted: 2016-07-21 05:45:00
by js
Thank you very much for your help, phspaelti and adryan. May I ask two supplementary questions:

Referring to your macro:

Code: Select all

$doc = Document.active
$headingStyle = $doc.styleWithName "Heading"
$headingSels = $doc.text.findAll $headingStyle
$headingTexts = $headingSels.arrayByMakingValuesDoCommand "subtext"
prompt $headingTexts
The result (the $headingTexts) in your macro is shown in a prompt. If instead I send it to a new file, I get the text as entries of a list, separated by quotes and commas. Of course I can get rid of these by a search and replace. But isn‘t there a means of getting the Text without these separators directly?

Referring to your macro

Code: Select all

$doc = Document.active
$loc = $doc.textSelection.location
while $loc >= 0
    $range = $doc.text.rangeOfParagraphContentAtIndex($loc)
    $loc = $range.location
    $attrs = $doc.text.displayAttributesAtIndex $loc
    if $attrs.tocLevelForStyleName($doc.activeTocStyleName) == @undefined
        $loc -= 1
    else
        break
    end
end
prompt $doc.text.subtextInRange($range)
This works fine. My ultimate ambition goes a bit further: It is to do get the $headingTexts of ALL instances of a search expression in the document. Should I write a macro that reiterates the one you offered across the whole document, collecting the headings appearing in the macros as ($doc.text.subtextInRange($range) one by one. Or would one of you code warriors know how to get all instances right from the outset?

Re: List with TOC entries

Posted: 2016-07-21 06:23:20
by phspaelti
Hello js,
I ended the macro with a prompt simply for demonstration purposes. As mentioned the texts will be in a structure called an array. How you continue from there will depend on what you want to do.
As you found out, if you just print out a raw array, you will get something with lots of commas and quotes. You can join the bits of an array much more nicely like this:

Code: Select all

prompt $headingTexts.join('')
The join command will glue the bits together using whatever is between the quotes (In this case nothing.)
You can use this join command in several ways. If you want the texts in a new document, you can do it like this:

Code: Select all

Document.newWithText $headingTexts.join('')
Or you can put the texts on the clipboard like this:

Code: Select all

Write Clipboard $headingTexts.join('')
Instead of joining them in a list, you can process the texts one at a time like this:

Code: Select all

Foreach $headingText in $headingTexts
    … do some stuff …
end
Okay, now from your last comment, I take it you would prefer a macro that collects the TOC without having a known style that marks them. You can use the "Kino loop" technique to do that too.
That will look like this:

Code: Select all

$doc = Document.active
$headingTexts = Array.new
$loc = 0
while $loc < $doc.text.length
    $range = $doc.text.rangeOfParagraphAtIndex($loc)
    $attrs = $doc.text.displayAttributesAtIndex $loc
    if $attrs.tocLevelForStyleName($doc.activeTocStyleName)
        $headingTexts.push $doc.text.subtextInRange($range)
    end
    $loc = $range.bound
end
prompt $headingTexts.join('')
If we're gonna collect the TOC from the whole file, it makes more sense to work forward. Note that here I changed things back to using rangeOfParagraphAtIndex, so we'll get the returns for each TOC item too. If you don't want that, the code could be adjusted a bit.
Hope this helps

Re: List with TOC entries

Posted: 2016-07-21 11:56:26
by js
Thanks Philip for this renewed help, and explaining how to use .join(''). This is what I was hoping to get.

As to your new macro, it works well. But it does the same thing as the first one: If makes a list of all headings. That fine for finding a search word within the headings themselves. In fact if you a search in the main text, the headings which have that search word are selected by Nisus Writer, in the Navigator on the left. It‘s just that you cannot take them out. And that is what the first macro is for.

The second macro is for a different purpose: It should create a list of those headings the text of which has my search word, even if it is not contained in the heading itself. You provided a macro for doing this for a single item. That works well. But how about a complete list of those headings having my search word, even if not in the heading itself, but only in the text UNDER that heading. Sorry to sound complicated. But the aim is a very practical one: To show me all past „titles“ under which a certain search word figures.

Re: List with TOC entries

Posted: 2016-07-21 16:15:20
by phspaelti
js wrote:As to your new macro, it works well. But it does the same thing as the first one
Okay, so your TOC headings do all have the heading style. In that case those two macros will give the same result. In that case I'll stick with the "find $style" version, since it's simpler.

And now I finally got what you want, I think. You are starting with a non-contiguous selection, and you want all the closest preceding headings to each selected bit. I'm also going to assume that you want each heading only once, even if a note contains multiple selected bits (= find hits).

And yes, you could use the earlier macro (the "backward Kino loop") inside a loop like this:

Code: Select all

foreach $sel in $doc.textSelections
    … find the preceding heading …
end
But when you're working with macros, this kind of solution, while intuitive, is not really the best approach. The better approach is to do it like this:
  1. Get the selections
  2. Get the headings
  3. Walk through the two arrays in parallel, and keep advancing to match selections with preceding headings. Throw away any headings you don't need, and keep the ones you want
So the whole thing can be done with a single loop. It's not really that difficult, but this time I'm appending it as a finished macro. The macro assumes:
  • That you start with a non-contiguous selection already made
  • That your headings have the style name "Heading"
  • That you want the output in a new file
Finally note that this macro will almost certainly not work right if the selections include bits in tables, footnotes, headers, footers, comments, etc.

Re: List with TOC entries

Posted: 2016-07-22 05:30:19
by js
A double thank you to philip, one for the macro itself. It does exactly what I wanted. And one for his explanations, very helful to better understand the workings of Nisus macro language.