readrecipegraph.lua 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. local current_element = {}
  2. local key_ids
  3. local recipes
  4. local item_ids
  5. local parse_error
  6. local yEd_detected
  7. local SLAXML = dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/saveload/slaxml/slaxml.lua")
  8. local parser = SLAXML:parser{
  9. startElement = function(name,nsURI,nsPrefix) -- When "<foo" or <x:foo is seen
  10. if parse_error then return end
  11. current_element.type = name
  12. end,
  13. attribute = function(name,value,nsURI,nsPrefix) -- attribute found on current element
  14. if parse_error then return end
  15. if current_element.type == "key" then
  16. current_element[name] = value
  17. elseif current_element.type == "node" then
  18. if name == "id" then
  19. current_element.id = value
  20. end
  21. elseif current_element.type == "edge" then
  22. if name == "id" then
  23. current_element.id = value
  24. elseif name == "target" then
  25. current_element.target = value
  26. elseif name == "source" then
  27. current_element.source = value
  28. end
  29. elseif current_element.type == "graphml" and name == "xmlns:yed" then
  30. yEd_detected = true
  31. elseif current_element.type == "data" and name == "key" then
  32. current_element.key = key_ids[value]
  33. elseif current_element.type == "Shape" and name == "type" then -- A yEd node's shape, record this as fallback data in case the actual data isn't set
  34. current_element.nodeshape = value
  35. elseif current_element.type == "Arrows" and name == "target" then -- A yEd edge's arrow type, use as a fallback in case edge type "returns" isn't set
  36. current_element.arrowtarget = value
  37. end
  38. end,
  39. text = function(text,cdata) -- text and CDATA nodes (cdata is true for cdata nodes)
  40. if parse_error then return end
  41. if current_element.type == "data" then
  42. if current_element.key == "quantity" then
  43. local quantity = tonumber(text)
  44. if not quantity then parse_error = "failed to parse quantity in " .. dump(current_element) return end
  45. current_element[current_element.key] = quantity
  46. else
  47. current_element[current_element.key] = text
  48. end
  49. elseif current_element.type == "NodeLabel" then -- This is a yEd node, record its label as a source of fallback data if the user's been messing about with the graph and didn't set the actual data values
  50. current_element.nodelabel = text
  51. elseif current_element.type == "EdgeLabel" then -- This is a yEd edge, record its label etc
  52. current_element.edgelabel = tonumber(text)
  53. end
  54. end,
  55. closeElement = function(name,nsURI) -- When "</foo>" or </x:foo> or "/>" is seen
  56. if parse_error then return end
  57. if name == "node" then
  58. if yEd_detected and current_element.node_type == nil and current_element.nodelabel ~= nil then -- Fall back to yEd data
  59. if current_element.nodeshape == "roundedrectangle" then
  60. --current_element.node_type = "item" -- don't bother to set this, we don't care about anything other than setting the item id
  61. item_ids[current_element.id] = current_element.nodelabel
  62. elseif current_element.nodeshape == "diamond" then
  63. current_element.node_type = "recipe"
  64. current_element.craft_type = current_element.nodelabel
  65. end
  66. end
  67. if current_element.node_type == "item" then
  68. if not current_element.id then parse_error = name .. " " .. dump(current_element) .. " did not have an id" return end
  69. if not current_element.item then parse_error = "item node " .. current_element.id .. " had no item data" return end
  70. item_ids[current_element.id] = current_element.item
  71. elseif current_element.node_type == "recipe" then
  72. if not current_element.id then parse_error = name .. " " .. dump(current_element) .. " did not have an id" return end
  73. local new_recipe = {craft_type=current_element.craft_type}
  74. if current_element.recipe_extra_data then
  75. local extra_data = minetest.deserialize(current_element.recipe_extra_data)
  76. for k, v in pairs(extra_data) do
  77. new_recipe[k] = v
  78. end
  79. end
  80. recipes[current_element.id] = new_recipe
  81. end
  82. current_element = {}
  83. elseif name == "edge" then
  84. if not current_element.id then parse_error = name .. " " .. dump(current_element) .. " did not have an id" return end
  85. if not current_element.target then parse_error = name .. " " .. dump(current_element) .. " did not have a target" return end
  86. if not current_element.source then parse_error = name .. " " .. dump(current_element) .. " did not have a source" return end
  87. if yEd_detected then -- yEd fallback
  88. if not current_element.quantity and current_element.edgelabel then
  89. current_element.quantity = current_element.edgelabel
  90. end
  91. if not current_element.edge_type and current_element.targetarrow == "white_delta" then
  92. current_element.edge_type = "returns"
  93. end
  94. end
  95. if not current_element.quantity then parse_error = name .. " " .. dump(current_element) .. " did not have a quantity" return end
  96. local item_target = item_ids[current_element.target]
  97. local item_source = item_ids[current_element.source]
  98. local recipe_target = recipes[current_element.target]
  99. local recipe_source = recipes[current_element.source]
  100. if item_source and recipe_target then
  101. recipe_target.input = recipe_target.input or {}
  102. recipe_target.input[item_source] = current_element.quantity
  103. elseif current_element.edge_type == "output" then
  104. if not recipe_source then parse_error = name .. " " .. dump(current_element) .. " output did not have a resolvable source" return end
  105. recipe_source.output = item_target.." "..tostring(current_element.quantity) --ItemStack({name=item, count=current_element.quantity})
  106. elseif recipe_source and item_target then
  107. recipe_source.returns = recipe_source.returns or {}
  108. recipe_source.returns[item_target] = current_element.quantity
  109. else
  110. parse_error = name .. " " .. dump(current_element) .. " did not have a resolvable target or source" return
  111. end
  112. current_element = {}
  113. elseif name == "key" then
  114. if not current_element.id then parse_error = name .. " " .. dump(current_element) .. " did not have an id" return end
  115. key_ids[current_element.id] = current_element["attr.name"]
  116. current_element = {}
  117. end
  118. end,
  119. }
  120. return function(xml)
  121. recipes = {}
  122. item_ids = {}
  123. key_ids = {}
  124. parse_error = nil
  125. yEd_detected = false
  126. parser:parse(xml,{stripWhitespace=true})
  127. for _, recipe in pairs(recipes) do
  128. -- If there's no output and one "returns", make the returns into an output.
  129. if recipe.output == nil then
  130. if recipe.returns then
  131. local count = 0
  132. for k, v in pairs(recipe.returns) do
  133. count = count + 1
  134. end
  135. if count == 1 then
  136. local item, count = next(recipe.returns)
  137. recipe.output = item .. " " .. tostring(count)
  138. recipe.returns = nil
  139. else
  140. if not parse_error then
  141. parse_error = ""
  142. else
  143. parse_error = parse_error .. "/n"
  144. end
  145. parse_error = parse_error .. "Recipe with no output and multiple returns detected, cannot guess output: " .. dump(recipe)
  146. end
  147. end
  148. end
  149. end
  150. local returns = recipes
  151. item_ids = nil
  152. recipes = nil
  153. key_ids = nil
  154. return returns, parse_error
  155. end