- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
import { isBoolean, isNumeric } from "../../utils/string.js";
/**
* the function used to decompress zlib/gzip data
* @ignore
*/
let inflateFunction;
/**
* set and interpret a TMX property value
* @ignore
*/
function setTMXValue(name, type, value) {
let match;
if (typeof(value) !== "string") {
// Value is already normalized (e.g. with JSON maps)
return value;
}
switch (type) {
case "int" :
case "float" :
value = Number(value);
break;
case "bool" :
value = (value === "true");
break;
default :
// try to parse it anyway
if (!value || isBoolean(value)) {
// if value not defined or boolean
value = value ? (value === "true") : true;
}
else if (isNumeric(value)) {
// check if numeric
value = Number(value);
}
else if (value.search(/^json:/i) === 0) {
// try to parse it
match = value.split(/^json:/i)[1];
try {
value = JSON.parse(match);
}
catch (e) {
throw new Error("Unable to parse JSON: " + match);
}
}
else if (value.search(/^eval:/i) === 0) {
// try to evaluate it
match = value.split(/^eval:/i)[1];
try {
// eslint-disable-next-line
value = Function("'use strict';return (" + match + ")")();
}
catch (e) {
throw new Error("Unable to evaluate: " + match);
}
}
else if (
((match = value.match(/^#([\da-fA-F])([\da-fA-F]{3})$/))) ||
((match = value.match(/^#([\da-fA-F]{2})([\da-fA-F]{6})$/)))
) {
value = "#" + match[2] + match[1];
}
// normalize values
if (name.search(/^(ratio|anchorPoint)$/) === 0) {
// convert number to vector
if (typeof(value) === "number") {
value = {
"x" : value,
"y" : value
};
}
}
}
// return the interpreted value
return value;
}
/**
* @ignore
*/
function parseAttributes(obj, elt) {
// do attributes
if (elt.attributes && elt.attributes.length > 0) {
for (let j = 0; j < elt.attributes.length; j++) {
const attribute = elt.attributes.item(j);
if (typeof(attribute.name) !== "undefined") {
// DOM4 (Attr no longer inherit from Node)
obj[attribute.name] = attribute.value;
} else {
// else use the deprecated ones
obj[attribute.nodeName] = attribute.nodeValue;
}
}
}
}
/**
* Normalize TMX format to Tiled JSON format
* @ignore
*/
function normalize(obj, item) {
let nodeName = item.nodeName;
switch (nodeName) {
case "data": {
let data = parse(item);
data.encoding = data.encoding || "xml";
// decode chunks for infinite maps
if (typeof data.chunks !== "undefined") {
obj.chunks = obj.chunks || [];
// infinite maps containing chunk data
data.chunks.forEach((chunk) => {
obj.chunks.push({
x: +chunk.x,
y: +chunk.y,
// chunk width is in tiles
width: +chunk.width,
// chunk height is in tiles
height: +chunk.height,
data: decode(chunk.text, data.encoding, data.compression)
});
});
obj.encoding = "none";
}
// Bug on if condition: when parsing data, data.text is sometimes defined when chunks are present
if (typeof data.text !== "undefined" && typeof obj.chunks === "undefined") {
// Finite maps
obj.data = decode(data.text, data.encoding, data.compression);
obj.encoding = "none";
}
break;
}
case "chunk":
obj.chunks = obj.chunks || [];
obj.chunks.push(parse(item));
break;
case "imagelayer":
case "layer":
case "objectgroup":
case "group": {
let layer = parse(item);
layer.type = (nodeName === "layer" ? "tilelayer" : nodeName);
if (layer.image) {
layer.image = layer.image.source;
}
obj.layers = obj.layers || [];
obj.layers.push(layer);
break;
}
case "animation":
obj.animation = parse(item).frames;
break;
case "frame":
case "object": {
const name = nodeName + "s";
obj[name] = obj[name] || [];
obj[name].push(parse(item));
break;
}
case "tile": {
let tile = parse(item);
if (tile.image) {
tile.imagewidth = tile.image.width;
tile.imageheight = tile.image.height;
tile.image = tile.image.source;
}
obj.tiles = obj.tiles || {};
obj.tiles[tile.id] = tile;
break;
}
case "tileset": {
let tileset = parse(item);
if (tileset.image) {
tileset.imagewidth = tileset.image.width;
tileset.imageheight = tileset.image.height;
tileset.image = tileset.image.source;
}
obj.tilesets = obj.tilesets || [];
obj.tilesets.push(tileset);
break;
}
case "polygon":
case "polyline": {
obj[nodeName] = [];
// Get a point array
let points = parse(item).points.split(" ");
// And normalize them into an array of vectors
for (let i = 0; i < points.length; i++) {
const v = points[i].split(",");
obj[nodeName].push({
"x" : +v[0],
"y" : +v[1]
});
}
break;
}
case "properties":
obj.properties = parse(item);
break;
case "property": {
const property = parse(item);
// for custom properties, text is used
const value = (typeof property.value !== "undefined") ? property.value : property.text;
obj[property.name] = setTMXValue(
property.name,
// in XML type is undefined for "string" values
property.type || "string",
value
);
break;
}
default:
obj[nodeName] = parse(item);
break;
}
}
/**
* a collection of utility functions for parsing TMX maps
* @namespace TMXUtils
*/
/**
* decompress and decode zlib/gzip data
* @memberof TMXUtils
* @param {string} input - Base64 encoded and compressed data
* @param {string} format - compressed data format ("gzip","zlib", "zstd")
* @returns {Uint32Array} Decoded and decompress data
*/
function decompress(data, format) {
if (typeof inflateFunction === "function") {
return inflateFunction(data, format);
} else {
throw new Error("GZIP/ZLIB compressed TMX Tile Map not supported!");
}
}
/**
* Decode a CSV encoded array into a binary array
* @memberof TMXUtils
* @param {string} input- - CSV formatted data (only numbers, everything else will be converted to NaN)
* @returns {number[]} Decoded data
*/
function decodeCSV(input) {
let entries = input.replace("\n", "").trim().split(",");
let result = [];
for (let i = 0; i < entries.length; i++) {
result.push(+entries[i]);
}
return result;
}
/**
* Decode a base64 encoded string into a byte array
* @memberof TMXUtils
* @param {string} input - Base64 encoded data
* @param {number} [bytes] - number of bytes per array entry
* @returns {Uint32Array} Decoded data
*/
function decodeBase64AsArray(input, bytes = 1) {
let dec = globalThis.atob(input.replace(/[^A-Za-z0-9\+\/\=]/g, ""));
let ar = new Uint32Array(dec.length / bytes);
for (let i = 0, len = dec.length / bytes; i < len; i++) {
ar[i] = 0;
for (let j = bytes - 1; j >= 0; --j) {
ar[i] += dec.charCodeAt((i * bytes) + j) << (j << 3);
}
}
return ar;
}
/**
* set the function used to inflate gzip/zlib data
* @memberof TMXUtils
* @param {Func} fn - inflate function
*/
export function setInflateFunction(fn) {
inflateFunction = fn;
}
/**
* Decode a encoded array into a binary array
* @memberof TMXUtils
* @param {string} data - data to be decoded
* @param {string} [encoding="none"] - data encoding ("csv", "base64", "xml")
* @returns {number[]} Decoded data
*/
export function decode(data, encoding, compression) {
compression = compression || "none";
encoding = encoding || "none";
switch (encoding) {
case "csv":
return decodeCSV(data);
case "base64":
if (compression !== "none") {
data = decompress(data, compression);
} else {
data = decodeBase64AsArray(data, 4);
}
return data;
case "none":
return data;
case "xml":
throw new Error("XML encoding is deprecated, use base64 instead");
default:
throw new Error("Unknown layer encoding: " + encoding);
}
}
/**
* Parse a XML TMX object and returns the corresponding javascript object
* @memberof TMXUtils
* @param {Document} xml - XML TMX object
* @returns {object} Javascript object
*/
export function parse(xml) {
// Create the return object
let obj = {};
let text = "";
if (xml.nodeType === 1) {
// do attributes
parseAttributes(obj, xml);
}
// do children
if (xml.hasChildNodes()) {
let children = xml.childNodes;
for (const node of children) {
switch (node.nodeType) {
case 1:
normalize(obj, node);
break;
case 3:
text += node.nodeValue.trim();
break;
}
}
}
if (text) {
obj.text = text;
}
return obj;
}
/**
* Apply TMX Properties to the given object
* @memberof TMXUtils
* @param {object} obj - object to apply the properties to
* @param {object} data - TMX data object
* @returns {object} obj
*/
export function applyTMXProperties(obj, data) {
let properties = data.properties;
let types = data.propertytypes;
if (typeof(properties) !== "undefined") {
for (let property in properties) {
if (properties.hasOwnProperty(property)) {
let type = "string";
let name = property;
let value = properties[property];
// proof-check for new and old JSON format
if (typeof properties[property].name !== "undefined") {
name = properties[property].name;
}
if (typeof(types) !== "undefined") {
type = types[property];
} else if (typeof properties[property].type !== "undefined") {
type = properties[property].type;
}
if (typeof properties[property].value !== "undefined") {
value = properties[property].value;
}
// set the value
obj[name] = setTMXValue(name, type, value);
}
}
}
}