Why boolean variable not set as true

I try to do tagging of \description environment,but i noticed strange issue. when i redefine \descriptionlabel command,i set true for boolean variable,but after the second item my variable has false value,so my tagging works incorrect. I tried to debug commands,and as i expected,at first i have \list command,at the second - I have two \descriptionlabel commands,and after that i have \endlist command,so all works as expected,but why my boolean variable not set as true? Help me please,why this happens with my variable? Thanks everybody very much for the help.

\documentclass{article}
\usepackage{tagpdf}
\tagpdfsetup{tabsorder=structure,uncompress,activate-all,interwordspace=true}
\ExplSyntaxOn
\makeatletter
\int_gzero_new:N \g__list_level_int
%commands for tagging of lists.
\let\start@list=\list
\let\end@list=\endlist
\let\desc@label=\descriptionlabel

\renewcommand{\list}[2]{
\int_case:nnF{\g__list_level_int}{{0}{}}{
%we need check,if we have items in previous level,and if yes,close mc and struct before starting of the list.
\bool_if:cTF{l__first_item_\int_use:N \g__list_level_int _bool}{\tagmcend\tagstructend
%We close previous LI and LBody
\tagstructend\tagstructend
\bool_set_false:c{l__first_item_\int_use:N \g__list_level_int _bool}
}
{}
\tagstructbegin{tag=LI}
\tagstructbegin{tag=LBody}
}
\int_gincr:N \g__list_level_int
\bool_if_exist:cTF{l__first_item_\int_use:N \g__list_level_int _bool}{}{\bool_new:c{l__first_item_\int_use:N \g__list_level_int _bool}}
\tagstructbegin{tag=L}
\start@list{#1}{#2}
}

\renewcommand{\endlist}{
\end@list
\bool_if:cTF{l__first_item_\int_use:N \g__list_level_int _bool}{
\tagmcend
\tagstructend
\tagstructend
\tagstructend
\bool_set_false:c{l__first_item_\int_use:N \g__list_level_int _bool}
}
{
%if we haven't items,but we have level more then one,we have sublist before,so we should close LI and LBody.
\int_case:nnF{\g__list_level_int-1}{{0}{}}{\tagstructend\tagstructend}
}
\int_gdecr:N \g__list_level_int
\tagstructend
}

\renewcommand{\descriptionlabel}[1]{
\bool_if:cTF{l__first_item_\int_use:N \g__list_level_int _bool}{
\message{true~value}
%we have an item before,so we must close the last item,LBody and LI.
\tagmcend\tagstructend\tagstructend\tagstructend}{
\message{false~value}
%this code executed twice,but must executed only the first time. Is it expl3 issue?
\bool_set_true:c{l__first_item_\int_use:N \g__list_level_int _bool}
}
\tagstructbegin{tag=LI}
\tagstructbegin{tag=Lbl}
\tagmcbegin{tag=Lbl}
\desc@label{#1}
\tagmcend
\tagstructend
\tagstructbegin{tag=LBody}
\tagstructbegin{tag=P}
\tagmcbegin{tag=P}
}
\makeatother
\ExplSyntaxOff

\begin{document}
\tagstructbegin{tag=Document}
\begin{description}
\item test
\item new test
\end{description}
\tagstructend
\end{document}

• @Andrew while I quite agree with you remark I would suspect that the problem here doesn't lie in tagpdf but in the code. Without having tried, a description label is normally set in a box and so a local boolean can't escape – Ulrike Fischer Mar 2 '20 at 6:45
• The code is hardly readable. Using \ExplSyntaxOn but no blank lines between functions and no indentation at all, what a pity! For a start, \int_gzero_new:N{\g__list_level_int} is incorrect: since it is a N argument (single token), there should be no braces around \g__list_level_int. Also, \bool_if:NTF{\l__first_item_\int_eval:N{\g__list_level_int}_bool} can't work. What was desired here is probably \bool_if:cTF { l__first_item_ \int_use:N \g__list_level_int _bool }. – frougon Mar 2 '20 at 8:22
• Suggested reading: The LaTeX3 Programming Language and LaTeX3 style guide, then the beginning of The LaTeX3 interfaces. – frougon Mar 2 '20 at 8:22
• There are a few similar errors and... what is the purpose of \int_case:nnF{\g__list_level_int}{{0}{}}{}? – frougon Mar 2 '20 at 8:30
• @frougon I edited my question,but after changes there is no changes. As for ask about my case,i removed it,because it really not need in this code and i forgot remove it before. I still not understood,why my variable loose true value,because i checked it after the setting true,and it have true value,but in the next command it have false value. May be it's expl3 issue,which should send on github? – Aleksandr Kozlovskiy Mar 2 '20 at 15:31

1 Answer

Caveat: my knowledge of tagpdf is close to zero, so I can't say whether it is used correctly here. I can only speak for TeX, LaTeX and expl3. :-)

As hinted by @UlrikeFischer, your bool appears to be set inside a group. After making the bool global (+ using \bool_gset_false:c, etc.) and fixing the various things I saw, it appears to work as you want.

Explanation of the local vs. global problem

If you read article.cls, you'll see that it does:

\newenvironment{description}
{\list{}{\labelwidth\z@ \itemindent-\leftmargin
\let\makelabel\descriptionlabel}}
{\endlist}
\newcommand*\descriptionlabel[1]{\hspace\labelsep
\normalfont\bfseries #1}


Thus, your \descriptionlabel command is temporarily known under the name \makelabel when LaTeX is processing the description environment of your document. Then, in the LaTeX kernel source code (here, ltlists.dtx), we have:

\def\@item[#1]{%
...
\sbox\@tempboxa{\makelabel{#1}}%
\global\setbox\@labels\hbox{...}
...
}


This is in the replacement text of \@item, which implements the core of the \item command. \@tempboxa is used in the \hbox{...}. So, your \descriptionlabel command is executed (under the name \makelabel, but this isn't very important) inside the second argument of \sbox. As a consequence, it is executed inside a box, thus inside an implicit group. This happens for each label of your description environment. When the \sbox command has finished its job, the implicit group has already ended, and this automatically restored all variables and commands that were set locally inside the group to the state they had when the group was started (TeX does that for all groups; in particular, this happens for all boxes created with TeX primitives such as \hbox, \vbox, \vtop or \vcenter, since such box commands are always executed inside an implicit group).

Therefore, if you use \bool_set_true:N or \bool_set_false:N inside \descriptionlabel, the setting will be done inside the \sbox (actually, the \hbox TeX primitive \sbox is based on) and its effect will be cancelled as soon as the box ends. This is even before the \item text has started. So, your local setting doesn't survive until the next time you check it. On the contrary, global settings done inside a group (including inside a box) affect all grouping levels; their effect is not reverted when the group ends. That is why it works as you want with \bool_gset_true:N, \bool_gset_false:N or similar global assignments.

It is true that expl3 doesn't look if the variable name starts with l_ or g_: this is indeed only for us, mere humans. What matters to TeX is whether you use a local or a global assignment (\bool_set_* vs. \bool_gset_*, etc.).

Other remarks

Please use (if you can) expl3 naming conventions and coding style as mentioned in The LaTeX3 Programming Language, The LaTeX3 style guide and The LaTeX3 interfaces. For me, your code was almost impossible to work with prior to heavy reformatting.

Also use \cs_new_eq:NN instead of \let when defining new names for existing functions, otherwise there is a possibility that you are overwriting existing functions, with possibly very hard-to-debug side effects (the probability of this happening is rather high with the names you chose here: \start@list, \end@list and \desc@label; better use expl3 naming conventions as I did below: \__aleksandr_orig_start_list:nn, \__aleksandr_orig_end_list: and \__aleksandr_orig_descriptionlabel:n).

Another thing that can make the code easier to work with: this

\int_compare:nNnF { \g__list_level_int } = { 0 }
{ ... }


is easier to read than that:

\int_case:nnF { \g__list_level_int }
{ {0} {} }
{ ... }


in my opinion.

\documentclass{article}
\usepackage{xparse}
\usepackage{tagpdf}
\tagpdfsetup{tabsorder=structure,uncompress,activate-all,interwordspace=true}

\ExplSyntaxOn

% The declaration is global. The integer is initially equal to 0 (see
% interface3.pdf).
\int_new:N \g__aleksandr_list_level_int

\cs_new_eq:NN \__aleksandr_orig_start_list:nn \list
\cs_new_eq:NN \__aleksandr_orig_end_list: \endlist
\cs_new_eq:NN \__aleksandr_orig_descriptionlabel:n \descriptionlabel

% Simple macro to reduce redundancy and the length of some lines. It expands
% to the list boolean variable name for the current level **without its
% backslash**.
\cs_new:Npn \__aleksandr_list_level_bool_name:
{
g__first_item_ \int_use:N \g__aleksandr_list_level_int _bool
}

\RenewDocumentCommand \list { }
{
\int_compare:nNnF { \g__aleksandr_list_level_int } = { 0 }
{
% We need check, if we have items in previous level,and if yes, close mc
% and struct before starting of the list.
\bool_if:cT { \__aleksandr_list_level_bool_name: }
{
\tagmcend \tagstructend
%We close previous LI and LBody
\tagstructend \tagstructend
\bool_gset_false:c { \__aleksandr_list_level_bool_name: }
}

\tagstructbegin { tag=LI }
\tagstructbegin { tag=LBody }
}

\int_gincr:N \g__aleksandr_list_level_int
\bool_if_exist:cF { \__aleksandr_list_level_bool_name: }
{ \bool_new:c { \__aleksandr_list_level_bool_name: } }

\tagstructbegin { tag=L }
\__aleksandr_orig_start_list:nn
}

\RenewDocumentCommand \endlist { }
{
\__aleksandr_orig_end_list:
\bool_if:cTF { \__aleksandr_list_level_bool_name: }
{
\tagmcend
\tagstructend
\tagstructend
\tagstructend
\bool_gset_false:c { \__aleksandr_list_level_bool_name: }
}
{
% If we haven't items, but we have level more then one, we have sublist
% before, so we should close LI and LBody.
\int_compare:nNnF { \g__aleksandr_list_level_int - 1 } = { 0 }
{ \tagstructend \tagstructend }
}
\int_gdecr:N \g__aleksandr_list_level_int
\tagstructend
}

\RenewDocumentCommand \descriptionlabel { m }
{
\bool_if:cTF { \__aleksandr_list_level_bool_name: }
{
\iow_term:n { True~value }
% We have an item before, so we must close the last item, LBody and LI.
\tagmcend \tagstructend \tagstructend \tagstructend
}
{
\iow_term:n { False~value }
% This code is only executed once.
\bool_gset_true:c { \__aleksandr_list_level_bool_name: }
}
\tagstructbegin {tag=LI}
\tagstructbegin {tag=Lbl}
\tagmcbegin {tag=Lbl}
\__aleksandr_orig_descriptionlabel:n {#1}
\tagmcend
\tagstructend
\tagstructbegin {tag=LBody}
\tagstructbegin {tag=P}
\tagmcbegin {tag=P}
}

\ExplSyntaxOff

\begin{document}

\tagstructbegin{tag=Document}
\begin{description}
\item test
\item new test
\end{description}
\tagstructend

\end{document}


This prints:

False value
True value


on the terminal, which I believe is what you expected.

• ,@UlrikeFischer, After changing local variable on global and using gset_true,instead of set_true problem solved. But i not understood,why i have problem with local variable? When local variable become false and why it become false,but not disappeared,which caused an error,such as undefined controle sequence? As i considered,expl3 not pay attention on g and l,and it needd only for readability and usability of the code. But now i understood,that expl3 package see in l or g. But if,for example,my variable will be named as \a_bool,expl3 package will deside,that it global variable,or local? – Aleksandr Kozlovskiy Mar 2 '20 at 18:21
• previous comment was also for you (i dont know,how to show,if i want that my comment see more than one user). – Aleksandr Kozlovskiy Mar 2 '20 at 18:31
• @AleksandrKozlovskiy I updated my answer regarding your questions on the local vs. global problem; I hope it makes sense to you. With @ in comments on this site, you can only mention one person, but since these comments are below my answer, I am always notified about them even if you don't @ me. I realized a bit late that maybe using indentation is very difficult for you; if so, I understand and hope you didn't find my comments too “harsh.” :-) – frougon Mar 2 '20 at 23:40
• Where i can find file ltlists.dtx,to see full code of this file and lualatex fully? Because now in pdflatex it works ok,but in lualatex it not works correct,because of tagpdf issue with group. As i understood according to your answer,i open mc in one group,and close it in other group,so it works incorrect,so i should open and close mc in the same group,i.e after closing of box in \item command,because of tagpdf issue in lua mode. – Aleksandr Kozlovskiy Mar 3 '20 at 19:52
• @AleksandrKozlovskiy ltlists.dtx can be found here (direct link). What you say is plausible: I only tested with pdflatex and not the tagpdf things, as I don't know this package. – frougon Mar 3 '20 at 20:29