Module:String: Difference between revisions

    (update renamed variable)
    (Implement the merge of Module:Join, Module:Str endswith, Module:PatternCount and Module:Text count into this module per their TfDs. Review and discussion is at https://en.wikipedia.org/w/index.php?title=Module_talk:String&oldid=899040020#Edit_request_to_implement_merges)
    Line 42: Line 42:
    ]]
    ]]
    function str.len( frame )
    function str.len( frame )
    local new_args = str._getParameters( frame.args, {'s'} );
    local new_args = str._getParameters( frame.args, {'s'} )
    local s = new_args['s'] or '';
    local s = new_args['s'] or ''
    return mw.ustring.len( s )
    return mw.ustring.len( s )
    end
    end
    Line 71: Line 71:
    ]]
    ]]
    function str.sub( frame )
    function str.sub( frame )
    local new_args = str._getParameters( frame.args, { 's', 'i', 'j' } );
    local new_args = str._getParameters( frame.args, { 's', 'i', 'j' } )
    local s = new_args['s'] or '';
    local s = new_args['s'] or ''
    local i = tonumber( new_args['i'] ) or 1;
    local i = tonumber( new_args['i'] ) or 1
    local j = tonumber( new_args['j'] ) or -1;
    local j = tonumber( new_args['j'] ) or -1


    local len = mw.ustring.len( s );
    local len = mw.ustring.len( s )


    -- Convert negatives for range checking
    -- Convert negatives for range checking
    if i < 0 then
    if i < 0 then
    i = len + i + 1;
    i = len + i + 1
    end
    end
    if j < 0 then
    if j < 0 then
    j = len + j + 1;
    j = len + j + 1
    end
    end


    if i > len or j > len or i < 1 or j < 1 then
    if i > len or j > len or i < 1 or j < 1 then
    return str._error( 'String subset index out of range' );
    return str._error( 'String subset index out of range' )
    end
    end
    if j < i then
    if j < i then
    return str._error( 'String subset indices out of order' );
    return str._error( 'String subset indices out of order' )
    end
    end


    Line 151: Line 151:
    function str._match( s, pattern, start, match_index, plain_flag, nomatch )
    function str._match( s, pattern, start, match_index, plain_flag, nomatch )
    if s == '' then
    if s == '' then
    return str._error( 'Target string is empty' );
    return str._error( 'Target string is empty' )
    end
    end
    if pattern == '' then
    if pattern == '' then
    return str._error( 'Pattern string is empty' );
    return str._error( 'Pattern string is empty' )
    end
    end
    start = tonumber(start) or 1
    start = tonumber(start) or 1
    if math.abs(start) < 1 or math.abs(start) > mw.ustring.len( s ) then
    if math.abs(start) < 1 or math.abs(start) > mw.ustring.len( s ) then
    return str._error( 'Requested start is out of range' );
    return str._error( 'Requested start is out of range' )
    end
    end
    if match_index == 0 then
    if match_index == 0 then
    return str._error( 'Match index is out of range' );
    return str._error( 'Match index is out of range' )
    end
    end
    if plain_flag then
    if plain_flag then
    pattern = str._escapePattern( pattern );
    pattern = str._escapePattern( pattern )
    end
    end


    Line 173: Line 173:
    else
    else
    if start > 1 then
    if start > 1 then
    s = mw.ustring.sub( s, start );
    s = mw.ustring.sub( s, start )
    end
    end


    local iterator = mw.ustring.gmatch(s, pattern);
    local iterator = mw.ustring.gmatch(s, pattern)
    if match_index > 0 then
    if match_index > 0 then
    -- Forward search
    -- Forward search
    for w in iterator do
    for w in iterator do
    match_index = match_index - 1;
    match_index = match_index - 1
    if match_index == 0 then
    if match_index == 0 then
    result = w;
    result = w
    break;
    break
    end
    end
    end
    end
    else
    else
    -- Reverse search
    -- Reverse search
    local result_table = {};
    local result_table = {}
    local count = 1;
    local count = 1
    for w in iterator do
    for w in iterator do
    result_table[count] = w;
    result_table[count] = w
    count = count + 1;
    count = count + 1
    end
    end


    result = result_table[ count + match_index ];
    result = result_table[ count + match_index ]
    end
    end
    end
    end
    Line 201: Line 201:
    if result == nil then
    if result == nil then
    if nomatch == nil then
    if nomatch == nil then
    return str._error( 'Match not found' );
    return str._error( 'Match not found' )
    else
    else
    return nomatch;
    return nomatch
    end
    end
    else
    else
    return result;
    return result
    end
    end
    end
    end
    -- This is the entry point for #invoke:String|match
    -- This is the entry point for #invoke:String|match
    function str.match( frame )
    function str.match( frame )
    local new_args = str._getParameters( frame.args, {'s', 'pattern', 'start', 'match', 'plain', 'nomatch'} );
    local new_args = str._getParameters( frame.args, {'s', 'pattern', 'start', 'match', 'plain', 'nomatch'} )
    local s = new_args['s'] or '';
    local s = new_args['s'] or ''
    local start = tonumber( new_args['start'] ) or 1;
    local start = tonumber( new_args['start'] ) or 1
    local plain_flag = str._getBoolean( new_args['plain'] or false );
    local plain_flag = str._getBoolean( new_args['plain'] or false )
    local pattern = new_args['pattern'] or '';
    local pattern = new_args['pattern'] or ''
    local match_index = math.floor( tonumber(new_args['match']) or 1 );
    local match_index = math.floor( tonumber(new_args['match']) or 1 )
    local nomatch = new_args['nomatch'];
    local nomatch = new_args['nomatch']


    return str._match( s, pattern, start, match_index, plain_flag, nomatch )
    return str._match( s, pattern, start, match_index, plain_flag, nomatch )
    Line 248: Line 248:
    ]]
    ]]
    function str.pos( frame )
    function str.pos( frame )
    local new_args = str._getParameters( frame.args, {'target', 'pos'} );
    local new_args = str._getParameters( frame.args, {'target', 'pos'} )
    local target_str = new_args['target'] or '';
    local target_str = new_args['target'] or ''
    local pos = tonumber( new_args['pos'] ) or 0;
    local pos = tonumber( new_args['pos'] ) or 0


    if pos == 0 or math.abs(pos) > mw.ustring.len( target_str ) then
    if pos == 0 or math.abs(pos) > mw.ustring.len( target_str ) then
    return str._error( 'String index out of range' );
    return str._error( 'String index out of range' )
    end
    end


    return mw.ustring.sub( target_str, pos, pos );
    return mw.ustring.sub( target_str, pos, pos )
    end
    end


    Line 274: Line 274:
    ]]
    ]]
    function str.str_find( frame )
    function str.str_find( frame )
    local new_args = str._getParameters( frame.args, {'source', 'target'} );
    local new_args = str._getParameters( frame.args, {'source', 'target'} )
    local source_str = new_args['source'] or '';
    local source_str = new_args['source'] or ''
    local target_str = new_args['target'] or '';
    local target_str = new_args['target'] or ''


    if target_str == '' then
    if target_str == '' then
    return 1;
    return 1
    end
    end


    Line 320: Line 320:
    ]]
    ]]
    function str.find( frame )
    function str.find( frame )
    local new_args = str._getParameters( frame.args, {'source', 'target', 'start', 'plain' } );
    local new_args = str._getParameters( frame.args, {'source', 'target', 'start', 'plain' } )
    local source_str = new_args['source'] or '';
    local source_str = new_args['source'] or ''
    local pattern = new_args['target'] or '';
    local pattern = new_args['target'] or ''
    local start_pos = tonumber(new_args['start']) or 1;
    local start_pos = tonumber(new_args['start']) or 1
    local plain = new_args['plain'] or true;
    local plain = new_args['plain'] or true


    if source_str == '' or pattern == '' then
    if source_str == '' or pattern == '' then
    return 0;
    return 0
    end
    end


    plain = str._getBoolean( plain );
    plain = str._getBoolean( plain )


    local start = mw.ustring.find( source_str, pattern, start_pos, plain )
    local start = mw.ustring.find( source_str, pattern, start_pos, plain )
    Line 361: Line 361:
    ]]
    ]]
    function str.replace( frame )
    function str.replace( frame )
    local new_args = str._getParameters( frame.args, {'source', 'pattern', 'replace', 'count', 'plain' } );
    local new_args = str._getParameters( frame.args, {'source', 'pattern', 'replace', 'count', 'plain' } )
    local source_str = new_args['source'] or '';
    local source_str = new_args['source'] or ''
    local pattern = new_args['pattern'] or '';
    local pattern = new_args['pattern'] or ''
    local replace = new_args['replace'] or '';
    local replace = new_args['replace'] or ''
    local count = tonumber( new_args['count'] );
    local count = tonumber( new_args['count'] )
    local plain = new_args['plain'] or true;
    local plain = new_args['plain'] or true


    if source_str == '' or pattern == '' then
    if source_str == '' or pattern == '' then
    return source_str;
    return source_str
    end
    end
    plain = str._getBoolean( plain );
    plain = str._getBoolean( plain )


    if plain then
    if plain then
    pattern = str._escapePattern( pattern );
    pattern = str._escapePattern( pattern )
    replace = mw.ustring.gsub( replace, "%%", "%%%%" ); --Only need to escape replacement sequences.
    replace = mw.ustring.gsub( replace, "%%", "%%%%" ) --Only need to escape replacement sequences.
    end
    end


    local result;
    local result


    if count ~= nil then
    if count ~= nil then
    result = mw.ustring.gsub( source_str, pattern, replace, count );
    result = mw.ustring.gsub( source_str, pattern, replace, count )
    else
    else
    result = mw.ustring.gsub( source_str, pattern, replace );
    result = mw.ustring.gsub( source_str, pattern, replace )
    end
    end


    return result;
    return result
    end
    end


    Line 392: Line 392:
         simple function to pipe string.rep to templates.
         simple function to pipe string.rep to templates.
    ]]
    ]]
    function str.rep( frame )
    function str.rep( frame )
    local repetitions = tonumber( frame.args[2] )
    local repetitions = tonumber( frame.args[2] )
    Line 418: Line 417:
    local pattern_str = frame.args[1]
    local pattern_str = frame.args[1]
    if not pattern_str then
    if not pattern_str then
    return str._error( 'No pattern string specified' );
    return str._error( 'No pattern string specified' )
    end
    end
    local result = str._escapePattern( pattern_str )
    local result = str._escapePattern( pattern_str )
    return result
    return result
    end
    --[[
    count
    This function counts the number of occurrences of one string in another.
    ]]
    function str.count(frame)
    local args = str._getParameters(frame.args, {'source', 'pattern', 'plain'})
    local source = args.source or ''
    local pattern = args.pattern or ''
    local plain = str._getBoolean(args.plain or true)
    if plain then
    pattern = str._escapePattern(pattern)
    end
    local _, count = mw.ustring.gsub(source, pattern, '')
    return count
    end
    --[[
    endswith
    This function determines whether a string ends with another string.
    ]]
    function str.endswith(frame)
    local args = str._getParameters(frame.args, {'source', 'pattern'})
    local source = args.source or ''
    local pattern = args.pattern or ''
    if pattern == '' then
    -- All strings end with the empty string.
    return "yes"
    end
    if mw.ustring.sub(source, -mw.ustring.len(pattern), -1) == pattern then
    return "yes"
    else
    return ""
    end
    end
    --[[
    join
    Join all non empty arguments together; the first argument is the separator.
    Usage:
    {{#invoke:String|join|sep|one|two|three}}
    ]]
    function str.join(frame)
    local args = {}
    local sep
    for _, v in ipairs( frame.args ) do
    if sep then
    if v ~= '' then
    table.insert(args, v)
    end
    else
    sep = v
    end
    end
    return table.concat( args, sep or '' )
    end
    end


    Line 431: Line 487:
    ]]
    ]]
    function str._getParameters( frame_args, arg_list )
    function str._getParameters( frame_args, arg_list )
    local new_args = {};
    local new_args = {}
    local index = 1;
    local index = 1
    local value;
    local value


    for i,arg in ipairs( arg_list ) do
    for _, arg in ipairs( arg_list ) do
    value = frame_args[arg]
    value = frame_args[arg]
    if value == nil then
    if value == nil then
    value = frame_args[index];
    value = frame_args[index]
    index = index + 1;
    index = index + 1
    end
    end
    new_args[arg] = value;
    new_args[arg] = value
    end
    end


    return new_args;
    return new_args
    end
    end


    Line 451: Line 507:
    ]]
    ]]
    function str._error( error_str )
    function str._error( error_str )
    local frame = mw.getCurrentFrame();
    local frame = mw.getCurrentFrame()
    local error_category = frame.args.error_category or 'Errors reported by Module String';
    local error_category = frame.args.error_category or 'Errors reported by Module String'
    local ignore_errors = frame.args.ignore_errors or false;
    local ignore_errors = frame.args.ignore_errors or false
    local no_category = frame.args.no_category or false;
    local no_category = frame.args.no_category or false


    if str._getBoolean(ignore_errors) then
    if str._getBoolean(ignore_errors) then
    return '';
    return ''
    end
    end


    local error_str = '<strong class="error">String Module Error: ' .. error_str .. '</strong>';
    local error_str = '<strong class="error">String Module Error: ' .. error_str .. '</strong>'
    if error_category ~= '' and not str._getBoolean( no_category ) then
    if error_category ~= '' and not str._getBoolean( no_category ) then
    error_str = '[[Category:' .. error_category .. ']]' .. error_str;
    error_str = '[[Category:' .. error_category .. ']]' .. error_str
    end
    end


    return error_str;
    return error_str
    end
    end


    Line 472: Line 528:
    ]]
    ]]
    function str._getBoolean( boolean_str )
    function str._getBoolean( boolean_str )
    local boolean_value;
    local boolean_value


    if type( boolean_str ) == 'string' then
    if type( boolean_str ) == 'string' then
    boolean_str = boolean_str:lower();
    boolean_str = boolean_str:lower()
    if boolean_str == 'false' or boolean_str == 'no' or boolean_str == '0'
    if boolean_str == 'false' or boolean_str == 'no' or boolean_str == '0'
    or boolean_str == '' then
    or boolean_str == '' then
    boolean_value = false;
    boolean_value = false
    else
    else
    boolean_value = true;
    boolean_value = true
    end
    end
    elseif type( boolean_str ) == 'boolean' then
    elseif type( boolean_str ) == 'boolean' then
    boolean_value = boolean_str;
    boolean_value = boolean_str
    else
    else
    error( 'No boolean value found' );
    error( 'No boolean value found' )
    end
    end
    return boolean_value
    return boolean_value
    Line 495: Line 551:
    ]]
    ]]
    function str._escapePattern( pattern_str )
    function str._escapePattern( pattern_str )
    return mw.ustring.gsub( pattern_str, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" );
    return mw.ustring.gsub( pattern_str, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" )
    end
    end


    return str
    return str