xml.lua 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. -- MIT License
  2. --
  3. -- Copyright (c) 2019 monolifed
  4. --
  5. -- Permission is hereby granted, free of charge, to any person obtaining a copy
  6. -- of this software and associated documentation files (the "Software"), to deal
  7. -- in the Software without restriction, including without limitation the rights
  8. -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. -- copies of the Software, and to permit persons to whom the Software is
  10. -- furnished to do so, subject to the following conditions:
  11. --
  12. -- The above copyright notice and this permission notice shall be included in all
  13. -- copies or substantial portions of the Software.
  14. --
  15. -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. -- SOFTWARE.
  22. local table_concat = table.concat
  23. local table_sort = table.sort
  24. local table_insert = table.insert
  25. local table_remove = table.remove
  26. local string_format = string.format
  27. local string_sub = string.sub
  28. local string_gsub = string.gsub
  29. local string_find = string.find
  30. local string_gmatch = string.gmatch
  31. local escaped = {
  32. ['''] = [[']],
  33. ['"'] = [["]],
  34. ['&' ] = [[&]],
  35. ['>' ] = [[>]],
  36. ['&lt;' ] = [[<]],
  37. }
  38. -- (table) from attribute string ------------------
  39. local re_attr = [[(%w+)=(["'])(.-)%2]]
  40. local fromstring = function(s)
  41. local t = {}
  42. for k, _, v in string_gmatch(s, re_attr) do
  43. for e, c in pairs(escaped) do v = string_gsub(v, e, c) end
  44. t[k] = v
  45. end
  46. return t
  47. end
  48. -- (attribute string) from table --
  49. local fromtable = function(t)
  50. if not t then return "" end
  51. local s, sn = {}, 1
  52. for k, v in pairs(t) do
  53. v = tostring(v)
  54. for e, c in pairs(escaped) do v = string_gsub(v, c, e) end
  55. s[sn] = string_format(' %s="%s"', k, v); sn = sn + 1
  56. end
  57. table_sort(s)
  58. return table_concat(s)
  59. end
  60. -- XML string to DOM formatted table --
  61. local re_tag = "<(/?)([%w:]+)(.-)(/?)>" -- ls, tag, attr, rs
  62. local endtag_unexpected = "Unexpected end tag '%s'"
  63. local endtag_mismatched = "Cannot close '%s' with '%s'"
  64. local endtag_missing = "Missing end tag for '%s'"
  65. -- Based on classic Lua XML parser by Roberto Ierusalimschy
  66. local parse = function(s)
  67. local stack = {}
  68. local top = {}
  69. table_insert(stack, top)
  70. local si, ei, ls, tag, attr, rs
  71. local i = 1
  72. local text
  73. while true do
  74. si, ei, ls, tag, attr, rs = string_find(s, re_tag, i)
  75. if not si then break end
  76. text = string_sub(s, i, si - 1)
  77. if string_find(text, "%S") then
  78. table_insert(top, text)
  79. end
  80. if rs == "/" then -- self-closing tag
  81. table_insert(top, {_name = tag, _attr = fromstring(attr), empty = 1})
  82. elseif ls == "" then -- start tag
  83. top = {_name = tag, _attr = fromstring(attr)}
  84. table_insert(stack, top)
  85. else -- end tag
  86. local toclose = table_remove(stack)
  87. top = stack[#stack]
  88. if #stack < 1 then
  89. return nil, endtag_unexpected:format(tag)
  90. end
  91. if toclose._name ~= tag then
  92. return nil, endtag_mismatched:format(toclose._name, tag)
  93. end
  94. table_insert(top, toclose)
  95. end
  96. i = ei + 1
  97. end
  98. text = string_sub(s, i)
  99. if string_find(text, "%S") then
  100. table_insert(stack[#stack], text)
  101. end
  102. if #stack > 1 then
  103. return nil, endtag_missing:format(stack[#stack]._name)
  104. end
  105. return stack[1]
  106. end
  107. -- DOM formatted table to XML string --
  108. local _toxml
  109. _toxml = function(t, sub)
  110. if not sub then sub = {} end
  111. local tag = t._name
  112. local attr = fromtable(t._attr)
  113. if #t > 0 then
  114. sub[#sub + 1] = string_format("<%s%s>\n", tag, attr)
  115. for _, v in ipairs(t) do
  116. if type(v) == "table" then
  117. _toxml(v, sub)
  118. else
  119. sub[#sub + 1] = string.format("%s\n", v)
  120. end
  121. end
  122. sub[#sub + 1] = string_format("</%s>\n", tag)
  123. else
  124. sub[#sub + 1] = string_format("<%s%s/>\n", tag, attr)
  125. end
  126. return table_concat(sub)
  127. end
  128. return {
  129. parse = parse,
  130. toxml = function(t) return _toxml(t, {}) end
  131. }