% \iffalse meta-comment % Copyright (C) 2026 Andreas Willibald Weber % % This work may be distributed and/or modified under the conditions of the % LaTeX Project Public License, either version 1.3c of this license or (at your % option) any later version. The latest version of the license is available at % https://www.latex-project.org/lppl.txt. % % This work has the LPPL maintenance status `maintained'. The Current % Maintainer of this work is Andreas Willibald Weber. % % This file is part of the `kanban-board' package. The complete list of files % constituting the work appears in the main source file kanban-board.dtx. % \fi % % \begin{macrocode} %<*package> % \end{macrocode} % % \subsection{Deadline engine} % % Deadline classification uses anchored regular expressions before any % numeric conversion. The five supported notations enter one component % validator and one formatter. Bounded project-month labels use a separate % validated path, and arbitrary descriptive labels remain neutral and % unchanged. The engine separates syntax recognition, Gregorian validation, % display formatting, and temporal classification. This separation lets a % task keep a neutral descriptive deadline without misclassifying it as a % broken date. Diagnostics are issued when a task is declared, so a warning % names the line of the task rather than the end of the board. % % \paragraph{Typed parsing and calendar state.} % Date processing needs the normalized input, the capture groups of the last % match, numeric components, maximum-day calculations, project-month state, % reference-date state, display fragments, and explicit status flags. Each % role has its own variable, so no scratch register carries two meanings % across nested tests. The scratch variables of the kernel are deliberately % not used, because other packages may overwrite them between calls. % \begin{macrocode} \tl_new:N \l__kanban_deadline_tl \seq_new:N \l__kanban_deadline_match_seq \int_new:N \l__kanban_deadline_day_int \int_new:N \l__kanban_deadline_month_int \int_new:N \l__kanban_deadline_year_int \int_new:N \l__kanban_deadline_max_day_int \int_new:N \l__kanban_swap_int \int_new:N \l__kanban_deadline_key_int \int_new:N \l__kanban_reference_key_int \int_new:N \l__kanban_project_month_int \int_new:N \l__kanban_current_month_int \int_new:N \l__kanban_project_start_day_int \int_new:N \l__kanban_project_start_month_int \int_new:N \l__kanban_project_start_year_int \int_new:N \l__kanban_project_boundary_day_int \int_new:N \l__kanban_reference_day_int \int_new:N \l__kanban_reference_month_int \int_new:N \l__kanban_reference_year_int \tl_new:N \l__kanban_reference_tl \tl_new:N \l__kanban_display_day_tl \tl_new:N \l__kanban_display_month_tl \tl_new:N \l__kanban_display_year_tl \tl_new:N \l__kanban_display_format_tl \tl_new:N \l__kanban_dimension_tl \bool_new:N \l__kanban_valid_date_bool \bool_new:N \l__kanban_valid_project_month_bool \bool_new:N \l__kanban_date_syntax_bool \bool_new:N \l__kanban_date_components_bool \bool_new:N \l__kanban_ambiguous_slash_bool \bool_new:N \l__kanban_project_month_syntax_bool % \end{macrocode} % % The two syntax flags have distinct meanings. The syntax flag records that % the input has the shape of a date, which is the condition for a warning. % The components flag records that one of the exact notations captured year, % month, and day, which is the condition for validation. % % \paragraph{Maximum days in a Gregorian month.} % Component validation needs the number of permitted days in the parsed % month. February has 29 days in years divisible by 400, or divisible by 4 % but not by 100. April, June, September, and November have 30 days. All % other valid months have 31. The divisibility predicate reads the year % component that the parser has already assigned. % \begin{macrocode} \prg_new_conditional:Npnn \__kanban_year_divisible:n #1 {p} { \int_compare:nNnTF {\int_mod:nn {\l__kanban_deadline_year_int} {#1}} = {0} {\prg_return_true:} {\prg_return_false:} } \cs_new_protected:Npn \__kanban_set_max_day: { \int_case:nnF {\l__kanban_deadline_month_int} { {2} { \bool_if:nTF { \__kanban_year_divisible_p:n {400} || ( \__kanban_year_divisible_p:n {4} && ! \__kanban_year_divisible_p:n {100} ) } {\int_set:Nn \l__kanban_deadline_max_day_int {29}} {\int_set:Nn \l__kanban_deadline_max_day_int {28}} } {4}{\int_set:Nn \l__kanban_deadline_max_day_int {30}} {6}{\int_set:Nn \l__kanban_deadline_max_day_int {30}} {9}{\int_set:Nn \l__kanban_deadline_max_day_int {30}} {11}{\int_set:Nn \l__kanban_deadline_max_day_int {30}} } {\int_set:Nn \l__kanban_deadline_max_day_int {31}} } % \end{macrocode} % % \paragraph{Gregorian component validation and capture conversion.} % Once a notation has assigned year, month, and day, the validator checks % that the year is positive, that the month lies between 1 and 12, and that % the day lies in the range permitted by the month and the leap-year rule. % The tests are nested so that the maximum-day helper never sees an invalid % month. The capture helpers provide the single conversion path from % regular-expression text to integers. The three-argument helper receives the % capture positions of year, month, and day and also records that the input % had both date syntax and complete components. % \begin{macrocode} \cs_new_protected:Npn \__kanban_validate_date: { \bool_set_false:N \l__kanban_valid_date_bool \int_compare:nNnT {\l__kanban_deadline_year_int} > {0} { \int_compare:nNnT {\l__kanban_deadline_month_int} > {0} { \int_compare:nNnT {\l__kanban_deadline_month_int} < {13} { \__kanban_set_max_day: \int_compare:nNnT {\l__kanban_deadline_day_int} > {0} { \int_compare:nNnT {\l__kanban_deadline_day_int} < {\l__kanban_deadline_max_day_int + 1} {\bool_set_true:N \l__kanban_valid_date_bool} } } } } } \cs_new_protected:Npn \__kanban_set_match:Nn #1#2 { \int_set:Nn #1 {\seq_item:Nn \l__kanban_deadline_match_seq {#2}} } \cs_new_protected:Npn \__kanban_set_components:nnn #1#2#3 { \bool_set_true:N \l__kanban_date_syntax_bool \bool_set_true:N \l__kanban_date_components_bool \__kanban_set_match:Nn \l__kanban_deadline_year_int {#1} \__kanban_set_match:Nn \l__kanban_deadline_month_int {#2} \__kanban_set_match:Nn \l__kanban_deadline_day_int {#3} } % \end{macrocode} % % \paragraph{Recognition of the five date notations.} % The parser recognizes dotted day-first dates, hyphenated ISO dates, % slash-separated year-first dates, and the two locale-dependent slash forms. % Every notation requires a four-digit year. Month and day may have one or % two digits. For an ambiguous year-last slash date, the selected date format % determines the first interpretation. If that interpretation is impossible, % the parser swaps day and month once and validates the alternative, which % accepts unambiguous input such as |31/12/2026| under any format. % % Input that has the shape of a date but matches none of the exact notations, % for example a two-digit year, sets only the syntax flag. Such input is % reported as an invalid date and is never silently treated as a label. This % protects against |18.07.26| being read as a date in the year 26. % % The input is expanded through |\text_expand:n| before matching. Ordinary % macros are expanded to their text, so a macro that holds a date is % classified. Robust and protected commands are left intact, so a deadline % such as |\emph{soon}| is a neutral label and cannot derail the expanded % assignment. % \begin{macrocode} \cs_new_protected:Npn \__kanban_parse_date:nn #1#2 { \bool_set_false:N \l__kanban_valid_date_bool \bool_set_false:N \l__kanban_date_syntax_bool \bool_set_false:N \l__kanban_date_components_bool \bool_set_false:N \l__kanban_ambiguous_slash_bool \tl_set:Nx \l__kanban_deadline_tl {\text_expand:n {#1}} \tl_trim_spaces:N \l__kanban_deadline_tl \exp_args:NnV \regex_extract_once:nnNTF {\A([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{4})\Z} \l__kanban_deadline_tl \l__kanban_deadline_match_seq {\__kanban_set_components:nnn {4}{3}{2}} { \exp_args:NnV \regex_extract_once:nnNTF {\A([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})\Z} \l__kanban_deadline_tl \l__kanban_deadline_match_seq {\__kanban_set_components:nnn {2}{3}{4}} { \exp_args:NnV \regex_extract_once:nnNTF {\A([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})\Z} \l__kanban_deadline_tl \l__kanban_deadline_match_seq {\__kanban_set_components:nnn {2}{3}{4}} { \exp_args:NnV \regex_extract_once:nnNTF {\A([0-9]{1,2})/([0-9]{1,2})/([0-9]{4})\Z} \l__kanban_deadline_tl \l__kanban_deadline_match_seq { \bool_set_true:N \l__kanban_ambiguous_slash_bool \bool_if:nTF { \str_if_eq_p:ee {#2} {english} || \str_if_eq_p:ee {#2} {german} } {\__kanban_set_components:nnn {4}{3}{2}} {\__kanban_set_components:nnn {4}{2}{3}} } { \exp_args:NnV \regex_match:nnT {\A[0-9]{1,4}[.\-/][0-9]{1,2}[.\-/][0-9]{1,4}\Z} \l__kanban_deadline_tl {\bool_set_true:N \l__kanban_date_syntax_bool} } } } } \bool_if:NT \l__kanban_date_components_bool {\__kanban_validate_date:} \bool_if:nT { \l__kanban_ambiguous_slash_bool && ! \l__kanban_valid_date_bool } { \int_set_eq:NN \l__kanban_swap_int \l__kanban_deadline_day_int \int_set_eq:NN \l__kanban_deadline_day_int \l__kanban_deadline_month_int \int_set_eq:NN \l__kanban_deadline_month_int \l__kanban_swap_int \__kanban_validate_date: } } % \end{macrocode} % % \paragraph{Canonical and display formatting.} % Accepted dates are stored internally in one unambiguous year, month, and % day representation. At rendering time the same numeric components are % formatted according to the notation selected for the board. Keeping % canonical storage and presentation separate ensures that |date-format| % affects input interpretation and display but never chronological % comparison. The padding helpers are expandable so that the canonical form % can be produced inside an expanded definition. The display macro keeps the % literal deadline for invalid dates and descriptive labels, so those values % remain printable. % \begin{macrocode} \cs_new:Npn \__kanban_two_digits:n #1 { \int_compare:nNnT {#1} < {10} {0} \int_to_arabic:n {#1} } \cs_new:Npn \__kanban_four_digits:n #1 { \int_compare:nNnT {#1} < {1000} {0} \int_compare:nNnT {#1} < {100} {0} \int_compare:nNnT {#1} < {10} {0} \int_to_arabic:n {#1} } \cs_new:Npn \__kanban_canonical_date: { \__kanban_four_digits:n {\l__kanban_deadline_year_int} -\__kanban_two_digits:n {\l__kanban_deadline_month_int} -\__kanban_two_digits:n {\l__kanban_deadline_day_int} } \cs_new_protected:Npn \kanban@preparedeadline #1 { \cs_set:Npn \kb@displaydeadline {#1} \__kanban_parse_date:nn {#1} {\kanban@boarddateformat} \bool_if:NT \l__kanban_valid_date_bool { \tl_set:Nx \l__kanban_display_day_tl {\__kanban_two_digits:n {\l__kanban_deadline_day_int}} \tl_set:Nx \l__kanban_display_month_tl {\__kanban_two_digits:n {\l__kanban_deadline_month_int}} \tl_set:Nx \l__kanban_display_year_tl {\__kanban_four_digits:n {\l__kanban_deadline_year_int}} \tl_set:Nx \l__kanban_display_format_tl {\kanban@boarddateformat} \str_case:VnF \l__kanban_display_format_tl { {us} { \cs_set:Npx \kb@displaydeadline { \l__kanban_display_month_tl / \l__kanban_display_day_tl / \l__kanban_display_year_tl } } {english} { \cs_set:Npx \kb@displaydeadline { \l__kanban_display_day_tl / \l__kanban_display_month_tl / \l__kanban_display_year_tl } } {german} { \cs_set:Npx \kb@displaydeadline { \l__kanban_display_day_tl . \l__kanban_display_month_tl . \l__kanban_display_year_tl } } {iso} { \cs_set:Npx \kb@displaydeadline { \l__kanban_display_year_tl - \l__kanban_display_month_tl - \l__kanban_display_day_tl } } {ymd} { \cs_set:Npx \kb@displaydeadline { \l__kanban_display_year_tl / \l__kanban_display_month_tl / \l__kanban_display_day_tl } } } { } } } % \end{macrocode} % % \paragraph{Project-month syntax.} % Project deadlines such as |M1| are relative to the configured start date % rather than to a calendar date written on the card. The parser accepts a % positive integer of at most six significant digits and allows leading % zeros, so |M01| denotes the first month while |M0| and |M000| remain % invalid. It records recognized but invalid |M|-prefixed numeric syntax % separately and leaves all other text neutral. The bound keeps malformed % input away from integer arithmetic while remaining far beyond any % realistic project duration. The input is expanded through % |\text_expand:n| in the same way as a calendar date. % \begin{macrocode} \cs_new_protected:Npn \__kanban_parse_project_month:n #1 { \bool_set_false:N \l__kanban_valid_project_month_bool \bool_set_false:N \l__kanban_project_month_syntax_bool \tl_set:Nx \l__kanban_deadline_tl {\text_expand:n {#1}} \tl_trim_spaces:N \l__kanban_deadline_tl \exp_args:NnV \regex_match:nnT {\AM[0-9]+\Z} \l__kanban_deadline_tl {\bool_set_true:N \l__kanban_project_month_syntax_bool} \exp_args:NnV \regex_extract_once:nnNTF {\AM0*([1-9][0-9]{0,5})\Z} \l__kanban_deadline_tl \l__kanban_deadline_match_seq { \int_set:Nn \l__kanban_project_month_int {\seq_item:Nn \l__kanban_deadline_match_seq {2}} \bool_set_true:N \l__kanban_valid_project_month_bool } { } } % \end{macrocode} % % \paragraph{Reference-date loading and calendar classification.} % Classification is evaluated against the board-local reference date. If % none was configured, the engine reads the compilation year, month, and day % from TeX. A configured value is already canonical, so one exact expression % extracts it. Calendar dates are then converted to order-preserving integers % of the form $10000y+100m+d$. A smaller value is overdue and an equal value % is due now. The two |etoolbox| Booleans are the only facts the card % renderer consumes. % \begin{macrocode} \cs_new_protected:Npn \__kanban_load_reference_date: { \tl_set:Nx \l__kanban_reference_tl {\kanban@thereference} \tl_if_blank:VTF \l__kanban_reference_tl { \int_set:Nn \l__kanban_reference_year_int {\year} \int_set:Nn \l__kanban_reference_month_int {\month} \int_set:Nn \l__kanban_reference_day_int {\day} } { \exp_args:NnV \regex_extract_once:nnN {\A([0-9]{4})-([0-9]{2})-([0-9]{2})\Z} \l__kanban_reference_tl \l__kanban_deadline_match_seq \int_set:Nn \l__kanban_reference_year_int {\seq_item:Nn \l__kanban_deadline_match_seq {2}} \int_set:Nn \l__kanban_reference_month_int {\seq_item:Nn \l__kanban_deadline_match_seq {3}} \int_set:Nn \l__kanban_reference_day_int {\seq_item:Nn \l__kanban_deadline_match_seq {4}} } } \cs_new_protected:Npn \__kanban_classify_calendar_date: { \__kanban_load_reference_date: \int_set:Nn \l__kanban_deadline_key_int {\l__kanban_deadline_year_int * 10000 + \l__kanban_deadline_month_int * 100 + \l__kanban_deadline_day_int} \int_set:Nn \l__kanban_reference_key_int {\l__kanban_reference_year_int * 10000 + \l__kanban_reference_month_int * 100 + \l__kanban_reference_day_int} \int_compare:nNnTF {\l__kanban_deadline_key_int} < {\l__kanban_reference_key_int} {\booltrue{kb@overdue}} { \int_compare:nNnT {\l__kanban_deadline_key_int} = {\l__kanban_reference_key_int} {\booltrue{kb@duenow}} } } % \end{macrocode} % % \paragraph{Project-month classification.} % A project month begins on the configured start-day number, not necessarily % on the first day of a calendar month. The classifier computes the nominal % month offset from the project start, clamps the boundary day to the last % valid day of a shorter reference month, and subtracts one when the % reference date has not yet reached that boundary. The reference year and % month are placed temporarily in the date component variables so that the % shared maximum-day function can be reused. % \begin{macrocode} \cs_new_protected:Npn \__kanban_classify_project_month: { \__kanban_load_reference_date: \int_set:Nn \l__kanban_current_month_int {(\l__kanban_reference_year_int - \l__kanban_project_start_year_int) * 12 + (\l__kanban_reference_month_int - \l__kanban_project_start_month_int) + 1} \int_set_eq:NN \l__kanban_deadline_year_int \l__kanban_reference_year_int \int_set_eq:NN \l__kanban_deadline_month_int \l__kanban_reference_month_int \__kanban_set_max_day: \int_set_eq:NN \l__kanban_project_boundary_day_int \l__kanban_project_start_day_int \int_compare:nNnT {\l__kanban_project_boundary_day_int} > {\l__kanban_deadline_max_day_int} {\int_set_eq:NN \l__kanban_project_boundary_day_int \l__kanban_deadline_max_day_int} \int_compare:nNnT {\l__kanban_reference_day_int} < {\l__kanban_project_boundary_day_int} {\int_decr:N \l__kanban_current_month_int} \int_compare:nNnTF {\l__kanban_current_month_int} > {\l__kanban_project_month_int} {\booltrue{kb@overdue}} { \int_compare:nNnT {\l__kanban_current_month_int} = {\l__kanban_project_month_int} {\booltrue{kb@duenow}} } } % \end{macrocode} % % \begin{macro}{\kb@evaloverdue} % This internal entry point reduces every deadline value to two % renderer-facing facts, overdue and due now. It resets both facts, tries the % calendar grammar first, and otherwise distinguishes a project month from a % neutral label. A project month is classified only when the board has a % valid start-date snapshot. The function issues no diagnostics, because % every invalid value has already been reported when its task was declared. % \begin{macrocode} \cs_new_protected:Npn \kb@evaloverdue #1 { \boolfalse{kb@overdue} \boolfalse{kb@duenow} \__kanban_parse_date:nn {#1} {\kanban@boarddateformat} \bool_if:NTF \l__kanban_valid_date_bool {\__kanban_classify_calendar_date:} { \bool_if:NF \l__kanban_date_syntax_bool { \__kanban_parse_project_month:n {#1} \bool_if:NT \l__kanban_valid_project_month_bool { \__kanban_parse_date:nn {\kanban@thestart} {\kanban@boarddateformat} \bool_if:NT \l__kanban_valid_date_bool { \int_set_eq:NN \l__kanban_project_start_day_int \l__kanban_deadline_day_int \int_set_eq:NN \l__kanban_project_start_month_int \l__kanban_deadline_month_int \int_set_eq:NN \l__kanban_project_start_year_int \l__kanban_deadline_year_int \__kanban_classify_project_month: } } } } } % \end{macrocode} % \end{macro} % % \paragraph{Deadline diagnostics at declaration time.} % The collector calls this check for every accepted task that carries a % deadline. A value with date syntax that fails validation, and a value with % project-month syntax that lies outside the accepted range, each produce one % named warning. Because the check runs while the task declaration is being % read, the warning carries the line number of the task. Descriptive labels % produce no diagnostic. % \begin{macrocode} \cs_new_protected:Npn \__kanban_deadline_diagnose:n #1 { \__kanban_parse_date:nn {#1} {\kanban@boarddateformat} \bool_if:NF \l__kanban_valid_date_bool { \bool_if:NTF \l__kanban_date_syntax_bool {\msg_warning:nnn {kanban-board}{invalid-deadline}{#1}} { \__kanban_parse_project_month:n {#1} \bool_if:nT { \l__kanban_project_month_syntax_bool && ! \l__kanban_valid_project_month_bool } {\msg_warning:nnn {kanban-board}{invalid-project-month}{#1}} } } } \cs_new_protected:Npn \kanban@diagnosedeadline #1 {\tl_if_blank:nF {#1} {\__kanban_deadline_diagnose:n {#1}}} % \end{macrocode} % % \paragraph{Validated persistent and board-local date setters.} % Start and reference dates enter the package through several configuration % scopes, but every accepted non-empty value is stored canonically. The % persistent setters update the defaults for future boards globally. The % board setters update only the current environment. Empty values clear the % corresponding setting. Invalid non-empty values keep the previously % accepted value and issue the shared named warning. The expanded definitions % store a self-contained canonical date rather than a reference to the % mutable parsing integers. % \begin{macrocode} \cs_new_protected:Npn \kanban@setstartdate #1 { \tl_if_blank:nTF {#1} {\gdef\kanban@startdate{}} { \__kanban_parse_date:nn {#1} {\kanban@dateformat} \bool_if:NTF \l__kanban_valid_date_bool {\xdef\kanban@startdate{\__kanban_canonical_date:}} {\msg_warning:nnnn {kanban-board}{invalid-date-setting} {startdate}{#1}} } } \cs_new_protected:Npn \kanban@setboardstartdate #1 { \tl_if_blank:nTF {#1} {\def\kanban@thestart{}} { \__kanban_parse_date:nn {#1} {\kanban@boarddateformat} \bool_if:NTF \l__kanban_valid_date_bool {\edef\kanban@thestart{\__kanban_canonical_date:}} {\msg_warning:nnnn {kanban-board}{invalid-date-setting} {board~startdate}{#1}} } } \cs_new_protected:Npn \kanban@setreferencedate #1 { \tl_if_blank:nTF {#1} {\gdef\kanban@referencedate{}} { \__kanban_parse_date:nn {#1} {\kanban@dateformat} \bool_if:NTF \l__kanban_valid_date_bool {\xdef\kanban@referencedate{\__kanban_canonical_date:}} {\msg_warning:nnnn {kanban-board}{invalid-date-setting} {reference-date}{#1}} } } \cs_new_protected:Npn \kanban@setboardreferencedate #1 { \tl_if_blank:nTF {#1} {\def\kanban@thereference{}} { \__kanban_parse_date:nn {#1} {\kanban@boarddateformat} \bool_if:NTF \l__kanban_valid_date_bool {\edef\kanban@thereference{\__kanban_canonical_date:}} {\msg_warning:nnnn {kanban-board}{invalid-date-setting} {board~reference-date}{#1}} } } % \end{macrocode} % % \paragraph{Safe dimension recognition.} % The column-gap setters must reject malformed input before TeX attempts a % dimension assignment, because an unrestricted assignment can consume later % document tokens while searching for a unit. The validator accepts three % forms. The first is a complete explicit dimension in a standard TeX unit, % with optional spaces before the unit and an optional |true| prefix on the % physical units. The second is one existing dimension or skip register. The % third is one token whose meaning is a dimension or glue parameter of TeX, % such as |\parindent| or |\baselineskip|. Those parameters are primitives % rather than registers, so the register tests do not recognize them. The % constant lists the meaning of all 21 dimension parameters and all 15 glue % parameters of TeX, and the membership test compares the meaning of the % candidate token, which also accepts a |\let| copy of a parameter. The % three math glue parameters are excluded because they cannot be assigned % to a length. The validator does not evaluate arbitrary macros or % dimension expressions at this boundary. The input is expanded through % |\text_expand:n|, so a macro that expands to a dimension is accepted while % a robust command is left intact and rejected. The two LaTeX2e-facing % wrappers translate the conditional and the named message for the % configuration layer. % \begin{macrocode} \str_const:Nx \c__kanban_tex_lengths_str { | \token_to_meaning:N \parindent | \token_to_meaning:N \mathsurround | \token_to_meaning:N \lineskiplimit | \token_to_meaning:N \hsize | \token_to_meaning:N \vsize | \token_to_meaning:N \maxdepth | \token_to_meaning:N \splitmaxdepth | \token_to_meaning:N \boxmaxdepth | \token_to_meaning:N \hfuzz | \token_to_meaning:N \vfuzz | \token_to_meaning:N \delimitershortfall | \token_to_meaning:N \nulldelimiterspace | \token_to_meaning:N \scriptspace | \token_to_meaning:N \predisplaysize | \token_to_meaning:N \displaywidth | \token_to_meaning:N \displayindent | \token_to_meaning:N \overfullrule | \token_to_meaning:N \hangindent | \token_to_meaning:N \hoffset | \token_to_meaning:N \voffset | \token_to_meaning:N \emergencystretch | \token_to_meaning:N \lineskip | \token_to_meaning:N \baselineskip | \token_to_meaning:N \parskip | \token_to_meaning:N \abovedisplayskip | \token_to_meaning:N \abovedisplayshortskip | \token_to_meaning:N \belowdisplayskip | \token_to_meaning:N \belowdisplayshortskip | \token_to_meaning:N \leftskip | \token_to_meaning:N \rightskip | \token_to_meaning:N \topskip | \token_to_meaning:N \splittopskip | \token_to_meaning:N \tabskip | \token_to_meaning:N \spaceskip | \token_to_meaning:N \xspaceskip | \token_to_meaning:N \parfillskip | } \prg_new_protected_conditional:Npnn \__kanban_dimension_token:N #1 {T,F,TF} { \token_if_dim_register:NTF #1 {\prg_return_true:} { \token_if_skip_register:NTF #1 {\prg_return_true:} { \exp_args:NNx \str_if_in:NnTF \c__kanban_tex_lengths_str { | \token_to_meaning:N #1 | } {\prg_return_true:} {\prg_return_false:} } } } \prg_new_protected_conditional:Npnn \__kanban_dimension_valid:n #1 {T,F,TF} { \tl_set:Nx \l__kanban_dimension_tl {\text_expand:n {#1}} \tl_trim_spaces:N \l__kanban_dimension_tl \exp_args:NnV \regex_match:nnTF {\A[+\-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)\s* (?:(?:true\s*)?(?:pt|pc|in|bp|cm|mm|dd|cc|sp)|ex|em)\Z} \l__kanban_dimension_tl {\prg_return_true:} { \exp_args:NV \tl_if_single_token:nTF \l__kanban_dimension_tl { \exp_args:NV \__kanban_dimension_token:NTF \l__kanban_dimension_tl {\prg_return_true:} {\prg_return_false:} } {\prg_return_false:} } } \cs_new_protected:Npn \kanban@validatedimensionTF #1#2#3 {\__kanban_dimension_valid:nTF {#1}{#2}{#3}} \cs_new_protected:Npn \kanban@dimensionwarning #1#2 {\msg_warning:nnnn {kanban-board}{invalid-dimension}{#1}{#2}} \ExplSyntaxOff % \end{macrocode} % % \begin{macrocode} % % \end{macrocode}