6

I need to typeset a critical edition of a small philosophical work.

Editorial and typesetting rules require to number sentences at the beginning of each paragraph.

That is easy for 1-sentence paragraphs. But for multi-sentence paragraphs I need to print the range.

How to incrementally number sentences in paragraphs as a range, like the sequential numbers in square brackets shown below?

Example


Talk on the Practical Reality

[1] Reality is at once a simple and a complicated word.

[2-3] For an ordinary, unaspiring human being, reality is a far cry. At every moment life offers him frustration.

[4-6] His life is the life of desire. When his desires are not fulfilled, he is frustrated. And when his desires are fulfilled, still he feels no abiding satisfaction; therefore, according to him everything is unreal.

(etc.)


MWE

I have a MWE, which only number paragraphs:

\documentclass{book}

\newcounter{para}
\newcommand\mypara{\par[\refstepcounter{para}\thepara] \space}

\begin{document}

\mypara (This should be [1]) Reality is at once a simple and a complicated word.

\mypara (This should be [2-3]) For an ordinary, unaspiring human being, reality is a far cry.  At every moment life offers him frustration.

\mypara (this should be [4-6]. His life is the life of desire.  When his desires are not fulfilled, he is frustrated. And when his desires are fulfilled, still he feels no abiding satisfaction; therefore, according to him everything is unreal.

\end{document}

Answers and packages (but insufficient)

I am aware of these answers:

But they do not seem to help my need, to number the range of sentences in paragraphs.

  • This answer may help, but with a quite complex macro:

How to count the number of words in a sentence, the number of sentences in a paragraph, and the number of paragraph?

Question

Is there a package, or a simpler macro, to help incrementally number sentences in paragraphs, as a range?

Thanks in advance.

6
  • Can't you count the sentences using one of those solutions and just use the count at the start and end of each paragraph? If you want the range in the margin, you may find it occasionally ends up on the wrong page. If you put it in the text, you shouldn't have that problem.
    – cfr
    Commented May 25 at 0:48
  • Thanks for the idea, but no, I need to follow given editorial and typesetting rules. Sentence numbers, including ranges, need to be at the beginning of each paragraph, like shown above.
    – gsl
    Commented May 25 at 5:49
  • I am sorry, I am not that fluent with LaTeX, I was unable to understand the suggestion and its implications. Would you consider writing an answer to a non-specialist like myself, perhaps with some code?
    – gsl
    Commented May 25 at 10:26
  • It was only an idea ;). If nobody else answers, I'll see what I can do, but I'm hoping somebody else answers. That is, somebody who knows what they're doing ....
    – cfr
    Commented May 25 at 17:10
  • 1
    I should say that I thought you wanted the sentences counted automatically. It is quite doable if you are prepared to mark the sentences. It's easy to handle the paragraphs automatically using the hooks the kernel now provides. And you can do it in any engine. (Which may not matter if you use LuaTeX anyhow.)
    – cfr
    Commented May 28 at 4:50

2 Answers 2

3

Here's a re-implementation of yannisl's answer which does not depend on Lua (so should work with any engine), avoids the need to start and end the environment for each paragraph and eliminates the need to explicitly capture the content of the paragraphs using the b specifier. Instead, the code is automatically added to the start and end of paragraphs.

Which is all well and good, but it is not for free. Everything has its price. In this case, the price is dependence on multiple compilations. You need at least two and sometimes three compilations for the numbering to be correct. This is because the numbers are written at the start of the paragraph, when only the first sentence's number is known for this run. To get the number of the last sentence, we record its value at the end of the paragraph and read it back on the next run. So, initially, the final sentences of all paragraphs will be zero and you will see things like '9-0' in the output. That is expected.

An alternative would be to capture the paragraph, but there are some downsides to doing that, so I decided not to complicate things.

\documentclass{article}
% ateb: https://tex.stackexchange.com/a/719068/ addaswyd o ateb yannisl: https://tex.stackexchange.com/a/719024/

\ExplSyntaxOn
\int_new:N \g_gsl_sentences_int
\int_new:N \g_gsl_pars_int
\property_new:nnnn { sinpar }{ now }{ 0 }{ \int_use:N \g_gsl_sentences_int }
\cs_new_protected:Npn \sn #1 \n {
  \int_gincr:N \g_gsl_sentences_int
  #1 ~
}

\NewDocumentEnvironment{segmentation} {}
{
  \hook_gput_code:nnn { para/begin } { gsl }
  {
    \int_gincr:N \g_gsl_pars_int
    \hbox_gset:Nn \g_para_indent_box { [\int_to_arabic:n {\g_gsl_sentences_int} -- \property_ref:ee { gsl@para@\int_to_arabic:n {\g_gsl_pars_int} } {sinpar} ] ~ }
  }
  \hook_gput_code:nnn { para/end } { gsl }
  {
    \property_record:ee { gsl@para@\int_to_arabic:n {\g_gsl_pars_int} } { sinpar } 
  }
}{
  \para_end:
  \hook_gremove_code:nn { para / begin } { gsl }
  \hook_gremove_code:nn { para / end } { gsl }
}

\ExplSyntaxOff
\begin{document}
\parindent0pt
\begin{segmentation}
  \sn Myfirst sentence.\n
  \sn My second sentence.\n
  \sn My third sentence.\n 

  \sn My fourth sentence.\n 
  \sn My fifth sentence.\n
  \sn His life is the life of desire.\n 
  \sn My fifth sentence.\n
  \sn His life is the life of desire.\n 

  \sn This is the next in line!\n
  \sn This is the next in line!\n
\end{segmentation}

Here's some regular text in sentences which aren't marked and in paragraphs which don't get the special treatment.

And here's another.
These sentences aren't marked or counted either.
That's because we finished the \texttt{segmentation} environment and haven't started another.

\begin{segmentation}
  \sn Here we go again.\n
  \sn Counting sentences one-by-one.\n
  
  \sn However, we don't have to add the environment explicitly.\n
  \sn So it is a bit more automatic.\n
\end{segmentation}
\thispagestyle{empty}
\end{document}

numbering sentences by paragraph

Note: the truth is, this was going to depend on Lua because I planned to just add the hooks wrapper around yannisl's code. But I know nothing about Lua and couldn't even figure out how to do that.

EDIT

This version offers ranges and/or sentence numbers on a per-environment or general basis and provides a key-value interface for customising the appearance of both.

\glsset{<list of key-values>} or an optional argument to the segmentation environment may be used for the customisation. All settings are local.

  • sentence numbers=<true|false> determines whether numbers are printed.
    • Default is true; initially false.
  • sentence ranges=<true|false> determines whether ranges are printed.
    • Default is true; initially true.
  • sentence number format={<code taking one argument>} determines the format of sentence numbers, where #1 is the sentence number.
  • sentence range format={<code taking two arguments>} determines the format of ranges, where #1 is the first sentence in the paragraph and #2 is the second.

Note that this will obviously be slightly slower than the less flexible code above.

\documentclass{article}
% ateb: https://tex.stackexchange.com/a/719068/ addaswyd o ateb yannisl: https://tex.stackexchange.com/a/719024/
\usepackage{xcolor}

\ExplSyntaxOn
\int_new:N \g_gsl_sentences_int
\int_new:N \g_gsl_pars_int
\property_new:nnnn { sinpar }{ now }{ 0 }{ \int_use:N \g_gsl_sentences_int }

\cs_new_protected_nopar:Nn \gsl_sentence_no_format:n
{
  \textsubscript{ \textcolor{gray}{ #1 } }
}
\cs_set_protected_nopar:Nn \gsl_sentence_range_format:nn
{
  [ #1 -- #2 ]
}

\keys_define:nn { gsl }
{
  sentence ~ number ~ format .code:n = {
    \cs_set_protected_nopar:Nn \gsl_sentence_no_format:n { #1 }
  },
  sentence ~ numbers .bool_set:N = \g_gsl_sentence_nos_bool,
  sentence ~ numbers .initial:n = false,
  sentence ~ numbers .default:n = true,
  sentence ~ range ~ format .code:n = {
    \cs_set_protected_nopar:Nn \gsl_sentence_range_format:nn { #1 }
  },
  sentence ~ ranges .bool_set:N = \g_gsl_sentence_ranges_bool,
  sentence ~ ranges .initial:n = true,
  sentence ~ ranges .default:n = true,
}

\cs_new_protected:Npn \sn #1 \n {
  \int_gincr:N \g_gsl_sentences_int
  #1 \bool_if:NT \g_gsl_sentence_nos_bool 
  {
    \gsl_sentence_no_format:n { \int_to_arabic:n {\g_gsl_sentences_int} } 
  } ~
}

\NewDocumentEnvironment{segmentation} { o }
{
  \IfValueT { #1 } 
  {
    \keys_set:nn { gsl } { #1 }
  }
  \bool_if:NT \g_gsl_sentence_ranges_bool
  {
    \hook_gput_code:nnn { para/begin } { gsl }
    {
      \int_gincr:N \g_gsl_pars_int
%       \hbox_gset:Nn \g_para_indent_box { [\int_to_arabic:n {\g_gsl_sentences_int} -- \property_ref:ee { gsl@para@\int_to_arabic:n {\g_gsl_pars_int} } {sinpar} ] ~ }
      \hbox_gset:Nn \g_para_indent_box { \gsl_sentence_range_format:nn { \int_to_arabic:n {\g_gsl_sentences_int} } { \property_ref:ee { gsl@para@\int_to_arabic:n {\g_gsl_pars_int} } {sinpar} } ~ }
    }
    \hook_gput_code:nnn { para/end } { gsl }
    {
      \property_record:ee { gsl@para@\int_to_arabic:n {\g_gsl_pars_int} } { sinpar } 
    }
  }
}{
  \para_end:
  \bool_if:NT \g_gsl_sentence_ranges_bool
  {
    \hook_gremove_code:nn { para / begin } { gsl }
    \hook_gremove_code:nn { para / end } { gsl }
  }
}

\NewDocumentCommand \gslset { m }
{
  \keys_set:nn { gsl } { #1 }
}

\ExplSyntaxOff
\pagestyle{empty}
\begin{document}
\begin{segmentation}
  \sn My first sentence.\n
  \sn My second sentence.\n
  \sn My third sentence.\n 

  \sn My fourth sentence.\n 
  \sn My fifth sentence.\n
  \sn His life is the life of desire.\n 
  \sn My fifth sentence.\n
  \sn His life is the life of desire.\n 

  \sn This is the next in line!\n
  \sn This is the next in line!\n
\end{segmentation}

Here's some regular text in sentences which aren't marked and in paragraphs which don't get the special treatment.

And here's another.
These sentences aren't marked or counted either.
That's because we finished the \texttt{segmentation} environment and haven't started another.

\begin{segmentation}
  \sn Here we go again.\n
  \sn Counting sentences one-by-one.\n
  
  \sn However, we don't have to add the environment explicitly.\n
  \sn So it is a bit more automatic.\n
\end{segmentation}

\begin{segmentation}[sentence ranges=false]
  \sn My first sentence.\n
  \sn My second sentence.\n
  \sn My third sentence.\n 

  \sn My fourth sentence.\n 
  \sn My fifth sentence.\n
  \sn His life is the life of desire.\n 
  \sn My fifth sentence.\n
  \sn His life is the life of desire.\n 

  \sn This is the next in line!\n
  \sn This is the next in line!\n
\end{segmentation}

Here's some regular text in sentences which aren't marked and in paragraphs which don't get the special treatment.

And here's another.
These sentences aren't marked or counted either.
That's because we finished the \texttt{segmentation} environment and haven't started another.

\begin{segmentation}[sentence numbers]
  \sn Here we go again.\n
  \sn Counting sentences one-by-one.\n
  
  \sn However, we don't have to add the environment explicitly.\n
  \sn So it is a bit more automatic.\n
\end{segmentation}

\gslset{%
  sentence ranges=true,
  sentence numbers=true,
  sentence range format={\textit{\textcolor{blue}{\small (#1--#2)}}},
  sentence number format={\textsuperscript{\textcolor{red}{#1}}},
}%

\begin{segmentation}
  \sn My first sentence.\n
  \sn My second sentence.\n
  \sn My third sentence.\n 

  \sn My fourth sentence.\n 
  \sn My fifth sentence.\n
  \sn His life is the life of desire.\n 
  \sn My fifth sentence.\n
  \sn His life is the life of desire.\n 

  \sn This is the next in line!\n
  \sn This is the next in line!\n
\end{segmentation}

Here's some regular text in sentences which aren't marked and in paragraphs which don't get the special treatment.

And here's another.
These sentences aren't marked or counted either.
That's because we finished the \texttt{segmentation} environment and haven't started another.

\begin{segmentation}
  \sn Here we go again.\n
  \sn Counting sentences one-by-one.\n
  
  \sn However, we don't have to add the environment explicitly.\n
  \sn So it is a bit more automatic.\n
\end{segmentation}
\end{document}

The blue and red is hideous, but illustrates the idea.

hideous and less hideous numbering and non-numbering

23
  • 2
    I think since you using hooks, maybe try also with \obeylines this might avoid the \sn, \n marking and would be the simplest API.
    – yannisl
    Commented May 28 at 5:16
  • 1
    @cfr \obeylines will turn everything into paragraphs. In this case you will need to check for an empty capture, i.e., the blank lines and discard them. But you can avoid the \sn and \n. Just each sentence on one line.
    – yannisl
    Commented May 28 at 5:42
  • 1
    @gsl Is there a style guide for this type of annotation somewhere on the web? And yes I can turn this into a small package.
    – yannisl
    Commented May 28 at 9:32
  • 1
    Thank you @yannisl ! I need to follow the editorial guide of the publisher I work for. It is not online, one could look at an abridged version here: libraweb.net/Norme.pdf (btw, that is not the publisher I work for) It is based on both French and Italian typographical tradition, with a bit of British rules from before 20th century. A main source for information would be the Italian volume «Regole editoriali, tipografiche e redazionali.» by F Serra. The volume contains two nice contributions by A. Olschki and M. Mardersteig
    – gsl
    Commented May 28 at 11:10
  • 1
    @cfr This works perfectly!
    – gsl
    Commented May 28 at 14:40
3

Run this with LuaLaTeX. Sentence segmentation is a major topic in Natural Language Processing, so you will have to mark the beginning of a sentence with \sn and the end with stop . If a sentence ends say with a ; or ! you will still need to add the stop afterwards as it uses a delimited macro. This shouldn't be too cumbersome, and if you generating these through a machine learning program, you can arrange for this to be added automatically. Paragraphs are marked with the environment segmentation.

Tips! You can change the macro delimiter from . to another symbol depending on your use case. David Carlisle or another user might be able to change the macro to capture an eol character automatically thus automating the delimiter. (I dont' remember how to do it). The code will fail for RTL scripts such as Hebrew, Arabic for other scripts, including east Asian scripts, such as Japanese or Chinese as long as you mark the sentences, it shouldn't give you any issues and the reason why I used Lua, as is more versatile when it comes to using non-Latin scripts.

You can change the tex.print(str3) to (str1) or (str2) for differnt styles of marking the sentences.

enter image description here

enter image description here

\documentclass{article}
\usepackage{fontspec}
\usepackage{luacode}

\begin{luacode}
  M=M or {}
  M.data ={}
  M.last =1
  M.initial=1
  M.insertdata=function(x)
    table.insert(M.data,x)
    M.last=M.last + 1
    end
   M.clear=function()
     for k=1,#M.data do
     table.remove(M.data,1)
     end
   end   
\end{luacode}

\long\def\sn#1\n{%
\directlua{
 M.insertdata('#1')
}}

\long\def\printparagraph{{%
\parindent=0pt
%tex.print("\\itshape "..v,"["..(k+initial-1).."]".." ")
\directlua{
  local data = M.data
  local initial = M.initial
  local current_length= table.getn(data)+initial
  tex.print("[".. initial.. "-"..(current_length-1).."]")
  for k,v in pairs(data) do
  local str  = "\\itshape "..v.." "
  local str1 = "\\itshape "..v,"["..(k+initial-1).."]".." "
  local str2 =  "\\itshape "..v,"["..(k+initial-1).."]".." "
  local str3  = "\\itshape "..v.."\\textsubscript{"..(k+initial-1).."}"
  tex.print(str3)
  M.initial=current_length
end   
}}\par}

 
\def\mypara{
 ------------------------------\par
\directlua{
  M.clear()
}}

\NewDocumentEnvironment{segmentation}{+b}
{\mypara #1\printparagraph}
{}
\begin{document}
\parindent0pt
\begin{segmentation}
\sn Myfirst sentence.\n
\sn My second sentence.\n
\sn My third sentence.\n 
\end{segmentation}
\begin{segmentation}
\sn My fourth sentence.\n 
\sn My fifth sentence.\n
\sn His life is the life of desire.\n 
\sn My fifth sentence.\n
\sn His life is the life of desire.\n 
\end{segmentation}
\begin{segmentation}
\sn This is the next in line!\n
\sn This is the next in line!\n
\end{segmentation}
\end{document}
9
  • Thank you, this is working almost fine. Is there a way to: 1. make it precise; now the range at first paragraph says "[1-4]:, while it should be "[1-3]". Same for other paragraphs, they are off by +1? 2. Remove the sentence numbers inside the paragraphs (like in my example, they only appear at the left). 3. Is there a way to hide the dot, like at "! ."where necessary, perhaps making it invisible?
    – gsl
    Commented May 27 at 16:32
  • 1
    This is very impressive, but \def\veryshortnamewhichisverylikelytoconflictwithsomethingimportant is not good practice. You can always say \newcommand\sn{} before so you get an error if there's a problem. Or you can use expl3, obviously. (And, yes, I know it is perfectly OK here, but it might be less OK with the addition of some packages and it is not good as an example of how to do things.)
    – cfr
    Commented May 28 at 3:05
  • 2
    @cfr sorry if my response came as if I am disparaging expl3 code. I use it a lot myself, and Iam glad you posted an alternative exp3 solution. This was the spirit of this site in its early years, where posts provided many alternative solutions, each with its own advantages and disadvantages. My first attempt was actually using an expl3 hybrid. Captured the sentences through a delimited macro (as the user needs to mark them ) then put them in a clist, so I can iterate and put the counters etc. Then I opted for Lua, as I wanted to try it with various non-latin scripts.
    – yannisl
    Commented May 28 at 5:10
  • 1
    @yannisl It's OK. Give me another 2 minutes and I would have deleted that comment. I already almost deleted it twice. And I know you use expl3. If I understood Lua, I wouldn't have re-implemented yours anyway - all I meant to do was add it into the paragraph hooks. But I couldn't make it work. Either I got errors or the document compiled but produced no actual output. Not terribly surprising. I don't understand Lua at all.
    – cfr
    Commented May 28 at 5:14
  • 1
    @yannisl I will thoroughly test both answers in my current work. In case, I will re-evaluate. It seems to me, beside the single compilation, your answer is a bit faster. Sometimes my work includes several hundred pages, and speed is quite often an important aspect. Thank you again for your work. I am learning a lot. As they say, I owe you one.
    – gsl
    Commented May 28 at 5:28

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .