quick框架之functions詳解

--[[

Copyright (c) 2011-2014 chukong-inc.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

]]

--------------------------------
-- @module functions

--[[--

提供一組常用函數,以及對 Lua 標準庫的擴展

]]

--[[--

輸出格式化字符串

~~~ lua

printf("The value = %d", 100)

~~~

@param string fmt 輸出格式
@param [mixed ...] 更多參數

]]
function printf(fmt, ...)
    print(string.format(tostring(fmt), ...))
end

--[[--

檢查並嘗試轉換爲數值,如果無法轉換則返回 0

@param mixed value 要檢查的值
@param [integer base] 進制,默認爲十進制

@return number

]]
function checknumber(value, base)
    return tonumber(value, base) or 0
end

--[[--

檢查並嘗試轉換爲整數,如果無法轉換則返回 0

@param mixed value 要檢查的值

@return integer

]]
function checkint(value)
    return math.round(checknumber(value))
end

--[[--

檢查並嘗試轉換爲布爾值,除了 nil 和 false,其他任何值都會返回 true

@param mixed value 要檢查的值

@return boolean

]]
function checkbool(value)
    return (value ~= nil and value ~= false)
end

--[[--

檢查值是否是一個表格,如果不是則返回一個空表格

@param mixed value 要檢查的值

@return table

]]
function checktable(value)
    if type(value) ~= "table" then value = {} end
    return value
end

--[[--

如果表格中指定 key 的值爲 nil,或者輸入值不是表格,返回 false,否則返回 true

@param table hashtable 要檢查的表格
@param mixed key 要檢查的鍵名

@return boolean

]]
function isset(hashtable, key)
    local t = type(hashtable)
    return (t == "table" or t == "userdata") and hashtable[key] ~= nil
end

--[[--

深度克隆一個值

~~~ lua

-- 下面的代碼,t2 是 t1 的引用,修改 t2 的屬性時,t1 的內容也會發生變化
local t1 = {a = 1, b = 2}
local t2 = t1
t2.b = 3    -- t1 = {a = 1, b = 3} <-- t1.b 發生變化

-- clone() 返回 t1 的副本,修改 t2 不會影響 t1
local t1 = {a = 1, b = 2}
local t2 = clone(t1)
t2.b = 3    -- t1 = {a = 1, b = 2} <-- t1.b 不受影響

~~~

@param mixed object 要克隆的值

@return mixed

]]
function clone(object)
    local lookup_table = {}		--這個table用來記錄需要拷貝的對象的拷貝狀態
    local function _copy(object)--這個函數實現拷貝細節
        if type(object) ~= "table" then 	--如果對象類型不是table,就直接返回這個對象就可以了
            return object
        elseif lookup_table[object] then 	--如果這個對象在拷貝的過程中被拷貝過,那就直接返回,只是一個遞歸的過程
            return lookup_table[object]
        end
        local new_table = {}				--拷貝出來的新表
        lookup_table[object] = new_table
        for key, value in pairs(object) do 	--拷貝過程
            new_table[_copy(key)] = _copy(value)
        end
        return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object)
end

--[[--

創建一個類

~~~ lua

-- 定義名爲 Shape 的基礎類
local Shape = class("Shape")

-- ctor() 是類的構造函數,在調用 Shape.new() 創建 Shape 對象實例時會自動執行
function Shape:ctor(shapeName)
    self.shapeName = shapeName
    printf("Shape:ctor(%s)", self.shapeName)
end

-- 爲 Shape 定義個名爲 draw() 的方法
function Shape:draw()
    printf("draw %s", self.shapeName)
end

--

-- Circle 是 Shape 的繼承類
local Circle = class("Circle", Shape)

function Circle:ctor()
    -- 如果繼承類覆蓋了 ctor() 構造函數,那麼必須手動調用父類構造函數
    -- 類名.super 可以訪問指定類的父類
    Circle.super.ctor(self, "circle")
    self.radius = 100
end

function Circle:setRadius(radius)
    self.radius = radius
end

-- 覆蓋父類的同名方法
function Circle:draw()
    printf("draw %s, raidus = %0.2f", self.shapeName, self.raidus)
end

--

local Rectangle = class("Rectangle", Shape)

function Rectangle:ctor()
    Rectangle.super.ctor(self, "rectangle")
end

--

local circle = Circle.new()             -- 輸出: Shape:ctor(circle)
circle:setRaidus(200)
circle:draw()                           -- 輸出: draw circle, radius = 200.00

local rectangle = Rectangle.new()       -- 輸出: Shape:ctor(rectangle)
rectangle:draw()                        -- 輸出: draw rectangle

~~~

### 高級用法

class() 除了定義純 Lua 類之外,還可以從 C++ 對象繼承類。

比如需要創建一個工具欄,並在添加按鈕時自動排列已有的按鈕,那麼我們可以使用如下的代碼:

~~~ lua

-- 從 cc.Node 對象派生 Toolbar 類,該類具有 cc.Node 的所有屬性和行爲
local Toolbar = class("Toolbar", function()
    return display.newNode() -- 返回一個 cc.Node 對象
end)

-- 構造函數
function Toolbar:ctor()
    self.buttons = {} -- 用一個 table 來記錄所有的按鈕
end

-- 添加一個按鈕,並且自動設置按鈕位置
function Toolbar:addButton(button)
    -- 將按鈕對象加入 table
    self.buttons[#self.buttons + 1] = button

    -- 添加按鈕對象到 cc.Node 中,以便顯示該按鈕
    -- 因爲 Toolbar 是從 cc.Node 繼承的,所以可以使用 addChild() 方法
    self:addChild(button)

    -- 按照按鈕數量,調整所有按鈕的位置
    local x = 0
    for _, button in ipairs(self.buttons) do
        button:setPosition(x, 0)
        -- 依次排列按鈕,每個按鈕之間間隔 10 點
        x = x + button:getContentSize().width + 10
    end
end

~~~

class() 的這種用法讓我們可以在 C++ 對象基礎上任意擴展行爲。

既然是繼承,自然就可以覆蓋 C++ 對象的方法:

~~~ lua

function Toolbar:setPosition(x, y)
    -- 由於在 Toolbar 繼承類中覆蓋了 cc.Node 對象的 setPosition() 方法
    -- 所以我們要用以下形式才能調用到 cc.Node 原本的 setPosition() 方法
    getmetatable(self).setPosition(self, x, y)

    printf("x = %0.2f, y = %0.2f", x, y)
end

~~~

**注意:** Lua 繼承類覆蓋的方法並不能從 C++ 調用到。也就是說通過 C++ 代碼調用這個 cc.Node 對象的 setPosition() 方法時,並不會執行我們在 Lua 中定義的 Toolbar:setPosition() 方法。

@param string classname 類名
@param [mixed super] 父類或者創建對象實例的函數

@return table

]]
function class(classname, super)
    local superType = type(super)	--獲取父類類型
    local cls 		--聲明即將要創建的子類

    if superType ~= "function" and superType ~= "table" then 	--如果沒有父類或父類不是上述類型作如下處理
        superType = nil
        super = nil
    end

    if superType == "function" or (super and super.__ctype == 1) then 	--如果父類是lua函數,或C++類
        -- inherited from native C++ Object
        cls = {}

        if superType == "table" then 	--如果父類是一個table,就拷貝table裏面的內容到子類
            -- copy fields from super
            for k,v in pairs(super) do cls[k] = v end
            cls.__create = super.__create
            cls.super    = super
        else 							--如果父類是一個函數,則做下面的處理
            cls.__create = super
            cls.ctor = function() end
        end

        cls.__cname = classname
        cls.__ctype = 1

        function cls.new(...)	--創建子類的實例
            local instance = cls.__create(...)
            -- copy fields from class to native object
            for k,v in pairs(cls) do instance[k] = v end
            instance.class = cls
            instance:ctor(...)
            return instance
        end

    else
        -- inherited from Lua Object
        if super then
            cls = {}
            setmetatable(cls, {__index = super})
            cls.super = super
        else
            cls = {ctor = function() end}
        end

        cls.__cname = classname
        cls.__ctype = 2 -- lua
        cls.__index = cls

        function cls.new(...)
            local instance = setmetatable({}, cls)
            instance.class = cls
            instance:ctor(...)
            return instance
        end
    end

    return cls
end

--[[--

如果對象是指定類或其子類的實例,返回 true,否則返回 false

~~~ lua

local Animal = class("Animal")
local Duck = class("Duck", Animal)

print(iskindof(Duck.new(), "Animal")) -- 輸出 true

~~~

@param mixed obj 要檢查的對象
@param string classname 類名

@return boolean

]]
function iskindof(obj, classname)
    local t = type(obj)
    local mt
    if t == "table" then
        mt = getmetatable(obj) 		--通過元表找到父類
    elseif t == "userdata" then
        mt = tolua.getpeer(obj)
    end

    while mt do
        if mt.__cname == classname then 	--如果父類的名字匹配返回true
            return true
        end
        mt = mt.super
    end

    return false
end

--[[--

載入一個模塊

import() 與 require() 功能相同,但具有一定程度的自動化特性。

假設我們有如下的目錄結構:

~~~

app/
app/classes/
app/classes/MyClass.lua
app/classes/MyClassBase.lua
app/classes/data/Data1.lua
app/classes/data/Data2.lua

~~~

MyClass 中需要載入 MyClassBase 和 MyClassData。如果用 require(),MyClass 內的代碼如下:

~~~ lua

local MyClassBase = require("app.classes.MyClassBase")
local MyClass = class("MyClass", MyClassBase)

local Data1 = require("app.classes.data.Data1")
local Data2 = require("app.classes.data.Data2")

~~~

假如我們將 MyClass 及其相關文件換一個目錄存放,那麼就必須修改 MyClass 中的 require() 命令,否則將找不到模塊文件。

而使用 import(),我們只需要如下寫:

~~~ lua

local MyClassBase = import(".MyClassBase")
local MyClass = class("MyClass", MyClassBase)

local Data1 = import(".data.Data1")
local Data2 = import(".data.Data2")

~~~

當在模塊名前面有一個"." 時,import() 會從當前模塊所在目錄中查找其他模塊。因此 MyClass 及其相關文件不管存放到什麼目錄裏,我們都不再需要修改 MyClass 中的 import() 命令。這在開發一些重複使用的功能組件時,會非常方便。

我們可以在模塊名前添加多個"." ,這樣 import() 會從更上層的目錄開始查找模塊。

~

不過 import() 只有在模塊級別調用(也就是沒有將 import() 寫在任何函數中)時,才能夠自動得到當前模塊名。如果需要在函數中調用 import(),那麼就需要指定當前模塊名:

~~~ lua

# MyClass.lua

# 這裏的 ... 是隱藏參數,包含了當前模塊的名字,所以最好將這行代碼寫在模塊的第一行
local CURRENT_MODULE_NAME = ...

local function testLoad()
    local MyClassBase = import(".MyClassBase", CURRENT_MODULE_NAME)
    # 更多代碼
end

~~~

@param string moduleName 要載入的模塊的名字
@param [string currentModuleName] 當前模塊名

@return module

]]
function import(moduleName, currentModuleName)
    local currentModuleNameParts
    local moduleFullName = moduleName
    local offset = 1

    while true do
        if string.byte(moduleName, offset) ~= 46 then -- .   --這裏返回字符的整數形式
            moduleFullName = string.sub(moduleName, offset)  --截取字符串
            if currentModuleNameParts and #currentModuleNameParts > 0 then
                moduleFullName = table.concat(currentModuleNameParts, ".") .. "." .. moduleFullName
            end
            break
        end
        offset = offset + 1

        if not currentModuleNameParts then
            -- 這個import最重要的就是這個debug.getlocal(3,1)思想的使用了
            -- debug.getlocal()可以用來檢查任意活動函數的局部變量,第一個參數是查詢的函數棧層,第二個參數是查詢的變量索引
            -- (這裏要解釋一下要查詢的函數變量的查詢方法,這裏的變量指的是,在函數當前作用於內活躍的變量)
            -- 第一個返回值查詢變量的名字,第二個返回值是查詢變量的值
            -- 這裏重點解釋一下第一個參數爲什麼是3!
            -- 還拿上面的例子,MyClass和MyClassBase在同一個目錄,要想在直接import(".MyClassBase")
            -- 就必須回到MyClass所在的目錄,1代表咱們現在分析的import函數本身,2代表調用這個import函數的那個地方(也就是Myclass這裏)
            -- 3就代表了MyClass所在的目錄了
            if not currentModuleName then
                local n,v = debug.getlocal(3, 1)
                currentModuleName = v
            end

            currentModuleNameParts = string.split(currentModuleName, ".")   --以.號切分模塊目錄文件名
        end
        table.remove(currentModuleNameParts, #currentModuleNameParts)       --刪除並返回table末尾的元素
    end

    return require(moduleFullName)
end

--[[--

將 Lua 對象及其方法包裝爲一個匿名函數

在 quick-cocos2d-x 中,許多功能需要傳入一個 Lua 函數做參數,然後在特定事件發生時就會調用傳入的函數。例如觸摸事件、幀事件等等。

~~~ lua

local MyScene = class("MyScene", function()
    return display.newScene("MyScene")
end)

function MyScene:ctor()
    self.frameTimeCount = 0
    -- 註冊幀事件
    self:addNodeEventListener(cc.NODE_ENTER_FRAME_EVENT, self.onEnterFrame)
    self:scheduleUpdate()
end

function MyScene:onEnterFrame(dt)
    self.frameTimeCount = self.frameTimeCount + dt
end

~~~

上述代碼執行時將出錯,報告"Invalid self" ,這就是因爲 C++ 無法識別 Lua 對象方法。因此在調用我們傳入的 self.onEnterFrame 方法時沒有提供正確的參數。

要讓上述的代碼正常工作,就需要使用 handler() 進行一下包裝:

~~~ lua

function MyScene:ctor()
    self.frameTimeCount = 0
    -- 註冊幀事件
    self:addNodeEventListener(cc.ENTER_FRAME_EVENT, handler(self, self.onEnterFrame))
    self:scheduleUpdate()
end

~~~

實際上,除了 C++ 回調 Lua 函數之外,在其他所有需要回調的地方都可以使用 handler()。

@param mixed obj Lua 對象
@param function method 對象方法

@return function

]]
function handler(obj, method)
    return function(...)
        return method(obj, ...)
    end
end


--------------------------------
-- @module math

-- start --

--------------------------------
-- 根據系統時間初始化隨機數種子,讓後續的 math.random() 返回更隨機的值
-- @function [parent=#math] newrandomseed

-- end --

function math.newrandomseed()
    local ok, socket = pcall(function()
        return require("socket")
    end)

    if ok then
        -- 如果集成了 socket 模塊,則使用 socket.gettime() 獲取隨機數種子
        math.randomseed(socket.gettime())
    else
        math.randomseed(os.time())
    end
    math.random()
    math.random()
    math.random()
    math.random()
end

-- start --

--------------------------------
-- 對數值進行四捨五入,如果不是數值則返回 0
-- @function [parent=#math] round
-- @param number value 輸入值
-- @return number#number 

-- end --

function math.round(value)
    value = checknumber(value)
    return math.floor(value + 0.5)
end

-- start --

--------------------------------
-- 角度轉弧度
-- @function [parent=#math] angle2radian

-- end --

function math.angle2radian(angle)
	return angle*math.pi/180
end

-- start --

--------------------------------
-- 弧度轉角度
-- @function [parent=#math] radian2angle

-- end --

function math.radian2angle(radian)
	return radian/math.pi*180
end



--------------------------------
-- @module io

-- start --

--------------------------------
-- 檢查指定的文件或目錄是否存在,如果存在返回 true,否則返回 false
-- @function [parent=#io] exists
-- @param string path 要檢查的文件或目錄的完全路徑
-- @return boolean#boolean 

--[[--

檢查指定的文件或目錄是否存在,如果存在返回 true,否則返回 false

可以使用 cc.FileUtils:fullPathForFilename() 函數查找特定文件的完整路徑,例如:

~~~ lua

local path = cc.FileUtils:getInstance():fullPathForFilename("gamedata.txt")
if io.exists(path) then
    ....
end

~~~

]]

-- end --

function io.exists(path)
    local file = io.open(path, "r")
    if file then
        io.close(file)
        return true
    end
    return false
end

-- start --

--------------------------------
-- 讀取文件內容,返回包含文件內容的字符串,如果失敗返回 nil
-- @function [parent=#io] readfile
-- @param string path 文件完全路徑
-- @return string#string 

--[[--

讀取文件內容,返回包含文件內容的字符串,如果失敗返回 nil

io.readfile() 會一次性讀取整個文件的內容,並返回一個字符串,因此該函數不適宜讀取太大的文件。

]]

-- end --

function io.readfile(path)
    local file = io.open(path, "r")
    if file then
        local content = file:read("*a")
        io.close(file)
        return content
    end
    return nil
end

-- start --

--------------------------------
-- 以字符串內容寫入文件,成功返回 true,失敗返回 false
-- @function [parent=#io] writefile
-- @param string path 文件完全路徑
-- @param string content 要寫入的內容
-- @param string mode 寫入模式,默認值爲 "w+b"
-- @return boolean#boolean 

--[[--

以字符串內容寫入文件,成功返回 true,失敗返回 false

"mode 寫入模式" 參數決定 io.writefile() 如何寫入內容,可用的值如下:

-   "w+" : 覆蓋文件已有內容,如果文件不存在則創建新文件
-   "a+" : 追加內容到文件尾部,如果文件不存在則創建文件

此外,還可以在 "寫入模式" 參數最後追加字符 "b" ,表示以二進制方式寫入數據,這樣可以避免內容寫入不完整。

**Android 特別提示:** 在 Android 平臺上,文件只能寫入存儲卡所在路徑,assets 和 data 等目錄都是無法寫入的。

]]

-- end --

function io.writefile(path, content, mode)
    mode = mode or "w+b"
    local file = io.open(path, mode)
    if file then
        if file:write(content) == nil then return false end
        io.close(file)
        return true
    else
        return false
    end
end

-- start --

--------------------------------
-- 拆分一個路徑字符串,返回組成路徑的各個部分
-- @function [parent=#io] pathinfo
-- @param string path 要分拆的路徑字符串
-- @return table#table 

--[[--

拆分一個路徑字符串,返回組成路徑的各個部分

~~~ lua

local pathinfo  = io.pathinfo("/var/app/test/abc.png")

-- 結果:
-- pathinfo.dirname  = "/var/app/test/"
-- pathinfo.filename = "abc.png"
-- pathinfo.basename = "abc"
-- pathinfo.extname  = ".png"

~~~

]]

-- end --

-- 這個函數的實現方法是判斷最後一個/的位置和.的位置
-- 用while循環來記錄最後一個/的位置爲pos
-- .的位置爲extpos

function io.pathinfo(path)
    local pos = string.len(path)
    local extpos = pos + 1
    while pos > 0 do
        local b = string.byte(path, pos)
        if b == 46 then -- 46 = char "."
            extpos = pos
        elseif b == 47 then -- 47 = char "/"
            break
        end
        pos = pos - 1
    end

    local dirname = string.sub(path, 1, pos)
    local filename = string.sub(path, pos + 1)
    extpos = extpos - pos
    local basename = string.sub(filename, 1, extpos - 1)
    local extname = string.sub(filename, extpos)
    return {
        dirname = dirname,
        filename = filename,
        basename = basename,
        extname = extname
    }
end

-- start --

--------------------------------
-- 返回指定文件的大小,如果失敗返回 false
-- @function [parent=#io] filesize
-- @param string path 文件完全路徑
-- @return integer#integer 

-- end --

function io.filesize(path)
    local size = false
    local file = io.open(path, "r")
    if file then
        local current = file:seek()
        size = file:seek("end")     --這行代碼直接返回文件尺寸,單位kb
        file:seek("set", current)
        io.close(file)
    end
    return size
end


--------------------------------
-- @module table

-- start --

--------------------------------
-- 計算表格包含的字段數量
-- @function [parent=#table] nums
-- @param table t 要檢查的表格
-- @return integer#integer 

--[[--

計算表格包含的字段數量

Lua table 的 "#" 操作只對依次排序的數值下標數組有效,table.nums() 則計算 table 中所有不爲 nil 的值的個數。

]]

-- end --

function table.nums(t)
    local count = 0
    for k, v in pairs(t) do
        count = count + 1
    end
    return count
end

-- start --

--------------------------------
-- 返回指定表格中的所有鍵
-- @function [parent=#table] keys
-- @param table hashtable 要檢查的表格
-- @return table#table 

--[[--

返回指定表格中的所有鍵

~~~ lua

local hashtable = {a = 1, b = 2, c = 3}
local keys = table.keys(hashtable)
-- keys = {"a", "b", "c"}

~~~

]]

-- end --

function table.keys(hashtable)
    local keys = {}
    for k, v in pairs(hashtable) do
        keys[#keys + 1] = k
    end
    return keys
end

-- start --

--------------------------------
-- 返回指定表格中的所有值
-- @function [parent=#table] values
-- @param table hashtable 要檢查的表格
-- @return table#table 

--[[--

返回指定表格中的所有值

~~~ lua

local hashtable = {a = 1, b = 2, c = 3}
local values = table.values(hashtable)
-- values = {1, 2, 3}

~~~

]]

-- end --

function table.values(hashtable)
    local values = {}
    for k, v in pairs(hashtable) do
        values[#values + 1] = v
    end
    return values
end

-- start --

--------------------------------
-- 將來源表格中所有鍵及其值複製到目標表格對象中,如果存在同名鍵,則覆蓋其值
-- @function [parent=#table] merge
-- @param table dest 目標表格
-- @param table src 來源表格

--[[--

將來源表格中所有鍵及其值複製到目標表格對象中,如果存在同名鍵,則覆蓋其值

~~~ lua

local dest = {a = 1, b = 2}
local src  = {c = 3, d = 4}
table.merge(dest, src)
-- dest = {a = 1, b = 2, c = 3, d = 4}

~~~

]]

-- end --

function table.merge(dest, src)
    for k, v in pairs(src) do
        dest[k] = v
    end
end

-- start --

--------------------------------
-- 在目標表格的指定位置插入來源表格,如果沒有指定位置則連接兩個表格
-- @function [parent=#table] insertto
-- @param table dest 目標表格
-- @param table src 來源表格
-- @param integer begin 插入位置,默認最後

--[[--

在目標表格的指定位置插入來源表格,如果沒有指定位置則連接兩個表格

~~~ lua

local dest = {1, 2, 3}
local src  = {4, 5, 6}
table.insertto(dest, src)
-- dest = {1, 2, 3, 4, 5, 6}

dest = {1, 2, 3}
table.insertto(dest, src, 5)
-- dest = {1, 2, 3, nil, 4, 5, 6}

~~~

]]

-- end --

function table.insertto(dest, src, begin)
	begin = checkint(begin)
	if begin <= 0 then
		begin = #dest + 1
	end

	local len = #src
	for i = 0, len - 1 do
		dest[i + begin] = src[i + 1]
	end
end

-- start --

--------------------------------
-- 從表格中查找指定值,返回其索引,如果沒找到返回 false
-- @function [parent=#table] indexof
-- @param table array 表格
-- @param mixed value 要查找的值
-- @param integer begin 起始索引值
-- @return integer#integer 

--[[--

從表格中查找指定值,返回其索引,如果沒找到返回 false

~~~ lua

local array = {"a", "b", "c"}
print(table.indexof(array, "b")) -- 輸出 2

~~~

]]

-- end --

function table.indexof(array, value, begin)
    for i = begin or 1, #array do
        if array[i] == value then return i end
    end
	return false
end

-- start --

--------------------------------
-- 從表格中查找指定值,返回其 key,如果沒找到返回 nil
-- @function [parent=#table] keyof
-- @param table hashtable 表格
-- @param mixed value 要查找的值
-- @return string#string  該值對應的 key

--[[--

從表格中查找指定值,返回其 key,如果沒找到返回 nil

~~~ lua

local hashtable = {name = "dualface", comp = "chukong"}
print(table.keyof(hashtable, "chukong")) -- 輸出 comp

~~~

]]

-- end --

function table.keyof(hashtable, value)
    for k, v in pairs(hashtable) do
        if v == value then return k end
    end
    return nil
end

-- start --

--------------------------------
-- 從表格中刪除指定值,返回刪除的值的個數
-- @function [parent=#table] removebyvalue
-- @param table array 表格
-- @param mixed value 要刪除的值
-- @param boolean removeall 是否刪除所有相同的值
-- @return integer#integer 

--[[--

從表格中刪除指定值,返回刪除的值的個數

~~~ lua

local array = {"a", "b", "c", "c"}
print(table.removebyvalue(array, "c", true)) -- 輸出 2

~~~

]]

-- end --

function table.removebyvalue(array, value, removeall)
    local c, i, max = 0, 1, #array
    while i <= max do
        if array[i] == value then
            table.remove(array, i)
            c = c + 1
            i = i - 1
            max = max - 1
            if not removeall then break end
        end
        i = i + 1
    end
    return c
end

-- start --

--------------------------------
-- 對錶格中每一個值執行一次指定的函數,並用函數返回值更新表格內容
-- @function [parent=#table] map
-- @param table t 表格
-- @param function fn 函數

--[[--

對錶格中每一個值執行一次指定的函數,並用函數返回值更新表格內容

~~~ lua

local t = {name = "dualface", comp = "chukong"}
table.map(t, function(v, k)
    -- 在每一個值前後添加括號
    return "[" .. v .. "]"
end)

-- 輸出修改後的表格內容
for k, v in pairs(t) do
    print(k, v)
end

-- 輸出
-- name [dualface]
-- comp [chukong]

~~~

fn 參數指定的函數具有兩個參數,並且返回一個值。原型如下:

~~~ lua

function map_function(value, key)
    return value
end

~~~

]]

-- end --

function table.map(t, fn)
    for k, v in pairs(t) do
        t[k] = fn(v, k)
    end
end

-- start --

--------------------------------
-- 對錶格中每一個值執行一次指定的函數,但不改變表格內容
-- @function [parent=#table] walk
-- @param table t 表格
-- @param function fn 函數

--[[--

對錶格中每一個值執行一次指定的函數,但不改變表格內容

~~~ lua

local t = {name = "dualface", comp = "chukong"}
table.walk(t, function(v, k)
    -- 輸出每一個值
    print(v)
end)

~~~

fn 參數指定的函數具有兩個參數,沒有返回值。原型如下:

~~~ lua

function map_function(value, key)

end

~~~

]]

-- end --

function table.walk(t, fn)
    for k,v in pairs(t) do
        fn(v, k)
    end
end

-- start --

--------------------------------
-- 對錶格中每一個值執行一次指定的函數,如果該函數返回 false,則對應的值會從表格中刪除
-- @function [parent=#table] filter
-- @param table t 表格
-- @param function fn 函數

--[[--

對錶格中每一個值執行一次指定的函數,如果該函數返回 false,則對應的值會從表格中刪除

~~~ lua

local t = {name = "dualface", comp = "chukong"}
table.filter(t, function(v, k)
    return v ~= "dualface" -- 當值等於 dualface 時過濾掉該值
end)

-- 輸出修改後的表格內容
for k, v in pairs(t) do
    print(k, v)
end

-- 輸出
-- comp chukong

~~~

fn 參數指定的函數具有兩個參數,並且返回一個 boolean 值。原型如下:

~~~ lua

function map_function(value, key)
    return true or false
end

~~~

]]

-- end --

function table.filter(t, fn)
    for k, v in pairs(t) do
        if not fn(v, k) then t[k] = nil end
    end
end

-- start --

--------------------------------
-- 遍歷表格,確保其中的值唯一
-- @function [parent=#table] unique
-- @param table t 表格
-- @param boolean bArray t是否是數組,是數組,t中重複的項被移除後,後續的項會前移
-- @return table#table  包含所有唯一值的新表格

--[[--

遍歷表格,確保其中的值唯一

~~~ lua

local t = {"a", "a", "b", "c"} -- 重複的 a 會被過濾掉
local n = table.unique(t)

for k, v in pairs(n) do
    print(v)
end

-- 輸出
-- a
-- b
-- c

~~~

]]

-- end --

function table.unique(t, bArray)
    local check = {}
    local n = {}
    local idx = 1
    for k, v in pairs(t) do
        if not check[v] then
            if bArray then
                n[idx] = v
                idx = idx + 1
            else
                n[k] = v
            end
            check[v] = true
        end
    end
    return n
end


--------------------------------
-- @module string


string._htmlspecialchars_set = {}
string._htmlspecialchars_set["&"] = "&"
string._htmlspecialchars_set["\""] = """
string._htmlspecialchars_set["'"] = "'"
string._htmlspecialchars_set["<"] = "<"
string._htmlspecialchars_set[">"] = ">"

-- start --

--------------------------------
-- 將特殊字符轉爲 HTML 轉義符
-- @function [parent=#string] htmlspecialchars
-- @param string input 輸入字符串
-- @return string#string  轉換結果

--[[--

將特殊字符轉爲 HTML 轉義符

~~~ lua

print(string.htmlspecialchars("<ABC>"))
-- 輸出 <ABC>

~~~

]]

-- end --

function string.htmlspecialchars(input)
    for k, v in pairs(string._htmlspecialchars_set) do
        input = string.gsub(input, k, v)
    end
    return input
end

-- start --

--------------------------------
-- 將 HTML 轉義符還原爲特殊字符,功能與 string.htmlspecialchars() 正好相反
-- @function [parent=#string] restorehtmlspecialchars
-- @param string input 輸入字符串
-- @return string#string  轉換結果

--[[--

將 HTML 轉義符還原爲特殊字符,功能與 string.htmlspecialchars() 正好相反

~~~ lua

print(string.restorehtmlspecialchars("<ABC>"))
-- 輸出 <ABC>

~~~

]]

-- end --

function string.restorehtmlspecialchars(input)
    for k, v in pairs(string._htmlspecialchars_set) do
        input = string.gsub(input, v, k)
    end
    return input
end

-- start --

--------------------------------
-- 將字符串中的 \n 換行符轉換爲 HTML 標記
-- @function [parent=#string] nl2br
-- @param string input 輸入字符串
-- @return string#string  轉換結果

--[[--

將字符串中的 \n 換行符轉換爲 HTML 標記

~~~ lua

print(string.nl2br("Hello\nWorld"))
-- 輸出
-- Hello<br />World

~~~

]]

-- end --

function string.nl2br(input)
    return string.gsub(input, "\n", "<br />")
end

-- start --

--------------------------------
-- 將字符串中的特殊字符和 \n 換行符轉換爲 HTML 轉移符和標記
-- @function [parent=#string] text2html
-- @param string input 輸入字符串
-- @return string#string  轉換結果

--[[--

將字符串中的特殊字符和 \n 換行符轉換爲 HTML 轉移符和標記

~~~ lua

print(string.text2html("<Hello>\nWorld"))
-- 輸出
-- <Hello><br />World

~~~

]]

-- end --

function string.text2html(input)
    input = string.gsub(input, "\t", "    ")
    input = string.htmlspecialchars(input)
    input = string.gsub(input, " ", " ")
    input = string.nl2br(input)
    return input
end

-- start --

--------------------------------
-- 用指定字符或字符串分割輸入字符串,返回包含分割結果的數組
-- @function [parent=#string] split
-- @param string input 輸入字符串
-- @param string delimiter 分割標記字符或字符串
-- @return array#array  包含分割結果的數組

--[[--

用指定字符或字符串分割輸入字符串,返回包含分割結果的數組

~~~ lua

local input = "Hello,World"
local res = string.split(input, ",")
-- res = {"Hello", "World"}

local input = "Hello-+-World-+-Quick"
local res = string.split(input, "-+-")
-- res = {"Hello", "World", "Quick"}

~~~

]]

-- end --

function string.split(input, delimiter)
    input = tostring(input)
    delimiter = tostring(delimiter)
    if (delimiter=='') then return false end
    local pos,arr = 0, {}
    -- for each divider found
    for st,sp in function() return string.find(input, delimiter, pos, true) end do
        table.insert(arr, string.sub(input, pos, st - 1))
        pos = sp + 1
    end
    table.insert(arr, string.sub(input, pos))
    return arr
end

-- start --

--------------------------------
-- 去除輸入字符串頭部的空白字符,返回結果
-- @function [parent=#string] ltrim
-- @param string input 輸入字符串
-- @return string#string  結果
-- @see string.rtrim, string.trim

--[[--

去除輸入字符串頭部的空白字符,返回結果

~~~ lua

local input = "  ABC"
print(string.ltrim(input))
-- 輸出 ABC,輸入字符串前面的兩個空格被去掉了

~~~

空白字符包括:

-   空格
-   製表符 \t
-   換行符 \n
-   回到行首符 \r

]]

-- end --

function string.ltrim(input)
    return string.gsub(input, "^[ \t\n\r]+", "")
end

-- start --

--------------------------------
-- 去除輸入字符串尾部的空白字符,返回結果
-- @function [parent=#string] rtrim
-- @param string input 輸入字符串
-- @return string#string  結果
-- @see string.ltrim, string.trim

--[[--

去除輸入字符串尾部的空白字符,返回結果

~~~ lua

local input = "ABC  "
print(string.rtrim(input))
-- 輸出 ABC,輸入字符串最後的兩個空格被去掉了

~~~

]]

-- end --

function string.rtrim(input)
    return string.gsub(input, "[ \t\n\r]+$", "")
end

-- start --

--------------------------------
-- 去掉字符串首尾的空白字符,返回結果
-- @function [parent=#string] trim
-- @param string input 輸入字符串
-- @return string#string  結果
-- @see string.ltrim, string.rtrim

--[[--

去掉字符串首尾的空白字符,返回結果

]]

-- end --

function string.trim(input)
    input = string.gsub(input, "^[ \t\n\r]+", "")
    return string.gsub(input, "[ \t\n\r]+$", "")
end

-- start --

--------------------------------
-- 將字符串的第一個字符轉爲大寫,返回結果
-- @function [parent=#string] ucfirst
-- @param string input 輸入字符串
-- @return string#string  結果

--[[--

將字符串的第一個字符轉爲大寫,返回結果

~~~ lua

local input = "hello"
print(string.ucfirst(input))
-- 輸出 Hello

~~~

]]

-- end --

function string.ucfirst(input)
    return string.upper(string.sub(input, 1, 1)) .. string.sub(input, 2)
end

local function urlencodechar(char)
    return "%" .. string.format("%02X", string.byte(char))
end

-- start --

--------------------------------
-- 將字符串轉換爲符合 URL 傳遞要求的格式,並返回轉換結果
-- @function [parent=#string] urlencode
-- @param string input 輸入字符串
-- @return string#string  轉換後的結果
-- @see string.urldecode

--[[--

將字符串轉換爲符合 URL 傳遞要求的格式,並返回轉換結果

~~~ lua

local input = "hello world"
print(string.urlencode(input))
-- 輸出
-- hello%20world

~~~

]]

-- end --

function string.urlencode(input)
    -- convert line endings
    input = string.gsub(tostring(input), "\n", "\r\n")
    -- escape all characters but alphanumeric, '.' and '-'
    input = string.gsub(input, "([^%w%.%- ])", urlencodechar)
    -- convert spaces to "+" symbols
    return string.gsub(input, " ", "+")
end

-- start --

--------------------------------
-- 將 URL 中的特殊字符還原,並返回結果
-- @function [parent=#string] urldecode
-- @param string input 輸入字符串
-- @return string#string  轉換後的結果
-- @see string.urlencode

--[[--

將 URL 中的特殊字符還原,並返回結果

~~~ lua

local input = "hello%20world"
print(string.urldecode(input))
-- 輸出
-- hello world

~~~

]]

-- end --

function string.urldecode(input)
    input = string.gsub (input, "+", " ")
    input = string.gsub (input, "%%(%x%x)", function(h) return string.char(checknumber(h,16)) end)
    input = string.gsub (input, "\r\n", "\n")
    return input
end

-- start --

--------------------------------
-- 計算 UTF8 字符串的長度,每一箇中文算一個字符
-- @function [parent=#string] utf8len
-- @param string input 輸入字符串
-- @return integer#integer  長度

--[[--

計算 UTF8 字符串的長度,每一箇中文算一個字符

~~~ lua

local input = "你好World"
print(string.utf8len(input))
-- 輸出 7

~~~

]]

-- end --

function string.utf8len(input)
    local len  = string.len(input)
    local left = len
    local cnt  = 0
    local arr  = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}
    while left ~= 0 do
        local tmp = string.byte(input, -left)
        local i   = #arr
        while arr[i] do
            if tmp >= arr[i] then
                left = left - i
                break
            end
            i = i - 1
        end
        cnt = cnt + 1
    end
    return cnt
end

-- start --

--------------------------------
-- 將數值格式化爲包含千分位分隔符的字符串
-- @function [parent=#string] formatnumberthousands
-- @param number num 數值
-- @return string#string  格式化結果

--[[--

將數值格式化爲包含千分位分隔符的字符串

~~~ lua

print(string.formatnumberthousands(1924235))
-- 輸出 1,924,235

~~~

]]

-- end --

function string.formatnumberthousands(num)
    local formatted = tostring(checknumber(num))
    local k
    while true do
        formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
        if k == 0 then break end
    end
    return formatted
end

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章