tiled_xml_to_dict.gd 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. # The MIT License (MIT)
  2. #
  3. # Copyright (c) 2018 George Marques
  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. tool
  23. extends Reference
  24. # Reads a TMX file from a path and return a Dictionary with the same structure
  25. # as the JSON map format
  26. # Returns an error code if failed
  27. func read_tmx(path):
  28. var parser = XMLParser.new()
  29. var err = parser.open(path)
  30. if err != OK:
  31. printerr("Error opening TMX file '%s'." % [path])
  32. return err
  33. while parser.get_node_type() != XMLParser.NODE_ELEMENT:
  34. err = parser.read()
  35. if err != OK:
  36. printerr("Error parsing TMX file '%s' (around line %d)." % [path, parser.get_current_line()])
  37. return err
  38. if parser.get_node_name().to_lower() != "map":
  39. printerr("Error parsing TMX file '%s'. Expected 'map' element.")
  40. return ERR_INVALID_DATA
  41. var data = attributes_to_dict(parser)
  42. if not "infinite" in data:
  43. data.infinite = false
  44. data.type = "map"
  45. data.tilesets = []
  46. data.layers = []
  47. err = parser.read()
  48. if err != OK:
  49. printerr("Error parsing TMX file '%s' (around line %d)." % [path, parser.get_current_line()])
  50. return err
  51. while err == OK:
  52. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  53. if parser.get_node_name() == "map":
  54. break
  55. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  56. if parser.get_node_name() == "tileset":
  57. # Empty element means external tileset
  58. if not parser.is_empty():
  59. var tileset = parse_tileset(parser)
  60. if typeof(tileset) != TYPE_DICTIONARY:
  61. # Error happened
  62. return err
  63. data.tilesets.push_back(tileset)
  64. else:
  65. var tileset_data = attributes_to_dict(parser)
  66. if not "source" in tileset_data:
  67. printerr("Error parsing TMX file '%s'. Missing tileset source (around line %d)." % [path, parser.get_current_line()])
  68. return ERR_INVALID_DATA
  69. data.tilesets.push_back(tileset_data)
  70. elif parser.get_node_name() == "layer":
  71. var layer = parse_tile_layer(parser, data.infinite)
  72. if typeof(layer) != TYPE_DICTIONARY:
  73. printerr("Error parsing TMX file '%s'. Invalid tile layer data (around line %d)." % [path, parser.get_current_line()])
  74. return ERR_INVALID_DATA
  75. data.layers.push_back(layer)
  76. elif parser.get_node_name() == "imagelayer":
  77. var layer = parse_image_layer(parser)
  78. if typeof(layer) != TYPE_DICTIONARY:
  79. printerr("Error parsing TMX file '%s'. Invalid image layer data (around line %d)." % [path, parser.get_current_line()])
  80. return ERR_INVALID_DATA
  81. data.layers.push_back(layer)
  82. elif parser.get_node_name() == "objectgroup":
  83. var layer = parse_object_layer(parser)
  84. if typeof(layer) != TYPE_DICTIONARY:
  85. printerr("Error parsing TMX file '%s'. Invalid object layer data (around line %d)." % [path, parser.get_current_line()])
  86. return ERR_INVALID_DATA
  87. data.layers.push_back(layer)
  88. elif parser.get_node_name() == "group":
  89. var layer = parse_group_layer(parser, data.infinite)
  90. if typeof(layer) != TYPE_DICTIONARY:
  91. printerr("Error parsing TMX file '%s'. Invalid group layer data (around line %d)." % [path, parser.get_current_line()])
  92. return ERR_INVALID_DATA
  93. data.layers.push_back(layer)
  94. elif parser.get_node_name() == "properties":
  95. var prop_data = parse_properties(parser)
  96. if typeof(prop_data) == TYPE_STRING:
  97. return prop_data
  98. data.properties = prop_data.properties
  99. data.propertytypes = prop_data.propertytypes
  100. err = parser.read()
  101. return data
  102. # Reads a TSX and return a tileset dictionary
  103. # Returns an error code if fails
  104. func read_tsx(path):
  105. var parser = XMLParser.new()
  106. var err = parser.open(path)
  107. if err != OK:
  108. printerr("Error opening TSX file '%s'." % [path])
  109. return err
  110. while parser.get_node_type() != XMLParser.NODE_ELEMENT:
  111. err = parser.read()
  112. if err != OK:
  113. printerr("Error parsing TSX file '%s' (around line %d)." % [path, parser.get_current_line()])
  114. return err
  115. if parser.get_node_name().to_lower() != "tileset":
  116. printerr("Error parsing TMX file '%s'. Expected 'map' element.")
  117. return ERR_INVALID_DATA
  118. var tileset = parse_tileset(parser)
  119. return tileset
  120. # Parses a tileset element from the XML and return a dictionary
  121. # Return an error code if fails
  122. func parse_tileset(parser):
  123. var err = OK
  124. var data = attributes_to_dict(parser)
  125. data.tiles = {}
  126. err = parser.read()
  127. while err == OK:
  128. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  129. if parser.get_node_name() == "tileset":
  130. break
  131. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  132. if parser.get_node_name() == "tile":
  133. var attr = attributes_to_dict(parser)
  134. var tile_data = parse_tile_data(parser)
  135. if typeof(tile_data) != TYPE_DICTIONARY:
  136. # Error happened
  137. return tile_data
  138. if "properties" in tile_data and "propertytypes" in tile_data:
  139. if not "tileproperties" in data:
  140. data.tileproperties = {}
  141. data.tilepropertytypes = {}
  142. data.tileproperties[str(attr.id)] = tile_data.properties
  143. data.tilepropertytypes[str(attr.id)] = tile_data.propertytypes
  144. tile_data.erase("tileproperties")
  145. tile_data.erase("tilepropertytypes")
  146. data.tiles[str(attr.id)] = tile_data
  147. elif parser.get_node_name() == "image":
  148. var attr = attributes_to_dict(parser)
  149. if not "source" in attr:
  150. printerr("Error loading image tag. No source attribute found (around line %d)." % [parser.get_current_line()])
  151. return ERR_INVALID_DATA
  152. data.image = attr.source
  153. if "width" in attr:
  154. data.imagewidth = attr.width
  155. if "height" in attr:
  156. data.imageheight = attr.height
  157. elif parser.get_node_name() == "properties":
  158. var prop_data = parse_properties(parser)
  159. if typeof(prop_data) != TYPE_DICTIONARY:
  160. # Error happened
  161. return prop_data
  162. data.properties = prop_data.properties
  163. data.propertytypes = prop_data.propertytypes
  164. err = parser.read()
  165. return data
  166. # Parses the data of a single tile from the XML and return a dictionary
  167. # Returns an error code if fails
  168. func parse_tile_data(parser):
  169. var err = OK
  170. var data = {}
  171. var obj_group = {}
  172. if parser.is_empty():
  173. return data
  174. err = parser.read()
  175. while err == OK:
  176. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  177. if parser.get_node_name() == "tile":
  178. return data
  179. elif parser.get_node_name() == "objectgroup":
  180. data.objectgroup = obj_group
  181. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  182. if parser.get_node_name() == "image":
  183. # If there are multiple images in one tile we only use the last one.
  184. var attr = attributes_to_dict(parser)
  185. if not "source" in attr:
  186. printerr("Error loading image tag. No source attribute found (around line %d)." % [parser.get_current_line()])
  187. return ERR_INVALID_DATA
  188. data.image = attr.source
  189. data.imagewidth = attr.width
  190. data.imageheight = attr.height
  191. elif parser.get_node_name() == "objectgroup":
  192. obj_group = attributes_to_dict(parser)
  193. for attr in ["width", "height", "offsetx", "offsety"]:
  194. if not attr in obj_group:
  195. data[attr] = 0
  196. if not "opacity" in data:
  197. data.opacity = 1
  198. if not "visible" in data:
  199. data.visible = true
  200. if parser.is_empty():
  201. data.objectgroup = obj_group
  202. elif parser.get_node_name() == "object":
  203. if not "objects" in obj_group:
  204. obj_group.objects = []
  205. var obj = parse_object(parser)
  206. if typeof(obj) != TYPE_DICTIONARY:
  207. # Error happened
  208. return obj
  209. obj_group.objects.push_back(obj)
  210. elif parser.get_node_name() == "properties":
  211. var prop_data = parse_properties(parser)
  212. data["properties"] = prop_data.properties
  213. data["propertytypes"] = prop_data.propertytypes
  214. err = parser.read()
  215. return data
  216. # Parses the data of a single object from the XML and return a dictionary
  217. # Returns an error code if fails
  218. static func parse_object(parser):
  219. var err = OK
  220. var data = attributes_to_dict(parser)
  221. if not parser.is_empty():
  222. err = parser.read()
  223. while err == OK:
  224. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  225. if parser.get_node_name() == "object":
  226. break
  227. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  228. if parser.get_node_name() == "properties":
  229. var prop_data = parse_properties(parser)
  230. data["properties"] = prop_data.properties
  231. data["propertytypes"] = prop_data.propertytypes
  232. elif parser.get_node_name() == "point":
  233. data.point = true
  234. elif parser.get_node_name() == "ellipse":
  235. data.ellipse = true
  236. elif parser.get_node_name() == "polygon" or parser.get_node_name() == "polyline":
  237. var points = []
  238. var points_raw = parser.get_named_attribute_value("points").split(" ", false, 0)
  239. for pr in points_raw:
  240. points.push_back({
  241. "x": float(pr.split(",")[0]),
  242. "y": float(pr.split(",")[1]),
  243. })
  244. data[parser.get_node_name()] = points
  245. err = parser.read()
  246. return data
  247. # Parses a tile layer from the XML and return a dictionary
  248. # Returns an error code if fails
  249. func parse_tile_layer(parser, infinite):
  250. var err = OK
  251. var data = attributes_to_dict(parser)
  252. data.type = "tilelayer"
  253. if not "x" in data:
  254. data.x = 0
  255. if not "y" in data:
  256. data.y = 0
  257. if infinite:
  258. data.chunks = []
  259. else:
  260. data.data = []
  261. var current_chunk = null
  262. var encoding = ""
  263. if not parser.is_empty():
  264. err = parser.read()
  265. while err == OK:
  266. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  267. if parser.get_node_name() == "layer":
  268. break
  269. elif parser.get_node_name() == "chunk":
  270. data.chunks.push_back(current_chunk)
  271. current_chunk = null
  272. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  273. if parser.get_node_name() == "data":
  274. var attr = attributes_to_dict(parser)
  275. if "compression" in attr:
  276. data.compression = attr.compression
  277. if "encoding" in attr:
  278. encoding = attr.encoding
  279. if attr.encoding != "csv":
  280. data.encoding = attr.encoding
  281. if not infinite:
  282. err = parser.read()
  283. if err != OK:
  284. return err
  285. if attr.encoding != "csv":
  286. data.data = parser.get_node_data().strip_edges()
  287. else:
  288. var csv = parser.get_node_data().split(",", false)
  289. for v in csv:
  290. data.data.push_back(int(v.strip_edges()))
  291. elif parser.get_node_name() == "tile":
  292. var gid = int(parser.get_named_attribute_value_safe("gid"))
  293. if infinite:
  294. current_chunk.data.push_back(gid)
  295. else:
  296. data.data.push_back(gid)
  297. elif parser.get_node_name() == "chunk":
  298. current_chunk = attributes_to_dict(parser)
  299. current_chunk.data = []
  300. if encoding != "":
  301. err = parser.read()
  302. if err != OK:
  303. return err
  304. if encoding != "csv":
  305. current_chunk.data = parser.get_node_data().strip_edges()
  306. else:
  307. var csv = parser.get_node_data().split(",", false)
  308. for v in csv:
  309. current_chunk.data.push_back(int(v.strip_edges()))
  310. elif parser.get_node_name() == "properties":
  311. var prop_data = parse_properties(parser)
  312. if typeof(prop_data) == TYPE_STRING:
  313. return prop_data
  314. data.properties = prop_data.properties
  315. data.propertytypes = prop_data.propertytypes
  316. err = parser.read()
  317. return data
  318. # Parses an object layer from the XML and return a dictionary
  319. # Returns an error code if fails
  320. func parse_object_layer(parser):
  321. var err = OK
  322. var data = attributes_to_dict(parser)
  323. data.type = "objectgroup"
  324. data.objects = []
  325. if not parser.is_empty():
  326. err = parser.read()
  327. while err == OK:
  328. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  329. if parser.get_node_name() == "objectgroup":
  330. break
  331. if parser.get_node_type() == XMLParser.NODE_ELEMENT:
  332. if parser.get_node_name() == "object":
  333. data.objects.push_back(parse_object(parser))
  334. elif parser.get_node_name() == "properties":
  335. var prop_data = parse_properties(parser)
  336. if typeof(prop_data) != TYPE_DICTIONARY:
  337. # Error happened
  338. return prop_data
  339. data.properties = prop_data.properties
  340. data.propertytypes = prop_data.propertytypes
  341. err = parser.read()
  342. return data
  343. # Parses an image layer from the XML and return a dictionary
  344. # Returns an error code if fails
  345. func parse_image_layer(parser):
  346. var err = OK
  347. var data = attributes_to_dict(parser)
  348. data.type = "imagelayer"
  349. data.image = ""
  350. if not parser.is_empty():
  351. err = parser.read()
  352. while err == OK:
  353. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  354. if parser.get_node_name().to_lower() == "imagelayer":
  355. break
  356. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  357. if parser.get_node_name().to_lower() == "image":
  358. var image = attributes_to_dict(parser)
  359. if not image.has("source"):
  360. printerr("Missing source attribute in imagelayer (around line %d)." % [parser.get_current_line()])
  361. return ERR_INVALID_DATA
  362. data.image = image.source
  363. elif parser.get_node_name() == "properties":
  364. var prop_data = parse_properties(parser)
  365. if typeof(prop_data) != TYPE_DICTIONARY:
  366. # Error happened
  367. return prop_data
  368. data.properties = prop_data.properties
  369. data.propertytypes = prop_data.propertytypes
  370. err = parser.read()
  371. return data
  372. # Parses a group layer from the XML and return a dictionary
  373. # Returns an error code if fails
  374. func parse_group_layer(parser, infinite):
  375. var err = OK
  376. var result = attributes_to_dict(parser)
  377. result.type = "group"
  378. result.layers = []
  379. if not parser.is_empty():
  380. err = parser.read()
  381. while err == OK:
  382. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  383. if parser.get_node_name().to_lower() == "group":
  384. break
  385. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  386. if parser.get_node_name() == "layer":
  387. var layer = parse_tile_layer(parser, infinite)
  388. if typeof(layer) != TYPE_DICTIONARY:
  389. printerr("Error parsing TMX file. Invalid tile layer data (around line %d)." % [parser.get_current_line()])
  390. return ERR_INVALID_DATA
  391. result.layers.push_back(layer)
  392. elif parser.get_node_name() == "imagelayer":
  393. var layer = parse_image_layer(parser)
  394. if typeof(layer) != TYPE_DICTIONARY:
  395. printerr("Error parsing TMX file. Invalid image layer data (around line %d)." % [parser.get_current_line()])
  396. return ERR_INVALID_DATA
  397. result.layers.push_back(layer)
  398. elif parser.get_node_name() == "objectgroup":
  399. var layer = parse_object_layer(parser)
  400. if typeof(layer) != TYPE_DICTIONARY:
  401. printerr("Error parsing TMX file. Invalid object layer data (around line %d)." % [parser.get_current_line()])
  402. return ERR_INVALID_DATA
  403. result.layers.push_back(layer)
  404. elif parser.get_node_name() == "group":
  405. var layer = parse_group_layer(parser, infinite)
  406. if typeof(layer) != TYPE_DICTIONARY:
  407. printerr("Error parsing TMX file. Invalid group layer data (around line %d)." % [parser.get_current_line()])
  408. return ERR_INVALID_DATA
  409. result.layers.push_back(layer)
  410. elif parser.get_node_name() == "properties":
  411. var prop_data = parse_properties(parser)
  412. if typeof(prop_data) == TYPE_STRING:
  413. return prop_data
  414. result.properties = prop_data.properties
  415. result.propertytypes = prop_data.propertytypes
  416. err = parser.read()
  417. return result
  418. # Parses properties data from the XML and return a dictionary
  419. # Returns an error code if fails
  420. static func parse_properties(parser):
  421. var err = OK
  422. var data = {
  423. "properties": {},
  424. "propertytypes": {},
  425. }
  426. if not parser.is_empty():
  427. err = parser.read()
  428. while err == OK:
  429. if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
  430. if parser.get_node_name() == "properties":
  431. break
  432. elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
  433. if parser.get_node_name() == "property":
  434. var prop_data = attributes_to_dict(parser)
  435. if not (prop_data.has("name") and prop_data.has("value")):
  436. printerr("Missing information in custom properties (around line %d)." % [parser.get_current_line()])
  437. return ERR_INVALID_DATA
  438. data.properties[prop_data.name] = prop_data.value
  439. if prop_data.has("type"):
  440. data.propertytypes[prop_data.name] = prop_data.type
  441. else:
  442. data.propertytypes[prop_data.name] = "string"
  443. err = parser.read()
  444. return data
  445. # Reads the attributes of the current element and return them as a dictionary
  446. static func attributes_to_dict(parser):
  447. var data = {}
  448. for i in range(parser.get_attribute_count()):
  449. var attr = parser.get_attribute_name(i)
  450. var val = parser.get_attribute_value(i)
  451. if val.is_valid_integer():
  452. val = int(val)
  453. elif val.is_valid_float():
  454. val = float(val)
  455. elif val == "true":
  456. val = true
  457. elif val == "false":
  458. val = false
  459. data[attr] = val
  460. return data