%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2000-2024. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%

%%----------------------------------------------------------------------
%% Purpose : Scanner for text encoded Megaco/H.248 messages
%%----------------------------------------------------------------------

-module('megaco_text_scanner').
-moduledoc false.

-export([scan/1, skip_sep_chars/2]).

-include_lib("megaco/include/megaco.hrl").
-include_lib("megaco/src/engine/megaco_message_internal.hrl").
-include("megaco_text_tokens.hrl").

-define(LOWER1(Char),
	if
	    (Char >= $A) andalso (Char =< $Z) ->
		Char - ($A - $a);
	    true ->
		Char
	end).

%% This is used when we _know_ it to be upper case
-define(LOWER2(Char), Char - ($A - $a)).

scan(Bin) when is_binary(Bin) ->
    Chars = erlang:binary_to_list(Bin),
    tokens1(Chars, 1, []);
scan(Chars) when is_list(Chars) ->
    tokens1(Chars, 1, []).

%% As long as we dont know the version, we will loop in this function
tokens1(Chars, Line, Acc) ->
    case any_chars(Chars, Line, 1) of
	{token, Token, [], LatestLine} ->
	    %% We got to the end without actually getting a version token.
	    Tokens = [{endOfMessage, LatestLine, endOfMessage}, Token | Acc],
	    {error, no_version_found, lists:reverse(Tokens), Line};

        %% -- Version token for version 1 --
        {token, {'SafeChars',_,"!/1"} = Token, Rest, LatestLine} ->
            tokens2(Rest, LatestLine, 1, [Token | Acc]);

        {token, {'SafeChars',_,"megaco/1"} = Token, Rest, LatestLine} ->
            tokens2(Rest, LatestLine, 1, [Token | Acc]);


        %% -- Version token for version 2 --
        {token, {'SafeChars',_,"!/2"} = Token, Rest, LatestLine} ->
            tokens2(Rest, LatestLine, 2, [Token | Acc]);

        {token, {'SafeChars',_,"megaco/2"} = Token, Rest, LatestLine} ->
            tokens2(Rest, LatestLine, 2, [Token | Acc]);


        %% -- Version token for version 3 --
        {token, {'SafeChars',_,"!/3"} = Token, Rest, LatestLine} ->
            tokens2(Rest, LatestLine, 3, [Token | Acc]);

        {token, {'SafeChars',_,"megaco/3"} = Token, Rest, LatestLine} ->
            tokens2(Rest, LatestLine, 3, [Token | Acc]);


        %% -- Version token for version X --
        {token, {'SafeChars',_,[$!,$/| Vstr]} = Token, Rest, LatestLine} ->
	    case guess_version(Vstr) of
		{ok, V} ->
		    tokens2(Rest, LatestLine, V, [Token | Acc]);
		{error, Reason} ->
		    {error, Reason, LatestLine}
	    end;

        {token, {'SafeChars',_,[$m,$e,$g,$a,$c,$o,$/|Vstr]} = Token, Rest, LatestLine} ->
	    case guess_version(Vstr) of
		{ok, V} ->
		    tokens2(Rest, LatestLine, V, [Token | Acc]);
		{error, Reason} ->
		    {error, Reason, LatestLine}
	    end;

	%% -- Other tokens --
	{token, Token, Rest, LatestLine} ->
	    tokens1(Rest, LatestLine, [Token | Acc]);

	{bad_token, Token, _Rest, _LatestLine} ->
	    {error, {bad_token, [Token, Acc]}, Line}
    end.

tokens2(Chars, Line0, Version, Tokens0) ->
    case tokens3(Chars, Line0, Tokens0, Version) of
	{ok, Tokens, Line} ->
	    {ok, Tokens, Version, Line};
	Error ->
	    Error
    end.

tokens3(Chars, Line, Acc, Version) ->
    %%     d("tokens2 -> entry with"
    %%       "~n   Chars: ~s"
    %%       "~n   Line:  ~p", [Chars, Line]),
    case any_chars(Chars, Line, Version) of
	{token, Token, [], LatestLine} ->
	    %%  	    d("tokens2 -> Token: ~n~p", [Token]),
	    Tokens = [{endOfMessage, LatestLine, endOfMessage}, Token | Acc],
	    {ok, lists:reverse(Tokens), Line};

	{token, Token, Rest, LatestLine} ->
	    %%  	    d("tokens2 -> Token: ~n~p", [Token]),
	    tokens3(Rest, LatestLine, [Token | Acc], Version);

	{bad_token, Token, _Rest, _LatestLine} ->
	    {error, {bad_token, [Token, Acc]}, Line}
    end.


guess_version([C]) when (48 =< C) and (C =< 57) ->
    {ok, C-48};
guess_version(Str) when is_list(Str) ->
    case (catch list_to_integer(Str)) of
	I when is_integer(I) ->
	    {ok, I};
	_ ->
	    {error, {invalid_version, Str}}
    end.


%% Returns {token,     Token, Rest, LatestLine}
%% Returns {bad_token, Token, Rest, LatestLine}
any_chars([Char | Rest], Line, Version) ->
    case ?classify_char(Char) of
	safe_char_upper ->
	    safe_chars(Rest, [Char], [?LOWER2(Char)], Line, Version);
	safe_char ->
	    safe_chars(Rest, [Char], [Char], Line, Version);
	rest_char ->
	    case Char of
		?SemiColonToken ->
		    comment_chars(Rest, Line);
		_ ->
		    rest_chars(Rest, [Char], Line)
	    end;
	double_quote ->
	    quoted_chars(Rest, [], Line);
	white_space ->
	    sep_chars(Rest, Line);
	end_of_line ->
	    sep_chars(Rest, Line);
	bad_char ->
	    %% {bad_token, {'SEP', Line, Char}, Rest, Line}
	    {bad_token, {'AnyChars', Line, Char}, Rest, Line}
    end;
any_chars([] = All, Line, _Version) ->
    {token, {'SEP', Line, end_of_input}, All, Line}.

comment_chars([Char | Rest], Line) ->
    case ?classify_char(Char) of
	safe_char_upper ->
	    comment_chars(Rest, Line);
	safe_char ->
	    comment_chars(Rest, Line);
	rest_char ->
	    comment_chars(Rest, Line);
	white_space -> 
	    comment_chars(Rest, Line);
	end_of_line ->
	    sep_chars(Rest, Line);
	_ when Char =:= 22 ->
	    comment_chars(Rest, Line);
	_ ->
	    %% {bad_token, {'SEP', Line, Char}, Rest, Line}
	    {bad_token, {'CommentChars', Line, Char}, Rest, Line}
    end;
comment_chars([] = All, Line) ->
    {token, {'SEP', Line}, All, Line}.
    
sep_chars([Char | Rest] = All, Line) ->
    case ?classify_char(Char) of
	safe_char_upper ->
	    {token, {'SEP', Line}, All, Line};
	safe_char ->
	    {token, {'SEP', Line}, All, Line};
	rest_char when Char =:= ?SemiColonToken ->
	    comment_chars(Rest, Line);
	rest_char ->
	    rest_chars(Rest, [Char], Line);
	white_space -> 
	    sep_chars(Rest, Line);
	end_of_line ->
	    sep_chars(Rest, Line + 1);
	_ ->
	    %% {bad_token, {'SEP', Line, Char}, Rest, Line}
	    {bad_token, {'SepChars', Line, Char}, Rest, Line}
    end;
sep_chars([] = All, Line) ->
    {token, {'SEP', Line}, All, Line}.

rest_chars(Rest, [?ColonToken], Line) ->
    {token, {'COLON',   Line}, Rest, Line};
rest_chars(Rest, [AccChar], Line) ->
    TokenTag =
	case AccChar of
	    ?EqualToken   -> 'EQUAL';
	    ?NequalToken  -> 'NEQUAL';
	    ?LesserToken  -> 'LESSER';
	    ?GreaterToken -> 'GREATER';
	    ?LbrktToken   -> 'LBRKT';
	    ?RbrktToken   -> 'RBRKT';
	    ?LsbrktToken  -> 'LSBRKT';
	    ?RsbrktToken  -> 'RSBRKT';
	    ?LparToken    -> 'LPAR';
	    ?RparToken    -> 'RPAR';
	    ?VbarToken    -> 'VBAR';
	    ?CommaToken   -> 'COMMA'
	end,
    {Rest2, Line2} = skip_sep_chars(Rest, Line),
    {token, {TokenTag, Line}, Rest2, Line2}.

skip_sep_chars([Char | Rest] = All, Line) ->
    case ?classify_char2(Char) of
	rest_char when Char =:= ?SemiColonToken ->
	    skip_comment_chars(Rest, Line);
	white_space -> 
	    skip_sep_chars(Rest, Line);
	end_of_line ->
	    skip_sep_chars(Rest, Line + 1);
	_ ->
	    {All, Line}
    end;
skip_sep_chars([] = All, Line) ->
    {All, Line}.

skip_comment_chars([Char | Rest] = All, Line) ->
    case ?classify_char(Char) of
	safe_char_upper ->
	    skip_comment_chars(Rest, Line);
	safe_char ->
	    skip_comment_chars(Rest, Line);
	rest_char ->
	    skip_comment_chars(Rest, Line);
	double_quote ->
	    skip_comment_chars(Rest, Line);
	white_space -> 
	    skip_comment_chars(Rest, Line);
	end_of_line ->
	    skip_sep_chars(Rest, Line + 1);
	_ ->
	    {All, Line}
    end;
skip_comment_chars([] = All, Line) ->
    {All, Line}.

quoted_chars([Char | Rest], Acc, Line) ->
    case ?classify_char(Char) of
	safe_char_upper ->
	    quoted_chars(Rest, [Char | Acc], Line);
	safe_char ->
	    quoted_chars(Rest, [Char | Acc], Line);
	rest_char ->
	    quoted_chars(Rest, [Char | Acc], Line);
	white_space -> 
	    quoted_chars(Rest, [Char | Acc], Line);
	double_quote -> 
	    {token, {'QuotedChars', Line, lists:reverse(Acc)}, Rest, Line};
	_ ->
	    {bad_token, {'QuotedChars', Line, Char}, Rest, Line}
    end;
quoted_chars([] = All, _Acc, Line) ->
    {bad_token, {'QuotedChars', Line, end_of_input}, All, Line}.
    
safe_chars([Char | Rest] = All, Acc, LowerAcc, Line, Version) ->
    %%     d("safe_chars -> entry with"
    %%       "~n   Char:     ~p"
    %%       "~n   LowerAcc: ~p", [Char, LowerAcc]),
    case ?classify_char3(Char) of
	safe_char_upper ->
	    safe_chars(Rest, [Char | Acc], 
		       [?LOWER2(Char) | LowerAcc], Line, Version);
	safe_char ->
	    safe_chars(Rest, [Char | Acc], [Char | LowerAcc], Line, Version);
	_ ->
	    LowerSafeChars = lists:reverse(LowerAcc),
	    TokenTag = select_token(LowerSafeChars, Version),
	    SafeChars = lists:reverse(Acc),
	    case TokenTag of
		'MtpToken' ->
		    %% 'MtpToken' 'LBRKT' OctetString 'RBRKT'
		    special_chars(All, LowerSafeChars, Line, TokenTag);
		'LocalToken' ->
		    %% 'LocalToken' 'LBRKT' OctetString 'RBRKT'
		    special_chars(All, SafeChars, Line, TokenTag);
		'RemoteToken' ->
		    %% 'RemoteToken' 'LBRKT' OctetString 'RBRKT'
		    special_chars(All, SafeChars, Line, TokenTag);
		'DigitMapToken' -> 
		    %% 'DigitMapToken'
		    %% 'DigitMapToken' 'EQUAL' Name
		    %% 'DigitMapToken' 'EQUAL' Name 'LBRKT' Value 'RBRKT'
		    %% 'DigitMapToken' 'EQUAL' 'LBRKT' Value 'RBRKT'
		    %% 'DigitMapToken' 'LBRKT' Value 'RBRKT'
		    special_chars(All, LowerSafeChars, Line, TokenTag);
		_ ->
		    {token, {TokenTag, Line, LowerSafeChars}, All, Line}
	    end
    end;
safe_chars([] = All, _Acc, LowerAcc, Line, Version) ->
    LowerSafeChars = lists:reverse(LowerAcc),
    TokenTag = select_token(LowerSafeChars, Version),
    %%SafeChars = lists:reverse(Acc),
    {token, {TokenTag, Line, LowerSafeChars}, All, Line}.
    
collect_safe_chars([Char | Rest] = All, LowerAcc) ->
    case ?classify_char3(Char) of
	safe_char_upper ->
	    collect_safe_chars(Rest, [?LOWER2(Char) | LowerAcc]);
	safe_char ->
	    collect_safe_chars(Rest, [Char | LowerAcc]);
	_ ->
	    {All, lists:reverse(LowerAcc)}
    end;
collect_safe_chars([] = Rest, LowerAcc) ->
    {Rest, lists:reverse(LowerAcc)}.

special_chars(All, SafeChars, Line, TokenTag) ->
    {Rest, Line2} = skip_sep_chars(All, Line),
    case Rest of
	[?LbrktToken | Rest2] ->
	    {token, {'OctetString', _, OctetString}, Rest4, Line4} =
		octet_string(Rest2, Line2),
	    case Rest4 of
		[?RbrktToken | Rest6] ->
		    Token = 
			case TokenTag of
			    'MtpToken' ->
				%% 'MtpToken' 'LBRKT' OctetString 'RBRKT'
				{'MtpAddressToken', Line, OctetString};
			    'LocalToken' ->
				%% 'LocalToken' 'LBRKT' OctetString 'RBRKT'
				PGs = property_groups(OctetString), 
				{'LocalDescriptorToken', Line, PGs};
			    'RemoteToken' ->
				%% 'RemoteToken' 'LBRKT' OctetString 'RBRKT'
				PGs = property_groups(OctetString), 
				{'RemoteDescriptorToken', Line, PGs};
			    'DigitMapToken' ->
				%% 'DigitMapToken' 'LBRKT' OctetString 'RBRKT'
				DMV = digit_map_value(OctetString),
				DMD = #'DigitMapDescriptor'{digitMapValue = DMV},
				{'DigitMapDescriptorToken', Line, DMD}
			end,
		    {token, Token, Rest6, Line4};
		_ when TokenTag =:= 'DigitMapToken' ->
		    %% 'DigitMapToken'
		    {token, {'DigitMapToken', Line, SafeChars}, All, Line};
		_ ->
		    {token, {'SafeChars', Line, SafeChars}, All, Line}
	    end;
	[?EqualToken | Rest2] when TokenTag =:= 'DigitMapToken' ->
	    {Rest3, Line3} = skip_sep_chars(Rest2, Line2),
	    {Rest4, DigitMapName} = collect_safe_chars(Rest3, []),
	    {Rest6, Line6, DMD} = 
		if
		    DigitMapName =:= [] ->
			{Rest3, Line3, #'DigitMapDescriptor'{}};
		    true ->
			{Rest5, Line5} = skip_sep_chars(Rest4, Line3),
			{Rest5, Line5, #'DigitMapDescriptor'{digitMapName  = DigitMapName}}
		end,
	    case Rest6 of
		[?LbrktToken | Rest7] ->
		    {token, {'OctetString', _, OctetString}, Rest8, Line8} = 
			octet_string(Rest7, Line6),
		    case Rest8 of
			[?RbrktToken | Rest10] ->
			    %% 'DigitMapToken' 'EQUAL' 'LBRKT' OctetString 'RBRKT'
			    %% 'DigitMapToken' 'EQUAL' Name 'LBRKT' OctetString 'RBRKT'
			    {Rest11, Line11} = skip_sep_chars(Rest10, Line8),
			    DMV = digit_map_value(OctetString),
			    DMD2 = DMD#'DigitMapDescriptor'{digitMapValue = DMV},
			    {token, {'DigitMapDescriptorToken', Line, DMD2}, Rest11, Line11};
			_ when DMD#'DigitMapDescriptor'.digitMapName /= asn1_NOVALUE ->
			    %% 'DigitMapToken' 'EQUAL' Name
			    {token, {'DigitMapDescriptorToken', Line, DMD}, Rest4, Line3};
			_ ->
			    %% 'DigitMapToken'
			    {token, {'DigitMapToken', Line, SafeChars}, All, Line}
		    end;
		_ when DMD#'DigitMapDescriptor'.digitMapName /= asn1_NOVALUE ->
		    %% 'DigitMapToken' 'EQUAL' Name
		    {token, {'DigitMapDescriptorToken', Line, DMD}, Rest4, Line3};
		_ ->
		    %% 'DigitMapToken'
		    {token, {'DigitMapToken', Line, SafeChars}, All, Line}
	    end;
	_  when TokenTag =:= 'DigitMapToken' ->
	    %% 'DigitMapToken'
	    {token, {'DigitMapToken', Line, SafeChars}, All, Line};
	_ ->
	    %% 'DigitMapToken'
	    {token, {'SafeChars', Line, SafeChars}, All, Line}
    end.

octet_string(Chars, Line) ->
    {Chars2, Line2} = skip_sep_chars(Chars, Line),
    Acc = [],
    {Rest, RevChars, RestLine} = octet_string(Chars2, Acc, Line2),
    {RevChars2, _} = skip_sep_chars(RevChars, RestLine),
    OctetString = lists:reverse(RevChars2),
    {token, {'OctetString', Line, OctetString}, Rest, RestLine}.
    
    
octet_string([Char | Rest] = All, Acc, Line) ->
    if
	(Char =:= ?CrToken) ->
	    octet_string(Rest, [Char | Acc], Line + 1);
	(Char =:= ?LfToken) ->
	    octet_string(Rest, [Char | Acc], Line + 1);
	(Char >= 8#1) andalso (Char =< 8#174) ->
	    octet_string(Rest, [Char | Acc], Line);
	(Char >= 8#176) andalso (Char =< 8#377) ->
	    octet_string(Rest, [Char | Acc], Line);
	(Char =:= ?BackslashToken) ->
	    case Rest of
		[?RbrktToken | _Rest2] ->
		    %% OTP-4357
		    octet_string(Rest, 
				 [?RbrktToken, ?BackslashToken | Acc], Line);
		_ ->
		    octet_string(Rest, [Char | Acc], Line)
	    end;
	true ->
	    {All, Acc, Line}
    end;
octet_string([] = All, Acc, Line) ->
    {All, Acc, Line}.
	

%% digitMapValue      = ["T" COLON Timer COMMA]
%%			["S" COLON Timer COMMA] 
%%			["L" COLON Timer COMMA] 
%%			["Z" COLON Timer COMMA] digitMap  
digit_map_value(Chars) ->
    digit_map_value(Chars, #'DigitMapValue'{}).

%% NOTE: The swap of the digitMapBody and the durationTimer is
%% intentional. The reason is a problem with the flex scanner.
%% Hopefully this is temporary...
%% The values are swapped back later by the parser...
digit_map_value([Char, ?ColonToken | Rest] = All, DMV) ->
    case ?LOWER1(Char) of
	$t -> digit_map_timer(All, Rest, #'DigitMapValue'.startTimer, DMV);
	$s -> digit_map_timer(All, Rest, #'DigitMapValue'.shortTimer, DMV);
	$l -> digit_map_timer(All, Rest, #'DigitMapValue'.longTimer, DMV);
% 	$z -> digit_map_timer(All, Rest, #'DigitMapValue'.durationTimer, DMV);
% 	_  -> DMV#'DigitMapValue'{digitMapBody = All}
	$z -> digit_map_timer(All, Rest, #'DigitMapValue'.digitMapBody, DMV);
	_  -> DMV#'DigitMapValue'{durationTimer = All}
    end;
digit_map_value(Chars, DMV) ->
    DMV#'DigitMapValue'{durationTimer = Chars}.

digit_map_timer(All, Chars, TimerPos, DMV) ->
    {Rest, Digits} = collect_safe_chars(Chars, []),
    {Rest2, _} = skip_sep_chars(Rest, 0),
   case {Rest2, catch list_to_integer(Digits)} of
       {[?CommaToken | Rest3], Int} when is_integer(Int) andalso 
                                         (Int >= 0) andalso 
					 (element(TimerPos, DMV) =:= asn1_NOVALUE) ->
	   {Rest4, _} = skip_sep_chars(Rest3, 0),
	   DMV2 = setelement(TimerPos, DMV, Int),
	   digit_map_value(Rest4, DMV2);
       _ ->
	   DMV#'DigitMapValue'{digitMapBody = All}
   end.

%% ============================================================================
%% <prev-parser-stuff>
%% 
%% This stuff was originally in the parser(s), but was, 
%% for performance reasons, moved to the scanner(s). This 
%% scanner does not make it faster, but the flex scanner 
%% does, which is why the move was made.
%% 

property_groups(OctetString) ->
    Group  = [],
    Groups = [],
    property_name(OctetString, Group, Groups).

property_name([Char | Rest] = All, Group, Groups) ->
    if 
        ?white_space(Char) ->
            property_name(Rest, Group, Groups);
        ?end_of_line(Char) ->
            property_name(Rest, Group, Groups);
        true ->
            Name = [],
            do_property_name(All, Name, Group, Groups)
    end;
property_name([] = All, Group, Groups) ->
    Name = [],
    do_property_name(All, Name, Group, Groups).

do_property_name([Char | Rest], Name, Group, Groups) 
  when (Char =:= $=) andalso (Name =/= []) ->
    %% Now we have a complete name
    if
	(Name =:= "v") andalso (Group =/= []) ->
	    %% v= is a property group delimiter,
	    %% lets create yet another property group.
	    Groups2 = [lists:reverse(Group) | Groups],
	    Group2 = [],
	    property_value(Rest, Name, Group2, Groups2);
	true ->
	    %% Use current property group
	    property_value(Rest, Name, Group, Groups)
    end;
do_property_name([Char | Rest], Name, Group, Groups) ->
    case ?classify_char4(Char) of
        safe_char_upper ->
            do_property_name(Rest, [Char | Name], Group, Groups);
        safe_char ->
            do_property_name(Rest, [Char | Name], Group, Groups);
        _ ->
            throw({error, {bad_prop_name, lists:reverse(Name), Char}})
    end;
do_property_name([], [], [], Groups) ->
    lists:reverse(Groups);
do_property_name([], [], Group, Groups) ->
    Group2 = lists:reverse(Group),
    lists:reverse([Group2 | Groups]);
do_property_name([], Name, Group, Groups) when Name =/= [] ->
    %% Assume end of line
    Value  = [],
    PP     = make_property_parm(Name, Value),
    Group2 = lists:reverse([PP | Group]),
    lists:reverse([Group2 | Groups]).

-ifdef(megaco_scanner_inline).
-compile({inline,[{property_value,4}]}).
-endif.
property_value(Chars, Name, Group, Groups) ->
    Value = [],
    do_property_value(Chars, Name, Value, Group, Groups).

do_property_value([Char | Rest], Name, Value, Group, Groups) ->
    if
        ?end_of_line(Char) ->
            %% Now we have a complete "name=value" pair
            PP = make_property_parm(Name, Value),
            property_name(Rest, [PP | Group], Groups);
        true ->
            do_property_value(Rest, Name, [Char | Value], Group, Groups)
    end;
do_property_value([], Name, Value, Group, Groups) ->
    %% Assume end of line
    PP = make_property_parm(Name, Value),
    Group2 = lists:reverse([PP | Group]),
    lists:reverse([Group2 | Groups]).

-ifdef(megaco_scanner_inline).
-compile({inline,[{make_property_parm,2}]}).
-endif.
make_property_parm(Name, Value) ->
    %% Record name, name field, value field, extraInfo field
    {'PropertyParm', 
     lists:reverse(Name), 
     [lists:reverse(Value)], 
     asn1_NOVALUE}.


%% </prev-parser-stuff>
%% ===========================================================================

select_token([$o, $- | LowerText], Version) ->
    select_token(LowerText, Version);
select_token([$w, $- | LowerText], Version) ->
    select_token(LowerText, Version);
select_token(LowerText, Version) ->
    case LowerText of
        "add"                   -> 'AddToken';
        "a"                     -> 'AddToken';
        "andlgc"                when (Version >= 3) -> 'AndAUDITSelectToken'; % v3
        "audit"                 -> 'AuditToken';
        "at"                    -> 'AuditToken';
        "auditcapability"       -> 'AuditCapToken';
        "ac"                    -> 'AuditCapToken';
        "auditvalue"            -> 'AuditValueToken';
        "av"                    -> 'AuditValueToken';
	"authentication"        -> 'AuthToken';
        "au"                    -> 'AuthToken';
        "both"                  when (Version >= 3) -> 'BothToken';         % v3
        "b"                     when (Version >= 3) -> 'BothToken';         % v3
        "bothway"               -> 'BothwayToken';
        "bw"                    -> 'BothwayToken';
        "brief"                 -> 'BriefToken';
        "br"                    -> 'BriefToken';
        "buffer"                -> 'BufferToken';
        "bf"                    -> 'BufferToken';
        "context"               -> 'CtxToken';
        "c"                     -> 'CtxToken';
        "contextattr"           when (Version >= 3) -> 'ContextAttrToken';  % v3
        "ct"                    when (Version >= 3) -> 'ContextAttrToken';  % v3
        "contextlist"           when (Version >= 3) -> 'ContextListToken';  % v3
        "clt"                   when (Version >= 3) -> 'ContextListToken';  % v3
        "contextaudit"          -> 'ContextAuditToken';
        "ca"                    -> 'ContextAuditToken';
	"digitmap"              -> 'DigitMapToken';
	"dm"                    -> 'DigitMapToken';
        "spadirection"          when (Version >= 3) -> 'DirectionToken';    % v3
        "direction"             when (Version >= 3) -> 'DirectionToken';    % v3 (pre-v3a/v3b)
        "spadi"                 when (Version >= 3) -> 'DirectionToken';    % v3
        "di"                    when (Version >= 3) -> 'DirectionToken';    % v3 (pre-v3a/v3b)
        "discard"               -> 'DiscardToken';
        "ds"                    -> 'DiscardToken';
        "disconnected"          -> 'DisconnectedToken';
        "dc"                    -> 'DisconnectedToken';
        "delay"                 -> 'DelayToken';
        "dl"                    -> 'DelayToken';
        "delete"                -> 'DeleteToken';
        "de"                    -> 'DeleteToken';
        "duration"              -> 'DurationToken';
        "dr"                    -> 'DurationToken';
        "embed"                 -> 'EmbedToken';
        "em"                    -> 'EmbedToken';
        "emergency"             -> 'EmergencyToken';
        "eg"                    -> 'EmergencyToken';
        "emergencyofftoken"     -> 'EmergencyOffToken';
        "emergencyoff"          when (Version >= 3) -> 'EmergencyOffToken';   % v3
        "ego"                   -> 'EmergencyOffToken';
        "emergencyvalue"        when (Version >= 3) -> 'EmergencyValueToken'; % v3 
        "egv"                   when (Version >= 3) -> 'EmergencyValueToken'; % v3
        "error"                 -> 'ErrorToken';
        "er"                    -> 'ErrorToken';
        "eventbuffer"           -> 'EventBufferToken';
        "eb"                    -> 'EventBufferToken';
        "events"                -> 'EventsToken';
        "e"                     -> 'EventsToken';
        "external"              when (Version >= 3) -> 'ExternalToken';     % v3
        "ex"                    when (Version >= 3) -> 'ExternalToken';     % v3
        "failover"              -> 'FailoverToken';
        "fl"                    -> 'FailoverToken';
        "forced"                -> 'ForcedToken';
        "fo"                    -> 'ForcedToken';
        "graceful"              -> 'GracefulToken';
        "gr"                    -> 'GracefulToken';
        "h221"                  -> 'H221Token';
        "h223"                  -> 'H223Token';
        "h226"                  -> 'H226Token';
        "handoff"               -> 'HandOffToken';
        "ho"                    -> 'HandOffToken';
        "iepscall"              when (Version >= 3) -> 'IEPSToken';         % v3
        "ieps"                  when (Version >= 3) -> 'IEPSToken';         % v3
        "inactive"              -> 'InactiveToken';
        "in"                    -> 'InactiveToken';
        "internal"              when (Version >= 3) -> 'InternalToken';     % v3
        "it"                    when (Version >= 3) -> 'InternalToken';     % v3
        "immackrequired"        -> 'ImmAckRequiredToken';
        "ia"                    -> 'ImmAckRequiredToken';
        "inservice"             -> 'InSvcToken';
        "intersignal"           when (Version >= 3) -> 'IntsigDelayToken'; % v3
        "spais"                 when (Version >= 3) -> 'IntsigDelayToken'; % v3
        "intbyevent"            -> 'InterruptByEventToken';
        "ibe"                   -> 'InterruptByEventToken';
        "intbysigdescr"         -> 'InterruptByNewSignalsDescrToken';
        "ibs"                   -> 'InterruptByNewSignalsDescrToken';
        "iv"                    -> 'InSvcToken';
        "isolate"               -> 'IsolateToken';
        "is"                    -> 'IsolateToken';
	"iterationtoken"        when (Version >= 3) -> 'IterationToken'; % v3
	"ir"                    when (Version >= 3) -> 'IterationToken'; % v3
        "keepactive"            -> 'KeepActiveToken';
        "ka"                    -> 'KeepActiveToken';
	"local"                 -> 'LocalToken';
        "l"                     -> 'LocalToken';
        "localcontrol"          -> 'LocalControlToken';
        "lockstep"              -> 'LockStepToken';
        "sp"                    -> 'LockStepToken';
        "o"                     -> 'LocalControlToken';
        "loopback"              -> 'LoopbackToken';
        "lb"                    -> 'LoopbackToken';
        "media"                 -> 'MediaToken';
        "m"                     -> 'MediaToken';
        %% "megaco"                -> 'MegacopToken';
	%% "!"                     -> 'megacoptoken';
	"segment"               when (Version >= 3) -> 'MessageSegmentToken'; % v3
	"sm"                    when (Version >= 3) -> 'MessageSegmentToken'; % v3
        "method"                -> 'MethodToken';
        "mt"                    -> 'MethodToken';
        "mtp"                   -> 'MtpToken';
        "mgcidtotry"            -> 'MgcIdToken';
        "mg"                    -> 'MgcIdToken';
        "mode"                  -> 'ModeToken';
        "mo"                    -> 'ModeToken';
        "modify"                -> 'ModifyToken';
        "mf"                    -> 'ModifyToken';
        "modem"                 -> 'ModemToken';
        "md"                    -> 'ModemToken';
        "move"                  -> 'MoveToken';
        "mv"                    -> 'MoveToken';
        "mux"                   -> 'MuxToken';
        "mx"                    -> 'MuxToken';
        "nevernotify"           when (Version >= 3) -> 'NeverNotifyToken'; % v3
        "nbnn"                  when (Version >= 3) -> 'NeverNotifyToken'; % v3
        "notify"                -> 'NotifyToken';
        "n"                     -> 'NotifyToken';
        "notifycompletion"      -> 'NotifyCompletionToken';
        "nc"                    -> 'NotifyCompletionToken';
        "immediatenotify"       when (Version >= 3) -> 'NotifyImmediateToken';    % v3
        "nbin"                  when (Version >= 3) -> 'NotifyImmediateToken';    % v3
        "regulatednotify"       when (Version >= 3) -> 'NotifyRegulatedToken';    % v3
        "nbrn"                  when (Version >= 3) -> 'NotifyRegulatedToken';    % v3
        "nx64kservice"          when (Version >= 2) -> 'Nx64kToken';              % v2
        "n64"                   when (Version >= 2) -> 'Nx64kToken';              % v2
        "observedevents"        -> 'ObservedEventsToken';
        "oe"                    -> 'ObservedEventsToken';
        "oneway"                -> 'OnewayToken';
        "ow"                    -> 'OnewayToken';
        "onewayboth"            when (Version >= 3) -> 'OnewayBothToken';     % v3
        "owb"                   when (Version >= 3) -> 'OnewayBothToken';     % v3
        "onewayexternal"        when (Version >= 3) -> 'OnewayExternalToken'; % v3
        "owe"                   when (Version >= 3) -> 'OnewayExternalToken'; % v3
        "off"                   -> 'OffToken';
        "on"                    -> 'OnToken';
        "onoff"                 -> 'OnOffToken';
        "oo"                    -> 'OnOffToken';
        "orlgc"                 when (Version >= 3) -> 'OrAUDITselectToken';  % v3
        "otherreason"           -> 'OtherReasonToken';
        "or"                    -> 'OtherReasonToken';
        "outofservice"          -> 'OutOfSvcToken';
        "os"                    -> 'OutOfSvcToken';
        "packages"              -> 'PackagesToken';
        "pg"                    -> 'PackagesToken';
        "pending"               -> 'PendingToken';
        "pn"                    -> 'PendingToken';
        "priority"              -> 'PriorityToken';
        "pr"                    -> 'PriorityToken';
        "profile"               -> 'ProfileToken';
        "pf"                    -> 'ProfileToken';
        "reason"                -> 'ReasonToken';
        "re"                    -> 'ReasonToken';
        "receiveonly"           -> 'RecvonlyToken';
        "requestid"             when (Version >= 3) -> 'RequestIDToken';    % v3
        "rq"                    when (Version >= 3) -> 'RequestIDToken';    % v3
        "rc"                    -> 'RecvonlyToken';
        "reply"                 -> 'ReplyToken';
        "p"                     -> 'ReplyToken';
        "reseteventsdescriptor" when (Version >= 3) -> 'ResetEventsDescriptorToken'; % v3
        "rse"                   when (Version >= 3) -> 'ResetEventsDescriptorToken'; % v3
        "transactionresponseack"-> 'ResponseAckToken';
        "k"                     -> 'ResponseAckToken';
        "restart"               -> 'RestartToken';
        "rs"                    -> 'RestartToken';
	"remote"                -> 'RemoteToken';
	"r"                     -> 'RemoteToken';
        "sparequestid"          -> 'RequestIDToken';
        "sparq"                 -> 'RequestIDToken';
        "reservedgroup"         -> 'ReservedGroupToken';
        "rg"                    -> 'ReservedGroupToken';
        "reservedvalue"         -> 'ReservedValueToken';
        "rv"                    -> 'ReservedValueToken';
        "end"                   when (Version >= 3) -> 'SegmentationCompleteToken'; % v3
        "&"                     when (Version >= 3) -> 'SegmentationCompleteToken'; % v3
        "sendonly"              -> 'SendonlyToken';
        "so"                    -> 'SendonlyToken';
        "sendreceive"           -> 'SendrecvToken';
        "sr"                    -> 'SendrecvToken';
        "services"              -> 'ServicesToken';
        "sv"                    -> 'ServicesToken';
        "servicestates"         -> 'ServiceStatesToken';
        "si"                    -> 'ServiceStatesToken';
        "servicechange"         -> 'ServiceChangeToken';
        "sc"                    -> 'ServiceChangeToken';
        "servicechangeaddress"  -> 'ServiceChangeAddressToken';
        "ad"                    -> 'ServiceChangeAddressToken';
        "servicechangeinc"      when (Version >= 3) -> 'ServiceChangeIncompleteToken'; % v3
        "sic"                   when (Version >= 3) -> 'ServiceChangeIncompleteToken'; % v3
        "signallist"            -> 'SignalListToken';
        "sl"                    -> 'SignalListToken';
        "signals"               -> 'SignalsToken';
        "sg"                    -> 'SignalsToken';
        "signaltype"            -> 'SignalTypeToken';
        "sy"                    -> 'SignalTypeToken';
        "statistics"            -> 'StatsToken';
        "sa"                    -> 'StatsToken';
        "stream"                -> 'StreamToken';
        "st"                    -> 'StreamToken';
        "subtract"              -> 'SubtractToken';
        "s"                     -> 'SubtractToken';
        "synchisdn"             -> 'SynchISDNToken';
        "sn"                    -> 'SynchISDNToken';
        "terminationstate"      -> 'TerminationStateToken';
        "ts"                    -> 'TerminationStateToken';
        "test"                  -> 'TestToken';
        "te"                    -> 'TestToken';
        "timeout"               -> 'TimeOutToken';
        "to"                    -> 'TimeOutToken';
        "topology"              -> 'TopologyToken';
        "tp"                    -> 'TopologyToken';
        "transaction"           -> 'TransToken';
        "t"                     -> 'TransToken';
        "v18"                   -> 'V18Token';
        "v22"                   -> 'V22Token';
        "v22b"                  -> 'V22bisToken';
        "v32"                   -> 'V32Token';
        "v32b"                  -> 'V32bisToken';
        "v34"                   -> 'V34Token';
        "v76"                   -> 'V76Token';
        "v90"                   -> 'V90Token';
        "v91"                   -> 'V91Token';
        "version"               -> 'VersionToken';
        "v"                     -> 'VersionToken';
	[_,_,_,_,_,_,_,_,$t,_,_,_,_,_,_,_,_] -> % Could be a time-stamp
	    [D1,D2,D3,D4,D5,D6,D7,D8,_,T1,T2,T3,T4,T5,T6,T7,T8] = LowerText,
	    select_TimeStampToken(D1,D2,D3,D4,D5,D6,D7,D8,
				  T1,T2,T3,T4,T5,T6,T7,T8);
	_                       -> 'SafeChars'
    end.

select_TimeStampToken(D1,D2,D3,D4,D5,D6,D7,D8,
		      T1,T2,T3,T4,T5,T6,T7,T8)
  when ($0 =< D1) andalso (D1 =< $9) andalso 
       ($0 =< D2) andalso (D2 =< $9) andalso 
       ($0 =< D3) andalso (D3 =< $9) andalso 
       ($0 =< D4) andalso (D4 =< $9) andalso 
       ($0 =< D5) andalso (D5 =< $9) andalso 
       ($0 =< D6) andalso (D6 =< $9) andalso 
       ($0 =< D7) andalso (D7 =< $9) andalso 
       ($0 =< D8) andalso (D8 =< $9) andalso 
       ($0 =< T1) andalso (T1 =< $9) andalso 
       ($0 =< T2) andalso (T2 =< $9) andalso 
       ($0 =< T3) andalso (T3 =< $9) andalso 
       ($0 =< T4) andalso (T4 =< $9) andalso 
       ($0 =< T5) andalso (T5 =< $9) andalso 
       ($0 =< T6) andalso (T6 =< $9) andalso 
       ($0 =< T7) andalso (T7 =< $9) andalso 
       ($0 =< T8) andalso (T8 =< $9) -> 
    'TimeStampToken';
select_TimeStampToken(_D1,_D2,_D3,_D4,_D5,_D6,_D7,_D8,
		      _T1,_T2,_T3,_T4,_T5,_T6,_T7,_T8) ->
    'SafeChars'.


%% d(F) ->
%%     d(F, []).

%% d(F, A) ->
%%     d(get(dbg), F, A).

%% d(true, F, A) ->
%%     io:format("DBG:~p:" ++ F ++ "~n", [?MODULE|A]);
%% d(_, _, _) ->
%%     ok.
