# Expl3 and environ to fill boxes title, upper part, and lower part from only user text

The following will take a piece of text and put each piece ending with a blank line in a box using tcolorbox. Each input is preceded by the time stamps. I'd like the code to fill the upper part of the box with the first piece of text ending with a blank line it finds, with small font and then the second line for the lower part, and so on. If for the last box it finds only one piece of text then it should fill a box without using \tcblower.

I started to write a new command, which is "bubbledual" but the environment "rightbubbles" must be further developed.

\documentclass{article}
\usepackage[many]{tcolorbox}
\usepackage{varwidth}
\usepackage{environ}
\usepackage{xparse}

\newlength{\bubblesep}
\newlength{\bubblewidth}
\setlength{\bubblesep}{2pt}
\AtBeginDocument{\setlength{\bubblewidth}{.75\textwidth}}

\newcommand{\bubble}[2][]{%
\tcbox[
on line,
arc=2.5mm,
rounded corners,
title=#1,halign title=right
]{\begin{varwidth}{\bubblewidth}#2\end{varwidth}}%
}

\newcommand{\bubbledual}[3][]{%
\tcbox[
on line,
arc=2.5mm,
rounded corners,
title=#1,halign title=right
]{\begin{varwidth}{\bubblewidth}#2\end{varwidth}}%
\tcblower
{\begin{varwidth}{\bubblewidth}#3\end{varwidth}}%
}

\ExplSyntaxOn
\seq_new:N \l__itt_bubbles_seq
\tl_new:N \l__itt_bubbles_first_tl
\tl_new:N \l__itt_bubbles_last_tl

\NewEnviron{rightbubbles}[1][]
{
\begin{flushright}
\sffamily
\seq_set_split:NnV \l__itt_bubbles_seq { \par } \BODY
\int_compare:nTF { \seq_count:N \l__itt_bubbles_seq < 2 }
{
\bubble[#1]{\BODY}\par
}
{
\seq_map_inline:Nn \l__itt_bubbles_seq
{
\bubble[#1]{##1}
\par\nointerlineskip
}
}
\end{flushright}
}
\ExplSyntaxOff

\begin{document}

\begin{rightbubbles}[ member A ]
20:20:11" 26 nov. 2020

Right-aligned gray bubbles with black text

20:23:12" 26 nov. 2020

Bubbles only break after a paragraph (equivalent to an enter press
when chatting). Long message with multiple lines will be kept in one bubble.

20:27:54" 26 nov. 2020

Left and right edges are round.

20:30:06" 26 nov. 2020

Single line ...
\end{rightbubbles}

\end{document}

• It is not too difficult to scan for the \newline commands and to split the arguments. However, your \bubbledual does not seem to work, basically because \tcbox does not like \tcblower. The whole construction with varwidth environments in the tcolorbox is a bit dubious. For the \bubble you could just use the hbox key, and for the others it would be interesting to know what you want to achieve. In short, repair \bubbledual and then others can split the arguments, or ask a separate question on how to properly do \bubbledual.
– user229669
Nov 26, 2020 at 18:38

Late to the party, but here's a way to do it: split the input with \regex_split:nnN; this will also cope with empty messages and with multiple message lines.

\documentclass{article}
\usepackage[many]{tcolorbox}
\usepackage{varwidth}
%\usepackage{xparse} % only needed for LaTeX prior to 2020-10-01

\newlength{\bubblesep}
\newlength{\bubblewidth}
\setlength{\bubblesep}{2pt}
\AtBeginDocument{\setlength{\bubblewidth}{.75\textwidth}}

\ExplSyntaxOn

\seq_new:N \l__itt_bubbles_seq
\tl_new:N \l__itt_bubbles_tl
\tl_new:N \l__itt_bubbles_title_tl
\dim_new:N \l__itt_bubbles_width_dim
\box_new:N \l__itt_bubbles_title_box
\box_new:N \l__itt_bubbles_upper_box
\box_new:N \l__itt_bubbles_lower_box

\NewDocumentEnvironment{rightbubbles}{O{} +b}
{
\itt_bubbles:nnn { flushright } { #1 } { #2 }
}
{}
\NewDocumentEnvironment{leftbubbles}{O{} +b}
{
\itt_bubbles:nnn { flushleft } { #1 } { #2 }
}
{}

\cs_new_protected:Nn \itt_bubbles:nnn
{
\begin{#1}
\sffamily
\tl_set:Nn \l__itt_bubbles_title_tl { #2 }
\tl_clear:N \l__itt_bubbles_tl
\regex_split:nnN { (\d\d:\d\d:\d\d\".*?\c{\par}) } { #3 } \l__itt_bubbles_seq
\seq_pop_left:NN \l__itt_bubbles_seq \l_tmpa_tl % discard the first empty item
\seq_map_indexed_function:NN \l__itt_bubbles_seq \__itt_bubbles_prepare:nn
\tl_use:N \l__itt_bubbles_tl
\end{#1}
}

\cs_new_protected:Nn \__itt_bubbles_prepare:nn
{
\int_if_odd:nTF { #1 }
{% start a bubble % title and top
\tl_put_right:Nn \l__itt_bubbles_tl
{
\__itt_bubbles_make:Vnn \l__itt_bubbles_title_tl { #2 }
}
}
{
\tl_put_right:Nn \l__itt_bubbles_tl { { #2 } }
}
}

\cs_new_protected:Nn \__itt_bubbles_make:nnn
{
\hbox_set:Nn \l__itt_bubbles_title_box { #1 }
\hbox_set:Nn \l__itt_bubbles_upper_box { \begin{varwidth}{\bubblewidth}#2\end{varwidth}}
\hbox_set:Nn \l__itt_bubbles_lower_box { \begin{varwidth}{\bubblewidth}#3\end{varwidth}}
\dim_set:Nn \l__itt_bubbles_width_dim
{
\dim_max:nn { \box_wd:N \l__itt_bubbles_title_box } { \box_wd:N \l__itt_bubbles_upper_box }
}
\dim_set:Nn \l__itt_bubbles_width_dim
{
\dim_max:nn { \l__itt_bubbles_width_dim } { \box_wd:N \l__itt_bubbles_lower_box }
}
\begin{tcolorbox}[
left=1em,right=1em,
width=\dim_eval:n { \l__itt_bubbles_width_dim + 3em },
on~line,
arc=2.5mm,
rounded~corners,
halign~title=right,
title=\box_use:N \l__itt_bubbles_title_box,
]
\box_use:N \l__itt_bubbles_upper_box
\tcblower
\box_use:N \l__itt_bubbles_lower_box
\end{tcolorbox}
\par
}
\cs_generate_variant:Nn \__itt_bubbles_make:nnn { V }
\ExplSyntaxOff

\begin{document}

\begin{rightbubbles}[member A]
20:20:11" 26 nov. 2020

Right-aligned gray bubbles with black text

20:23:12" 26 nov. 2020

Bubbles only break after a paragraph (equivalent to an enter press
when chatting). Long message with multiple lines will be kept in one bubble.

20:27:54" 26 nov. 2020

Left and right edges are round.

Another paragraph.

20:30:06" 26 nov. 2020

20:30:06" 26 nov. 2020

Single line ...
\end{rightbubbles}

\begin{leftbubbles}[member B]
20:20:11" 26 nov. 2020

Right-aligned gray bubbles with black text

20:23:12" 26 nov. 2020

Bubbles only break after a paragraph (equivalent to an enter press
when chatting). Long message with multiple lines will be kept in one bubble.
\end{leftbubbles}

\end{document}


This does something of the sort. (I prefer the cleaner, traditional \defs, but you can convert this to expl3 if you need.) This code comes with a variant of your bubble box that allows for a \tcblower.

\documentclass{article}
\usepackage[many]{tcolorbox}
\usepackage{varwidth}
\usepackage{environ}
\usepackage{xparse}

\newlength{\bubblesep}
\newlength{\bubblewidth}
\setlength{\bubblesep}{2pt}
\AtBeginDocument{\setlength{\bubblewidth}{.75\textwidth}}

\newcommand{\bubble}[2][]{%
\tcbox[
on line,
arc=2.5mm,
rounded corners,
title=#1,halign title=right
]{\begin{varwidth}{\bubblewidth}#2\end{varwidth}}%
}

\newcommand{\bubbledual}[3][]{%
\setbox0\hbox{\begin{varwidth}{\bubblewidth}
\small #2
\end{varwidth}}%
\setbox1\hbox{\begin{varwidth}{\bubblewidth}
#3
\end{varwidth}}%
\ifdim\wd0>\wd1\relax
\edef\mybubblewidth{\the\dimexpr\wd0+1.2cm}%
\else
\edef\mybubblewidth{\the\dimexpr\wd1+1.2cm}%
\fi
\begin{tcolorbox}[width=\mybubblewidth,
arc=2.5mm,
rounded corners,
title=#1,halign title=right
]
\begin{varwidth}{\bubblewidth}
{\small #2}
\end{varwidth}%
\tcblower
\begin{varwidth}{\bubblewidth}
#3
\end{varwidth}%
\end{tcolorbox}}

\def\bubblesplit#1\newline#2\newline{\ifx#2\relax\relax
\bubble[\mytitle]{#1}%
\else
\bubbledual[\mytitle]{#1}{#2}%
\fi
}%

\ExplSyntaxOn
\seq_new:N \l__itt_bubbles_seq
\tl_new:N \l__itt_bubbles_first_tl
\tl_new:N \l__itt_bubbles_last_tl

\NewEnviron{rightbubbles}[1][]
{
\begin{flushright}
\sffamily
\seq_set_split:NnV \l__itt_bubbles_seq { \par } \BODY
\int_compare:nTF { \seq_count:N \l__itt_bubbles_seq < 2 }
{
%\bubble[#1]{\BODY}\par
\def\mytitle{#1}%
\let\oldnewline\newline
\let\newline\relax
\expandafter\bubblesplit##1\newline\newline
\let\newline\oldnewline
}
{
\seq_map_inline:Nn \l__itt_bubbles_seq
{
%\bubble[#1]{##1}
\def\mytitle{#1}%
\let\oldnewline\newline
\let\newline\relax
\expandafter\bubblesplit##1\newline\newline
\let\newline\oldnewline
\par\nointerlineskip
}
}
\end{flushright}
}
\ExplSyntaxOff

\begin{document}

\begin{rightbubbles}[ member A ]
20:20:11" 26 nov. 2020\newline
Right-aligned gray bubbles with black text

20:23:12" 26 nov. 2020\newline
Bubbles only break after a paragraph (equivalent to an enter press
when chatting). Long message with multiple lines will be kept in one bubble.

20:27:54" 26 nov. 2020\newline
Left and right edges are round.

20:30:06" 26 nov. 2020\newline
Single line \dots

a line without time stamp
\end{rightbubbles}

\end{document}


• \newline in \BODY are here to show in the end result file what i want. The text of \BODY will not contain any code, only user pasted text. The environment should take the first piece of text ending with a blank line and fill the up part of a box and take the following to fill the low part. If it finds only one line it should put it in a non-split box. In my OP the command \seq_map_inline:Nn \l__itt_bubbles_seq does not allow using #2 for the item following #1 in the sequence. I would have used #1 for the up part and #2 for the lower. Also with a condition checking if #1 is the last item. Nov 27, 2020 at 4:44
• @user1850133 According to what I find, your \bubbledual does not work. At the very least the above has a working command. I might well be that it is not what you wanted to achieve, but it does achieve what I got from your description. And sorry, I cannot comprehend your comment.
– user229669
Nov 27, 2020 at 4:48
• my comment was initially too long and i had to shrink it. With this code i should be able to simply copy and paste from a text output from for example pidgin, or an sms backup tool. I shouldn't need to add any piece of code like "\newline", "\par", etc. Blank lines, OK. rightbubbles should take the two first lines and fill the upper and lower parts of box respectively. At the end if only one line is found it should fill a box without \tcblower invocation. Nov 27, 2020 at 7:13
• Lines are split into items by \seq_set_split:NnV ... using \par as delimiter. Currently \seq_map_inline:Nn ...` takes only one item and puts it in a box using the argument "##1". I want instead a function taking for each box two consecutive items. Nov 27, 2020 at 7:15