% phonenumbers package: phonenumbers.sty
% LaTeX package for formatting telephone numbers
% Author: K. Wehr
% Version: 2.5
% Date: 2022-07-01
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3
% of this license or (at your option) any later version.
% The latest version of this license is in
% http://www.latex-project.org/lppl.txt
% and version 1.3 or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
\NeedsTeXFormat{LaTeX2e}[2021-11-15]
\ProvidesExplPackage {phonenumbers} {2022-07-01} {2.5} {Telephone number package}

\RequirePackage {l3keys2e}

\clist_const:Nn \c_phone_ziffern_clist {0,1,2,3,4,5,6,7,8,9}
\clist_const:Nn \c_phone_gliederungszeichen_clist {(,),[,],/}
\clist_const:Nn \c_phone_landeskennzahlen_clist {1,
20,
211,
212,
213,
216,
218,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229,
230,
231,
232,
233,
234,
235,
236,
237,
238,
239,
240,
241,
242,
243,
244,
245,
246,
247,
248,
249,
250,
251,
252,
253,
254,
255,
256,
257,
258,
260,
261,
262,
263,
264,
265,
266,
267,
268,
269,
27,
290,
291,
297,
298,
299,
30,
31,
32,
33,
34,
350,
351,
352,
353,
354,
355,
356,
357,
358,
359,
36,
370,
371,
372,
373,
374,
375,
376,
377,
378,
379,
380,
381,
382,
383,
385,
386,
387,
388,
389,
39,
40,
41,
420,
421,
423,
43,
44,
45,
46,
47,
48,
49,
500,
501,
502,
503,
504,
505,
506,
507,
508,
509,
51,
52,
53,
54,
55,
56,
57,
58,
590,
591,
592,
593,
594,
595,
596,
597,
598,
599,
60,
61,
62,
63,
64,
65,
66,
670,
672,
673,
674,
675,
676,
677,
678,
679,
680,
681,
682,
683,
685,
686,
687,
688,
689,
690,
691,
692,
7,
800,
808,
81,
82,
84,
850,
852,
853,
855,
856,
86,
870,
878,
880,
881,
882,
883,
886,
888,
90,
91,
92,
93,
94,
95,
960,
961,
962,
963,
964,
965,
966,
967,
968,
971,
972,
973,
974,
975,
976,
977,
979,
98,
991,
992,
993,
994,
995,
996,
998}

\tl_const:Nn \c_phone_bindestrich_tl { \kern 1pt - \kern 1pt }
\tl_const:Nn \c_phone_schraegstrich_tl { \kern 1pt \slash \kern 1pt }
\tl_const:Nn \c_phone_pluszeichen_tl { + \kern 1pt }

\str_new:N \l_phone_land_str
\str_new:N \l_phone_heimatland_str
\str_new:N \l_phone_auslandsvorwahltyp_str
\str_new:N \l_phone_vorwahl_str
\str_new:N \l_phone_vorwahldarstellung_str
\str_new:N \l_phone_vorwahltrennung_str
\str_new:N \l_phone_auslandsvorwahltrennung_str
\str_new:N \l_phone_heimatvorwahl_str
\str_new:N \l_phone_bereinigte_nummer_str
\str_new:N \l_phone_bereinigte_durchwahl_str
\str_new:N \l_phone_linktext_str

\int_new:N \l_phone_ziffernzahl_int
\int_new:N \l_phone_nummernlaenge_int
\int_new:N \l_phone_gruppierungsminimum_int
\int_new:N \l_phone_hauptnummernlaenge_int % Nummernlänge im nationalen Format ohne Durchwahl
\int_new:N \l_phone_vorwahllaenge_int
\int_new:N \l_phone_bindestrichposition_int

\tl_new:N \l_phone_ausgabetext_tl
\tl_new:N \l_phone_formatierte_nummer_tl

\bool_new:N \l_phone_vorwahl_gefunden_bool
\bool_new:N \l_phone_zeilenumbruch_bool
\bool_new:N \l_phone_eingabe_leer_bool
\bool_new:N \l_phone_durchwahl_leer_bool
\bool_new:N \l_phone_nummer_verlinken_bool
\bool_new:N \l_phone_teilnehmerrufnummer_gueltig_bool
\bool_new:N \l_phone_erstes_zeichen_bool
\bool_new:N \l_phone_zweites_zeichen_bool
\bool_new:N \l_phone_auslandsnummer_bool
\bool_new:N \l_phone_null_am_anfang_bool

\cs_generate_variant:Nn \str_if_eq:nnT {xnT}
\cs_generate_variant:Nn \str_if_eq:nnF {xnF}
\cs_generate_variant:Nn \str_if_eq:nnTF {xnTF}
\cs_generate_variant:Nn \str_case:nn {xn}
\cs_generate_variant:Nn \str_case:nnT {xnT}
\cs_generate_variant:Nn \str_case:nnF {xnF}
\cs_generate_variant:Nn \str_case:nnTF {xnTF}
\cs_generate_variant:Nn \str_tail:n {x}
\cs_generate_variant:Nn \tl_put_right:Nn {Nv}
\cs_generate_variant:Nn \msg_warning:nnn {onn}
\cs_generate_variant:Nn \msg_warning:nnn {nnV}

\msg_new:nnn {phonenumbers} {illegal character}
   {
      illegal~character~'#1'~in~phone~number~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {empty input}
   {
      empty~phone~number~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {empty extension}
   {
      empty~extension~(Durchwahl)~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {illegal extension}
   {
      extension~(Durchwahl)~of~
      \str_if_empty:NTF \l_phone_land_str
         {
            unsupported
         }
         {
            \use:c { c_phone_ \l_phone_land_str _landesadjektiv_tl }
         }
      \c_space_tl
      phone~number~ignored~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {odd extension}
   {
      \use:c { c_phone_ \l_phone_land_str _landesadjektiv_tl }
      \c_space_tl
      non-geographic~number~should~not~contain~an~extension~(Durchwahl)~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {subscriber number too short}
   {
      subscriber~number~(Teilnehmerrufnummer)~has~less~than~#1~digits~in~
      \use:c { c_phone_ \l_phone_land_str _landesadjektiv_tl }
      \c_space_tl
      phone~number~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {subscriber number too long}
   {
      subscriber~number~(Teilnehmerrufnummer)~has~more~than~#1~digits~in~
      \use:c { c_phone_ \l_phone_land_str _landesadjektiv_tl }
      \c_space_tl
      phone~number~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {illegal start of subscriber number}
   {
      subscriber~number~(Teilnehmerrufnummer)~starts~with~#1~in~
      \use:c { c_phone_ \l_phone_land_str _landesadjektiv_tl }
      \c_space_tl
      phone~number~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {missing subscriber number}
   {
      no~subscriber~number~(Teilnehmerrufnummer)~given~in~
      \use:c { c_phone_ \l_phone_land_str _landesadjektiv_tl }
      \c_space_tl
      phone~number~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {invalid area code}
   {
      unknown~area~code~(Vorwahl)~in~
      \use:c { c_phone_ \l_phone_land_str _landesadjektiv_tl }
      \c_space_tl
      phone~number~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {number too short}
   {
      \use:c { c_phone_ \l_phone_land_str _landesadjektiv_tl }
      \c_space_tl
      #1~number~has~less~than~#2~digits~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {number too long}
   {
      \use:c { c_phone_ \l_phone_land_str _landesadjektiv_tl }
      \c_space_tl
      #1~number~has~more~than~#2~digits~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {home country set}
   {
      home~country~set~to~
      \l_phone_heimatland_str
      \c_space_tl
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {home country cleared}
   {
      home~country~
      \l_phone_heimatland_str
      \c_space_tl
      has~been~deleted~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {home area code set}
   {
      Your~home~is~in~
      \tl_use:c { c_phone_ \l_phone_heimatland_str _ortsname_ #1 _tl }
      \c_space_tl
      (area~code~#1)~according~to~line~
      \msg_line_number:.
   }

\msg_new:nnn {phonenumbers} {home area code cleared}
   {
      home~area~code~
      \l_phone_heimatvorwahl_str
      \c_space_tl
      has~been~deleted~\msg_line_context:
   }

\msg_new:nnn {phonenumbers} {invalid home area code}
   {
      #1~unknown~as~
      \use:c { c_phone_ \l_phone_heimatland_str _landesadjektiv_tl }
      \c_space_tl
      geographic~area~code~(Ortsvorwahl)~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {invalid country code}
   {
      illegal~country~code~
      \msg_line_context:
   }

\msg_new:nnn {phonenumbers} {country code only}
   {
      phone~number~consists~of~a~country~code~only~
      \msg_line_context:
   }

\keys_define:nn {phonenumbers}
   {
      country .choices:nn = {DE,AT,FR,UK,US}
         {
            \str_set_eq:NN \l_phone_land_str \l_keys_choice_tl
         },
      country .initial:n = DE,
      country .value_required:n = true
   }

\keys_define:nn {phonenumbers}
   {
      home-country .choices:nn = {DE,AT,FR,UK,US,none}
         {
            \str_if_eq:VnTF \l_keys_choice_tl {none}
               {
                  \str_if_empty:NF \l_phone_heimatland_str
                     {
                        \msg_info:nn {phonenumbers} {home country cleared}
                        \str_clear:N \l_phone_heimatland_str
                     }
               }
               {
                  \str_set_eq:NN \l_phone_heimatland_str \l_keys_choice_tl
                  \msg_info:nn {phonenumbers} {home country set}
               }

            \str_if_empty:NF \l_phone_heimatvorwahl_str
               {
                  \msg_info:nn {phonenumbers} {home area code cleared}
                  \str_clear:N \l_phone_heimatvorwahl_str
               }
         },
      home-country .initial:n = none,
      home-country .value_required:n = true
   }

\keys_define:nn {phonenumbers}
   {
      foreign .choices:nn = {off,european,american,international}
         {
            \str_set_eq:NN \l_phone_auslandsvorwahltyp_str \l_keys_choice_tl
         },
      foreign .initial:n = off,
      foreign .default:n = international
   }

\keys_define:nn {phonenumbers}
   {
      area-code .choices:nn = {number,place,place-and-number}
         {
            \str_set_eq:NN \l_phone_vorwahldarstellung_str \l_keys_choice_tl
         },
      area-code .initial:n = number,
      area-code .value_required:n = true
   }

\keys_define:nn {phonenumbers}
   {
      area-code-sep .choices:nn = {space,slash,brackets,hyphen}
         {
            \str_set_eq:NN \l_phone_vorwahltrennung_str \l_keys_choice_tl
         },
      area-code-sep .initial:n = slash,
      area-code-sep .value_required:n = true
   }

\keys_define:nn {phonenumbers}
   {
      foreign-area-code-sep .choices:nn = {space,brackets}
         {
            \str_set_eq:NN \l_phone_auslandsvorwahltrennung_str \l_keys_choice_tl
         },
      foreign-area-code-sep .initial:n = space,
      foreign-area-code-sep .value_required:n = true
   }

\keys_define:nn {phonenumbers}
   {
      link .choices:nn = {on,off}
         {
            \str_if_eq:VnTF \l_keys_choice_tl {on}
               {
                  \bool_set_true:N \l_phone_nummer_verlinken_bool
               }
               {
                  \bool_set_false:N \l_phone_nummer_verlinken_bool
               }
         },
      link .initial:n = on,
      link .value_required:n = true
   }

\keys_define:nn {phonenumbers}
   {
      home-area-code .code:n =
         {
            \str_if_eq:nnTF {#1} {none}
               {
                  \str_if_empty:NF \l_phone_heimatvorwahl_str
                     {
                        \msg_info:nn {phonenumbers} {home area code cleared}
                        \str_clear:N \l_phone_heimatvorwahl_str
                     }
               }
               {
                  \str_if_empty:NT \l_phone_heimatland_str
                     {
                        \str_set_eq:NN \l_phone_heimatland_str \l_phone_land_str
                        \msg_info:nn {phonenumbers} {home country set}
                     }

                  \use:c { phone_ \l_phone_heimatland_str _heimatvorwahl_erlaubt:nT } {#1}
                     {
                        % Auslassen der Vorwahl möglich, Heimatvorwahl setzen:
                        \str_set:Nn \l_phone_heimatvorwahl_str {#1}
                        \msg_info:nnn {phonenumbers} {home area code set} {#1}
                     }
               }
         },
      home-area-code .initial:n = none,
      home-area-code .value_required:n = true
   }

\keys_define:nn {phonenumbers}
   {
      group-min .choices:nn = {3,4,5,6,7}
         {
            \int_set_eq:NN \l_phone_gruppierungsminimum_int \l_keys_choice_tl
         },
      group-min .initial:n = {3} ,
      group-min .value_required:n = true
   }

% einen Nummernteil von vorne in Zweiergruppen gruppiert schreiben, sofern er nicht kürzer als das Gruppierungsminimum ist
\cs_new_protected:Npn \phone_von_vorne_gruppiert_schreiben:n #1
   {
      \int_set:Nn \l_phone_ziffernzahl_int { \tl_count:n {#1} }
      \tl_clear:N \l_phone_formatierte_nummer_tl

      \int_step_inline:nn {\l_phone_ziffernzahl_int}
         {
            \tl_put_right:Nx \l_phone_formatierte_nummer_tl { \tl_item:nn {#1} {##1} }

            % Nach jeder zweiten Ziffer wird ein kleiner Abstand eingefügt, wenn die Ziffernzahl nicht unterhalb des Gruppierungsminimums liegt.
            \bool_lazy_all:nT
               {
                  { ! \int_compare_p:nNn { \l_phone_ziffernzahl_int } < { \l_phone_gruppierungsminimum_int } }
                  { \int_if_even_p:n {##1} }
                  { \int_compare_p:nNn {##1} < { \l_phone_ziffernzahl_int } }
               }
               {
                  \tl_put_right:Nn \l_phone_formatierte_nummer_tl {\,}
               }
         }

      \tl_put_right:NV \l_phone_ausgabetext_tl \l_phone_formatierte_nummer_tl
   }

\cs_generate_variant:Nn \phone_von_vorne_gruppiert_schreiben:n {x,V}

% einen Nummernteil in Zweiergruppen gruppiert mit führender Einzelziffer schreiben, sofern er nicht kürzer als das Gruppierungsminimum ist
\cs_new_protected:Npn \phone_versetzt_gruppiert_schreiben:n #1
   {
      \int_set:Nn \l_phone_ziffernzahl_int { \tl_count:n {#1} }
      \tl_clear:N \l_phone_formatierte_nummer_tl

      \int_step_inline:nn {\l_phone_ziffernzahl_int}
         {
            \tl_put_right:Nx \l_phone_formatierte_nummer_tl { \tl_item:nn {#1} {##1} }

            % Nach der 1., 3., 5., ... Ziffer wird ein kleiner Abstand eingefügt, wenn die Ziffernzahl nicht unterhalb des Gruppierungsminimums liegt.
            \bool_lazy_all:nT
               {
                  { ! \int_compare_p:nNn { \l_phone_ziffernzahl_int } < { \l_phone_gruppierungsminimum_int } }
                  { \int_if_odd_p:n {##1} }
                  { \int_compare_p:nNn {##1} < { \l_phone_ziffernzahl_int } }
               }
               {
                  \tl_put_right:Nn \l_phone_formatierte_nummer_tl {\,}
               }
         }

      \tl_put_right:NV \l_phone_ausgabetext_tl \l_phone_formatierte_nummer_tl
   }

\cs_generate_variant:Nn \phone_versetzt_gruppiert_schreiben:n {x}

% einen Nummernteil von hinten in Zweiergruppen gruppiert schreiben, sofern er nicht kürzer als das Gruppierungsminimum ist
\cs_new_protected:Npn \phone_von_hinten_gruppiert_schreiben:n #1
   {
      \int_set:Nn \l_phone_ziffernzahl_int { \tl_count:n {#1} }
      \tl_clear:N \l_phone_formatierte_nummer_tl

      \int_step_inline:nnnn {\l_phone_ziffernzahl_int} {-1} {1}
         {
            \tl_put_left:Nx \l_phone_formatierte_nummer_tl { \tl_item:nn {#1} {##1} }

            % Nach jeder zweiten Ziffer wird ein kleiner Abstand eingefügt, wenn die Ziffernzahl nicht unterhalb des Gruppierungsminimums liegt.
            \bool_lazy_all:nT
               {
                  { ! \int_compare_p:nNn { \l_phone_ziffernzahl_int } < { \l_phone_gruppierungsminimum_int } }
                  { \int_if_even_p:n { \l_phone_ziffernzahl_int + 1 - ##1 } }
                  { \int_compare_p:nNn {##1} > {1} }
               }
               {
                  \tl_put_left:Nn \l_phone_formatierte_nummer_tl {\,}
               }
         }

      \tl_put_right:NV \l_phone_ausgabetext_tl \l_phone_formatierte_nummer_tl
   }

\cs_generate_variant:Nn \phone_von_hinten_gruppiert_schreiben:n {x,V}

% #1: Vorwahl, #2: Soll die führende Null ausgegeben werden?
\cs_new_protected:Npn \phone_geklammerte_ziffernvorwahl_schreiben:nN #1#2
   {
      % Klammern werden nur bei Ortsvorwahlen gesetzt
      \clist_if_in:cnTF { c_phone_ \l_phone_land_str _ortsvorwahlen_clist } {#1}
         {
            \tl_put_right:Nn \l_phone_ausgabetext_tl {(}
            \use:c { phone_ \l_phone_land_str _ziffernvorwahl_schreiben:nN } {#1} #2
            \tl_put_right:Nn \l_phone_ausgabetext_tl {)}
         }
         {
            \use:c { phone_ \l_phone_land_str _ziffernvorwahl_schreiben:nN } {#1} #2
         }
   }

% #1: Landeskennzahl
\cs_new_protected:Npn \phone_gruppierte_auslandsvorwahl_schreiben:n #1
   {
      \str_case:Vn \l_phone_auslandsvorwahltyp_str
         {
            {international} { \tl_put_right:Nn \l_phone_ausgabetext_tl { \c_phone_pluszeichen_tl #1 } }
            {european} { \phone_von_hinten_gruppiert_schreiben:n { 00 #1 } }
            {american} { \phone_von_hinten_gruppiert_schreiben:n { 011 #1 } }
         }
   }

\cs_generate_variant:Nn \phone_gruppierte_auslandsvorwahl_schreiben:n {x}

% #1: Landeskennzahl
\cs_new_protected:Npn \phone_ungruppierte_auslandsvorwahl_schreiben:n #1
   {
      \tl_put_right:Nx \l_phone_ausgabetext_tl
         {
            \str_case:VnF \l_phone_auslandsvorwahltyp_str
               {
                  {european} { 00 #1 }
                  {american} { 011 #1 }
               }
               {
                  \c_phone_pluszeichen_tl #1
               }
         }
   }

\cs_generate_variant:Nn \phone_ungruppierte_auslandsvorwahl_schreiben:n {V}

% #1: eingebene Nummer
\cs_new_protected:Npn \phone_nummer_ueberpruefen:n #1
   {
      \tl_if_blank:nTF {#1}
         {
            \msg_warning:nn {phonenumbers} {empty input}
            \bool_set_true:N \l_phone_eingabe_leer_bool
         }
         {
            \bool_set_false:N \l_phone_eingabe_leer_bool
         }

      \str_clear:N \l_phone_bereinigte_nummer_str

      \bool_set_true:N \l_phone_erstes_zeichen_bool
      \bool_set_false:N \l_phone_zweites_zeichen_bool
      \bool_set_false:N \l_phone_auslandsnummer_bool
      \bool_set_false:N \l_phone_null_am_anfang_bool

      \tl_map_inline:nn {#1}
         {
            \clist_if_in:NnTF \c_phone_ziffern_clist {##1}
               {
                  % Ziffer eingelesen
                  \str_put_right:Nn \l_phone_bereinigte_nummer_str {##1}

                  \bool_lazy_and:nnT {\l_phone_erstes_zeichen_bool} { \str_if_eq_p:nn {##1} {0} }
                     {
                        \bool_set_true:N \l_phone_null_am_anfang_bool
                     }

                  \bool_if:NT \l_phone_zweites_zeichen_bool
                     {
                        \bool_set_false:N \l_phone_zweites_zeichen_bool

                        \bool_lazy_and:nnT {\l_phone_null_am_anfang_bool} { \str_if_eq_p:nn {##1} {0} }
                           {
                              \bool_set_true:N \l_phone_auslandsnummer_bool
                              \str_clear:N \l_phone_bereinigte_nummer_str
                           }
                     }

                  \bool_if:NT \l_phone_erstes_zeichen_bool
                     {
                        \bool_set_false:N \l_phone_erstes_zeichen_bool
                        \bool_set_true:N \l_phone_zweites_zeichen_bool
                     }
               }
               {
                  % keine Ziffer eingelesen
                  \bool_lazy_and:nnTF {\l_phone_erstes_zeichen_bool} { \str_if_eq_p:nn {##1} {+} }
                     {
                        % führendes + eingelesen
                        \bool_set_true:N \l_phone_auslandsnummer_bool
                        \bool_set_false:N \l_phone_erstes_zeichen_bool
                     }
                     {
                        \clist_if_in:NnF \c_phone_gliederungszeichen_clist {##1}
                           {
                              \str_if_eq:nnTF {##1} {-}
                                 {
                                    % Bindestrich eingelesen
                                    \str_put_right:Nn \l_phone_bereinigte_nummer_str {-}
                                 }
                                 {
                                    % unerlaubtes Zeichen (z. B. Buchstaben) eingelesen
                                    \msg_warning:nnx {phonenumbers} {illegal character} {##1}
                                 }
                           }
                     }
               }
         }

      \bool_if:NT \l_phone_auslandsnummer_bool
         {
            \int_set:Nn \l_tmpa_int { \str_count:N \l_phone_bereinigte_nummer_str }

            \int_compare:nNnTF {\l_tmpa_int} > {1}
               {
                  \str_if_eq:xnTF { \str_head:N \l_phone_bereinigte_nummer_str } {1}
                     {
                        \str_set:Nn \l_phone_land_str {US}
                        \str_set:Nx \l_phone_bereinigte_nummer_str { \str_tail:N \l_phone_bereinigte_nummer_str }
                     }
                     {
                        \int_compare:nNnTF {\l_tmpa_int} > {2}
                           {
                              \str_case:xnTF { \str_range:Nnn \l_phone_bereinigte_nummer_str {1} {2} }
                                 {
                                    {33} { \str_set:Nn \l_phone_land_str {FR} }
                                    {43} { \str_set:Nn \l_phone_land_str {AT} }
                                    {44} { \str_set:Nn \l_phone_land_str {UK} }
                                    {49} { \str_set:Nn \l_phone_land_str {DE} }
                                 }
                                 {
                                    \str_set:Nx \l_phone_bereinigte_nummer_str { \str_range:Nnn \l_phone_bereinigte_nummer_str {3} {-1} }
                                    \str_put_left:Nn \l_phone_bereinigte_nummer_str {0}
                                 }
                                 {
                                    \int_compare:nNnTF {\l_tmpa_int} > {3}
                                       {
                                          \str_case:xnTF { \str_range:Nnn \l_phone_bereinigte_nummer_str {1} {3} }
                                             {
                                                {262} { \str_set:Nx \l_phone_bereinigte_nummer_str { \str_range:Nnn \l_phone_bereinigte_nummer_str {4} {-1} } }
                                                {508} { } % Saint-Pierre-et-Miquelon
                                                {590} { \str_set:Nx \l_phone_bereinigte_nummer_str { \str_range:Nnn \l_phone_bereinigte_nummer_str {4} {-1} } }
                                                {594} { \str_set:Nx \l_phone_bereinigte_nummer_str { \str_range:Nnn \l_phone_bereinigte_nummer_str {4} {-1} } }
                                                {596} { \str_set:Nx \l_phone_bereinigte_nummer_str { \str_range:Nnn \l_phone_bereinigte_nummer_str {4} {-1} } }
                                             }
                                             {
                                                \str_set:Nn \l_phone_land_str {FR}
                                                \str_put_left:Nn \l_phone_bereinigte_nummer_str {0}
                                             }
                                             {
                                                \str_clear:N \l_phone_land_str % Länge größer 3, nicht mit 262, 508, 590, 594 oder 596 beginnend
                                             }
                                       }
                                       {
                                          \str_clear:N \l_phone_land_str % Länge 3, nicht mit 33, 43, 44 oder 49 beginnend
                                       }
                                 }
                           }
                           {
                              \str_clear:N \l_phone_land_str % Länge 2, nicht mit 1 beginnend
                           }
                     }
               }
               {
                  \str_clear:N \l_phone_land_str % Länge kleiner oder gleich 1
               }
         }

      \bool_lazy_all:nT
         {
            { ! \str_if_empty_p:N \l_phone_land_str }
            { \bool_if_p:c { c_phone_ \l_phone_land_str _erlaubt_durchwahl_bool } }
            { \str_if_empty_p:N \l_phone_bereinigte_durchwahl_str }
         }
         {
            % Nummer ohne explizite Durchwahl in einem Land, das Durchwahlen erlaubt
            \int_set:Nn \l_phone_bindestrichposition_int {-1}

            \int_step_inline:nnnn { \str_count:N \l_phone_bereinigte_nummer_str } {-1} {1}
               {
                  \int_compare:nNnT {\l_phone_bindestrichposition_int} = {-1}
                     {
                        \str_if_eq:xnT { \str_item:Nn \l_phone_bereinigte_nummer_str {##1} } {-}
                           {
                              \int_set:Nn \l_phone_bindestrichposition_int {##1}
                           }
                     }
               }

            \int_compare:nNnF {\l_phone_bindestrichposition_int} = {-1}
               {
                  % Nummer enthält einen Bindestrich
                  \str_set:Nx \l_tmpa_str { \str_range:Nnn \l_phone_bereinigte_nummer_str {1} { \l_phone_bindestrichposition_int - 1 } }
                  \tl_remove_all:Nn \l_tmpa_str {-}

                  \phone_enthaelt_vorwahl:NT \l_tmpa_str
                     {
                        \int_compare:nNnT { \str_count:N \l_phone_vorwahl_str } < { \str_count:N \l_tmpa_str }
                           {
                              % dem Bindestrich geht eine Vorwahl voran und der Teil vor dem Bindestrich ist länger als die bloße Vorwahl
                              \bool_set_true:N \l_tmpa_bool
                           }
                     }

                  \str_if_eq:xnF { \str_head:N \l_tmpa_str } {0}
                     {
                        \bool_set_true:N \l_tmpa_bool
                        % Nummer beginnt nicht mit 0 (ist also eine reine Teilnehmerrufnummer)
                     }

                  \bool_if:NT \l_tmpa_bool
                     {
                        % Teil nach dem Bindestrich als Durchwahl abtrennen
                        \phone_durchwahl_ueberpruefen:x { \str_range:Nnn \l_phone_bereinigte_nummer_str { \l_phone_bindestrichposition_int + 1 } {-1} }
                        \str_set_eq:NN \l_phone_bereinigte_nummer_str \l_tmpa_str
                     }
               }
         }

         \tl_remove_all:Nn \l_phone_bereinigte_nummer_str {-}
   }

\cs_generate_variant:Nn \phone_nummer_ueberpruefen:n {x}

% #1: eingebene Durchwahl
\cs_new_protected:Npn \phone_durchwahl_ueberpruefen:n #1
   {
      \tl_if_blank:nTF {#1}
         {
            \msg_warning:nn {phonenumbers} {empty extension}
            \bool_set_true:N \l_phone_durchwahl_leer_bool
         }
         {
            \bool_set_false:N \l_phone_durchwahl_leer_bool
         }

      \str_clear:N \l_phone_bereinigte_durchwahl_str

      \tl_map_inline:nn {#1}
         {
            \clist_if_in:NnTF \c_phone_ziffern_clist {##1}
               {
                  \str_put_right:Nn \l_phone_bereinigte_durchwahl_str {##1}
               }
               {
                  \msg_warning:nnx {phonenumbers} {illegal character} {##1}
               }
         }
   }

\cs_generate_variant:Nn \phone_durchwahl_ueberpruefen:n {x}

\cs_new_protected:Npn \phone_nummer_ausgeben:
   {
      \bool_if:NF \l_phone_eingabe_leer_bool
         {
            \str_if_empty:NTF \l_phone_land_str
               {
                  \phone_nicht_unterstuetzte_nummer_schreiben:
               }
               {
                  \use:c {phone_ \l_phone_land_str _nummer_schreiben:}
               }
         }

      \bool_lazy_and:nnTF {\l_phone_nummer_verlinken_bool} { ! \str_if_empty_p:N \l_phone_linktext_str }
         {
            \IfPackageLoadedTF {hyperref}
               {
                  \href { tel \c_colon_str \l_phone_linktext_str } {\l_phone_ausgabetext_tl}
               }
               {
                  \l_phone_ausgabetext_tl
               }
         }
         {
            \l_phone_ausgabetext_tl
         }
   }

\NewDocumentCommand \setphonenumbers {m}
   {
      \keys_set:no {phonenumbers} {#1}
   }

\NewDocumentCommand \phonenumber {omo}
   {
      \group_begin:
      \IfValueT {#1}
         {
            \keys_set:no {phonenumbers} {#1}
         }

      \IfValueT {#3}
         {
            \phone_durchwahl_ueberpruefen:x {#3}
         }

      \phone_nummer_ueberpruefen:x {#2}

      \IfValueT {#3}
         {
            \bool_lazy_or:nnT
               { \str_if_empty_p:N \l_phone_land_str }
               { ! \bool_if_p:c { c_phone_ \l_phone_land_str _erlaubt_durchwahl_bool } }
               {
                  \bool_if:NF \l_phone_durchwahl_leer_bool
                     {
                        \msg_warning:nn {phonenumbers} {illegal extension}
                     }
               }
         }

      \phone_nummer_ausgeben:
      \group_end:
   }

\NewDocumentCommand \AreaCodesGeographic {o}
   {
      \group_begin:
      \IfValueT {#1}
         {
            \keys_set:no {phonenumbers} {#1}
         }

      \use:c { phone_ \l_phone_land_str _vorwahlliste_ausgeben:n } {ortsvorwahlen}
      \group_end:
   }

\NewDocumentCommand \AreaCodesNonGeographic {o}
   {
      \group_begin:
      \IfValueT {#1}
         {
            \keys_set:no {phonenumbers} {#1}
         }

      \use:c { phone_ \l_phone_land_str _vorwahlliste_ausgeben:n } {sondervorwahlen}
      \group_end:
   }

\NewDocumentCommand \CountryCodes { }
   {
      \bool_set_false:N \l_phone_zeilenumbruch_bool

      \clist_map_inline:Nn \c_phone_landeskennzahlen_clist
         {
            \bool_if:NTF \l_phone_zeilenumbruch_bool
               {
                  \\
               }
               {
                  \bool_set_true:N \l_phone_zeilenumbruch_bool
               }

            ##1
         }
   }

% #1: Nummer, die auf Enthaltensein einer Vorwahl überprüft wird
\prg_new_protected_conditional:Npnn \phone_enthaelt_vorwahl:N #1 {TF,T}
   {
      \bool_set_false:N \l_phone_vorwahl_gefunden_bool

      \int_set:Nn \l_phone_nummernlaenge_int { \str_count:N #1 }

      \int_step_inline:nnnn {6} {-1} {2}
         {
            \bool_if:NF \l_phone_vorwahl_gefunden_bool
               {
                  \int_compare:nT { \l_phone_nummernlaenge_int >= ##1 }
                     {
                        \str_set:Nx \l_phone_vorwahl_str { \str_range:Nnn #1 {1} {##1} }

                        \clist_if_in:cVT { c_phone_ \l_phone_land_str _vorwahlen_clist } \l_phone_vorwahl_str
                           {
                              \bool_set_true:N \l_phone_vorwahl_gefunden_bool
                           }
                     }
               }
         }

      \bool_if:NTF \l_phone_vorwahl_gefunden_bool
         {
            \prg_return_true:
         }
         {
            \prg_return_false:
         }
   }

% #1: Nummer, #2: Mindestlänge, #3: Höchstlänge, #4: Nummernart
\cs_new_protected:Npn \phone_nummernlaenge_ueberpruefen:nnnn #1#2#3#4
   {
      \int_set:Nn \l_tmpa_int { \str_count:n {#1} }

      \int_compare:nNnTF {\l_tmpa_int} < {#2}
         {
            \msg_warning:nnnn {phonenumbers} {number too short} {#4} {#2}
         }
         {
            \int_compare:nNnTF {\l_tmpa_int} > {#3}
               {
                  \msg_warning:nnnn {phonenumbers} {number too long} {#4} {#3}
               }
               {
                  \bool_set_true:N \l_phone_teilnehmerrufnummer_gueltig_bool
               }
         }
   }

\cs_generate_variant:Nn \phone_nummernlaenge_ueberpruefen:nnnn {V,o}

\cs_new_protected:Npn \phone_nicht_unterstuetzte_nummer_schreiben:
   {
      \int_set:Nn \l_phone_nummernlaenge_int { \str_count:N \l_phone_bereinigte_nummer_str }

      \bool_set_false:N \l_phone_vorwahl_gefunden_bool

      \int_step_inline:nnnn {3} {-1} {1}
         {
            \bool_if:NF \l_phone_vorwahl_gefunden_bool
               {
                  \int_compare:nT { \l_phone_nummernlaenge_int >= ##1 }
                     {
                        \str_set:Nx \l_tmpa_str { \str_range:Nnn \l_phone_bereinigte_nummer_str {1} {##1} }

                        \clist_if_in:NVT \c_phone_landeskennzahlen_clist \l_tmpa_str
                           {
                              \bool_set_true:N \l_phone_vorwahl_gefunden_bool

                              \phone_ungruppierte_auslandsvorwahl_schreiben:V \l_tmpa_str

                              \int_compare:nNnTF {##1} = {\l_phone_nummernlaenge_int}
                                 {
                                    \msg_warning:nn {phonenumbers} {country code only}
                                 }
                                 {
                                    \tl_put_right:NV \l_phone_ausgabetext_tl \c_space_tl
                                    \phone_von_hinten_gruppiert_schreiben:x { \str_range:Nnn \l_phone_bereinigte_nummer_str {##1 + 1} {-1} }

                                    \str_put_right:Nn \l_phone_linktext_str {+}
                                    \str_put_right:NV \l_phone_linktext_str \l_phone_bereinigte_nummer_str
                                 }
                           }
                     }
               }
         }

      \bool_if:NF \l_phone_vorwahl_gefunden_bool
         {
            \msg_warning:nnn {phonenumbers} {invalid country code}
            \tl_put_right:NV \l_phone_ausgabetext_tl \c_phone_pluszeichen_tl
            \tl_put_right:NV \l_phone_ausgabetext_tl \l_phone_bereinigte_nummer_str
         }
   }

\file_input:n {phonenumbers-AT.def}
\file_input:n {phonenumbers-DE.def}
\file_input:n {phonenumbers-FR.def}
\file_input:n {phonenumbers-UK.def}
\file_input:n {phonenumbers-US.def}

\ProcessKeysPackageOptions {phonenumbers}