parse-tmx 4.3.3 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -30
- package/dist/index.d.ts +11 -0
- package/dist/index.js +225 -203
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
[![NPM downloads][badge-npm-downloads]][url-npm]
|
|
6
6
|
[![NPM Unpacked Size (with version)][badge-npm-unpacked-size]][url-npm]
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
This package parses \*.tmx files written by [Tiled Map Editor](http://www.mapeditor.org/). It is inspired by [tmx-map-parser](https://github.com/praghus/tmx-map-parser).
|
|
9
|
+
|
|
10
|
+
Design notes live in [the specs](https://github.com/Esposter/Esposter/tree/main/openspec/specs/parse-tmx).
|
|
10
11
|
|
|
11
12
|
## Table of Contents
|
|
12
13
|
|
|
13
14
|
- 🚀 [Getting Started](#getting-started)
|
|
14
|
-
-
|
|
15
|
+
- 🧩 [Key Exports](#key-exports)
|
|
16
|
+
- 📝 [Usage](#usage)
|
|
15
17
|
- ⚖️ [License](#license)
|
|
16
18
|
|
|
17
19
|
---
|
|
@@ -22,33 +24,42 @@ A parser for [Tiled Map Editor](http://www.mapeditor.org/) \*.tmx files.
|
|
|
22
24
|
pnpm i parse-tmx
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
## <a name="
|
|
27
|
+
## <a name="key-exports">🧩 Key Exports</a>
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
| Export | Kind | Purpose |
|
|
30
|
+
| :----------------- | :-------- | :----------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `parseTileset` | function | Reads an external tileset file, as Tiled writes one, into the embedded tileset it holds, without a map |
|
|
32
|
+
| `parseTmx` | function | Parses a `.tmx` string into a `TMXParsed`, matching Phaser's tilemap export and supporting external tilesets |
|
|
33
|
+
| `PropertyType` | enum | The types a Tiled custom property can have, such as `int` or `string` |
|
|
34
|
+
| `Shape` | enum | The shapes a Tiled object can take, such as `rectangle` or `polygon` |
|
|
35
|
+
| `TMXLayerParsed` | interface | A layer as parsed: its tile data, flips, objects and properties |
|
|
36
|
+
| `TMXMapParsed` | class | The map element's attributes, with the defaults a map that omits them reads as |
|
|
37
|
+
| `TMXParsed` | class | The parse result, holding the parsed map as `map` |
|
|
38
|
+
| `TMXTilesetParsed` | type | A tileset as parsed: an embedded one or an external one |
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
## <a name="usage">📝 Usage</a>
|
|
30
41
|
|
|
31
42
|
```ts
|
|
32
43
|
import { parseTmx } from "parse-tmx";
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
|
|
45
|
+
// The *.tmx file is read as text, here from a URL
|
|
46
|
+
const xmlString = await (await fetch("/maps/dungeon.tmx")).text();
|
|
35
47
|
|
|
36
48
|
// Translates the tile flips in the layer data (default: false)
|
|
37
49
|
const translateFlips = true;
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
51
|
+
// parseTmx resolves to a neverthrow Result, so the parsed map is taken from its Ok branch and an error is rethrown
|
|
52
|
+
const { map } = (await parseTmx(xmlString, translateFlips)).match(
|
|
53
|
+
(tmxParsed) => tmxParsed,
|
|
54
|
+
(error) => {
|
|
55
|
+
throw error;
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
console.log(map.layers.length);
|
|
49
60
|
```
|
|
50
61
|
|
|
51
|
-
### Example data
|
|
62
|
+
### <a name="example-data">📄 Example data</a>
|
|
52
63
|
|
|
53
64
|
[TMX Map Format documentation](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/)
|
|
54
65
|
|
|
@@ -117,17 +128,6 @@ const loadMap = async () => {
|
|
|
117
128
|
}
|
|
118
129
|
```
|
|
119
130
|
|
|
120
|
-
### Commands
|
|
121
|
-
|
|
122
|
-
Run from `packages/parse-tmx/`:
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
pnpm build # compile to dist/
|
|
126
|
-
pnpm test # vitest watch mode (coverage is run from the repo root)
|
|
127
|
-
pnpm lint:fix # auto-fix lint
|
|
128
|
-
pnpm typecheck # type check
|
|
129
|
-
```
|
|
130
|
-
|
|
131
131
|
## <a name="license">⚖️ License</a>
|
|
132
132
|
|
|
133
133
|
This project is licensed under the [Apache-2.0 license](https://github.com/Esposter/Esposter/blob/main/LICENSE).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { Result, ResultAsync } from "neverthrow";
|
|
2
|
+
export declare enum PropertyType {
|
|
3
|
+
Bool = "bool",
|
|
4
|
+
Class = "class",
|
|
5
|
+
Color = "color",
|
|
6
|
+
Enum = "enum",
|
|
7
|
+
File = "file",
|
|
8
|
+
Float = "float",
|
|
9
|
+
Int = "int",
|
|
10
|
+
Object = "object",
|
|
11
|
+
String = "string"
|
|
12
|
+
}
|
|
2
13
|
export declare enum Shape {
|
|
3
14
|
Ellipse = "ellipse",
|
|
4
15
|
Point = "point",
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { InvalidOperationError, Operation, exhaustiveGuard, getResult, getResultAsync, normalizeString } from "@esposter/shared";
|
|
2
2
|
import sax from "sax";
|
|
3
|
+
// src/models/PropertyType.ts
|
|
4
|
+
let PropertyType = /* @__PURE__ */ function(PropertyType) {
|
|
5
|
+
PropertyType["Bool"] = "bool";
|
|
6
|
+
PropertyType["Class"] = "class";
|
|
7
|
+
PropertyType["Color"] = "color";
|
|
8
|
+
PropertyType["Enum"] = "enum";
|
|
9
|
+
PropertyType["File"] = "file";
|
|
10
|
+
PropertyType["Float"] = "float";
|
|
11
|
+
PropertyType["Int"] = "int";
|
|
12
|
+
PropertyType["Object"] = "object";
|
|
13
|
+
PropertyType["String"] = "string";
|
|
14
|
+
return PropertyType;
|
|
15
|
+
}({});
|
|
3
16
|
// src/models/Shape.ts
|
|
4
17
|
let Shape = /* @__PURE__ */ function(Shape) {
|
|
5
18
|
Shape["Ellipse"] = "ellipse";
|
|
@@ -72,21 +85,7 @@ let Encoding = /* @__PURE__ */ function(Encoding) {
|
|
|
72
85
|
Encoding["Csv"] = "csv";
|
|
73
86
|
return Encoding;
|
|
74
87
|
}({});
|
|
75
|
-
// src/services/
|
|
76
|
-
const getDecompressedBytes = async (bytes, compression) => {
|
|
77
|
-
const format = compression === "gzip" ? "gzip" : "deflate";
|
|
78
|
-
const decompressedTileStream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream(format));
|
|
79
|
-
return new Uint8Array(await new Response(decompressedTileStream).arrayBuffer());
|
|
80
|
-
};
|
|
81
|
-
// src/services/unpackTileBytes.ts
|
|
82
|
-
const unpackTileBytes = (bytes) => {
|
|
83
|
-
if (bytes.byteLength % Uint32Array.BYTES_PER_ELEMENT !== 0) throw new InvalidOperationError(Operation.Read, "TMXLayer", `expected whole ${Uint32Array.BYTES_PER_ELEMENT}-byte tiles, received ${bytes.byteLength} bytes`);
|
|
84
|
-
const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
85
|
-
const tiles = new Uint32Array(bytes.byteLength / Uint32Array.BYTES_PER_ELEMENT);
|
|
86
|
-
for (let index = 0; index < tiles.length; index++) tiles[index] = dataView.getUint32(index * Uint32Array.BYTES_PER_ELEMENT, true);
|
|
87
|
-
return [...tiles];
|
|
88
|
-
};
|
|
89
|
-
// src/services/decodeTileData.ts
|
|
88
|
+
// src/services/decodeCsv.ts
|
|
90
89
|
const CSV_SEPARATOR_CODE_POINT = ",".codePointAt(0) ?? 0;
|
|
91
90
|
const ZERO_CODE_POINT = "0".codePointAt(0) ?? 0;
|
|
92
91
|
const NINE_CODE_POINT = "9".codePointAt(0) ?? 0;
|
|
@@ -97,8 +96,10 @@ const decodeCsv = (text) => {
|
|
|
97
96
|
let hasDigit = false;
|
|
98
97
|
let isEnded = false;
|
|
99
98
|
for (let index = 0; index <= text.length; index++) {
|
|
100
|
-
const codePoint = text.codePointAt(index)
|
|
101
|
-
if (codePoint ===
|
|
99
|
+
const codePoint = text.codePointAt(index);
|
|
100
|
+
if (codePoint === void 0) {
|
|
101
|
+
if (hasDigit || tiles.length > 0) tiles.push(tile);
|
|
102
|
+
} else if (codePoint === CSV_SEPARATOR_CODE_POINT) {
|
|
102
103
|
tiles.push(tile);
|
|
103
104
|
tile = 0;
|
|
104
105
|
hasDigit = false;
|
|
@@ -112,6 +113,21 @@ const decodeCsv = (text) => {
|
|
|
112
113
|
}
|
|
113
114
|
return tiles;
|
|
114
115
|
};
|
|
116
|
+
// src/services/getDecompressedBytes.ts
|
|
117
|
+
const getDecompressedBytes = async (bytes, compression) => {
|
|
118
|
+
const format = compression === "gzip" ? "gzip" : "deflate";
|
|
119
|
+
const decompressedTileStream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream(format));
|
|
120
|
+
return new Uint8Array(await new Response(decompressedTileStream).arrayBuffer());
|
|
121
|
+
};
|
|
122
|
+
// src/services/unpackTileBytes.ts
|
|
123
|
+
const unpackTileBytes = (bytes) => {
|
|
124
|
+
if (bytes.byteLength % Uint32Array.BYTES_PER_ELEMENT !== 0) throw new InvalidOperationError(Operation.Read, "TMXLayer", `expected whole ${Uint32Array.BYTES_PER_ELEMENT}-byte tiles, received ${bytes.byteLength} bytes`);
|
|
125
|
+
const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
126
|
+
const tiles = new Uint32Array(bytes.byteLength / Uint32Array.BYTES_PER_ELEMENT);
|
|
127
|
+
for (let index = 0; index < tiles.length; index++) tiles[index] = dataView.getUint32(index * Uint32Array.BYTES_PER_ELEMENT, true);
|
|
128
|
+
return [...tiles];
|
|
129
|
+
};
|
|
130
|
+
// src/services/decodeTileData.ts
|
|
115
131
|
const decodeTileData = async (source) => {
|
|
116
132
|
const { compression, encoding, gids, text } = source;
|
|
117
133
|
if (encoding === void 0) return gids;
|
|
@@ -157,24 +173,13 @@ const assignTileData = async (pending, translateFlips) => {
|
|
|
157
173
|
} else owner.data = tiles;
|
|
158
174
|
}));
|
|
159
175
|
};
|
|
160
|
-
// src/
|
|
161
|
-
let PropertyType = /* @__PURE__ */ function(PropertyType) {
|
|
162
|
-
PropertyType["Bool"] = "bool";
|
|
163
|
-
PropertyType["Class"] = "class";
|
|
164
|
-
PropertyType["Color"] = "color";
|
|
165
|
-
PropertyType["File"] = "file";
|
|
166
|
-
PropertyType["Float"] = "float";
|
|
167
|
-
PropertyType["Int"] = "int";
|
|
168
|
-
PropertyType["Object"] = "object";
|
|
169
|
-
PropertyType["String"] = "string";
|
|
170
|
-
return PropertyType;
|
|
171
|
-
}({});
|
|
172
|
-
// src/services/closeTmxFrame.ts
|
|
176
|
+
// src/services/parsePropertyValue.ts
|
|
173
177
|
const parsePropertyValue = (propertyType, value) => {
|
|
174
178
|
switch (propertyType) {
|
|
175
179
|
case "bool": return value === "true";
|
|
176
180
|
case "class":
|
|
177
181
|
case "color":
|
|
182
|
+
case "enum":
|
|
178
183
|
case "file":
|
|
179
184
|
case "object":
|
|
180
185
|
case "string": return value;
|
|
@@ -183,6 +188,7 @@ const parsePropertyValue = (propertyType, value) => {
|
|
|
183
188
|
default: return exhaustiveGuard(propertyType);
|
|
184
189
|
}
|
|
185
190
|
};
|
|
191
|
+
// src/services/closeTmxFrame.ts
|
|
186
192
|
const closeTmxFrame = (frame) => {
|
|
187
193
|
switch (frame.tag) {
|
|
188
194
|
case "chunk":
|
|
@@ -223,6 +229,75 @@ const imageAttributeKindMap = {
|
|
|
223
229
|
width: "requiredNumber"
|
|
224
230
|
};
|
|
225
231
|
const createTmxImage = (attributes) => readTmxAttributes(attributes, imageAttributeKindMap);
|
|
232
|
+
// src/services/createDataSource.ts
|
|
233
|
+
const createDataSource = (compression, encoding) => ({
|
|
234
|
+
chunked: false,
|
|
235
|
+
compression,
|
|
236
|
+
encoding,
|
|
237
|
+
gids: [],
|
|
238
|
+
text: ""
|
|
239
|
+
});
|
|
240
|
+
// src/services/parseEnumAttribute.ts
|
|
241
|
+
const parseEnumAttribute = (operation, label, enumObject, value) => {
|
|
242
|
+
if (value === void 0) return void 0;
|
|
243
|
+
const enumValue = Object.values(enumObject).find((member) => member === value);
|
|
244
|
+
if (enumValue !== void 0) return enumValue;
|
|
245
|
+
throw new InvalidOperationError(Operation.Read, operation, `${value} is not a supported ${label}`);
|
|
246
|
+
};
|
|
247
|
+
// src/services/parseCompression.ts
|
|
248
|
+
const parseCompression = (value) => parseEnumAttribute(parseCompression.name, "compression", Compression, value);
|
|
249
|
+
// src/services/parseEncoding.ts
|
|
250
|
+
const parseEncoding = (value) => parseEnumAttribute(parseEncoding.name, "encoding", Encoding, value);
|
|
251
|
+
// src/services/openTileData.ts
|
|
252
|
+
const openTileData = (layer, attributes, context) => {
|
|
253
|
+
const source = createDataSource(parseCompression(attributes.compression), parseEncoding(attributes.encoding));
|
|
254
|
+
context.pending.push({
|
|
255
|
+
expectedCount: context.tileCount,
|
|
256
|
+
owner: layer,
|
|
257
|
+
source
|
|
258
|
+
});
|
|
259
|
+
return {
|
|
260
|
+
owner: layer,
|
|
261
|
+
tag: "data",
|
|
262
|
+
target: source,
|
|
263
|
+
text: ""
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
// src/services/openTmxDataChild.ts
|
|
267
|
+
const gidAttributeKindMap = { gid: "requiredNumber" };
|
|
268
|
+
const chunkAttributeKindMap = {
|
|
269
|
+
height: "requiredNumber",
|
|
270
|
+
width: "requiredNumber",
|
|
271
|
+
x: "requiredNumber",
|
|
272
|
+
y: "requiredNumber"
|
|
273
|
+
};
|
|
274
|
+
const openTmxDataChild = (parent, tag, attributes, context) => {
|
|
275
|
+
const source = parent.target;
|
|
276
|
+
switch (tag) {
|
|
277
|
+
case "chunk": {
|
|
278
|
+
if (parent.tag !== "data") return void 0;
|
|
279
|
+
const chunk = readTmxAttributes(attributes, chunkAttributeKindMap);
|
|
280
|
+
parent.owner.chunks ??= [];
|
|
281
|
+
parent.owner.chunks.push(chunk);
|
|
282
|
+
source.chunked = true;
|
|
283
|
+
const chunkSource = createDataSource(source.compression, source.encoding);
|
|
284
|
+
context.pending.push({
|
|
285
|
+
expectedCount: chunk.width * chunk.height,
|
|
286
|
+
owner: chunk,
|
|
287
|
+
source: chunkSource
|
|
288
|
+
});
|
|
289
|
+
return {
|
|
290
|
+
tag,
|
|
291
|
+
target: chunkSource,
|
|
292
|
+
text: ""
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
case "tile":
|
|
296
|
+
source.gids.push(readTmxAttributes(attributes, gidAttributeKindMap).gid);
|
|
297
|
+
return;
|
|
298
|
+
default: return;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
226
301
|
// src/services/openTmxLayer.ts
|
|
227
302
|
const groupAttributeKindMap = {
|
|
228
303
|
class: "string",
|
|
@@ -280,96 +355,88 @@ const openTmxProperties = (target) => {
|
|
|
280
355
|
text: ""
|
|
281
356
|
};
|
|
282
357
|
};
|
|
283
|
-
// src/services/parseEnumAttribute.ts
|
|
284
|
-
const parseEnumAttribute = (operation, label, enumObject, value) => {
|
|
285
|
-
if (value === void 0) return void 0;
|
|
286
|
-
const enumValue = Object.values(enumObject).find((member) => member === value);
|
|
287
|
-
if (enumValue !== void 0) return enumValue;
|
|
288
|
-
throw new InvalidOperationError(Operation.Read, operation, `${value} is not a supported ${label}`);
|
|
289
|
-
};
|
|
290
358
|
// src/services/openTmxLayerChild.ts
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
height: "requiredNumber",
|
|
294
|
-
width: "requiredNumber",
|
|
295
|
-
x: "requiredNumber",
|
|
296
|
-
y: "requiredNumber"
|
|
297
|
-
};
|
|
298
|
-
const parseCompression = (value) => parseEnumAttribute(parseCompression.name, "compression", Compression, value);
|
|
299
|
-
const parseEncoding = (value) => parseEnumAttribute(parseEncoding.name, "encoding", Encoding, value);
|
|
300
|
-
const createDataSource = (compression, encoding) => ({
|
|
301
|
-
chunked: false,
|
|
302
|
-
compression,
|
|
303
|
-
encoding,
|
|
304
|
-
gids: [],
|
|
305
|
-
text: ""
|
|
306
|
-
});
|
|
307
|
-
const openTileData = (layer, attributes, context) => {
|
|
308
|
-
const source = createDataSource(parseCompression(attributes.compression), parseEncoding(attributes.encoding));
|
|
309
|
-
context.pending.push({
|
|
310
|
-
expectedCount: context.tileCount,
|
|
311
|
-
owner: layer,
|
|
312
|
-
source
|
|
313
|
-
});
|
|
314
|
-
return {
|
|
315
|
-
owner: layer,
|
|
316
|
-
tag: "data",
|
|
317
|
-
target: source,
|
|
318
|
-
text: ""
|
|
319
|
-
};
|
|
320
|
-
};
|
|
321
|
-
const openTmxDataChild = (parent, tag, attributes, context) => {
|
|
322
|
-
const source = parent.target;
|
|
359
|
+
const openTmxLayerChild = (parent, tag, attributes, context) => {
|
|
360
|
+
if (parent.tag === "chunk" || parent.tag === "data") return openTmxDataChild(parent, tag, attributes, context);
|
|
323
361
|
switch (tag) {
|
|
324
|
-
case "
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
});
|
|
362
|
+
case "data": if (parent.tag === "layer") return openTileData(parent.target, attributes, context);
|
|
363
|
+
else return void 0;
|
|
364
|
+
case "group":
|
|
365
|
+
case "imagelayer":
|
|
366
|
+
case "layer":
|
|
367
|
+
case "objectgroup": if (parent.tag === "group") return openTmxLayer(parent.target.layers, tag, attributes);
|
|
368
|
+
else return void 0;
|
|
369
|
+
case "image":
|
|
370
|
+
if (parent.tag !== "imagelayer") return void 0;
|
|
371
|
+
parent.target.image = createTmxImage(attributes);
|
|
335
372
|
return {
|
|
336
373
|
tag,
|
|
337
|
-
target:
|
|
374
|
+
target: parent.target.image,
|
|
338
375
|
text: ""
|
|
339
376
|
};
|
|
340
|
-
|
|
341
|
-
case "tile":
|
|
342
|
-
source.gids.push(readTmxAttributes(attributes, gidAttributeKindMap).gid);
|
|
343
|
-
return;
|
|
377
|
+
case "properties": return openTmxProperties(parent.target);
|
|
344
378
|
default: return;
|
|
345
379
|
}
|
|
346
380
|
};
|
|
347
|
-
|
|
348
|
-
|
|
381
|
+
// src/services/createEmbeddedTileset.ts
|
|
382
|
+
const embeddedTilesetAttributeKindMap = {
|
|
383
|
+
columns: "requiredNumber",
|
|
384
|
+
firstgid: "requiredNumber",
|
|
385
|
+
margin: "number",
|
|
386
|
+
name: "requiredString",
|
|
387
|
+
spacing: "number",
|
|
388
|
+
tilecount: "requiredNumber",
|
|
389
|
+
tileheight: "requiredNumber",
|
|
390
|
+
tilewidth: "requiredNumber"
|
|
391
|
+
};
|
|
392
|
+
const createEmbeddedTileset = (attributes) => ({
|
|
393
|
+
...readTmxAttributes(attributes, embeddedTilesetAttributeKindMap),
|
|
394
|
+
tiles: []
|
|
395
|
+
});
|
|
396
|
+
// src/services/openTmxMapChild.ts
|
|
397
|
+
const externalTilesetAttributeKindMap = {
|
|
398
|
+
firstgid: "requiredNumber",
|
|
399
|
+
source: "requiredString"
|
|
400
|
+
};
|
|
401
|
+
const openTmxMapChild = (parent, name, tag, attributes) => {
|
|
402
|
+
const map = parent.target;
|
|
349
403
|
switch (tag) {
|
|
350
404
|
case "data":
|
|
351
|
-
|
|
352
|
-
|
|
405
|
+
case "export":
|
|
406
|
+
case "image":
|
|
407
|
+
case "object":
|
|
408
|
+
case "property": return;
|
|
409
|
+
case "editorsettings": {
|
|
410
|
+
const editorSettings = {};
|
|
411
|
+
map.editorsettings = editorSettings;
|
|
412
|
+
return {
|
|
413
|
+
tag,
|
|
414
|
+
target: editorSettings,
|
|
415
|
+
text: ""
|
|
416
|
+
};
|
|
417
|
+
}
|
|
353
418
|
case "group":
|
|
354
419
|
case "imagelayer":
|
|
355
420
|
case "layer":
|
|
356
|
-
case "objectgroup":
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
421
|
+
case "objectgroup": return openTmxLayer(map.layers, tag, attributes);
|
|
422
|
+
case "properties": return openTmxProperties(map);
|
|
423
|
+
case "tileset": {
|
|
424
|
+
if (attributes.source !== void 0) {
|
|
425
|
+
map.tilesets.push(readTmxAttributes(attributes, externalTilesetAttributeKindMap));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const tileset = createEmbeddedTileset(attributes);
|
|
429
|
+
map.tilesets.push(tileset);
|
|
362
430
|
return {
|
|
363
431
|
tag,
|
|
364
|
-
target:
|
|
432
|
+
target: tileset,
|
|
365
433
|
text: ""
|
|
366
434
|
};
|
|
367
|
-
|
|
368
|
-
default:
|
|
435
|
+
}
|
|
436
|
+
default: throw new InvalidOperationError(Operation.Read, openTmxMapChild.name, `${name} is not a map child`);
|
|
369
437
|
}
|
|
370
438
|
};
|
|
371
|
-
// src/services/
|
|
372
|
-
const NUMBER_REGEX = /^[+-]?\d+(?:\.\d+)?$/u;
|
|
439
|
+
// src/services/createObject.ts
|
|
373
440
|
const objectAttributeKindMap = {
|
|
374
441
|
gid: "number",
|
|
375
442
|
height: "requiredNumber",
|
|
@@ -390,7 +457,7 @@ const createObject = (attributes) => {
|
|
|
390
457
|
shape: "rectangle"
|
|
391
458
|
};
|
|
392
459
|
if (gid === void 0) return shaped;
|
|
393
|
-
return gid ? {
|
|
460
|
+
else return gid ? {
|
|
394
461
|
...shaped,
|
|
395
462
|
flips: parseFlips(gid),
|
|
396
463
|
gid: parseTileId(gid)
|
|
@@ -399,20 +466,25 @@ const createObject = (attributes) => {
|
|
|
399
466
|
gid
|
|
400
467
|
};
|
|
401
468
|
};
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return [Number(x), Number(y)];
|
|
405
|
-
});
|
|
469
|
+
// src/services/parseUntypedValue.ts
|
|
470
|
+
const NUMBER_REGEX = /^[+-]?\d+(?:\.\d+)?$/u;
|
|
406
471
|
const parseUntypedValue = (value) => {
|
|
407
472
|
if (value === "true") return true;
|
|
408
473
|
else if (value === "false") return false;
|
|
409
474
|
else if (NUMBER_REGEX.test(value)) return Number(value);
|
|
410
475
|
else return value;
|
|
411
476
|
};
|
|
477
|
+
// src/services/createTextProperties.ts
|
|
412
478
|
const createTextProperties = (attributes) => Object.entries(attributes).map(([name, value]) => ({
|
|
413
479
|
name,
|
|
414
480
|
value: parseUntypedValue(value)
|
|
415
481
|
}));
|
|
482
|
+
// src/services/parsePoints.ts
|
|
483
|
+
const parsePoints = (points) => points === "" ? [] : points.split(" ").map((point) => {
|
|
484
|
+
const [x = "", y = ""] = point.split(",");
|
|
485
|
+
return [Number(x), Number(y)];
|
|
486
|
+
});
|
|
487
|
+
// src/services/openTmxObjectChild.ts
|
|
416
488
|
const openTmxObjectChild = (parent, tag, attributes) => {
|
|
417
489
|
if (parent.tag === "objectgroup") {
|
|
418
490
|
if (tag === "properties") return openTmxProperties(parent.target);
|
|
@@ -456,7 +528,28 @@ const openTmxObjectChild = (parent, tag, attributes) => {
|
|
|
456
528
|
default: return;
|
|
457
529
|
}
|
|
458
530
|
};
|
|
459
|
-
// src/services/
|
|
531
|
+
// src/services/parsePropertyType.ts
|
|
532
|
+
const parsePropertyType = (value) => parseEnumAttribute(parsePropertyType.name, "property type", PropertyType, value) ?? "string";
|
|
533
|
+
// src/services/openTmxPropertyChild.ts
|
|
534
|
+
const propertyAttributeKindMap = { name: "requiredString" };
|
|
535
|
+
const openTmxPropertyChild = (parent, tag, attributes) => {
|
|
536
|
+
if (tag !== "property") return void 0;
|
|
537
|
+
const target = {
|
|
538
|
+
...readTmxAttributes(attributes, propertyAttributeKindMap),
|
|
539
|
+
value: ""
|
|
540
|
+
};
|
|
541
|
+
parent.target.push(target);
|
|
542
|
+
return {
|
|
543
|
+
propertyType: parsePropertyType(attributes.type),
|
|
544
|
+
tag,
|
|
545
|
+
target,
|
|
546
|
+
text: "",
|
|
547
|
+
valueAttribute: attributes.value
|
|
548
|
+
};
|
|
549
|
+
};
|
|
550
|
+
// src/services/parseTmxTagName.ts
|
|
551
|
+
const parseTmxTagName = (name) => Object.values(TMXTagName).find((tagName) => tagName === name);
|
|
552
|
+
// src/services/openTmxRoot.ts
|
|
460
553
|
const mapAttributeKindMap = {
|
|
461
554
|
backgroundcolor: "string",
|
|
462
555
|
compressionlevel: "number",
|
|
@@ -477,41 +570,6 @@ const mapAttributeKindMap = {
|
|
|
477
570
|
version: "requiredNumber",
|
|
478
571
|
width: "requiredNumber"
|
|
479
572
|
};
|
|
480
|
-
const embeddedTilesetAttributeKindMap = {
|
|
481
|
-
columns: "requiredNumber",
|
|
482
|
-
firstgid: "requiredNumber",
|
|
483
|
-
margin: "number",
|
|
484
|
-
name: "requiredString",
|
|
485
|
-
spacing: "number",
|
|
486
|
-
tilecount: "requiredNumber",
|
|
487
|
-
tileheight: "requiredNumber",
|
|
488
|
-
tilewidth: "requiredNumber"
|
|
489
|
-
};
|
|
490
|
-
const externalTilesetAttributeKindMap = {
|
|
491
|
-
firstgid: "requiredNumber",
|
|
492
|
-
source: "requiredString"
|
|
493
|
-
};
|
|
494
|
-
const tileAttributeKindMap = {
|
|
495
|
-
id: "requiredNumber",
|
|
496
|
-
probability: "number",
|
|
497
|
-
terrain: "string",
|
|
498
|
-
type: "string"
|
|
499
|
-
};
|
|
500
|
-
const animationFrameAttributeKindMap = {
|
|
501
|
-
duration: "requiredNumber",
|
|
502
|
-
tileid: "requiredNumber"
|
|
503
|
-
};
|
|
504
|
-
const exportAttributeKindMap = {
|
|
505
|
-
format: "requiredString",
|
|
506
|
-
target: "requiredString"
|
|
507
|
-
};
|
|
508
|
-
const propertyAttributeKindMap = { name: "requiredString" };
|
|
509
|
-
const createEmbeddedTileset = (attributes) => ({
|
|
510
|
-
...readTmxAttributes(attributes, embeddedTilesetAttributeKindMap),
|
|
511
|
-
tiles: []
|
|
512
|
-
});
|
|
513
|
-
const parseTmxTagName = (name) => Object.values(TMXTagName).find((tagName) => tagName === name);
|
|
514
|
-
const parsePropertyType = (value) => parseEnumAttribute(parsePropertyType.name, "property type", PropertyType, value) ?? "string";
|
|
515
573
|
const openTmxRoot = (name, attributes, context) => {
|
|
516
574
|
const tag = parseTmxTagName(name);
|
|
517
575
|
switch (tag) {
|
|
@@ -524,54 +582,16 @@ const openTmxRoot = (name, attributes, context) => {
|
|
|
524
582
|
text: ""
|
|
525
583
|
};
|
|
526
584
|
}
|
|
527
|
-
case "tileset":
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
};
|
|
585
|
+
case "tileset": if (attributes.source === void 0) return {
|
|
586
|
+
tag,
|
|
587
|
+
target: createEmbeddedTileset(attributes),
|
|
588
|
+
text: ""
|
|
589
|
+
};
|
|
590
|
+
else return void 0;
|
|
534
591
|
default: throw new InvalidOperationError(Operation.Read, openTmxRoot.name, `${name} is not a map or a tileset`);
|
|
535
592
|
}
|
|
536
593
|
};
|
|
537
|
-
|
|
538
|
-
const map = parent.target;
|
|
539
|
-
switch (tag) {
|
|
540
|
-
case "data":
|
|
541
|
-
case "export":
|
|
542
|
-
case "image":
|
|
543
|
-
case "object":
|
|
544
|
-
case "property": return;
|
|
545
|
-
case "editorsettings": {
|
|
546
|
-
const editorSettings = {};
|
|
547
|
-
map.editorsettings = editorSettings;
|
|
548
|
-
return {
|
|
549
|
-
tag,
|
|
550
|
-
target: editorSettings,
|
|
551
|
-
text: ""
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
case "group":
|
|
555
|
-
case "imagelayer":
|
|
556
|
-
case "layer":
|
|
557
|
-
case "objectgroup": return openTmxLayer(map.layers, tag, attributes);
|
|
558
|
-
case "properties": return openTmxProperties(map);
|
|
559
|
-
case "tileset": {
|
|
560
|
-
if (attributes.source !== void 0) {
|
|
561
|
-
map.tilesets.push(readTmxAttributes(attributes, externalTilesetAttributeKindMap));
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
const tileset = createEmbeddedTileset(attributes);
|
|
565
|
-
map.tilesets.push(tileset);
|
|
566
|
-
return {
|
|
567
|
-
tag,
|
|
568
|
-
target: tileset,
|
|
569
|
-
text: ""
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
default: throw new InvalidOperationError(Operation.Read, openTmxMapChild.name, `${name} is not a map child`);
|
|
573
|
-
}
|
|
574
|
-
};
|
|
594
|
+
// src/services/openTmxTileChild.ts
|
|
575
595
|
const openTmxTileChild = (parent, tag, attributes) => {
|
|
576
596
|
const tile = parent.target;
|
|
577
597
|
switch (tag) {
|
|
@@ -602,6 +622,13 @@ const openTmxTileChild = (parent, tag, attributes) => {
|
|
|
602
622
|
default: return;
|
|
603
623
|
}
|
|
604
624
|
};
|
|
625
|
+
// src/services/openTmxTilesetChild.ts
|
|
626
|
+
const tileAttributeKindMap = {
|
|
627
|
+
id: "requiredNumber",
|
|
628
|
+
probability: "number",
|
|
629
|
+
terrain: "string",
|
|
630
|
+
type: "string"
|
|
631
|
+
};
|
|
605
632
|
const openTmxTilesetChild = (parent, tag, attributes) => {
|
|
606
633
|
const tileset = parent.target;
|
|
607
634
|
switch (tag) {
|
|
@@ -626,20 +653,14 @@ const openTmxTilesetChild = (parent, tag, attributes) => {
|
|
|
626
653
|
default: return;
|
|
627
654
|
}
|
|
628
655
|
};
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
propertyType: parsePropertyType(attributes.type),
|
|
638
|
-
tag,
|
|
639
|
-
target,
|
|
640
|
-
text: "",
|
|
641
|
-
valueAttribute: attributes.value
|
|
642
|
-
};
|
|
656
|
+
// src/services/openTmxFrame.ts
|
|
657
|
+
const animationFrameAttributeKindMap = {
|
|
658
|
+
duration: "requiredNumber",
|
|
659
|
+
tileid: "requiredNumber"
|
|
660
|
+
};
|
|
661
|
+
const exportAttributeKindMap = {
|
|
662
|
+
format: "requiredString",
|
|
663
|
+
target: "requiredString"
|
|
643
664
|
};
|
|
644
665
|
const openTmxFrame = (parent, name, attributes, context) => {
|
|
645
666
|
if (parent === void 0) return openTmxRoot(name, attributes, context);
|
|
@@ -678,9 +699,10 @@ const openTmxFrame = (parent, name, attributes, context) => {
|
|
|
678
699
|
default: return;
|
|
679
700
|
}
|
|
680
701
|
};
|
|
681
|
-
// src/services/
|
|
682
|
-
const BYTE_ORDER_MARK_REGEX = /^\uFEFF/u;
|
|
702
|
+
// src/services/toTmxAttributes.ts
|
|
683
703
|
const toTmxAttributes = (attributes) => Object.fromEntries(Object.entries(attributes).map(([name, attribute]) => [name, typeof attribute === "string" ? attribute : attribute.value]));
|
|
704
|
+
// src/services/readTmxDocument.ts
|
|
705
|
+
const BYTE_ORDER_MARK_REGEX = /^/u;
|
|
684
706
|
const readTmxDocument = (xmlString, context) => {
|
|
685
707
|
const parser = sax.parser(true, {
|
|
686
708
|
normalize: false,
|
|
@@ -733,4 +755,4 @@ const parseTileset = (xmlString) => getResult(() => readTmxDocument(xmlString, {
|
|
|
733
755
|
if (root?.tag !== "tileset") throw new InvalidOperationError(Operation.Read, parseTileset.name, "has no embedded tileset root element");
|
|
734
756
|
return root.target;
|
|
735
757
|
}));
|
|
736
|
-
export { Shape, TMXMapParsed, TMXParsed, parseTileset, parseTmx };
|
|
758
|
+
export { PropertyType, Shape, TMXMapParsed, TMXParsed, parseTileset, parseTmx };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "parse-tmx",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "A parser for tmx.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"game-development",
|
|
@@ -47,18 +47,18 @@
|
|
|
47
47
|
"lint": "eslint .",
|
|
48
48
|
"lint:fix": "eslint --fix .",
|
|
49
49
|
"test": "vitest",
|
|
50
|
-
"typecheck": "
|
|
50
|
+
"typecheck": "tsrs --fast"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@esposter/shared": "4.
|
|
53
|
+
"@esposter/shared": "4.5.0",
|
|
54
54
|
"neverthrow": "^8.2.0",
|
|
55
55
|
"sax": "^1.6.1"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@esposter/configuration": "4.
|
|
59
|
-
"@esposter/shared-node": "4.
|
|
58
|
+
"@esposter/configuration": "4.5.0",
|
|
59
|
+
"@esposter/shared-node": "4.5.0",
|
|
60
60
|
"@types/sax": "^1.2.7",
|
|
61
|
-
"tsdown": "^0.23.
|
|
61
|
+
"tsdown": "^0.23.5"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "e30c001f3b4b15693e75fb53092d62a97ade4d2b"
|
|
64
64
|
}
|