tiled_xml_to_dict.gd 18 KB

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