module("xml",package.seeall)

-- symbolic name for tag index, this allows accessing the tag by var[xml.TAG]
TAG = 0
--url½âÂë
local function unicode_codepoint_as_utf8(codepoint)
   --
   -- codepoint is a number
   --
   if codepoint <= 127 then
      return string.char(codepoint)

   elseif codepoint <= 2047 then
      --
      -- 110yyyxx 10xxxxxx         <-- useful notation from http://en.wikipedia.org/wiki/Utf8
      --
      local highpart = math.floor(codepoint / 0x40)
      local lowpart  = codepoint - (0x40 * highpart)
      return string.char(0xC0 + highpart,
                         0x80 + lowpart)

   elseif codepoint <= 65535 then
      --
      -- 1110yyyy 10yyyyxx 10xxxxxx
      --
      local highpart  = math.floor(codepoint / 0x1000)
      local remainder = codepoint - 0x1000 * highpart
      local midpart   = math.floor(remainder / 0x40)
      local lowpart   = remainder - 0x40 * midpart

      highpart = 0xE0 + highpart
      midpart  = 0x80 + midpart
      lowpart  = 0x80 + lowpart
               
      --
      -- Check for an invalid characgter (thanks Andy R. at Adobe).
      -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070
      --
      if ( highpart == 0xE0 and midpart < 0xA0 ) or
         ( highpart == 0xED and midpart > 0x9F ) or
         ( highpart == 0xF0 and midpart < 0x90 ) or
         ( highpart == 0xF4 and midpart > 0x8F )
      then
         return "?"
      else
         return string.char(highpart,
                            midpart,
                            lowpart)
      end

   else
      --
      -- Not actually used in this JSON-parsing code, but included here for completeness.
      --
      -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx
      --
      local highpart  = math.floor(codepoint / 0x40000)
      local remainder = codepoint - 0x40000 * highpart
      local midA      = math.floor(remainder / 0x1000)
      remainder       = remainder - 0x1000 * midA
      local midB      = math.floor(remainder / 0x40)
      local lowpart   = remainder - 0x40 * midB

      return string.char(0xF0 + highpart,
                         0x80 + midA,
                         0x80 + midB,
                         0x80 + lowpart)
   end
end
-- sets or returns tag of a LuaXML object
function tag(var,tag)
  if type(var)~="table" then return end
  if type(tag)=="nil" then
    return var[TAG]
  end
  var[TAG] = tag
end

-- creates a new LuaXML object either by setting the metatable of an existing Lua table or by setting its tag
function new(arg)
  if type(arg)=="table" then
    setmetatable(arg,{__index=xml, __tostring=xml.str})
    return arg
  end
  local var={}
  setmetatable(var,{__index=xml, __tostring=xml.str})
  if type(arg)=="string" then var[TAG]=arg end
  return var
end

-- appends a new subordinate LuaXML object to an existing one, optionally sets tag
function append(var,tag)
  if type(var)~="table" then return end
  local newVar = new(tag)
  var[#var+1] = newVar
  return newVar
end

-- converts any Lua var into an XML string
function str(var,indent,tagValue)
  if type(var)=="nil" then return end
  local indent = indent or 0
  local indentStr=""
  for i = 1,indent do indentStr=indentStr.."  " end
  local tableStr=""

  if type(var)=="table" then
    local tag = var[TAG] or tagValue or type(var)
    local s = indentStr.."<"..tag
    for k,v in pairs(var) do -- attributes
      if type(k)=="string" then
        if type(v)=="table" and k~="_M" then --  otherwise recursiveness imminent
          tableStr = tableStr..str(v,indent+1,k)
        else
          s = s.." "..k.."=\""..xmlencode(tostring(v)).."\""
        end
      end
    end
    if #var==0 and #tableStr==0 then
      s = s.." />\n"
    elseif #var==1 and type(var[1])~="table" and #tableStr==0 then -- single element
      s = s..">"..xmlencode(tostring(var[1])).."</"..tag..">\n"
    else
      s = s..">\n"
      for k,v in ipairs(var) do -- elements
        if type(v)=="string" then
          s = s..indentStr.."  "..xmlencode(v).." \n"
        else
          s = s..str(v,indent+1)
        end
      end
      s=s..tableStr..indentStr.."</"..tag..">\n"
    end
    return s
  else
    local tag = type(var)
    return indentStr.."<"..tag.."> "..xmlencode(tostring(var)).." </"..tag..">\n"
  end
end


-- saves a Lua var as xml file
function save(var,filename)
  if not var then return end
  if not filename or #filename==0 then return end
  local file = io.open(filename,"w")
  file:write("<?xml version=\"1.0\"?>\n<!-- file \"",filename, "\", generated by LuaXML -->\n\n")
  file:write(str(var))
  io.close(file)
end


-- recursively parses a Lua table for a substatement fitting to the provided tag and attribute
function find(var, tag, attributeKey,attributeValue)
  -- check input:
  if type(var)~="table" then return end
  if type(tag)=="string" and #tag==0 then tag=nil end
  if type(attributeKey)~="string" or #attributeKey==0 then attributeKey=nil end
  if type(attributeValue)=="string" and #attributeValue==0 then attributeValue=nil end
  -- compare this table:
  if tag~=nil then
    --print("matching.."..var[TAG],#var)
    if var[TAG]==tag and ( attributeValue == nil or var[attributeKey]==attributeValue ) then
      return var
    end
  else
    if attributeValue == nil or var[attributeKey]==attributeValue then
      return var
    end
  end
  -- recursively parse subtags:
  for k,v in ipairs(var) do
    if type(v)=="table" then
      --print("..into "..v[TAG])
      local x = find(v, tag, attributeKey,attributeValue)
      if x then return x end
    end
  end
end

function xmlencode(value)
    value = string.gsub (value, "&", "&amp;");      -- '&' -> "&amp;"
    value = string.gsub (value, "<", "&lt;");       -- '<' -> "&lt;"
    value = string.gsub (value, ">", "&gt;");       -- '>' -> "&gt;"
    --value = string.gsub (value, "'", "&apos;");   -- '\'' -> "&apos;"
    value = string.gsub (value, "\"", "&quot;");    -- '"' -> "&quot;"
    -- replace non printable char -> "&#xD;"
    value = string.gsub(value, "([^%w%&%;%p%\t% ])",
        function (c)
            return string.format("&#x%X;", string.byte(c))
            --return string.format("&#x%02X;", string.byte(c))
            --return string.format("&#%02d;", string.byte(c))
        end);
    return value;
end

function xmldecode(value)
    value = string.gsub(value, "&#x([%x]+)%;",
        function(h)
            return unicode_codepoint_as_utf8(tonumber(h,16))
        end);
    value = string.gsub(value, "&#([0-9]+)%;",
        function(h)
            return unicode_codepoint_as_utf8(tonumber(h,10))
        end);
    value = string.gsub (value, "&quot;", "\"");
    value = string.gsub (value, "&apos;", "'");
    value = string.gsub (value, "&gt;", ">");
    value = string.gsub (value, "&lt;", "<");
    value = string.gsub (value, "&amp;", "&");
    return value;
end

function ParseArgs(s)
  local arg = {}
  string.gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a)
        arg[w] = xmldecode(a);
    end)
  return arg
end

function eval(xmlText)
  --eliminate BOM
  xmlText = string.gsub(xmlText, "^\239\187\191", "")
  local stack = {}
  local top = {}
  table.insert(stack, top)
  local ni,c,label,xarg, empty
  local i, j = 1, 1
  while true do
    local ni,j,c,label,xarg, empty = string.find(xmlText, "<(%/?)([%w_:]+)(.-)(%/?)>", i)
    if not ni then break end
    --print(label,xarg)
    local text = string.sub(xmlText, i, ni-1);
    if text:match('[^%s]+') then
        if (not text:match("<%?.-%?>")) and (not text:match("<!--.--->")) then 
            local top = stack[#stack]
            text = text:match("<!%[CDATA%[(.-)]]>") or text
            table.insert(top, text)
            --print(top[TAG])
            --print(text)
        end
    end
    if empty == "/" then  -- empty element tag
      local element = {}
      setmetatable(element,{__index=xml, __tostring=xml.str, __find=xml.find})
      --print('simple element:',label)
      string.gsub(xarg, "(%w+)=([\"'])(.-)%2", function (w, _, a)
        element[w] = xmldecode(a);
      end)
      element[TAG] = label
      local top = stack[#stack]
      table.insert(top, element)
    elseif c == "" then   -- start tag
      local element = {}
      setmetatable(element,{__index=xml, __tostring=xml.str, __find=xml.find})
      --print("openTag =",label);
      string.gsub(xarg, "(%w+)=([\"'])(.-)%2", function (w, _, a)
        element[w] = xmldecode(a);
      end)
      element[TAG] = label
      --print(element[TAG])
      table.insert(stack, element)   -- new level
    else  -- end tag
      --print("closeTag=",label);
      local toclose = table.remove(stack)  -- remove top
      local top = stack[#stack]
      if #stack < 1 then
        error("XmlParser: nothing to close with "..label)
      end
      if toclose[TAG] ~= label then
        error("XmlParser: trying to close "..toclose[TAG].." with "..label)
      end
      table.insert(top, toclose)
    end
    i = j+1
  end
  if #stack > 1 then
    error("XmlParser: unclosed "..stack[stack.n])
  end
  return stack[1][1] or {};
end
