Module:Format ISBN/data/generator

-- Module to generate data for Module:Format ISBN. -- Example usage: --  -- Console example: --  mw.log(p.main)

require("Module:No globals")

local p = {}

local xml_module = "Module:Format ISBN/data/generator/xml"

local function _objectify(object, this) setmetatable(object, this) this.__index = this return object end

-- Minimal XML parser. local Parser = {}

function Parser:new(data) local object = {_data = data, _cursor = 1} return _objectify(object, self) end

-- XML tag iterator. Returns: tag, content --  Tag is nil if no more tags, content is empty for non-value nodes. function Parser:next_tag local first, last, tag = self._data:find("<([^>]+)>", self._cursor) if first then local _, final, content = self._data:find("([^<]*)", last + 1) self._cursor = final return tag, content end end

-- Convenience List, adds an append method. local List = {}

function List:new local object = {} return _objectify(object, self) end

function List:append(item) table.insert(self, item) end

-- Returns: {low, high} local function _convert_range(range, length) local _, _, from, to = range:find("(%d+)[-](%d+)") return {tonumber(from:sub(1, length)), tonumber(to:sub(1, length))} end

-- Returns: {{digits, {{low, high}, ...}}, ...} local function _convert_into_rules(ranges, lengths) local rules = List:new for target = 1, 7 do		local assigned, is_assigned = List:new, false for i, length in ipairs(lengths) do			if length == target then assigned:append(_convert_range(ranges[i], length)) is_assigned = true end end if is_assigned then rules:append({8 - target, assigned}) end end return rules end

-- Returns: {id= "id", --         meta= {{"name", "value"}, ...}, --         data= {{"prefix", "agency", {digits, {{low, high}, ...}}, ...}, ...} local function _parse_document(document) local parser = Parser:new(document) local rangemessage, registration_groups = false, false local parsed = {id = nil, meta = List:new, data = List:new} local prefix, agency, rules, ranges, lengths = nil, nil, nil, nil, nil

for tag, content in parser.next_tag, parser do		if tag == "ISBNRangeMessage" then rangemessage = true parsed.id = tag end if tag == "RegistrationGroups" then registration_groups = true end if rangemessage then if tag:match("^Message.+$") then parsed.meta:append({tag, content}) end end if registration_groups then if tag == "Group" then prefix, agency, rules = nil, nil, nil elseif tag == "Prefix" then prefix = content elseif tag == "Agency" then agency = content elseif tag == "Rules" then ranges, lengths = List:new, List:new elseif tag == "Range" then ranges:append(content) elseif tag == "Length" then lengths:append(tonumber(content)) elseif tag == "/Rules" then rules = _convert_into_rules(ranges, lengths) elseif tag == "/Group" then parsed.data:append({prefix, agency, rules}) end end if tag == "/ISBNRangeMessage" then rangemessage = false end if tag == "/RegistrationGroups" then registration_groups = false end end return parsed end

-- Returns: {"line", ...} local function _generate_lua(parsed) local lua = List:new lua:append("-- " .. parsed.id) for _, pair in ipairs(parsed.meta) do lua:append("--  " .. pair[1] .. ": " .. pair[2]) end

lua:append("-- Format is: {[\"prefix\"] = {{digits, {from, to}, ...}, ...}, ...}") lua:append("local _data = {")

for _, data in ipairs(parsed.data) do		local prefix, agency, assigned = unpack(data) lua:append("\t[\"" .. prefix .. "\"] = { -- " .. agency)

for _, pair in ipairs(assigned) do			local digits, ranges = unpack(pair) local line = "\t\t{" .. digits for _, range in ipairs(ranges) do line = line .. ", {" .. range[1] .. ", " .. range[2] .. "}"			end lua:append(line .. "},") end

lua:append("\t},") end lua:append("}")

lua:append("return _data") return lua end

local function _load_data(data_source) local data = nil if data_source:match("^Module:") then data = require(data_source) else local title = mw.title.new(data_source) assert(title.exists, data_source .. " does not currently exist") data = title:getContent end

assert(type(data) == "string",			data_source .. " returned " .. type(data) .. ", expected string") assert(data:find("!DOCTYPE[ ]+ISBNRangeMessage"),			data_source .. " returned a string that does not contain ISBN RangeMessage XML") return data end

local function _main(data_source) local xml_data = _load_data(data_source) local parsed = _parse_document(xml_data) local lua = _generate_lua(parsed) return table.concat(lua, "\n") end

function p.main(frame) local data_source = xml_module if frame and frame.args then local args = frame.args data_source = args["xml_data"] or args[1] or xml_module end return _main(data_source) end

return p