123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- # The MIT License (MIT)
- #
- # Copyright (c) 2018 George Marques
- #
- # Permission is hereby granted, free of charge, to any person obtaining a copy
- # of this software and associated documentation files (the "Software"), to deal
- # in the Software without restriction, including without limitation the rights
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- # copies of the Software, and to permit persons to whom the Software is
- # furnished to do so, subject to the following conditions:
- #
- # The above copyright notice and this permission notice shall be included in all
- # copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- # SOFTWARE.
- tool
- extends Reference
- # Reads a TMX file from a path and return a Dictionary with the same structure
- # as the JSON map format
- # Returns an error code if failed
- func read_tmx(path):
- var parser = XMLParser.new()
- var err = parser.open(path)
- if err != OK:
- printerr("Error opening TMX file '%s'." % [path])
- return err
- while parser.get_node_type() != XMLParser.NODE_ELEMENT:
- err = parser.read()
- if err != OK:
- printerr("Error parsing TMX file '%s' (around line %d)." % [path, parser.get_current_line()])
- return err
- if parser.get_node_name().to_lower() != "map":
- printerr("Error parsing TMX file '%s'. Expected 'map' element.")
- return ERR_INVALID_DATA
- var data = attributes_to_dict(parser)
- if not "infinite" in data:
- data.infinite = false
- data.type = "map"
- data.tilesets = []
- data.layers = []
- err = parser.read()
- if err != OK:
- printerr("Error parsing TMX file '%s' (around line %d)." % [path, parser.get_current_line()])
- return err
- while err == OK:
- if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
- if parser.get_node_name() == "map":
- break
- elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
- if parser.get_node_name() == "tileset":
- # Empty element means external tileset
- if not parser.is_empty():
- var tileset = parse_tileset(parser)
- if typeof(tileset) != TYPE_DICTIONARY:
- # Error happened
- return err
- data.tilesets.push_back(tileset)
- else:
- var tileset_data = attributes_to_dict(parser)
- if not "source" in tileset_data:
- printerr("Error parsing TMX file '%s'. Missing tileset source (around line %d)." % [path, parser.get_current_line()])
- return ERR_INVALID_DATA
- data.tilesets.push_back(tileset_data)
- elif parser.get_node_name() == "layer":
- var layer = parse_tile_layer(parser, data.infinite)
- if typeof(layer) != TYPE_DICTIONARY:
- printerr("Error parsing TMX file '%s'. Invalid tile layer data (around line %d)." % [path, parser.get_current_line()])
- return ERR_INVALID_DATA
- data.layers.push_back(layer)
- elif parser.get_node_name() == "imagelayer":
- var layer = parse_image_layer(parser)
- if typeof(layer) != TYPE_DICTIONARY:
- printerr("Error parsing TMX file '%s'. Invalid image layer data (around line %d)." % [path, parser.get_current_line()])
- return ERR_INVALID_DATA
- data.layers.push_back(layer)
- elif parser.get_node_name() == "objectgroup":
- var layer = parse_object_layer(parser)
- if typeof(layer) != TYPE_DICTIONARY:
- printerr("Error parsing TMX file '%s'. Invalid object layer data (around line %d)." % [path, parser.get_current_line()])
- return ERR_INVALID_DATA
- data.layers.push_back(layer)
- elif parser.get_node_name() == "group":
- var layer = parse_group_layer(parser, data.infinite)
- if typeof(layer) != TYPE_DICTIONARY:
- printerr("Error parsing TMX file '%s'. Invalid group layer data (around line %d)." % [path, parser.get_current_line()])
- return ERR_INVALID_DATA
- data.layers.push_back(layer)
- elif parser.get_node_name() == "properties":
- var prop_data = parse_properties(parser)
- if typeof(prop_data) == TYPE_STRING:
- return prop_data
- data.properties = prop_data.properties
- data.propertytypes = prop_data.propertytypes
- err = parser.read()
- return data
- # Reads a TSX and return a tileset dictionary
- # Returns an error code if fails
- func read_tsx(path):
- var parser = XMLParser.new()
- var err = parser.open(path)
- if err != OK:
- printerr("Error opening TSX file '%s'." % [path])
- return err
- while parser.get_node_type() != XMLParser.NODE_ELEMENT:
- err = parser.read()
- if err != OK:
- printerr("Error parsing TSX file '%s' (around line %d)." % [path, parser.get_current_line()])
- return err
- if parser.get_node_name().to_lower() != "tileset":
- printerr("Error parsing TMX file '%s'. Expected 'map' element.")
- return ERR_INVALID_DATA
- var tileset = parse_tileset(parser)
- return tileset
- # Parses a tileset element from the XML and return a dictionary
- # Return an error code if fails
- func parse_tileset(parser):
- var err = OK
- var data = attributes_to_dict(parser)
- data.tiles = {}
- err = parser.read()
- while err == OK:
- if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
- if parser.get_node_name() == "tileset":
- break
- elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
- if parser.get_node_name() == "tile":
- var attr = attributes_to_dict(parser)
- var tile_data = parse_tile_data(parser)
- if typeof(tile_data) != TYPE_DICTIONARY:
- # Error happened
- return tile_data
- if "properties" in tile_data and "propertytypes" in tile_data:
- if not "tileproperties" in data:
- data.tileproperties = {}
- data.tilepropertytypes = {}
- data.tileproperties[str(attr.id)] = tile_data.properties
- data.tilepropertytypes[str(attr.id)] = tile_data.propertytypes
- tile_data.erase("tileproperties")
- tile_data.erase("tilepropertytypes")
- data.tiles[str(attr.id)] = tile_data
- elif parser.get_node_name() == "image":
- var attr = attributes_to_dict(parser)
- if not "source" in attr:
- printerr("Error loading image tag. No source attribute found (around line %d)." % [parser.get_current_line()])
- return ERR_INVALID_DATA
- data.image = attr.source
- if "width" in attr:
- data.imagewidth = attr.width
- if "height" in attr:
- data.imageheight = attr.height
- elif parser.get_node_name() == "properties":
- var prop_data = parse_properties(parser)
- if typeof(prop_data) != TYPE_DICTIONARY:
- # Error happened
- return prop_data
- data.properties = prop_data.properties
- data.propertytypes = prop_data.propertytypes
- err = parser.read()
- return data
- # Parses the data of a single tile from the XML and return a dictionary
- # Returns an error code if fails
- func parse_tile_data(parser):
- var err = OK
- var data = {}
- var obj_group = {}
- if parser.is_empty():
- return data
- err = parser.read()
- while err == OK:
- if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
- if parser.get_node_name() == "tile":
- return data
- elif parser.get_node_name() == "objectgroup":
- data.objectgroup = obj_group
- elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
- if parser.get_node_name() == "image":
- # If there are multiple images in one tile we only use the last one.
- var attr = attributes_to_dict(parser)
- if not "source" in attr:
- printerr("Error loading image tag. No source attribute found (around line %d)." % [parser.get_current_line()])
- return ERR_INVALID_DATA
- data.image = attr.source
- data.imagewidth = attr.width
- data.imageheight = attr.height
- elif parser.get_node_name() == "objectgroup":
- obj_group = attributes_to_dict(parser)
- for attr in ["width", "height", "offsetx", "offsety"]:
- if not attr in obj_group:
- data[attr] = 0
- if not "opacity" in data:
- data.opacity = 1
- if not "visible" in data:
- data.visible = true
- if parser.is_empty():
- data.objectgroup = obj_group
- elif parser.get_node_name() == "object":
- if not "objects" in obj_group:
- obj_group.objects = []
- var obj = parse_object(parser)
- if typeof(obj) != TYPE_DICTIONARY:
- # Error happened
- return obj
- obj_group.objects.push_back(obj)
- elif parser.get_node_name() == "properties":
- var prop_data = parse_properties(parser)
- data["properties"] = prop_data.properties
- data["propertytypes"] = prop_data.propertytypes
- err = parser.read()
- return data
- # Parses the data of a single object from the XML and return a dictionary
- # Returns an error code if fails
- static func parse_object(parser):
- var err = OK
- var data = attributes_to_dict(parser)
- if not parser.is_empty():
- err = parser.read()
- while err == OK:
- if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
- if parser.get_node_name() == "object":
- break
- elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
- if parser.get_node_name() == "properties":
- var prop_data = parse_properties(parser)
- data["properties"] = prop_data.properties
- data["propertytypes"] = prop_data.propertytypes
- elif parser.get_node_name() == "point":
- data.point = true
- elif parser.get_node_name() == "ellipse":
- data.ellipse = true
- elif parser.get_node_name() == "polygon" or parser.get_node_name() == "polyline":
- var points = []
- var points_raw = parser.get_named_attribute_value("points").split(" ", false, 0)
- for pr in points_raw:
- points.push_back({
- "x": float(pr.split(",")[0]),
- "y": float(pr.split(",")[1]),
- })
- data[parser.get_node_name()] = points
- err = parser.read()
- return data
- # Parses a tile layer from the XML and return a dictionary
- # Returns an error code if fails
- func parse_tile_layer(parser, infinite):
- var err = OK
- var data = attributes_to_dict(parser)
- data.type = "tilelayer"
- if not "x" in data:
- data.x = 0
- if not "y" in data:
- data.y = 0
- if infinite:
- data.chunks = []
- else:
- data.data = []
- var current_chunk = null
- var encoding = ""
- if not parser.is_empty():
- err = parser.read()
- while err == OK:
- if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
- if parser.get_node_name() == "layer":
- break
- elif parser.get_node_name() == "chunk":
- data.chunks.push_back(current_chunk)
- current_chunk = null
- elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
- if parser.get_node_name() == "data":
- var attr = attributes_to_dict(parser)
- if "compression" in attr:
- data.compression = attr.compression
- if "encoding" in attr:
- encoding = attr.encoding
- if attr.encoding != "csv":
- data.encoding = attr.encoding
- if not infinite:
- err = parser.read()
- if err != OK:
- return err
- if attr.encoding != "csv":
- data.data = parser.get_node_data().strip_edges()
- else:
- var csv = parser.get_node_data().split(",", false)
- for v in csv:
- data.data.push_back(int(v.strip_edges()))
- elif parser.get_node_name() == "tile":
- var gid = int(parser.get_named_attribute_value_safe("gid"))
- if infinite:
- current_chunk.data.push_back(gid)
- else:
- data.data.push_back(gid)
- elif parser.get_node_name() == "chunk":
- current_chunk = attributes_to_dict(parser)
- current_chunk.data = []
- if encoding != "":
- err = parser.read()
- if err != OK:
- return err
- if encoding != "csv":
- current_chunk.data = parser.get_node_data().strip_edges()
- else:
- var csv = parser.get_node_data().split(",", false)
- for v in csv:
- current_chunk.data.push_back(int(v.strip_edges()))
- elif parser.get_node_name() == "properties":
- var prop_data = parse_properties(parser)
- if typeof(prop_data) == TYPE_STRING:
- return prop_data
- data.properties = prop_data.properties
- data.propertytypes = prop_data.propertytypes
- err = parser.read()
- return data
- # Parses an object layer from the XML and return a dictionary
- # Returns an error code if fails
- func parse_object_layer(parser):
- var err = OK
- var data = attributes_to_dict(parser)
- data.type = "objectgroup"
- data.objects = []
- if not parser.is_empty():
- err = parser.read()
- while err == OK:
- if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
- if parser.get_node_name() == "objectgroup":
- break
- if parser.get_node_type() == XMLParser.NODE_ELEMENT:
- if parser.get_node_name() == "object":
- data.objects.push_back(parse_object(parser))
- elif parser.get_node_name() == "properties":
- var prop_data = parse_properties(parser)
- if typeof(prop_data) != TYPE_DICTIONARY:
- # Error happened
- return prop_data
- data.properties = prop_data.properties
- data.propertytypes = prop_data.propertytypes
- err = parser.read()
- return data
- # Parses an image layer from the XML and return a dictionary
- # Returns an error code if fails
- func parse_image_layer(parser):
- var err = OK
- var data = attributes_to_dict(parser)
- data.type = "imagelayer"
- data.image = ""
- if not parser.is_empty():
- err = parser.read()
- while err == OK:
- if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
- if parser.get_node_name().to_lower() == "imagelayer":
- break
- elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
- if parser.get_node_name().to_lower() == "image":
- var image = attributes_to_dict(parser)
- if not image.has("source"):
- printerr("Missing source attribute in imagelayer (around line %d)." % [parser.get_current_line()])
- return ERR_INVALID_DATA
- data.image = image.source
- elif parser.get_node_name() == "properties":
- var prop_data = parse_properties(parser)
- if typeof(prop_data) != TYPE_DICTIONARY:
- # Error happened
- return prop_data
- data.properties = prop_data.properties
- data.propertytypes = prop_data.propertytypes
- err = parser.read()
- return data
- # Parses a group layer from the XML and return a dictionary
- # Returns an error code if fails
- func parse_group_layer(parser, infinite):
- var err = OK
- var result = attributes_to_dict(parser)
- result.type = "group"
- result.layers = []
- if not parser.is_empty():
- err = parser.read()
- while err == OK:
- if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
- if parser.get_node_name().to_lower() == "group":
- break
- elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
- if parser.get_node_name() == "layer":
- var layer = parse_tile_layer(parser, infinite)
- if typeof(layer) != TYPE_DICTIONARY:
- printerr("Error parsing TMX file. Invalid tile layer data (around line %d)." % [parser.get_current_line()])
- return ERR_INVALID_DATA
- result.layers.push_back(layer)
- elif parser.get_node_name() == "imagelayer":
- var layer = parse_image_layer(parser)
- if typeof(layer) != TYPE_DICTIONARY:
- printerr("Error parsing TMX file. Invalid image layer data (around line %d)." % [parser.get_current_line()])
- return ERR_INVALID_DATA
- result.layers.push_back(layer)
- elif parser.get_node_name() == "objectgroup":
- var layer = parse_object_layer(parser)
- if typeof(layer) != TYPE_DICTIONARY:
- printerr("Error parsing TMX file. Invalid object layer data (around line %d)." % [parser.get_current_line()])
- return ERR_INVALID_DATA
- result.layers.push_back(layer)
- elif parser.get_node_name() == "group":
- var layer = parse_group_layer(parser, infinite)
- if typeof(layer) != TYPE_DICTIONARY:
- printerr("Error parsing TMX file. Invalid group layer data (around line %d)." % [parser.get_current_line()])
- return ERR_INVALID_DATA
- result.layers.push_back(layer)
- elif parser.get_node_name() == "properties":
- var prop_data = parse_properties(parser)
- if typeof(prop_data) == TYPE_STRING:
- return prop_data
- result.properties = prop_data.properties
- result.propertytypes = prop_data.propertytypes
- err = parser.read()
- return result
- # Parses properties data from the XML and return a dictionary
- # Returns an error code if fails
- static func parse_properties(parser):
- var err = OK
- var data = {
- "properties": {},
- "propertytypes": {},
- }
- if not parser.is_empty():
- err = parser.read()
- while err == OK:
- if parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
- if parser.get_node_name() == "properties":
- break
- elif parser.get_node_type() == XMLParser.NODE_ELEMENT:
- if parser.get_node_name() == "property":
- var prop_data = attributes_to_dict(parser)
- if not (prop_data.has("name") and prop_data.has("value")):
- printerr("Missing information in custom properties (around line %d)." % [parser.get_current_line()])
- return ERR_INVALID_DATA
- data.properties[prop_data.name] = prop_data.value
- if prop_data.has("type"):
- data.propertytypes[prop_data.name] = prop_data.type
- else:
- data.propertytypes[prop_data.name] = "string"
- err = parser.read()
- return data
- # Reads the attributes of the current element and return them as a dictionary
- static func attributes_to_dict(parser):
- var data = {}
- for i in range(parser.get_attribute_count()):
- var attr = parser.get_attribute_name(i)
- var val = parser.get_attribute_value(i)
- if val.is_valid_integer():
- val = int(val)
- elif val.is_valid_float():
- val = float(val)
- elif val == "true":
- val = true
- elif val == "false":
- val = false
- data[attr] = val
- return data
|