From dd4cd1d65347aedd7f95ab4204a421c7ae7d980b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Simi=C4=87?= Date: Thu, 3 Oct 2024 19:16:51 +0200 Subject: [PATCH 01/14] Fix multiline string properties (#75) * Test for multiline object properties. * Fix parsing of multiline string properties. --- pytiled_parser/parsers/tmx/properties.py | 5 ++--- tests/test_tiled_object_tmx.py | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pytiled_parser/parsers/tmx/properties.py b/pytiled_parser/parsers/tmx/properties.py index 1b20e3d4..2c6f7dc4 100644 --- a/pytiled_parser/parsers/tmx/properties.py +++ b/pytiled_parser/parsers/tmx/properties.py @@ -12,11 +12,10 @@ def parse(raw_properties: etree.Element) -> Properties: for raw_property in raw_properties.findall("property"): type_ = raw_property.attrib.get("type") - if "value" not in raw_property.attrib: + value_ = raw_property.attrib.get("value", raw_property.text) + if value_ is None: continue - value_ = raw_property.attrib["value"] - if type_ == "file": value = Path(value_) elif type_ == "color": diff --git a/tests/test_tiled_object_tmx.py b/tests/test_tiled_object_tmx.py index 702ced32..9b6a3848 100644 --- a/tests/test_tiled_object_tmx.py +++ b/tests/test_tiled_object_tmx.py @@ -129,6 +129,9 @@ + Hi +I can write multiple lines in here +That's pretty great """, @@ -144,6 +147,7 @@ "float property": 42.1, "int property": 8675309, "string property": "pytiled_parser rulez!1!!", + "multiline string property": "Hi\nI can write multiple lines in here\nThat's pretty great", }, ), ), From c8e7bbd45ed544fd3cc6d0d5bd21b35f07705c6c Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Thu, 3 Oct 2024 13:21:37 -0400 Subject: [PATCH 02/14] Update version and changelog for 2.2.7 --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 299c0ad3..ff02109e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +## [2.2.7] - 2024-10-03 + +Fixes a bug when using the TMX format, where multi-line String properties would not be correctly parsed, as they are placed differently in the XML than single line strings. (#75) + +The Tiled docs also state that this multi-line format may in the future be used for all property values, so this change will help to futureproof against that. + ## [2.2.6] - 2024-08-22 Fixes a bug where properties did not load as expected on objects when using object templates. As of this release, the functionality is such that if properties are defined on both an object, and it's template, they will both end up on the resulting object, with the ones defined directly on the object overriding any properties that have the same name from the template. It does not compare types, so a String property with the name `test` would override a number property with the name `test`, as an example. Comparing types could be done in the future, but is likely more complicated than it's worth doing right now. diff --git a/pyproject.toml b/pyproject.toml index f520bb6e..6869122d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pytiled_parser" -version = "2.2.6" +version = "2.2.7" description = "A library for parsing Tiled Map Editor maps and tilesets" readme = "README.md" authors = [ From 6d7ccd7ccd0ce2728c7436e1ac03ca34eb07274e Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Thu, 16 Jan 2025 15:38:49 -0500 Subject: [PATCH 03/14] Default to UTF-8 Encoding and Add Optional Encoding Argument (#81) --- CHANGELOG.md | 4 +++ pytiled_parser/parser.py | 27 +++++++++-------- pytiled_parser/parsers/json/layer.py | 12 ++++---- pytiled_parser/parsers/json/tiled_map.py | 18 +++++++---- pytiled_parser/parsers/json/tiled_object.py | 5 +++- pytiled_parser/parsers/json/tileset.py | 11 ++++--- pytiled_parser/parsers/tmx/layer.py | 13 ++++---- pytiled_parser/parsers/tmx/tiled_map.py | 15 ++++++---- pytiled_parser/parsers/tmx/tiled_object.py | 8 +++-- pytiled_parser/parsers/tmx/tileset.py | 9 ++++-- pytiled_parser/util.py | 22 +++++++------- pytiled_parser/world.py | 4 +-- tests/test_layer.py | 33 +++++++++++++-------- tests/test_tiled_object_json.py | 5 ++-- tests/test_tiled_object_tmx.py | 3 +- tests/test_world.py | 3 +- 16 files changed, 118 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff02109e..1e93fca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +## [2.2.8] - UNRELEASED + +Converts all file loading to use UTF-8 encoding by default. In most cases, all Tiled files will be exported from Tiled in UTF-8 encoding, however the python `open()` function uses the system default locale. The only case where Tiled would not have used UTF-8 is for JSON files when Tiled was compiled against Qt 5, which is only in some builds of Tiled from older systems. All XML files exported from Tiled will always be UTF-8. If someone happens to have a JSON file which was exported from Tiled on an encoding other than UTF-8, or for some other reason is in a different encoding. This can be switched using a new optional argument named `encoding` in the various public API `parse` functions such as `parse_map()`. This value is handed down through the pipeline of file loading in pytiled-parser, and will apply to every file loaded during the chain from this. This means that every file in a chain(for example, Map, Tileset, and Template File) must share the same encoding. This new argument is a string which is ultimately passed to the Python [open()](https://docs.python.org/3/library/functions.html#open) function. This change does introduce breaking changes in the underlying API which is not intended to be public facing, but if you are going deeper than the top level parse functions, you may need to adjust for this, as many of the underlying internal functions now have a mandatory encoding argument. + ## [2.2.7] - 2024-10-03 Fixes a bug when using the TMX format, where multi-line String properties would not be correctly parsed, as they are placed differently in the XML than single line strings. (#75) diff --git a/pytiled_parser/parser.py b/pytiled_parser/parser.py index 22918ecc..c7cb97d5 100644 --- a/pytiled_parser/parser.py +++ b/pytiled_parser/parser.py @@ -14,23 +14,24 @@ from pytiled_parser.world import parse_world as _parse_world -def parse_map(file: Path) -> TiledMap: +def parse_map(file: Path, encoding: str = "utf-8") -> TiledMap: """Parse the raw Tiled map into a pytiled_parser type Args: file: Path to the map file + encoding: The character encoding set to use when opening the file Returns: TiledMap: A parsed and typed TiledMap """ - parser = check_format(file) + parser = check_format(file, encoding) # The type ignores are because mypy for some reason thinks those functions return Any if parser == "tmx": - return tmx_map_parse(file) # type: ignore + return tmx_map_parse(file, encoding) # type: ignore else: try: - return json_map_parse(file) # type: ignore + return json_map_parse(file, encoding) # type: ignore except ValueError: raise UnknownFormat( "Unknown Map Format, please use either the TMX or JSON format. " @@ -38,26 +39,27 @@ def parse_map(file: Path) -> TiledMap: ) -def parse_tileset(file: Path) -> Tileset: +def parse_tileset(file: Path, encoding: str = "utf-8") -> Tileset: """Parse the raw Tiled Tileset into a pytiled_parser type Args: file: Path to the map file + encoding: The character encoding set to use when opening the file Returns: Tileset: A parsed and typed Tileset """ - parser = check_format(file) + parser = check_format(file, encoding) if parser == "tmx": - with open(file) as map_file: + with open(file, encoding=encoding) as map_file: raw_tileset = etree.parse(map_file).getroot() - return tmx_tileset_parse(raw_tileset, 1) + return tmx_tileset_parse(raw_tileset, 1, encoding) else: try: - with open(file) as my_file: + with open(file, encoding=encoding) as my_file: raw_tileset = json.load(my_file) - return json_tileset_parse(raw_tileset, 1) + return json_tileset_parse(raw_tileset, 1, encoding) except ValueError: raise UnknownFormat( "Unknowm Tileset Format, please use either the TSX or JSON format. " @@ -65,13 +67,14 @@ def parse_tileset(file: Path) -> Tileset: ) -def parse_world(file: Path) -> World: +def parse_world(file: Path, encoding: str = "utf-8") -> World: """Parse the raw world file into a pytiled_parser type Args: file: Path to the world file + encoding: The character encoding set to use when opening the file Returns: World: A parsed and typed World """ - return _parse_world(file) + return _parse_world(file, encoding) diff --git a/pytiled_parser/parsers/json/layer.py b/pytiled_parser/parsers/json/layer.py index 9682fc4e..d4ae6e69 100644 --- a/pytiled_parser/parsers/json/layer.py +++ b/pytiled_parser/parsers/json/layer.py @@ -298,6 +298,7 @@ def _parse_tile_layer(raw_layer: RawLayer) -> TileLayer: def _parse_object_layer( raw_layer: RawLayer, + encoding: str, parent_dir: Optional[Path] = None, ) -> ObjectLayer: """Parse the raw_layer to an ObjectLayer. @@ -310,7 +311,7 @@ def _parse_object_layer( """ objects = [] for object_ in raw_layer["objects"]: - objects.append(parse_object(object_, parent_dir)) + objects.append(parse_object(object_, encoding, parent_dir)) return ObjectLayer( tiled_objects=objects, @@ -339,7 +340,7 @@ def _parse_image_layer(raw_layer: RawLayer) -> ImageLayer: def _parse_group_layer( - raw_layer: RawLayer, parent_dir: Optional[Path] = None + raw_layer: RawLayer, encoding, parent_dir: Optional[Path] = None ) -> LayerGroup: """Parse the raw_layer to a LayerGroup. @@ -352,13 +353,14 @@ def _parse_group_layer( layers = [] for layer in raw_layer["layers"]: - layers.append(parse(layer, parent_dir=parent_dir)) + layers.append(parse(layer, encoding, parent_dir=parent_dir)) return LayerGroup(layers=layers, **_parse_common(raw_layer).__dict__) def parse( raw_layer: RawLayer, + encoding: str, parent_dir: Optional[Path] = None, ) -> Layer: """Parse a raw Layer into a pytiled_parser object. @@ -378,9 +380,9 @@ def parse( type_ = raw_layer["type"] if type_ == "objectgroup": - return _parse_object_layer(raw_layer, parent_dir) + return _parse_object_layer(raw_layer, encoding, parent_dir) elif type_ == "group": - return _parse_group_layer(raw_layer, parent_dir) + return _parse_group_layer(raw_layer, encoding, parent_dir) elif type_ == "imagelayer": return _parse_image_layer(raw_layer) elif type_ == "tilelayer": diff --git a/pytiled_parser/parsers/json/tiled_map.py b/pytiled_parser/parsers/json/tiled_map.py index a5113f54..299b9193 100644 --- a/pytiled_parser/parsers/json/tiled_map.py +++ b/pytiled_parser/parsers/json/tiled_map.py @@ -55,7 +55,7 @@ """ -def parse(file: Path) -> TiledMap: +def parse(file: Path, encoding: str) -> TiledMap: """Parse the raw Tiled map into a pytiled_parser type. Args: @@ -64,7 +64,7 @@ def parse(file: Path) -> TiledMap: Returns: TiledMap: A parsed TiledMap. """ - with open(file) as map_file: + with open(file, encoding=encoding) as map_file: raw_tiled_map = json.load(map_file) parent_dir = file.parent @@ -76,13 +76,14 @@ def parse(file: Path) -> TiledMap: if raw_tileset.get("source") is not None: # Is an external Tileset tileset_path = Path(parent_dir / raw_tileset["source"]) - parser = check_format(tileset_path) - with open(tileset_path) as raw_tileset_file: + parser = check_format(tileset_path, encoding) + with open(tileset_path, encoding=encoding) as raw_tileset_file: if parser == "tmx": raw_tileset_external = etree.parse(raw_tileset_file).getroot() tilesets[raw_tileset["firstgid"]] = parse_tmx_tileset( raw_tileset_external, raw_tileset["firstgid"], + encoding, external_path=tileset_path.parent, ) else: @@ -90,6 +91,7 @@ def parse(file: Path) -> TiledMap: tilesets[raw_tileset["firstgid"]] = parse_json_tileset( json.load(raw_tileset_file), raw_tileset["firstgid"], + encoding, external_path=tileset_path.parent, ) except ValueError: @@ -101,7 +103,7 @@ def parse(file: Path) -> TiledMap: # Is an embedded Tileset raw_tileset = cast(RawTileSet, raw_tileset) tilesets[raw_tileset["firstgid"]] = parse_json_tileset( - raw_tileset, raw_tileset["firstgid"] + raw_tileset, raw_tileset["firstgid"], encoding ) if isinstance(raw_tiled_map["version"], float): # pragma: no cover @@ -113,7 +115,10 @@ def parse(file: Path) -> TiledMap: map_ = TiledMap( map_file=file, infinite=raw_tiled_map.get("infinite", False), - layers=[parse_layer(layer_, parent_dir) for layer_ in raw_tiled_map["layers"]], + layers=[ + parse_layer(layer_, encoding, parent_dir) + for layer_ in raw_tiled_map["layers"] + ], map_size=Size(raw_tiled_map["width"], raw_tiled_map["height"]), next_layer_id=raw_tiled_map.get("nextlayerid"), next_object_id=raw_tiled_map["nextobjectid"], @@ -157,6 +162,7 @@ def parse(file: Path) -> TiledMap: map_.tilesets[new_firstgid] = parse_json_tileset( tiled_object.new_tileset, new_firstgid, + encoding, tiled_object.new_tileset_path, ) tiled_object.gid = tiled_object.gid + (new_firstgid - 1) diff --git a/pytiled_parser/parsers/json/tiled_object.py b/pytiled_parser/parsers/json/tiled_object.py index 527cf15e..e837bad9 100644 --- a/pytiled_parser/parsers/json/tiled_object.py +++ b/pytiled_parser/parsers/json/tiled_object.py @@ -291,6 +291,7 @@ def _get_parser(raw_object: RawObject) -> Callable[[RawObject], TiledObject]: def parse( raw_object: RawObject, + encoding: str, parent_dir: Optional[Path] = None, ) -> TiledObject: """Parse the raw object into a pytiled_parser version @@ -314,7 +315,9 @@ def parse( "A parent directory must be specified when using object templates." ) template_path = Path(parent_dir / raw_object["template"]) - template, new_tileset, new_tileset_path = load_object_template(template_path) + template, new_tileset, new_tileset_path = load_object_template( + template_path, encoding + ) if isinstance(template, dict): loaded_template = template["object"] diff --git a/pytiled_parser/parsers/json/tileset.py b/pytiled_parser/parsers/json/tileset.py index cbbc51de..7830c2b1 100644 --- a/pytiled_parser/parsers/json/tileset.py +++ b/pytiled_parser/parsers/json/tileset.py @@ -161,7 +161,9 @@ def _parse_grid(raw_grid: RawGrid) -> Grid: ) -def _parse_tile(raw_tile: RawTile, external_path: Optional[Path] = None) -> Tile: +def _parse_tile( + raw_tile: RawTile, encoding: str, external_path: Optional[Path] = None +) -> Tile: """Parse the raw_tile to a Tile object. Args: @@ -180,7 +182,7 @@ def _parse_tile(raw_tile: RawTile, external_path: Optional[Path] = None) -> Tile tile.animation.append(_parse_frame(frame)) if raw_tile.get("objectgroup") is not None: - tile.objects = parse_layer(raw_tile["objectgroup"]) + tile.objects = parse_layer(raw_tile["objectgroup"], encoding) if raw_tile.get("properties") is not None: tile.properties = parse_properties(raw_tile["properties"]) @@ -231,6 +233,7 @@ def _parse_tile(raw_tile: RawTile, external_path: Optional[Path] = None) -> Tile def parse( raw_tileset: RawTileSet, firstgid: int, + encoding: str, external_path: Optional[Path] = None, ) -> Tileset: """Parse the raw tileset into a pytiled_parser type @@ -309,12 +312,12 @@ def parse( assert raw_tile.get("id") is None raw_tile["id"] = int(raw_tile_id) tiles[raw_tile["id"]] = _parse_tile( - raw_tile, external_path=external_path + raw_tile, encoding, external_path=external_path ) else: for raw_tile in raw_tileset["tiles"]: tiles[raw_tile["id"]] = _parse_tile( - raw_tile, external_path=external_path + raw_tile, encoding, external_path=external_path ) tileset.tiles = tiles diff --git a/pytiled_parser/parsers/tmx/layer.py b/pytiled_parser/parsers/tmx/layer.py index ffb8e41e..648d0fe2 100644 --- a/pytiled_parser/parsers/tmx/layer.py +++ b/pytiled_parser/parsers/tmx/layer.py @@ -262,7 +262,7 @@ def _parse_tile_layer(raw_layer: etree.Element) -> TileLayer: def _parse_object_layer( - raw_layer: etree.Element, parent_dir: Optional[Path] = None + raw_layer: etree.Element, encoding: str, parent_dir: Optional[Path] = None ) -> ObjectLayer: """Parse the raw_layer to an ObjectLayer. @@ -274,7 +274,7 @@ def _parse_object_layer( """ objects = [] for object_ in raw_layer.findall("./object"): - objects.append(parse_object(object_, parent_dir)) + objects.append(parse_object(object_, encoding, parent_dir)) object_layer = ObjectLayer( tiled_objects=objects, @@ -316,7 +316,7 @@ def _parse_image_layer(raw_layer: etree.Element) -> ImageLayer: def _parse_group_layer( - raw_layer: etree.Element, parent_dir: Optional[Path] = None + raw_layer: etree.Element, encoding: str, parent_dir: Optional[Path] = None ) -> LayerGroup: """Parse the raw_layer to a LayerGroup. @@ -331,7 +331,7 @@ def _parse_group_layer( layers.append(_parse_tile_layer(layer)) for layer in raw_layer.findall("./objectgroup"): - layers.append(_parse_object_layer(layer, parent_dir)) + layers.append(_parse_object_layer(layer, encoding, parent_dir)) for layer in raw_layer.findall("./imagelayer"): layers.append(_parse_image_layer(layer)) @@ -350,6 +350,7 @@ def _parse_group_layer( def parse( raw_layer: etree.Element, + encoding: str, parent_dir: Optional[Path] = None, ) -> Layer: """Parse a raw Layer into a pytiled_parser object. @@ -369,9 +370,9 @@ def parse( type_ = raw_layer.tag if type_ == "objectgroup": - return _parse_object_layer(raw_layer, parent_dir) + return _parse_object_layer(raw_layer, encoding, parent_dir) elif type_ == "group": - return _parse_group_layer(raw_layer, parent_dir) + return _parse_group_layer(raw_layer, encoding, parent_dir) elif type_ == "imagelayer": return _parse_image_layer(raw_layer) elif type_ == "layer": diff --git a/pytiled_parser/parsers/tmx/tiled_map.py b/pytiled_parser/parsers/tmx/tiled_map.py index a7af55a6..16ac75ed 100644 --- a/pytiled_parser/parsers/tmx/tiled_map.py +++ b/pytiled_parser/parsers/tmx/tiled_map.py @@ -12,7 +12,7 @@ from pytiled_parser.util import check_format, parse_color -def parse(file: Path) -> TiledMap: +def parse(file: Path, encoding: str) -> TiledMap: """Parse the raw Tiled map into a pytiled_parser type. Args: @@ -21,7 +21,7 @@ def parse(file: Path) -> TiledMap: Returns: TiledMap: A parsed TiledMap. """ - with open(file) as map_file: + with open(file, encoding=encoding) as map_file: raw_map = etree.parse(map_file).getroot() parent_dir = file.parent @@ -33,19 +33,21 @@ def parse(file: Path) -> TiledMap: if raw_tileset.attrib.get("source") is not None: # Is an external Tileset tileset_path = Path(parent_dir / raw_tileset.attrib["source"]) - parser = check_format(tileset_path) - with open(tileset_path) as tileset_file: + parser = check_format(tileset_path, encoding) + with open(tileset_path, encoding=encoding) as tileset_file: if parser == "tmx": raw_tileset_external = etree.parse(tileset_file).getroot() tilesets[int(raw_tileset.attrib["firstgid"])] = parse_tmx_tileset( raw_tileset_external, int(raw_tileset.attrib["firstgid"]), + encoding, external_path=tileset_path.parent, ) elif parser == "json": tilesets[int(raw_tileset.attrib["firstgid"])] = parse_json_tileset( json.load(tileset_file), int(raw_tileset.attrib["firstgid"]), + encoding, external_path=tileset_path.parent, ) else: @@ -56,13 +58,13 @@ def parse(file: Path) -> TiledMap: else: # Is an embedded Tileset tilesets[int(raw_tileset.attrib["firstgid"])] = parse_tmx_tileset( - raw_tileset, int(raw_tileset.attrib["firstgid"]) + raw_tileset, int(raw_tileset.attrib["firstgid"]), encoding ) layers = [] for element in raw_map.findall("./*"): if element.tag in ["layer", "objectgroup", "imagelayer", "group"]: - layers.append(parse_layer(element, parent_dir)) + layers.append(parse_layer(element, encoding, parent_dir)) map_ = TiledMap( map_file=file, @@ -114,6 +116,7 @@ def parse(file: Path) -> TiledMap: map_.tilesets[new_firstgid] = parse_tmx_tileset( tiled_object.new_tileset, new_firstgid, + encoding, tiled_object.new_tileset_path, ) tiled_object.gid = tiled_object.gid + (new_firstgid - 1) diff --git a/pytiled_parser/parsers/tmx/tiled_object.py b/pytiled_parser/parsers/tmx/tiled_object.py index a2e16d53..a36cfd1a 100644 --- a/pytiled_parser/parsers/tmx/tiled_object.py +++ b/pytiled_parser/parsers/tmx/tiled_object.py @@ -245,7 +245,9 @@ def _get_parser(raw_object: etree.Element) -> Callable[[etree.Element], TiledObj return _parse_rectangle -def parse(raw_object: etree.Element, parent_dir: Optional[Path] = None) -> TiledObject: +def parse( + raw_object: etree.Element, encoding: str, parent_dir: Optional[Path] = None +) -> TiledObject: """Parse the raw object into a pytiled_parser version Args: @@ -267,7 +269,9 @@ def parse(raw_object: etree.Element, parent_dir: Optional[Path] = None) -> Tiled "A parent directory must be specified when using object templates." ) template_path = Path(parent_dir / raw_object.attrib["template"]) - template, new_tileset, new_tileset_path = load_object_template(template_path) + template, new_tileset, new_tileset_path = load_object_template( + template_path, encoding + ) if isinstance(template, etree.Element): new_object = template.find("./object") diff --git a/pytiled_parser/parsers/tmx/tileset.py b/pytiled_parser/parsers/tmx/tileset.py index 5d788af8..13933f9b 100644 --- a/pytiled_parser/parsers/tmx/tileset.py +++ b/pytiled_parser/parsers/tmx/tileset.py @@ -63,7 +63,9 @@ def _parse_transformations(raw_transformations: etree.Element) -> Transformation ) -def _parse_tile(raw_tile: etree.Element, external_path: Optional[Path] = None) -> Tile: +def _parse_tile( + raw_tile: etree.Element, encoding: str, external_path: Optional[Path] = None +) -> Tile: """Parse the raw_tile to a Tile object. Args: @@ -89,7 +91,7 @@ def _parse_tile(raw_tile: etree.Element, external_path: Optional[Path] = None) - object_element = raw_tile.find("./objectgroup") if object_element is not None: - tile.objects = parse_layer(object_element) + tile.objects = parse_layer(object_element, encoding) properties_element = raw_tile.find("./properties") if properties_element is not None: @@ -129,6 +131,7 @@ def _parse_tile(raw_tile: etree.Element, external_path: Optional[Path] = None) - def parse( raw_tileset: etree.Element, firstgid: int, + encoding: str, external_path: Optional[Path] = None, ) -> Tileset: tileset = Tileset( @@ -204,7 +207,7 @@ def parse( tiles = {} for tile_element in raw_tileset.findall("./tile"): tiles[int(tile_element.attrib["id"])] = _parse_tile( - tile_element, external_path=external_path + tile_element, encoding, external_path=external_path ) if tiles: tileset.tiles = tiles diff --git a/pytiled_parser/util.py b/pytiled_parser/util.py index 8ef4e257..089cdace 100644 --- a/pytiled_parser/util.py +++ b/pytiled_parser/util.py @@ -37,8 +37,8 @@ def parse_color(color: str) -> Color: raise ValueError("Improperly formatted color passed to parse_color") -def check_format(file_path: Path) -> str: - with open(file_path) as file: +def check_format(file_path: Path, encoding: str) -> str: + with open(file_path, encoding=encoding) as file: line = file.readline().rstrip().strip() if line[0] == "<": return "tmx" @@ -46,38 +46,38 @@ def check_format(file_path: Path) -> str: return "json" -def load_object_template(file_path: Path) -> Any: - template_format = check_format(file_path) +def load_object_template(file_path: Path, encoding: str) -> Any: + template_format = check_format(file_path, encoding) new_tileset = None new_tileset_path = None if template_format == "tmx": - with open(file_path) as template_file: + with open(file_path, encoding=encoding) as template_file: template = etree.parse(template_file).getroot() tileset_element = template.find("./tileset") if tileset_element is not None: tileset_path = Path(file_path.parent / tileset_element.attrib["source"]) - new_tileset = load_object_tileset(tileset_path) + new_tileset = load_object_tileset(tileset_path, encoding) new_tileset_path = tileset_path.parent else: - with open(file_path) as template_file: + with open(file_path, encoding=encoding) as template_file: template = json.load(template_file) if "tileset" in template: tileset_path = Path(file_path.parent / template["tileset"]["source"]) # type: ignore - new_tileset = load_object_tileset(tileset_path) + new_tileset = load_object_tileset(tileset_path, encoding) new_tileset_path = tileset_path.parent return (template, new_tileset, new_tileset_path) -def load_object_tileset(file_path: Path) -> Any: - tileset_format = check_format(file_path) +def load_object_tileset(file_path: Path, encoding: str) -> Any: + tileset_format = check_format(file_path, encoding) new_tileset = None - with open(file_path) as tileset_file: + with open(file_path, encoding=encoding) as tileset_file: if tileset_format == "tmx": new_tileset = etree.parse(tileset_file).getroot() else: diff --git a/pytiled_parser/world.py b/pytiled_parser/world.py index 67214a6a..64ddfed3 100644 --- a/pytiled_parser/world.py +++ b/pytiled_parser/world.py @@ -100,7 +100,7 @@ def _parse_world_map(raw_world_map: RawWorldMap, map_file: Path) -> WorldMap: ) -def parse_world(file: Path) -> World: +def parse_world(file: Path, encoding: str) -> World: """Parse the raw world into a pytiled_parser type Args: @@ -110,7 +110,7 @@ def parse_world(file: Path) -> World: World: A properly parsed [World][pytiled_parser.world.World] """ - with open(file) as world_file: + with open(file, encoding="utf-8") as world_file: raw_world = json.load(world_file) parent_dir = file.parent diff --git a/tests/test_layer.py b/tests/test_layer.py index 727fca45..bf47d1d9 100644 --- a/tests/test_layer.py +++ b/tests/test_layer.py @@ -1,4 +1,5 @@ """Tests for tilesets""" + import importlib.util import json import os @@ -72,23 +73,25 @@ def test_layer_integration(parser_type, layer_test): raw_layers_path = layer_test / "map.json" with open(raw_layers_path) as raw_layers_file: raw_layers = json.load(raw_layers_file)["layers"] - layers = [parse_json(raw_layer) for raw_layer in raw_layers] + layers = [ + parse_json(raw_layer, encoding="utf-8") for raw_layer in raw_layers + ] elif parser_type == "tmx": raw_layers_path = layer_test / "map.tmx" with open(raw_layers_path) as raw_layers_file: raw_layer = etree.parse(raw_layers_file).getroot() layers = [] for layer in raw_layer.findall("./layer"): - layers.append(parse_tmx(layer)) + layers.append(parse_tmx(layer, encoding="utf-8")) for layer in raw_layer.findall("./objectgroup"): - layers.append(parse_tmx(layer)) + layers.append(parse_tmx(layer, encoding="utf-8")) for layer in raw_layer.findall("./group"): - layers.append(parse_tmx(layer)) + layers.append(parse_tmx(layer, encoding="utf-8")) for layer in raw_layer.findall("./imagelayer"): - layers.append(parse_tmx(layer)) + layers.append(parse_tmx(layer, encoding="utf-8")) for layer in layers: fix_layer(layer) @@ -99,6 +102,7 @@ def test_layer_integration(parser_type, layer_test): assert layers == expected.EXPECTED + @pytest.mark.parametrize("parser_type", ["json", "tmx"]) def test_zstd_not_installed(parser_type): if parser_type == "json": @@ -106,7 +110,9 @@ def test_zstd_not_installed(parser_type): with open(raw_layers_path) as raw_layers_file: raw_layers = json.load(raw_layers_file)["layers"] with pytest.raises(ValueError): - layers = [parse_json(raw_layer) for raw_layer in raw_layers] + layers = [ + parse_json(raw_layer, encoding="utf-8") for raw_layer in raw_layers + ] elif parser_type == "tmx": raw_layers_path = ZSTD_LAYER_TEST / "map.tmx" with open(raw_layers_path) as raw_layers_file: @@ -114,25 +120,28 @@ def test_zstd_not_installed(parser_type): raw_layer = etree.parse(raw_layers_file).getroot() layers = [] for layer in raw_layer.findall("./layer"): - layers.append(parse_tmx(layer)) + layers.append(parse_tmx(layer, encoding="utf-8")) for layer in raw_layer.findall("./objectgroup"): - layers.append(parse_tmx(layer)) + layers.append(parse_tmx(layer, encoding="utf-8")) for layer in raw_layer.findall("./group"): - layers.append(parse_tmx(layer)) + layers.append(parse_tmx(layer, encoding="utf-8")) for layer in raw_layer.findall("./imagelayer"): - layers.append(parse_tmx(layer)) + layers.append(parse_tmx(layer, encoding="utf-8")) + def test_unknown_layer_type(): # We only test JSON here because due to the nature of the TMX format # there does not exist a scenario where pytiled_parser can attempt to - # parse an unknown layer type. In JSON a RuntimeError error will be + # parse an unknown layer type. In JSON a RuntimeError error will be # raised if an unknown type is provided. In TMX the layer will just # be ignored. raw_layers_path = UNKNOWN_LAYER_TYPE_TEST / "map.json" with open(raw_layers_path) as raw_layers_file: raw_layers = json.load(raw_layers_file)["layers"] with pytest.raises(RuntimeError): - layers = [parse_json(raw_layer) for raw_layer in raw_layers] + layers = [ + parse_json(raw_layer, encoding="utf-8") for raw_layer in raw_layers + ] diff --git a/tests/test_tiled_object_json.py b/tests/test_tiled_object_json.py index 90daf31b..16d24737 100644 --- a/tests/test_tiled_object_json.py +++ b/tests/test_tiled_object_json.py @@ -1,4 +1,5 @@ """Tests for objects""" + import json from contextlib import ExitStack as does_not_raise from pathlib import Path @@ -1110,7 +1111,7 @@ @pytest.mark.parametrize("raw_object_json,expected", OBJECTS) def test_parse_layer(raw_object_json, expected): raw_object = json.loads(raw_object_json) - result = parse(raw_object) + result = parse(raw_object, encoding="utf-8") assert result == expected @@ -1128,4 +1129,4 @@ def test_parse_no_parent_dir(): json_object = json.loads(raw_object) with pytest.raises(RuntimeError): - parse(json_object) + parse(json_object, encoding="utf-8") diff --git a/tests/test_tiled_object_tmx.py b/tests/test_tiled_object_tmx.py index 9b6a3848..0b6fd070 100644 --- a/tests/test_tiled_object_tmx.py +++ b/tests/test_tiled_object_tmx.py @@ -1,4 +1,5 @@ """Tests for objects""" + import xml.etree.ElementTree as etree from contextlib import ExitStack as does_not_raise from pathlib import Path @@ -491,6 +492,6 @@ @pytest.mark.parametrize("raw_object_tmx,expected", OBJECTS) def test_parse_layer(raw_object_tmx, expected): raw_object = etree.fromstring(raw_object_tmx) - result = parse(raw_object) + result = parse(raw_object, encoding="utf-8") assert result == expected diff --git a/tests/test_world.py b/tests/test_world.py index 1bed9817..8224e035 100644 --- a/tests/test_world.py +++ b/tests/test_world.py @@ -1,4 +1,5 @@ """Tests for worlds""" + import importlib.util import operator import os @@ -35,7 +36,7 @@ def test_world_integration(world_test): raw_world_path = world_test / "world.world" - casted_world = world.parse_world(raw_world_path) + casted_world = world.parse_world(raw_world_path, encoding="utf-8") # These fix calls sort the map list in the world by the map_file # attribute because we don't actually care about the order of the list From cf28bf54ee40b5c31d73d13ecc66b67d28117c5f Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Thu, 16 Jan 2025 15:40:47 -0500 Subject: [PATCH 04/14] Add explicit support or Python 3.13 --- .github/workflows/test.yml | 2 +- CHANGELOG.md | 2 ++ README.md | 2 +- pyproject.toml | 66 +++++++++++++++----------------------- 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c6b8b24..91bd5ce2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] architecture: ['x64'] steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e93fca6..2ae6a7e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [2.2.8] - UNRELEASED +Add explicit support for 3.13, previous versions work on 3.13 but it was not explicitly labeled as supported or being tested by CI. + Converts all file loading to use UTF-8 encoding by default. In most cases, all Tiled files will be exported from Tiled in UTF-8 encoding, however the python `open()` function uses the system default locale. The only case where Tiled would not have used UTF-8 is for JSON files when Tiled was compiled against Qt 5, which is only in some builds of Tiled from older systems. All XML files exported from Tiled will always be UTF-8. If someone happens to have a JSON file which was exported from Tiled on an encoding other than UTF-8, or for some other reason is in a different encoding. This can be switched using a new optional argument named `encoding` in the various public API `parse` functions such as `parse_map()`. This value is handed down through the pipeline of file loading in pytiled-parser, and will apply to every file loaded during the chain from this. This means that every file in a chain(for example, Map, Tileset, and Template File) must share the same encoding. This new argument is a string which is ultimately passed to the Python [open()](https://docs.python.org/3/library/functions.html#open) function. This change does introduce breaking changes in the underlying API which is not intended to be public facing, but if you are going deeper than the top level parse functions, you may need to adjust for this, as many of the underlying internal functions now have a mandatory encoding argument. ## [2.2.7] - 2024-10-03 diff --git a/README.md b/README.md index e06358b0..faba7630 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ integrate PyTiled Parser and [example code](https://api.arcade.academy/en/latest ## Supported Python Versions -pytiled-parser works on Python 3.6 - 3.12(and should continue to work on new versions). However we are not actively testing on Python 3.6 and 3.7 due to other development tooling not supporting them, however as long as it remains tenable to do so, we will support these versions and fix problems for them. +pytiled-parser works on Python 3.6 - 3.13(and should continue to work on new versions). However we are not actively testing on Python 3.6 and 3.7 due to other development tooling not supporting them, however as long as it remains tenable to do so, we will support these versions and fix problems for them. ## Installation diff --git a/pyproject.toml b/pyproject.toml index 6869122d..b68b3fbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,42 +4,36 @@ version = "2.2.7" description = "A library for parsing Tiled Map Editor maps and tilesets" readme = "README.md" authors = [ - {name="Benjamin Kirkbride", email="BenjaminKirkbride@gmail.com"}, - {name="Darren Eberly", email="Darren.Eberly@gmail.com"}, + { name = "Benjamin Kirkbride", email = "BenjaminKirkbride@gmail.com" }, + { name = "Darren Eberly", email = "Darren.Eberly@gmail.com" }, ] -maintainers = [ - {name="Darren Eberly", email="Darren.Eberly@gmail.com"} -] -license = {file = "LICENSE"} +maintainers = [{ name = "Darren Eberly", email = "Darren.Eberly@gmail.com" }] +license = { file = "LICENSE" } requires-python = ">=3.6" classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Software Development :: Libraries :: Python Modules" -] -dependencies = [ - "attrs >= 18.2.0", - "typing-extensions" + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Software Development :: Libraries :: Python Modules", ] +dependencies = ["attrs >= 18.2.0", "typing-extensions"] [project.urls] homepage = "https://github.com/pythonarcade/pytiled_parser" [project.optional-dependencies] -zstd = [ - "zstd" -] +zstd = ["zstd"] dev = [ "pytest", @@ -50,20 +44,12 @@ dev = [ "sphinx", "sphinx-sitemap", "myst-parser", - "furo" + "furo", ] -tests = [ - "pytest", - "pytest-cov", - "black", - "ruff", - "mypy" -] +tests = ["pytest", "pytest-cov", "black", "ruff", "mypy"] -build = [ - "build" -] +build = ["build"] [tool.setuptools.packages.find] include = ["pytiled_parser", "pytiled_parser.*"] @@ -82,7 +68,7 @@ branch = true show_missing = true [tool.mypy] -python_version = 3.11 +python_version = "3.13" warn_unused_configs = true warn_redundant_casts = true ignore_missing_imports = true @@ -93,4 +79,4 @@ ignore_errors = true [tool.ruff] exclude = ["__init__.py"] -ignore = ["E501"] \ No newline at end of file +ignore = ["E501"] From 1ade435d7db2dab3fef089a4e578d9b4039109b6 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Thu, 16 Jan 2025 15:41:07 -0500 Subject: [PATCH 05/14] Switch to PyPi Trusted Publishers --- .github/workflows/publish-to-pypi.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 5faeb7ca..4f0655df 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,12 +9,14 @@ jobs: name: Build and Publish to PyPI runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags') + permissions: + id-token: write steps: - uses: actions/checkout@master - - name: Set up Python 3.10 - uses: actions/setup-python@v3 + - name: Set up Python 3.13 + uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.13" - name: Install pypa/build run: >- python -m pip install --user ".[build]" @@ -23,5 +25,3 @@ jobs: python -m build --sdist --wheel --outdir dist/ - name: Publish Distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file From bdc671b1755ed832ff4243a115ccab105eaa59f1 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Thu, 16 Jan 2025 15:43:17 -0500 Subject: [PATCH 06/14] Another small encoding fix --- pytiled_parser/parsers/tmx/layer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytiled_parser/parsers/tmx/layer.py b/pytiled_parser/parsers/tmx/layer.py index 648d0fe2..3891ce3f 100644 --- a/pytiled_parser/parsers/tmx/layer.py +++ b/pytiled_parser/parsers/tmx/layer.py @@ -337,7 +337,7 @@ def _parse_group_layer( layers.append(_parse_image_layer(layer)) for layer in raw_layer.findall("./group"): - layers.append(_parse_group_layer(layer, parent_dir)) + layers.append(_parse_group_layer(layer, encoding, parent_dir)) # layers = [] # layers = [ # parse(child_layer, parent_dir=parent_dir) From 73f9d9d2b60b2c3bda63a9bb23d0c3ff52d826ea Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Thu, 16 Jan 2025 16:06:37 -0500 Subject: [PATCH 07/14] Explicit int and float property types. (#80) --- pytiled_parser/parsers/json/properties.py | 2 + pytiled_parser/parsers/tmx/properties.py | 4 +- pytiled_parser/properties.py | 2 +- .../expected.py | 48 +++++++++++++++ .../special_do_not_resave_from_tiled/map.json | 59 +++++++++++++++++++ .../special_do_not_resave_from_tiled/map.tmx | 13 ++++ .../tileset.json | 14 +++++ .../tileset.tsx | 4 ++ tests/test_map.py | 4 ++ 9 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 tests/test_data/map_tests/special_do_not_resave_from_tiled/expected.py create mode 100644 tests/test_data/map_tests/special_do_not_resave_from_tiled/map.json create mode 100644 tests/test_data/map_tests/special_do_not_resave_from_tiled/map.tmx create mode 100644 tests/test_data/map_tests/special_do_not_resave_from_tiled/tileset.json create mode 100644 tests/test_data/map_tests/special_do_not_resave_from_tiled/tileset.tsx diff --git a/pytiled_parser/parsers/json/properties.py b/pytiled_parser/parsers/json/properties.py index 62b2e17b..784125df 100644 --- a/pytiled_parser/parsers/json/properties.py +++ b/pytiled_parser/parsers/json/properties.py @@ -45,6 +45,8 @@ def parse(raw_properties: List[RawProperty]) -> Properties: value = Path(cast(str, raw_property["value"])) elif raw_property["type"] == "color": value = parse_color(cast(str, raw_property["value"])) + elif raw_property["type"] == "int": + value = round(raw_property["value"]) else: value = raw_property["value"] final[raw_property["name"]] = value diff --git a/pytiled_parser/parsers/tmx/properties.py b/pytiled_parser/parsers/tmx/properties.py index 2c6f7dc4..26b82433 100644 --- a/pytiled_parser/parsers/tmx/properties.py +++ b/pytiled_parser/parsers/tmx/properties.py @@ -20,7 +20,9 @@ def parse(raw_properties: etree.Element) -> Properties: value = Path(value_) elif type_ == "color": value = parse_color(value_) - elif type_ == "int" or type_ == "float": + elif type_ == "int": + value = round(float(value_)) + elif type_ == "float": value = float(value_) elif type_ == "bool": if value_ == "true": diff --git a/pytiled_parser/properties.py b/pytiled_parser/properties.py index f8bc0acf..a8d26b41 100644 --- a/pytiled_parser/properties.py +++ b/pytiled_parser/properties.py @@ -13,6 +13,6 @@ from .common_types import Color -Property = Union[float, Path, str, bool, Color] +Property = Union[int, float, Path, str, bool, Color] Properties = Dict[str, Property] diff --git a/tests/test_data/map_tests/special_do_not_resave_from_tiled/expected.py b/tests/test_data/map_tests/special_do_not_resave_from_tiled/expected.py new file mode 100644 index 00000000..16356569 --- /dev/null +++ b/tests/test_data/map_tests/special_do_not_resave_from_tiled/expected.py @@ -0,0 +1,48 @@ +from pathlib import Path + +from pytiled_parser import common_types, tiled_map, tileset + +EXPECTED = tiled_map.TiledMap( + map_file=None, + infinite=False, + layers=[], + map_size=common_types.Size(8, 6), + next_layer_id=2, + next_object_id=1, + orientation="orthogonal", + render_order="right-down", + tiled_version="1.9.1", + tile_size=common_types.Size(32, 32), + version="1.9", + background_color=common_types.Color(255, 0, 4, 255), + parallax_origin=common_types.OrderedPair(10, 15), + tilesets={ + 1: tileset.Tileset( + columns=8, + image=Path(Path(__file__).parent / "../../images/tmw_desert_spacing.png") + .absolute() + .resolve(), + image_width=265, + image_height=199, + firstgid=1, + margin=1, + spacing=1, + name="tile_set_image", + tile_count=48, + tiled_version="1.6.0", + tile_height=32, + tile_width=32, + version="1.6", + type="tileset", + ) + }, + properties={ + "bool property - true": True, + "color property": common_types.Color(73, 252, 255, 255), + "file property": Path("../../../../../../var/log/syslog"), + "float property": 1.23456789, + "int property": 13, + "broken int property": 14, + "string property": "Hello, World!!", + }, +) diff --git a/tests/test_data/map_tests/special_do_not_resave_from_tiled/map.json b/tests/test_data/map_tests/special_do_not_resave_from_tiled/map.json new file mode 100644 index 00000000..6b147a85 --- /dev/null +++ b/tests/test_data/map_tests/special_do_not_resave_from_tiled/map.json @@ -0,0 +1,59 @@ +{ "backgroundcolor":"#ff0004", + "compressionlevel":0, + "height":6, + "infinite":false, + "layers":[], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "parallaxoriginx":10, + "parallaxoriginy":15, + "properties":[ + { + "name":"bool property - true", + "type":"bool", + "value":true + }, + { + "name":"color property", + "type":"color", + "value":"#ff49fcff" + }, + { + "name":"file property", + "type":"file", + "value":"..\/..\/..\/..\/..\/..\/var\/log\/syslog" + }, + { + "name":"float property", + "type":"float", + "value":1.23456789 + }, + { + "name":"int property", + "type":"int", + "value":13 + }, + { + "name":"broken int property", + "type":"int", + "value":13.6 + }, + { + "name":"string property", + "type":"string", + "value":"Hello, World!!" + }], + "renderorder":"right-down", + "tiledversion":"1.9.1", + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "source":"tileset.json" + }], + "tilewidth":32, + "type":"map", + "version":"1.9", + "width":8 +} \ No newline at end of file diff --git a/tests/test_data/map_tests/special_do_not_resave_from_tiled/map.tmx b/tests/test_data/map_tests/special_do_not_resave_from_tiled/map.tmx new file mode 100644 index 00000000..ecdd0f87 --- /dev/null +++ b/tests/test_data/map_tests/special_do_not_resave_from_tiled/map.tmx @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tests/test_data/map_tests/special_do_not_resave_from_tiled/tileset.json b/tests/test_data/map_tests/special_do_not_resave_from_tiled/tileset.json new file mode 100644 index 00000000..0879fe64 --- /dev/null +++ b/tests/test_data/map_tests/special_do_not_resave_from_tiled/tileset.json @@ -0,0 +1,14 @@ +{ "columns":8, + "image":"..\/..\/images\/tmw_desert_spacing.png", + "imageheight":199, + "imagewidth":265, + "margin":1, + "name":"tile_set_image", + "spacing":1, + "tilecount":48, + "tiledversion":"1.9.0", + "tileheight":32, + "tilewidth":32, + "type":"tileset", + "version":"1.8" +} \ No newline at end of file diff --git a/tests/test_data/map_tests/special_do_not_resave_from_tiled/tileset.tsx b/tests/test_data/map_tests/special_do_not_resave_from_tiled/tileset.tsx new file mode 100644 index 00000000..8b1cf24b --- /dev/null +++ b/tests/test_data/map_tests/special_do_not_resave_from_tiled/tileset.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/test_map.py b/tests/test_map.py index 6083d0e3..4fe5a3b4 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -1,4 +1,5 @@ """Tests for maps""" + import importlib.util import os from pathlib import Path @@ -20,10 +21,12 @@ MAP_TESTS / "embedded_tileset", MAP_TESTS / "template", MAP_TESTS / "cross_format_tileset", + MAP_TESTS / "special_do_not_resave_from_tiled", ] JSON_INVALID_TILESET = MAP_TESTS / "json_invalid_tileset" + def fix_object(my_object): my_object.coordinates = OrderedPair( round(my_object.coordinates[0], 3), round(my_object.coordinates[1], 3) @@ -90,6 +93,7 @@ def test_map_integration(parser_type, map_test): fix_map(casted_map) assert casted_map == expected.EXPECTED + def test_json_invalid_tileset(): raw_map_path = JSON_INVALID_TILESET / "map.json" From 81e3dcbdc0462215df814a5a789432932e9699ee Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Thu, 16 Jan 2025 16:20:07 -0500 Subject: [PATCH 08/14] Ensure name field of objects is not overriden by templates (#79) --- pytiled_parser/parsers/json/tiled_object.py | 3 +++ tests/test_data/map_tests/template/expected.py | 2 +- tests/test_data/map_tests/template/map.json | 1 + tests/test_data/map_tests/template/map.tmx | 2 +- tests/test_data/map_tests/template/template-rectangle.tx | 2 +- 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pytiled_parser/parsers/json/tiled_object.py b/pytiled_parser/parsers/json/tiled_object.py index e837bad9..6ca3dabb 100644 --- a/pytiled_parser/parsers/json/tiled_object.py +++ b/pytiled_parser/parsers/json/tiled_object.py @@ -337,6 +337,9 @@ def parse( if not found: raw_object["properties"].append(prop) + elif key == "name": + if "name" not in raw_object: + raw_object["name"] = loaded_template[key] else: raw_object[key] = loaded_template[key] # type: ignore else: diff --git a/tests/test_data/map_tests/template/expected.py b/tests/test_data/map_tests/template/expected.py index 7470be29..acc5bd84 100644 --- a/tests/test_data/map_tests/template/expected.py +++ b/tests/test_data/map_tests/template/expected.py @@ -15,7 +15,7 @@ tiled_objects=[ tiled_object.Rectangle( id=2, - name="", + name="hello", rotation=0, size=common_types.Size(63.6585878103079, 38.2811778048473), coordinates=common_types.OrderedPair( diff --git a/tests/test_data/map_tests/template/map.json b/tests/test_data/map_tests/template/map.json index b372362d..25cab9e5 100644 --- a/tests/test_data/map_tests/template/map.json +++ b/tests/test_data/map_tests/template/map.json @@ -17,6 +17,7 @@ "value":"hello" }], "template":"template-rectangle.json", + "name": "hello", "x":98.4987608686521, "y":46.2385012811358 }, diff --git a/tests/test_data/map_tests/template/map.tmx b/tests/test_data/map_tests/template/map.tmx index b2138990..18c0a273 100644 --- a/tests/test_data/map_tests/template/map.tmx +++ b/tests/test_data/map_tests/template/map.tmx @@ -11,7 +11,7 @@ - + diff --git a/tests/test_data/map_tests/template/template-rectangle.tx b/tests/test_data/map_tests/template/template-rectangle.tx index 1959f0f2..94fe8e38 100644 --- a/tests/test_data/map_tests/template/template-rectangle.tx +++ b/tests/test_data/map_tests/template/template-rectangle.tx @@ -1,6 +1,6 @@