Modul:PageTree: Unterschied zwischen den Versionen

Aus skandinavien-wiki.net
w>PerfektesChaos
(hotfix)
K (27 Versionen von wikivoyage:Modul:PageTree importiert)
 
(12 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
--[=[ 2014-09-14
local PageTree = { suite  = "PageTree",
 
                  serial  = "2018-09-13",
Module:pageTree
                  item    = 56033297,
                  maxSub  = 10,
                  strings = { "segment",
                              "self",
                              "stamped",
                              "subpager",
                              "suppress" },
                  toggles = { "lazy",
                              "level",
                              "lineup",
                              "light",
                              "linked",
                              "limit",
                              "list" } }
--[=[
Module:PageTree
Rendering and administration of hierarchical wiki page structures
]=]
]=]
-- local globals
local Current = { maxSub = 10 }
local Sort    = false
local Strings = { "segment", "self", "stamped", "subpager", "suppress" }
local Toggles = { "lazy", "level", "lineup", "light", "linked", "list" }




Zeile 39: Zeile 47:
     local lucky, r
     local lucky, r
     if s:byte( 1, 1 ) == 47 then    -- "/"
     if s:byte( 1, 1 ) == 47 then    -- "/"
         if Current.suite then
         if PageTree.suite then
             s = Current.suite .. s
             s = PageTree.suite .. s
         end
         end
     end
     end
Zeile 49: Zeile 57:
     return r
     return r
end -- facility()
end -- facility()
local function factory( apply )
    -- Clone read-only table
    --    apply  -- table, with basic data elements, read-only
    -- Returns message with markup
    local r = { }
    for k, v in pairs( apply ) do
        r[ k ] = v
    end -- for k, v
    return r
end -- factory()




Zeile 57: Zeile 78:
     -- Returns true if to be hidden
     -- Returns true if to be hidden
     local r = false
     local r = false
     for k, v in pairs( Current.hide ) do
     for k, v in pairs( PageTree.hide ) do
         if ask:match( v ) then
         if ask:match( v ) then
             r = true
             r = true
Zeile 65: Zeile 86:
     return r
     return r
end -- fade()
end -- fade()
local function failsafe( apply )
    -- Clone read-only table
    --    apply  -- table, with basic data elements, read-only
    -- Returns message with markup
    local r = { }
    for k, v in pairs( apply ) do
        r[ k ] = v
    end -- for k, v
    return r
end -- failsafe()




Zeile 87: Zeile 95:
     local r, s, title
     local r, s, title
     local n = 0
     local n = 0
     for k, v in pairs( Current.pages ) do
     for k, v in pairs( PageTree.pages ) do
         n = n + 1
         n = n + 1
         s = v.seed
         s = v.seed
Zeile 100: Zeile 108:
                                       "(-)" .. s )
                                       "(-)" .. s )
                     end
                     end
                 elseif Current.linked and
                 elseif PageTree.linked and
                       title.isRedirect then
                       title.isRedirect then
                     table.insert( redirect,
                     table.insert( redirect,
Zeile 139: Zeile 147:
     local r
     local r
     if adopt:byte( 1, 1 ) == 47 then    -- "/"
     if adopt:byte( 1, 1 ) == 47 then    -- "/"
         r = Current.start .. adopt:sub( 2 )
         r = PageTree.start .. adopt:sub( 2 )
     else
     else
         r = adopt
         r = adopt
Zeile 149: Zeile 157:
     return r
     return r
end -- fair()
end -- fair()
local function fasten( adopt )
    -- Format restrictions
    --    adopt  -- string, with restriction entry
    -- Returns absolute page name, or false
    local designs = {
    autoconfirmed      = "background:#FFFF80",
    editeditorprotected = "background:#FFFF00;border:#FF0000 1px solid",
    superprotect        = "background:#FF0000;border:#FFFF00 9px solid",
    sysop              = "background:#FFFF00;border:#FF0000 3px solid",
    ["?????????"]      = "border:#FF0000 5px solid;color:#FF0000" }
    local restrictions = mw.text.split( adopt, ":" )
    local r = ""
    local start = "margin-left:2em;"
    local staff, strict, style
    for i = 1, #restrictions do
        strict, staff = restrictions[ i ]:match( "^(.*)=(.+)$" )
        strict = mw.text.trim( strict )
        if strict == "" then
            strict = "?????????"
        end
        style = designs[ staff ]
        if not style then
            style  = designs[ "?????????" ]
            strict = strict .. "?????????"
        end
        if start then
            style = start .. style
            start = false
        end
        style = style .. ";padding-left:3px;padding-right:3px;"
        r    = string.format( "%s<span style='%s'>%s</span>",
                          r, style, strict )
    end -- for i
    return r
end -- fasten()
local function fatal( alert )
    -- Format disaster message with class="error" and put into category
    --    alert  -- string, with message, or other data
    -- Returns message string with markup
    local ecat = mw.message.new( "Scribunto-common-error-category" )
    local r = type( alert )
    if r == "string" then
        r = alert
    else
        r = "???? " .. r
    end
    if ecat:isBlank() then
        ecat = ""
    else
        ecat = string.format( "[[Category:%s]]", ecat:plain() )
    end
    r = string.format( "<span class=\"error\">FATAL LUA ERROR %s</span>",
                      r )
        .. ecat
    return r
end -- fatal()




Zeile 155: Zeile 225:
     -- Find parent page
     -- Find parent page
     --    ancestor  -- string, with page name
     --    ancestor  -- string, with page name
     -- Returns page name of parent, or Current.series
     -- Returns page name of parent, or PageTree.series
     local r = ancestor:match( "^(.+)/[^/]+$" )
     local r = ancestor:match( "^(.+)/[^/]+$" )
     if not r then
     if not r then
         r = ancestor:match( "^([^:]+:).+$" )
         r = ancestor:match( "^([^:]+:).+$" )
         if not r then
         if not r then
             r = Current.series
             r = PageTree.series
         end
         end
     end
     end
Zeile 178: Zeile 248:


local function features( apply, access )
local function features( apply, access )
     -- Fill Current.pages with elements
     -- Fill PageTree.pages with elements
     --    apply  -- table, with definitions, read-only
     --    apply  -- table, with definitions, read-only
     --    access  -- string, with relative path of module
     --    access  -- string, with relative path of module
Zeile 196: Zeile 266:
                 if type( v.seed ) == "string" then
                 if type( v.seed ) == "string" then
                     s = v.seed
                     s = v.seed
                     e = failsafe( v )
                     e = factory( v )
                 end
                 end
             end
             end
Zeile 202: Zeile 272:
             if type( v ) == "table" then
             if type( v ) == "table" then
                 s = k
                 s = k
                 e = failsafe( v )
                 e = factory( v )
             end
             end
         elseif k == true then    -- root
         elseif k == true then    -- root
             if Current.pages[ true ] then
             if PageTree.pages[ true ] then
                 bad[ "true" ] = "duplicated"
                 bad[ "true" ] = "duplicated"
             elseif type( v ) == "table" then
             elseif type( v ) == "table" then
                 if type( v.seed ) == "string" then
                 if type( v.seed ) == "string" then
                     Current.pages[ true ]          = failsafe( v )
                     PageTree.pages[ true ]          = factory( v )
                     Current.pages[ true ].children = { }
                     PageTree.pages[ true ].children = { }
                 else
                 else
                     bad[ "true" ] = "seed missing"
                     bad[ "true" ] = "seed missing"
Zeile 226: Zeile 296:
             end
             end
             if s then
             if s then
                 if not Current.pages[ s ] then
                 if not PageTree.pages[ s ] then
                     e.seed = s
                     e.seed = s
                     if e.super then
                     if e.super then
Zeile 232: Zeile 302:
                             e.super = fair( e.super )
                             e.super = fair( e.super )
                         end
                         end
                     else
                     elseif e.super == nil then
                         e.super = father( s )
                         e.super = father( s )
                     end
                     end
                     e.children = { }
                     e.children = { }
                     Current.pages[ s ] = e
                     PageTree.pages[ s ] = e
                 end
                 end
             end
             end
Zeile 260: Zeile 330:


local function feed( access )
local function feed( access )
     -- Fill Current with data, if not yet set
     -- Fill PageTree with data, if not yet set
     --    access  -- string, with relative path of module
     --    access  -- string, with relative path of module
     -- Returns error message, if failed, or false, if fine
     -- Returns error message, if failed, or false, if fine
Zeile 267: Zeile 337:
         local s
         local s
         if type( r.maxSub ) == "number" then
         if type( r.maxSub ) == "number" then
             Current.maxSub = r.maxSub
             PageTree.maxSub = r.maxSub
         end
         end
         if type( r.stamp ) == "string" then
         if type( r.stamp ) == "string" then
             if Current.stamp then
             if PageTree.stamp then
                 if Current.stamp < r.stamp then
                 if PageTree.stamp < r.stamp then
                     Current.stamp = r.stamp
                     PageTree.stamp = r.stamp
                 end
                 end
             else
             else
                 Current.stamp = r.stamp
                 PageTree.stamp = r.stamp
             end
             end
         end
         end
Zeile 281: Zeile 351:
             s = mw.text.trim( r.start )
             s = mw.text.trim( r.start )
             if s ~= "" then
             if s ~= "" then
                 Current.start = s
                 PageTree.start = s
             end
             end
         end
         end
         if not Current.pages then
         if not PageTree.pages then
             Current.pages = { }
             PageTree.pages = { }
         end
         end
         if type( r.pages ) == "table" then
         if type( r.pages ) == "table" then
             if not Current.pages then
             if not PageTree.pages then
                 Current.pages = { }
                 PageTree.pages = { }
             end
             end
             s = features( r.pages, access )
             s = features( r.pages, access )
Zeile 312: Zeile 382:
     -- Format entry as link
     -- Format entry as link
     --    about    -- table, with entry
     --    about    -- table, with entry
     --                  .show   -- link title
     --                  .show       -- link title
     --                  .seed   -- page name
     --                  .seed       -- page name
     --                  .shift -- redirect target
     --                  .shift       -- redirect target
    --                  .protection  -- restrictions
     --    absolute  -- true, if real page name to be shown
     --    absolute  -- true, if real page name to be shown
     -- Returns string
     -- Returns string
Zeile 331: Zeile 402:
         r = string.format( "%s %s", r, about.suffix )
         r = string.format( "%s %s", r, about.suffix )
     end
     end
     if Current.linked and type( about.shift ) == "string" then
     if PageTree.linked and type( about.shift ) == "string" then
         r = string.format( "%s <small>&#8594;[[%s]]</small>",
         r = string.format( "%s <small>&#8594;[[%s]]</small>",
                           r, fair( about.shift ) )
                           r, fair( about.shift ) )
    end
    if PageTree.limit and type( about.protection ) == "string" then
        r = string.format( "%s %s",
                          r, fasten( about.protection ) )
     end
     end
     return r
     return r
Zeile 344: Zeile 419:
     --    adjust  -- string, to be standardized
     --    adjust  -- string, to be standardized
     -- Returns string with key
     -- Returns string with key
     if not Sort then
     if not PageTree.Sort then
         r, Sort = pcall( require, "Module:Sort" )
         r, PageTree.Sort = pcall( require, "Module:Sort" )
         if type( Sort ) == "table" then
         if type( PageTree.Sort ) == "table" then
             Sort = Sort.Sort()
             PageTree.Sort = PageTree.Sort.Sort()
         else
         else
             error( "Module:Sort not ready" )
             error( "Module:Sort not ready" )
         end
         end
     end
     end
     return string.upper( Sort.lex( adjust, "latin", false ) )
     return string.upper( PageTree.Sort.lex( adjust, "latin", false ) )
end -- filter()
end -- filter()


Zeile 380: Zeile 455:
     end
     end
     return ( a1.sort < a2.sort )
     return ( a1.sort < a2.sort )
end -- first()
local function firsthand( a1, a2 )
    -- Compare a1 with a2, considering .show
    --    a1  -- string, with page name
    --    a2  -- string, with page name
    -- Returns true if a1 < a2
    return first( a1, a2, true )
end -- first()
end -- first()


Zeile 389: Zeile 474:
     --    a2  -- string, with page name
     --    a2  -- string, with page name
     -- Returns true if a1 < a2
     -- Returns true if a1 < a2
     local e1 = Current.pages[ a1 ]
     local e1 = PageTree.pages[ a1 ]
     local e2 = Current.pages[ a2 ]
     local e2 = PageTree.pages[ a2 ]
     local r
     local r
     if e1.index then
     if e1.index then
Zeile 412: Zeile 497:
     --    ahead  -- string, with syntax in case of .lazy
     --    ahead  -- string, with syntax in case of .lazy
     local r
     local r
     if Current.lazy then
     if PageTree.lazy then
         r = ":"
         r = ":"
     else
     else
Zeile 419: Zeile 504:
     return r
     return r
end -- flag()
end -- flag()
local function flip( already, ahead, amount, above )
    -- Render subtree as expandable/collapsible list of entries
    --    already  -- number, of initially visible levels
    --    ahead    -- string, leading list syntax, either "#" or "*"
    --    amount  -- number, of leading elements
    --    above    -- table, with top element (not shown)
    --                .children -- will be shown
    -- Returns string with story
    local n = table.maxn( above.children )
    local r = ""
    if n > 0 then
        local live = ( already <= amount )
--      local span = "<span ></span>"
        local e, let, serial
        table.sort( above.children, firstly )
        for i = 1, n do
            e = PageTree.pages[ above.children[ i ] ]
            if e.list == false then
                let = PageTree.list
            elseif PageTree.hide then
                let = not fade( e.seed )
            else
                let = true
            end
            if let then
                local s
                if not e.less then
                    PageTree.item = PageTree.item + 1
                    serial        = string.format( "%s_%d",
                                                  PageTree.serial,
                                                  PageTree.item )
                    if table.maxn( e.children ) > 0 then
                        s = "mw-collapsible"
                        if amount >= already then
                            s = s .. " mw-collapsed"
                        end
                        r = string.format( "%s\n<div class='%s' %s %s>",
                                              r,
                                              s,
                                              "data-expandtext='[+]'",
                                              "data-collapsetext='[-]'" )
                        s = "</div>"
                    else
                        s = ""
                    end
                end
                r = string.format( "%s\n%s%s",
                                  r,
                                  string.rep( ahead, amount ),
                                  field( e, false ) )
                if not e.less then
                    local style
                    if amount >= already then
                        style  = " style='display:none'"
                    else
                        style = ""
                    end
                    r = string.format( "%s\n<div %s%s>\n%s\n</div>%s",
                                      r,
--                                    span,
                                      "class='mw-collapsible-content'",
                                      style,
                                      flip( already,
                                            ahead,
                                            amount + 1,
                                            e ),
                                      s )
                end
            end
        end -- for i
    end
    return r
end -- flip()




Zeile 426: Zeile 587:
     --    acquire  -- string, with page name
     --    acquire  -- string, with page name
     if type( acquire ) == "string" then
     if type( acquire ) == "string" then
         local e = Current.pages[ acquire ]
         local e = PageTree.pages[ acquire ]
         local s = false
         local s = false
         if e then
         if e then
Zeile 440: Zeile 601:
                     e                        = { children = { },
                     e                        = { children = { },
                                                 seed    = acquire }
                                                 seed    = acquire }
                     Current.pages[ acquire ] = e
                     PageTree.pages[ acquire ] = e
                 end
                 end
                 e.super = s
                 e.super = s
Zeile 457: Zeile 618:
local function fluent()
local function fluent()
     -- Collect all .children; add .super where missing
     -- Collect all .children; add .super where missing
    local let = true
     local e
     local e
    local let = true
     for k, v in pairs( PageTree.pages ) do
     for k, v in pairs( Current.pages ) do
         if v.super == nil then
         if not v.super then
             flow( k )
             flow( k )
         elseif not Current.pages[ v.super ] then
         elseif not PageTree.pages[ v.super ] then
             flow( v.super )
             flow( v.super )
         end
         end
     end -- for k, v
     end -- for k, v
     for k, v in pairs( Current.pages ) do
     for k, v in pairs( PageTree.pages ) do
         if Current.level then
         if PageTree.level then
             let = ( not v.seed:find( "/" ) )
             let = ( not v.seed:find( "/" ) )
         end
         end
         if let and v.super then
         if let and v.super then
             e = Current.pages[ v.super ]
             e = PageTree.pages[ v.super ]
             if e then
             if e then
                 table.insert( e.children, k )
                 table.insert( e.children, k )
Zeile 489: Zeile 650:
     --    all    -- true if all grandchildren shall be shown
     --    all    -- true if all grandchildren shall be shown
     -- Returns string with story
     -- Returns string with story
    local let, lift
     local n = table.maxn( above.children )
     local n = table.maxn( above.children )
     local r = ""
     local r = ""
     if n > 0 then
     if n > 0 then
         local e
         local e, let, lift
         local start = "\n" .. string.rep( ahead, amount )
         local start = "\n" .. string.rep( ahead, amount )
         table.sort( above.children, firstly )
         table.sort( above.children, firstly )
         for i = 1, n do
         for i = 1, n do
             e    = Current.pages[ above.children[ i ] ]
             e    = PageTree.pages[ above.children[ i ] ]
             lift = ( all or above.long )
             lift = ( all or above.long )
             if e.list == false then
             if e.list == false then
                 let = Current.list
                 let = PageTree.list
             elseif Current.hide then
             elseif PageTree.hide then
                 let = not fade( e.seed )
                 let = not fade( e.seed )
             else
             else
Zeile 509: Zeile 669:
                 r = string.format( "%s%s%s",
                 r = string.format( "%s%s%s",
                                   r,  start,  field( e, false ) )
                                   r,  start,  field( e, false ) )
                 if lift then
                 if lift and ( all or not e.less ) then
                     r = r .. follow( ahead,  amount + 1,  e,  all )
                     r = r .. follow( ahead,  amount + 1,  e,  all )
                 end
                 end
Zeile 522: Zeile 682:
local function formatAll()
local function formatAll()
     -- Render as single list of entries
     -- Render as single list of entries
    local r
     local collect = { }
     local collect = { }
    local let
     local n      = 0
     local n      = 0
     for k, v in pairs( Current.pages ) do
    local r, let
     for k, v in pairs( PageTree.pages ) do
         let = true
         let = true
         if v.list == false  and
         if v.list == false  and
           ( not Current.list or v.loose or k == true ) then
           ( not PageTree.list or v.loose or k == true ) then
             let = false
             let = false
         elseif Current.level and v.seed:find( "/" ) then
         elseif PageTree.level and v.seed:find( "/" ) then
             let = false
             let = false
         elseif Current.hide then
         elseif PageTree.hide then
             let = not fade( v.seed )
             let = not fade( v.seed )
         end
         end
Zeile 540: Zeile 699:
                 v.show = nil
                 v.show = nil
             end
             end
             if Current.light then
             if PageTree.light then
                 local j, k = v.seed:find( Current.start )
                 local j, k = v.seed:find( PageTree.start )
                 if j == 1 then
                 if j == 1 then
                     v.show = v.seed:sub( k + 1 )
                     v.show = v.seed:sub( k + 1 )
Zeile 552: Zeile 711:
     if n > 0 then
     if n > 0 then
         local start
         local start
         local long = ( not Current.light )
         local long = ( not PageTree.light )
         if Current.lineup then
         if PageTree.lineup then
             start = " * "
             start = " * "
         else
         else
             start = "\n" .. flag( "#" )
             start = "\n" .. flag( "#" )
         end
         end
         table.sort( collect, first )
         table.sort( collect, firsthand )
         r = ""
         r = ""
         for k, v in pairs( collect ) do
         for k, v in pairs( collect ) do
Zeile 571: Zeile 730:
     return r
     return r
end -- formatAll()
end -- formatAll()
local function formatExpand( ancestor, args )
    -- Render entire tree as collapsible list text
    --    ancestor  -- string, with name of root element, or false
    --    args      -- table, with control information
    -- Returns string with story, or false
    local init, r
    if type( ancestor ) == "string" then
        r = ancestor
    else
        r = true
    end
    r = PageTree.pages[ r ]
    if r then
        if type( PageTree.init ) == "number" then
            init = PageTree.init
            if PageTree.init < 1 then
                init = 1
            end
        else
            init = 1
        end
        if type( PageTree.serial ) ~= "string"
          or PageTree.serial == "" then
            PageTree.serial = "pageTree"
        end
        PageTree.item = 0
        r = flip( init,  flag( "*" ),  1,  r )
    else
        r = false
    end
    return r
end -- formatExpand()




Zeile 578: Zeile 772:
     --    ancestor  -- string, with name of root element, or false
     --    ancestor  -- string, with name of root element, or false
     -- Returns string with story
     -- Returns string with story
    local higher
     local sup = PageTree.self
     local sup = Current.self
     local higher, i, r
     local r
     if ancestor then
     if ancestor then
         higher = Current.pages[ ancestor ]
         higher = PageTree.pages[ ancestor ]
         if type( higher ) == "table" then
         if type( higher ) == "table" then
             higher.super = false
             higher.super = false
        end
    else
        local point = PageTree.pages[ sup ]
        if not point then
            sup = true
        elseif point.list == false then
            higher = PageTree.pages[ sup ]
            if type( higher ) == "table" then
                if not higher.loose then
                    sup = true
                end
            else
                sup = true
            end
         end
         end
     end
     end
     while true do
     for i = PageTree.maxSub, 0, -1 do
         higher = Current.pages[ sup ]
         higher = PageTree.pages[ sup ]
         if type( higher ) == "table" then
         if type( higher ) == "table" then
             higher.long = true
             higher.long = true
             sup        = higher.super
             sup        = higher.super
             if not sup then
             if not sup then
                 break    -- while
                 break    -- for
             end
             end
         else
         else
             higher = false
             higher = false
             break    -- while
             break    -- for
         end
         end
     end -- while true
     end   -- for --i
     if higher then
     if higher then
         r = follow( flag( "*" ),  1,  higher,  false )
         r = follow( flag( "*" ),  1,  higher,  false )
Zeile 618: Zeile 825:
     local n      = 1
     local n      = 1
     local reverse = { }
     local reverse = { }
     local sup    = Current.self
     local sup    = PageTree.self
     local r
     local r
     if type( sup ) == "string" and not sup:find( "/", 1, true ) then
     if type( sup ) == "string" and not sup:find( "/", 1, true ) then
         flow( sup )
         flow( sup )
         repeat
         repeat
             higher = Current.pages[ sup ]
             higher = PageTree.pages[ sup ]
             if type( higher ) == "table" then
             if type( higher ) == "table" then
                 sup          = higher.super
                 sup          = higher.super
Zeile 632: Zeile 839:
                 elseif sup then
                 elseif sup then
                     n = n + 1
                     n = n + 1
                     if n > Current.maxSub then
                     if n > PageTree.maxSub then
                         reverse[ n ] = { seed = "???????" }
                         reverse[ n ] = { seed = "???????" }
                         break    -- repeat
                         break    -- repeat
Zeile 687: Zeile 894:
         r = true
         r = true
     end
     end
     r = Current.pages[ r ]
     r = PageTree.pages[ r ]
     if r then
     if r then
         r = follow( flag( "#" ),  1,  r,  true )
         r = follow( flag( "#" ),  1,  r,  true )
Zeile 706: Zeile 913:
       type( args.service ) == "string"  and
       type( args.service ) == "string"  and
       type( args.suite ) == "string" then
       type( args.suite ) == "string" then
         Current.series  = args.series
         PageTree.series  = args.series
         Current.service = args.service
         PageTree.service = args.service
         Current.suite  = args.suite
         PageTree.suite  = args.suite
         if type( args.hide ) == "table" then
         if type( args.hide ) == "table" then
             Current.hide = args.hide
             PageTree.hide = args.hide
         elseif type( args.suppress ) == "string" then
         elseif type( args.suppress ) == "string" then
             Current.hide = { }
             PageTree.hide = { }
             table.insert( Current.hide, args.suppress )
             table.insert( PageTree.hide, args.suppress )
         end
         end
         if Current.series:match( "[:/]$" ) then
         if PageTree.series:match( "[:/]$" ) then
             Current.start = args.series
             PageTree.start = args.series
         else
         else
             Current.start = args.series .. "/"
             PageTree.start = args.series .. "/"
         end
         end
         r = feed( "/" .. Current.series )
         r = feed( "/" .. PageTree.series )
         if r then
         if r then
             r = fault( r )
             r = fault( r )
         else
         else
             local life = true
             local life = true
             if Current.service == "path"  or
             if PageTree.service == "path"  or
               Current.service == "subpages" then
               PageTree.service == "subpages" then
                 if args.self then
                 if args.self then
                     Current.self = args.self
                     PageTree.self = args.self
                 else
                 else
                     Current.page = mw.title.getCurrentTitle()
                     PageTree.page = mw.title.getCurrentTitle()
                     Current.self = Current.page.prefixedText
                     PageTree.self = PageTree.page.prefixedText
                 end
                 end
                 if not Current.pages[ Current.self ] then
                 if not PageTree.pages[ PageTree.self ] then
                     if type( Current.pages[ true ] ) == "table" then
                     if type( PageTree.pages[ true ] ) == "table" then
                         Current.self = true
                         PageTree.self = true
                     else
                     else
                         life = false
                         life = false
Zeile 742: Zeile 949:
             end
             end
             if life then
             if life then
                 if Current.service == "subpages" then
                 if PageTree.service == "subpages" then
                     r = formatSub( args.subpager, args.frame )
                     r = formatSub( args.subpager, args.frame )
                 elseif Current.service == "check" then
                 elseif PageTree.service == "check" then
                     Current.linked = args.linked
                     PageTree.linked = args.linked
                     r = failures()
                     r = failures()
                 else
                 else
                     for k, v in pairs( Toggles ) do
                     for k, v in pairs( PageTree.toggles ) do
                         Current[ v ] = args[ v ]
                         PageTree[ v ] = args[ v ]
                     end -- for k, v
                     end -- for k, v
                     if Current.service == "all" then
                     if PageTree.service == "all" then
                         r = formatAll()
                         r = formatAll()
                     else
                     else
Zeile 757: Zeile 964:
                         if type( args.segment ) == "string" then
                         if type( args.segment ) == "string" then
                             segment = fair( args.segment )
                             segment = fair( args.segment )
                             if not Current.pages[ segment ] then
                             if not PageTree.pages[ segment ] then
                                 Current.pages[ segment ] =
                                 PageTree.pages[ segment ] =
                                                     { seed    = segment,
                                                     { seed    = segment,
                                                       children = { },
                                                       children = { },
Zeile 766: Zeile 973:
                         end
                         end
                         fluent()
                         fluent()
                         if Current.service == "path" then
                         if PageTree.service == "path" then
                             r = formatPath( segment )
                             r = formatPath( segment )
                        elseif PageTree.service == "expand" then
                            r = formatExpand( segment, args )
                         else
                         else
                            if args.limit == "1"  or
                              args.limit == true then
                                PageTree.limit = true
                            end
                             r = formatTree( segment )
                             r = formatTree( segment )
                         end
                         end
                     end
                     end
                     if r and args.stamped and Current.stamp then
                     if r and args.stamped and PageTree.stamp then
                         local babel = mw.language.getContentLanguage()
                         local babel = mw.language.getContentLanguage()
                         local stamp = babel:formatDate( args.stamped,
                         local stamp = babel:formatDate( args.stamped,
                                                         Current.stamp )
                                                         PageTree.stamp )
                         r = stamp .. r
                         r = stamp .. r
                     end
                     end
Zeile 795: Zeile 1.008:
                     suite  = frame:getTitle() }
                     suite  = frame:getTitle() }
     local pars  = frame.args
     local pars  = frame.args
    local lucky  = false
     local r      = pars[ 1 ]
     local r      = pars[ 1 ]
     if r then
     if r then
Zeile 804: Zeile 1.016:
     end
     end
     if r then
     if r then
        local lucky
         params.frame = frame
         params.frame = frame
         for k, v in pairs( Strings ) do
         for k, v in pairs( PageTree.strings ) do
             if pars[ v ]  and  pars[ v ] ~= "" then
             if pars[ v ]  and  pars[ v ] ~= "" then
                 params[ v ] = pars[ v ]
                 params[ v ] = pars[ v ]
             end
             end
         end -- for k, v
         end -- for k, v
         for k, v in pairs( Toggles ) do
         for k, v in pairs( PageTree.toggles ) do
             if pars[ v ] then
             if pars[ v ] then
                 params[ v ] = ( pars[ v ] == "1" )
                 params[ v ] = ( pars[ v ] == "1" )
Zeile 816: Zeile 1.029:
         end -- for k, v
         end -- for k, v
         lucky, r = pcall( forward, params )
         lucky, r = pcall( forward, params )
        if not lucky then
            r = fatal( r )
        end
     else
     else
         r = "'1=' missing"
         r = fault( "'1=' missing" )
     end
     end
     if not lucky then
     if not r then
        r = fault( r )
    elseif not r then
         r = ""
         r = ""
     end
     end
     return r
     return r
end -- framed()
end -- framed()
PageTree.failsafe = function ( assert )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --    assert  -- string, with required version or "wikidata",
    --                or false
    -- Postcondition:
    --    Returns  string with appropriate version, or false
    local r
    local since = assert
    if since == "wikidata" then
        local item = PageTree.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local ent = mw.wikibase.getEntity( string.format( "Q%d",
                                                              item ) )
            if type( ent ) == "table" then
                local vsn = ent:formatPropertyValues( "P348" )
                if type( vsn ) == "table"  and
                  type( vsn.value ) == "string"  and
                  vsn.value ~= "" then
                    r = vsn.value
                end
            end
        end
    end
    if not r then
        if not since  or  since <= PageTree.serial then
            r = PageTree.serial
        else
            r = false
        end
    end
    return r
end -- PageTree.failsafe()




Zeile 844: Zeile 1.095:
function p.check( frame )
function p.check( frame )
     return  framed( frame, "check" )
     return  framed( frame, "check" )
end -- p.path
end -- p.check
 
function p.expand( frame )
    return  framed( frame, "expand" )
end -- p.expand


function p.path( frame )
function p.path( frame )
Zeile 865: Zeile 1.120:
     --              .suite    -- Module path
     --              .suite    -- Module path
     --              .self    -- page name, in service="path"
     --              .self    -- page name, in service="path"
    --              .limit    -- show restrictions
     local lucky, r = pcall( forward, args )
     local lucky, r = pcall( forward, args )
     return r or Current
     return r or PageTree
end -- p.test()
end -- p.test()
p.failsafe = function ( frame )
    -- Check or retrieve version information
    -- Precondition:
    --    frame  -- object; #invoke environment
    -- Postcondition:
    --    Return string with error message or ""
    -- Uses:
    --    PageTree.failsafe()
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return PageTree.failsafe( since )  or  ""
end -- p.failsafe()


return p
return p

Aktuelle Version vom 9. Februar 2023, 16:00 Uhr

Die Dokumentation für dieses Modul kann unter Modul:PageTree/doc erstellt werden

local PageTree = { suite   = "PageTree",
                   serial  = "2018-09-13",
                   item    = 56033297,
                   maxSub  = 10,
                   strings = { "segment",
                               "self",
                               "stamped",
                               "subpager",
                               "suppress" },
                   toggles = { "lazy",
                               "level",
                               "lineup",
                               "light",
                               "linked",
                               "limit",
                               "list" } }
--[=[
Module:PageTree
Rendering and administration of hierarchical wiki page structures
]=]



local function face( about )
    -- Ensure presence of entry title
    --     about  -- table, with entry
    --               .show   -- link title
    --               .seed   -- page name
    if not about.show then
       about.show = about.seed:match( "/([^/]+)$" )
       if not about.show then
           about.show = about.seed:match( "^[^:]+:(.+)$" )
           if not about.show then
               about.show = about.seed
           end
       end
    end
end -- face()



local function facility( access )
    -- Load data table
    --     access  -- string, with path of module
    --                        maybe relative, if starting with "/"
    local s = access
    local lucky, r
    if s:byte( 1, 1 ) == 47 then    -- "/"
        if PageTree.suite then
            s = PageTree.suite .. s
        end
    end
    lucky, r = pcall( mw.loadData, s )
    if type( r ) ~= "table" then
        r = string.format( "'%s' invalid",  s )
    end
    return r
end -- facility()



local function factory( apply )
    -- Clone read-only table
    --     apply  -- table, with basic data elements, read-only
    -- Returns message with markup
    local r = { }
    for k, v in pairs( apply ) do
        r[ k ] = v
    end -- for k, v
    return r
end -- factory()



local function fade( ask )
    -- Check whether page is to be hidden
    --     ask  -- string, with page name
    -- Returns true if to be hidden
    local r = false
    for k, v in pairs( PageTree.hide ) do
        if ask:match( v ) then
            r = true
            break -- for k, v
        end
    end -- for k, v
    return r
end -- fade()



local function failures()
    -- Check all pages
    local redirect = {}
    local unknown  = {}
    local r, s, title
    local n = 0
    for k, v in pairs( PageTree.pages ) do
        n = n + 1
        s = v.seed
        if type( s ) == "string" then
            title = mw.title.new( s )
            if not title then
                table.insert( unknown, s )
            elseif title.exists then
                if v.shift then
                    if not title.isRedirect then
                        table.insert( redirect,
                                      "(-)" .. s )
                    end
                elseif PageTree.linked and
                       title.isRedirect then
                    table.insert( redirect,
                                  "(+)" .. s )
                end
            else
                table.insert( unknown, s )
            end
        end
    end -- for k, v
    r = string.format( "n=%d", n )
    n = table.maxn( unknown )
    if n > 0 then
        s = "*** unknown:"
        for i = 1, n do
            r = string.format( "%s %s %s", r, s, unknown[ i ] )
            s = "|"
        end -- for i
    else
        n = table.maxn( redirect )
        if n > 0 then
            s = "*** unexpected redirect:"
            for i = 1, n do
                r = string.format( "%s %s %s", r, s, redirect[ i ] )
                s = "|"
            end -- for i
        end
    end
    return r
end -- failures()



local function fair( adopt )
    -- Expand relative page name, if necessary
    --     adopt  -- string, with page name
    -- Returns absolute page name, or false
    local r
    if adopt:byte( 1, 1 ) == 47 then    -- "/"
        r = PageTree.start .. adopt:sub( 2 )
    else
        r = adopt
    end
    r = mw.text.trim( r )
    if r == "" then
        r = false
    end
    return r
end -- fair()



local function fasten( adopt )
    -- Format restrictions
    --     adopt  -- string, with restriction entry
    -- Returns absolute page name, or false
    local designs = {
     autoconfirmed       = "background:#FFFF80",
     editeditorprotected = "background:#FFFF00;border:#FF0000 1px solid",
     superprotect        = "background:#FF0000;border:#FFFF00 9px solid",
     sysop               = "background:#FFFF00;border:#FF0000 3px solid",
     ["?????????"]       = "border:#FF0000 5px solid;color:#FF0000" }
    local restrictions = mw.text.split( adopt, ":" )
    local r = ""
    local start = "margin-left:2em;"
    local staff, strict, style
    for i = 1, #restrictions do
        strict, staff = restrictions[ i ]:match( "^(.*)=(.+)$" )
        strict = mw.text.trim( strict )
        if strict == "" then
            strict = "?????????"
        end
        style = designs[ staff ]
        if not style then
            style  = designs[ "?????????" ]
            strict = strict .. "?????????"
        end
        if start then
            style = start .. style
            start = false
        end
        style = style .. ";padding-left:3px;padding-right:3px;"
        r     = string.format( "%s<span style='%s'>%s</span>",
                           r, style, strict )
    end -- for i
    return r
end -- fasten()



local function fatal( alert )
    -- Format disaster message with class="error" and put into category
    --     alert   -- string, with message, or other data
    -- Returns message string with markup
    local ecat = mw.message.new( "Scribunto-common-error-category" )
    local r = type( alert )
    if r == "string" then
        r = alert
    else
        r = "???? " .. r
    end
    if ecat:isBlank() then
        ecat = ""
    else
        ecat = string.format( "[[Category:%s]]", ecat:plain() )
    end
    r = string.format( "<span class=\"error\">FATAL LUA ERROR %s</span>",
                       r )
        .. ecat
    return r
end -- fatal()



local function father( ancestor )
    -- Find parent page
    --     ancestor  -- string, with page name
    -- Returns page name of parent, or PageTree.series
    local r = ancestor:match( "^(.+)/[^/]+$" )
    if not r then
        r = ancestor:match( "^([^:]+:).+$" )
        if not r then
            r = PageTree.series
        end
    end
    return r
end -- father()



local function fault( alert )
    -- Format message with class="error"
    --     alert  -- string, with message
    -- Returns message with markup
    return string.format( "<span class=\"error\">%s</span>", alert )
end -- fault()



local function features( apply, access )
    -- Fill PageTree.pages with elements
    --     apply   -- table, with definitions, read-only
    --     access  -- string, with relative path of module
    -- Returns error message, if failed, or false, if fine
    local r, e, s
    local bad = { }
    local tmp = { }
    for k, v in pairs( apply ) do
        s = type( k )
        e = false
        if s == "number" then
            s = type( v )
            if s == "string" then
                s = v
                e = { }
            elseif s == "table" then
                if type( v.seed ) == "string" then
                    s = v.seed
                    e = factory( v )
                end
            end
        elseif s == "string" then
            if type( v ) == "table" then
                s = k
                e = factory( v )
            end
        elseif k == true then    -- root
            if PageTree.pages[ true ] then
                bad[ "true" ] = "duplicated"
            elseif type( v ) == "table" then
                if type( v.seed ) == "string" then
                    PageTree.pages[ true ]          = factory( v )
                    PageTree.pages[ true ].children = { }
                else
                    bad[ "true" ] = "seed missing"
                end
            else
                bad[ "true" ] = "invalid"
            end
        end
        if e then
            s = fair( s )
            if tmp[ s ] then
                bad[ s ] = "duplicated"
            else
                tmp[ s ] = true
            end
            if s then
                if not PageTree.pages[ s ] then
                    e.seed = s
                    if e.super then
                        if type( e.super ) == "string" then
                            e.super = fair( e.super )
                        end
                    elseif e.super == nil then
                        e.super = father( s )
                    end
                    e.children = { }
                    PageTree.pages[ s ] = e
                end
            end
        end
    end -- for k, v
    e = 0
    r = string.format( " in '%s'", access )
    for k, v in pairs( bad ) do
        e = e + 1
        r = string.format( "%s * [%s]: %s ", r, k, v )
    end -- for k, v
    if e == 0 then
        r = false
    elseif e == 1 then
        r = "Error" .. r
    else
        r = "Errors" .. r
    end
    return r
end -- features()



local function feed( access )
    -- Fill PageTree with data, if not yet set
    --     access  -- string, with relative path of module
    -- Returns error message, if failed, or false, if fine
    local r = facility( access )
    if type( r ) == "table" then
        local s
        if type( r.maxSub ) == "number" then
            PageTree.maxSub = r.maxSub
        end
        if type( r.stamp ) == "string" then
            if PageTree.stamp then
                if PageTree.stamp < r.stamp then
                    PageTree.stamp = r.stamp
                end
            else
                PageTree.stamp = r.stamp
            end
        end
        if type( r.start ) == "string" then
            s = mw.text.trim( r.start )
            if s ~= "" then
                PageTree.start = s
            end
        end
        if not PageTree.pages then
            PageTree.pages = { }
        end
        if type( r.pages ) == "table" then
            if not PageTree.pages then
                PageTree.pages = { }
            end
            s = features( r.pages, access )
            if s then
                r = s
            end
        end
        if type( r ) == "table" then
            if type( r.sub ) == "string" then
                r = feed( string.format( "%s/%s", access, r.sub ) )
            else
                r = false
            end
        end
    end
    return r
end -- feed()



local function field( about, absolute )
    -- Format entry as link
    --     about     -- table, with entry
    --                  .show        -- link title
    --                  .seed        -- page name
    --                  .shift       -- redirect target
    --                  .protection  -- restrictions
    --     absolute  -- true, if real page name to be shown
    -- Returns string
    local r
    if absolute then
        r = string.format( "[[%s]]", about.seed )
    else
        face( about )
        if about.show == about.seed then
            r = string.format( "[[%s]]", about.seed )
        else
            r = string.format( "[[%s|%s]]", about.seed, about.show )
        end
    end
    if type( about.suffix ) == "string" then
        r = string.format( "%s %s", r, about.suffix )
    end
    if PageTree.linked and type( about.shift ) == "string" then
        r = string.format( "%s <small>&#8594;[[%s]]</small>",
                           r, fair( about.shift ) )
    end
    if PageTree.limit and type( about.protection ) == "string" then
        r = string.format( "%s %s",
                           r, fasten( about.protection ) )
    end
    return r
end -- field()



local function filter( adjust )
    -- Create sort key (Latin ASCII upcased)
    --     adjust  -- string, to be standardized
    -- Returns string with key
    if not PageTree.Sort then
        r, PageTree.Sort = pcall( require, "Module:Sort" )
        if type( PageTree.Sort ) == "table" then
            PageTree.Sort = PageTree.Sort.Sort()
        else
            error( "Module:Sort not ready" )
        end
    end
    return string.upper( PageTree.Sort.lex( adjust, "latin", false ) )
end -- filter()



local function first( a1, a2, abs )
    -- Compare a1 with a2 in lexicographical order
    --     a1   -- table, with page entry
    --     a2   -- table, with page entry
    --     abs  -- true, if .show to be used rather than .seed
    -- Returns true if a1 < a2
    if not a1.sort then
        if abs then
            face( a1 )
            a1.sort = filter( a1.show )
        else
            a1.sort = filter( a1.seed )
        end
    end
    if not a2.sort then
        if abs then
            face( a2 )
            a2.sort = filter( a2.show )
        else
            a2.sort = filter( a2.seed )
        end
    end
    return ( a1.sort < a2.sort )
end -- first()



local function firsthand( a1, a2 )
    -- Compare a1 with a2, considering .show
    --     a1  -- string, with page name
    --     a2  -- string, with page name
    -- Returns true if a1 < a2
    return first( a1, a2, true )
end -- first()



local function firstly( a1, a2 )
    -- Compare a1 with a2, considering .index
    --     a1  -- string, with page name
    --     a2  -- string, with page name
    -- Returns true if a1 < a2
    local e1 = PageTree.pages[ a1 ]
    local e2 = PageTree.pages[ a2 ]
    local r
    if e1.index then
        if e2.index then
            r = ( e1.index < e2.index )
        else
            r = true
        end
    elseif e2.index then
        r = false
    else
        r = first( e1, e2, true )
    end
    return r
end -- firstly()



local function flag( ahead )
    -- Returns string with leading list syntax, either "#" or "*" or ":"
    --     ahead  -- string, with syntax in case of .lazy
    local r
    if PageTree.lazy then
        r = ":"
    else
        r = ahead
    end
    return r
end -- flag()



local function flip( already, ahead, amount, above )
    -- Render subtree as expandable/collapsible list of entries
    --     already  -- number, of initially visible levels
    --     ahead    -- string, leading list syntax, either "#" or "*"
    --     amount   -- number, of leading elements
    --     above    -- table, with top element (not shown)
    --                 .children -- will be shown
    -- Returns string with story
    local n = table.maxn( above.children )
    local r = ""
    if n > 0 then
        local live = ( already <= amount )
--      local span = "<span ></span>"
        local e, let, serial
        table.sort( above.children, firstly )
        for i = 1, n do
            e = PageTree.pages[ above.children[ i ] ]
            if e.list == false then
                let = PageTree.list
            elseif PageTree.hide then
                let = not fade( e.seed )
            else
                let = true
            end
            if let then
                local s
                if not e.less then
                    PageTree.item = PageTree.item + 1
                    serial        = string.format( "%s_%d",
                                                   PageTree.serial,
                                                   PageTree.item )
                    if table.maxn( e.children ) > 0 then
                        s = "mw-collapsible"
                        if amount >= already then
                            s = s .. " mw-collapsed"
                        end
                        r = string.format( "%s\n<div class='%s' %s %s>",
                                              r,
                                              s,
                                              "data-expandtext='[+]'",
                                              "data-collapsetext='[-]'" )
                        s = "</div>"
                    else
                        s = ""
                    end
                end
                r = string.format( "%s\n%s%s",
                                   r,
                                   string.rep( ahead, amount ),
                                   field( e, false ) )
                if not e.less then
                    local style
                    if amount >= already then
                        style  = " style='display:none'"
                    else
                        style = ""
                    end
                    r = string.format( "%s\n<div %s%s>\n%s\n</div>%s",
                                       r,
--                                     span,
                                       "class='mw-collapsible-content'",
                                       style,
                                       flip( already,
                                             ahead,
                                             amount + 1,
                                             e ),
                                       s )
                end
            end
        end -- for i
    end
    return r
end -- flip()



local function flow( acquire )
    -- Collect the .super in path
    --     acquire  -- string, with page name
    if type( acquire ) == "string" then
        local e = PageTree.pages[ acquire ]
        local s = false
        if e then
            s = e.super
        end
        if not s then
            s = acquire:match( "^(.+)/[^/]+$" )
            if not s then
                s = acquire:match( "^([^:]+:)" )
            end
            if s then
                if not e then
                    e                        = { children = { },
                                                 seed     = acquire }
                    PageTree.pages[ acquire ] = e
                end
                e.super = s
            elseif e then
                e.super = true
            end
        end
        if type( s ) == "string"  and  s~= acquire then
            flow( s )
        end
    end
end -- flow()



local function fluent()
    -- Collect all .children; add .super where missing
    local let = true
    local e
    for k, v in pairs( PageTree.pages ) do
        if v.super == nil then
            flow( k )
        elseif not PageTree.pages[ v.super ] then
            flow( v.super )
        end
    end -- for k, v
    for k, v in pairs( PageTree.pages ) do
        if PageTree.level then
            let = ( not v.seed:find( "/" ) )
        end
        if let and v.super then
            e = PageTree.pages[ v.super ]
            if e then
                table.insert( e.children, k )
            end
        end
    end -- for k, v
end -- fluent()



local function follow( ahead, amount, above, all )
    -- Render subtree as list of entries
    --     ahead   -- string, with leading list syntax, either "#" or "*"
    --     amount  -- number, of leading elements
    --     above   -- table, with top element (not shown)
    --                .children -- will be shown
    --     all     -- true if all grandchildren shall be shown
    -- Returns string with story
    local n = table.maxn( above.children )
    local r = ""
    if n > 0 then
        local e, let, lift
        local start = "\n" .. string.rep( ahead, amount )
        table.sort( above.children, firstly )
        for i = 1, n do
            e    = PageTree.pages[ above.children[ i ] ]
            lift = ( all or above.long )
            if e.list == false then
                let = PageTree.list
            elseif PageTree.hide then
                let = not fade( e.seed )
            else
                let = lift
            end
            if let then
                r = string.format( "%s%s%s",
                                   r,  start,  field( e, false ) )
                if lift and ( all or not e.less ) then
                    r = r .. follow( ahead,  amount + 1,  e,  all )
                end
            end
        end -- for i
    end
    return r
end -- follow()



local function formatAll()
    -- Render as single list of entries
    local collect = { }
    local n       = 0
    local r, let
    for k, v in pairs( PageTree.pages ) do
        let = true
        if v.list == false  and
           ( not PageTree.list or v.loose or k == true ) then
            let = false
        elseif PageTree.level and v.seed:find( "/" ) then
            let = false
        elseif PageTree.hide then
            let = not fade( v.seed )
        end
        if let then
            if v.show then
                v.show = nil
            end
            if PageTree.light then
                local j, k = v.seed:find( PageTree.start )
                if j == 1 then
                    v.show = v.seed:sub( k + 1 )
                end
            end
            n            = n + 1
            collect[ n ] = v
        end
    end -- for k, v
    if n > 0 then
        local start
        local long = ( not PageTree.light )
        if PageTree.lineup then
            start = " * "
        else
            start = "\n" .. flag( "#" )
        end
        table.sort( collect, firsthand )
        r = ""
        for k, v in pairs( collect ) do
            r = string.format( "%s%s%s",
                               r,
                               start,
                               field( v, long ) )
        end -- for k, v
    else
        r = false
    end
    return r
end -- formatAll()



local function formatExpand( ancestor, args )
    -- Render entire tree as collapsible list text
    --     ancestor  -- string, with name of root element, or false
    --     args      -- table, with control information
    -- Returns string with story, or false
    local init, r
    if type( ancestor ) == "string" then
        r = ancestor
    else
        r = true
    end
    r = PageTree.pages[ r ]
    if r then
        if type( PageTree.init ) == "number" then
            init = PageTree.init
            if PageTree.init < 1 then
                init = 1
            end
        else
            init = 1
        end
        if type( PageTree.serial ) ~= "string"
           or PageTree.serial == "" then
            PageTree.serial = "pageTree"
        end
        PageTree.item = 0
        r = flip( init,  flag( "*" ),  1,  r )
    else
        r = false
    end
    return r
end -- formatExpand()



local function formatPath( ancestor )
    -- Render tree as partially opened list
    --     ancestor  -- string, with name of root element, or false
    -- Returns string with story
    local sup = PageTree.self
    local higher, i, r
    if ancestor then
        higher = PageTree.pages[ ancestor ]
        if type( higher ) == "table" then
            higher.super = false
        end
    else
        local point = PageTree.pages[ sup ]
        if not point then
            sup = true
        elseif point.list == false then
            higher = PageTree.pages[ sup ]
            if type( higher ) == "table" then
                if not higher.loose then
                    sup = true
                end
            else
                sup = true
            end
        end
    end
    for i = PageTree.maxSub, 0, -1 do
        higher = PageTree.pages[ sup ]
        if type( higher ) == "table" then
            higher.long = true
            sup         = higher.super
            if not sup then
                break    -- for
            end
        else
            higher = false
            break    -- for
        end
    end    -- for --i
    if higher then
        r = follow( flag( "*" ),  1,  higher,  false )
    else
        r = false
    end
    return r
end -- formatPath()



local function formatSub( amend, around )
    -- Render tree as subpage hierarchy sequence
    --     amend   -- string, with name of template, or false
    --     around  -- object, with frame, or false
    -- Returns string with story, or false
    local higher
    local n       = 1
    local reverse = { }
    local sup     = PageTree.self
    local r
    if type( sup ) == "string" and not sup:find( "/", 1, true ) then
        flow( sup )
        repeat
            higher = PageTree.pages[ sup ]
            if type( higher ) == "table" then
                sup          = higher.super
                reverse[ n ] = higher
                if higher.loose then
                    n = -1
                    break    -- repeat
                elseif sup then
                    n = n + 1
                    if n > PageTree.maxSub then
                        reverse[ n ] = { seed = "???????" }
                        break    -- repeat
                    end
                else
                    break    -- repeat
                end
            else
                break    -- repeat
            end
        until not higher
    end
    if n > 1 then
        for i = n, 2, -1 do
            reverse[ i ] = field( reverse[ i ], false )
        end -- for i
        if amend then
            local frame
            local ordered = { }
            if around then
                frame = around
            else
                frame = mw.getCurrentFrame()
            end
            for i = n, 2, -1 do
                ordered[ n - i + 1 ] = reverse[ i ]
            end -- for i
            r = frame:expandTemplate{ title=amend, args=ordered }
        else
            r = ""
            for i = n, 2, -1 do
                if i < n then
                    r = r .. "&#160;&#62; "
                end
                r = r .. reverse[ i ]
            end -- for i
        end
    else
        r = false
    end
    return r
end -- formatSub()



local function formatTree( ancestor )
    -- Render entire tree as list text
    --     ancestor  -- string, with name of root element, or false
    -- Returns string with story, or false
    local r
    if type( ancestor ) == "string" then
        r = ancestor
    else
        r = true
    end
    r = PageTree.pages[ r ]
    if r then
        r = follow( flag( "#" ),  1,  r,  true )
    else
        r = false
    end
    return r
end -- formatTree()



local function forward( args )
    -- Execute main task
    --     args  -- table, with arguments
    -- Returns string with story, or false
    local r
    if type( args.series ) == "string"  and
       type( args.service ) == "string"  and
       type( args.suite ) == "string" then
        PageTree.series  = args.series
        PageTree.service = args.service
        PageTree.suite   = args.suite
        if type( args.hide ) == "table" then
            PageTree.hide = args.hide
        elseif type( args.suppress ) == "string" then
            PageTree.hide = { }
            table.insert( PageTree.hide, args.suppress )
        end
        if PageTree.series:match( "[:/]$" ) then
            PageTree.start = args.series
        else
            PageTree.start = args.series .. "/"
        end
        r = feed( "/" .. PageTree.series )
        if r then
            r = fault( r )
        else
            local life = true
            if PageTree.service == "path"  or
               PageTree.service == "subpages" then
                if args.self then
                    PageTree.self = args.self
                else
                    PageTree.page = mw.title.getCurrentTitle()
                    PageTree.self = PageTree.page.prefixedText
                end
                if not PageTree.pages[ PageTree.self ] then
                     if type( PageTree.pages[ true ] ) == "table" then
                        PageTree.self = true
                    else
                        life = false
                    end
                end
            end
            if life then
                if PageTree.service == "subpages" then
                    r = formatSub( args.subpager, args.frame )
                elseif PageTree.service == "check" then
                    PageTree.linked = args.linked
                    r = failures()
                else
                    for k, v in pairs( PageTree.toggles ) do
                        PageTree[ v ] = args[ v ]
                    end -- for k, v
                    if PageTree.service == "all" then
                        r = formatAll()
                    else
                        local segment
                        if type( args.segment ) == "string" then
                            segment = fair( args.segment )
                            if not PageTree.pages[ segment ] then
                                PageTree.pages[ segment ] =
                                                    { seed     = segment,
                                                      children = { },
                                                      super    = true,
                                                      list     = false }
                            end
                        end
                        fluent()
                        if PageTree.service == "path" then
                            r = formatPath( segment )
                        elseif PageTree.service == "expand" then
                            r = formatExpand( segment, args )
                        else
                            if args.limit == "1"  or
                               args.limit == true then
                                PageTree.limit = true
                            end
                            r = formatTree( segment )
                        end
                    end
                    if r and args.stamped and PageTree.stamp then
                        local babel = mw.language.getContentLanguage()
                        local stamp = babel:formatDate( args.stamped,
                                                        PageTree.stamp )
                        r = stamp .. r
                    end
                end
            else
                r = false
            end
        end
    end
    return r
end -- forward()



local function framed( frame, action )
    -- #invoke call
    --     action  -- string, with keyword
    local params = { service = action,
                     suite   = frame:getTitle() }
    local pars   = frame.args
    local r      = pars[ 1 ]
    if r then
        params.series = mw.text.trim( r )
        if params.series == "" then
            r = false
        end
    end
    if r then
        local lucky
        params.frame = frame
        for k, v in pairs( PageTree.strings ) do
            if pars[ v ]  and  pars[ v ] ~= "" then
                params[ v ] = pars[ v ]
            end
        end -- for k, v
        for k, v in pairs( PageTree.toggles ) do
            if pars[ v ] then
                params[ v ] = ( pars[ v ] == "1" )
            end
        end -- for k, v
        lucky, r = pcall( forward, params )
        if not lucky then
            r = fatal( r )
        end
    else
        r = fault( "'1=' missing" )
    end
    if not r then
        r = ""
    end
    return r
end -- framed()



PageTree.failsafe = function ( assert )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     assert  -- string, with required version or "wikidata",
    --                or false
    -- Postcondition:
    --     Returns  string with appropriate version, or false
    local r
    local since = assert
    if since == "wikidata" then
        local item = PageTree.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local ent = mw.wikibase.getEntity( string.format( "Q%d",
                                                              item ) )
            if type( ent ) == "table" then
                local vsn = ent:formatPropertyValues( "P348" )
                if type( vsn ) == "table"  and
                   type( vsn.value ) == "string"  and
                   vsn.value ~= "" then
                    r = vsn.value
                end
            end
        end
    end
    if not r then
        if not since  or  since <= PageTree.serial then
            r = PageTree.serial
        else
            r = false
        end
    end
    return r
end -- PageTree.failsafe()



-- Export
local p = { }

-- lazy   = do not number but use bullets or nothing
-- level  = top level entries only
-- light  = strip prefix
-- linked = show redirects
-- list   = show suppressed entries

function p.all( frame )
    return  framed( frame, "all" )
end -- p.all

function p.check( frame )
    return  framed( frame, "check" )
end -- p.check

function p.expand( frame )
    return  framed( frame, "expand" )
end -- p.expand

function p.path( frame )
    return  framed( frame, "path" )
end -- p.path

function p.subpages( frame )
    return  framed( frame, "subpages" )
end -- p.subpages

function p.tree( frame )
    return  framed( frame, "tree" )
end -- p.tree

function p.test( args )
    -- Debugging
    --     args  -- table, with arguments; mandatory:
    --              .series   -- tree
    --              .service  -- action mode
    --              .suite    -- Module path
    --              .self     -- page name, in service="path"
    --              .limit    -- show restrictions
    local lucky, r = pcall( forward, args )
    return r or PageTree
end -- p.test()

p.failsafe = function ( frame )
    -- Check or retrieve version information
    -- Precondition:
    --     frame  -- object; #invoke environment
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     PageTree.failsafe()
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return PageTree.failsafe( since )  or  ""
end -- p.failsafe()

return p