mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-02-04 08:50:33 +08:00
Calculated serialization type
This commit is contained in:
518
dist/ueblueprint.js
vendored
518
dist/ueblueprint.js
vendored
@@ -246,6 +246,29 @@ class IInput {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("./IEntity").default} IEntity
|
||||
*/
|
||||
|
||||
class CalculatedType {
|
||||
|
||||
#f
|
||||
|
||||
/**
|
||||
* @param {Function} f
|
||||
*/
|
||||
constructor(f) {
|
||||
this.#f = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {IEntity} entity
|
||||
*/
|
||||
calculate(entity) {
|
||||
return this.#f(entity)
|
||||
}
|
||||
}
|
||||
|
||||
class Observable {
|
||||
|
||||
/** @type {Map<String, Object[]>} */
|
||||
@@ -352,33 +375,6 @@ class Observable {
|
||||
}
|
||||
}
|
||||
|
||||
class SerializedType {
|
||||
|
||||
#types
|
||||
get types() {
|
||||
return this.#types
|
||||
}
|
||||
set types(v) {
|
||||
this.#types = v;
|
||||
}
|
||||
|
||||
#stringFallback
|
||||
get stringFallback() {
|
||||
return this.#stringFallback
|
||||
}
|
||||
set stringFallback(v) {
|
||||
this.#stringFallback = v;
|
||||
}
|
||||
|
||||
constructor([...acceptedTypes], stringFallback = true) {
|
||||
this.#types = [...new Set([
|
||||
...acceptedTypes,
|
||||
...(stringFallback ? [String] : [])
|
||||
])];
|
||||
this.#stringFallback = stringFallback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
@@ -410,13 +406,22 @@ class TypeInitialization {
|
||||
this.#value = v;
|
||||
}
|
||||
|
||||
/** @type {Boolean} */
|
||||
#serialized
|
||||
get serialized() {
|
||||
return this.#serialized
|
||||
}
|
||||
set serialized(v) {
|
||||
this.#serialized = v;
|
||||
}
|
||||
|
||||
static sanitize(value, targetType) {
|
||||
if (targetType === undefined) {
|
||||
targetType = value?.constructor;
|
||||
}
|
||||
if (
|
||||
targetType
|
||||
&& targetType !== SerializedType
|
||||
// value is not of type targetType
|
||||
&& !(value?.constructor === targetType || value instanceof targetType)
|
||||
) {
|
||||
value = new targetType(value);
|
||||
@@ -428,28 +433,32 @@ class TypeInitialization {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {(new () => T) | SerializedType | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
|
||||
* @typedef {(new () => T) | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
|
||||
* @param {Constructor|Array<Constructor>} type
|
||||
* @param {Boolean} showDefault
|
||||
* @param {any} value
|
||||
* @param {Boolean} serialized
|
||||
*/
|
||||
constructor(type, showDefault = true, value = undefined) {
|
||||
constructor(type, showDefault = true, value = undefined, serialized = false) {
|
||||
if (value === undefined) {
|
||||
if (type instanceof Array) {
|
||||
value = [];
|
||||
} else if (type instanceof SerializedType) {
|
||||
} else if (serialized) {
|
||||
value = "";
|
||||
} else {
|
||||
value = TypeInitialization.sanitize(new type());
|
||||
}
|
||||
}
|
||||
this.#showDefault = showDefault;
|
||||
this.#type = type;
|
||||
this.#showDefault = showDefault;
|
||||
this.#value = value;
|
||||
this.#serialized = serialized;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("./entity/LinearColorEntity").default} LinearColorEntity
|
||||
* @typedef {import("./entity/IEntity").default} IEntity
|
||||
*/
|
||||
|
||||
class Utility {
|
||||
@@ -507,6 +516,26 @@ class Utility {
|
||||
return location
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {IEntity}
|
||||
* @param {Object} target Object holding the data
|
||||
* @param {String[]} keys The chained keys to access from object in order to get the value
|
||||
* @param {Boolean} defaultValue Value to return in case from doesn't have it
|
||||
* @returns {any} The value in from corresponding to the keys or defaultValue otherwise
|
||||
*/
|
||||
static isSerialized(entity, keys, propertyDefinition = Utility.objectGet(entity.constructor.attributes, keys)) {
|
||||
if (propertyDefinition instanceof CalculatedType) {
|
||||
return Utility.isSerialized(entity, keys, propertyDefinition.calculate(entity))
|
||||
}
|
||||
if (propertyDefinition instanceof TypeInitialization) {
|
||||
if (propertyDefinition.serialized) {
|
||||
return true
|
||||
}
|
||||
return Utility.isSerialized(entity, keys, propertyDefinition.type)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from an object, gives defaultValue in case of failure
|
||||
* @param {Object} target Object holding the data
|
||||
@@ -575,6 +604,9 @@ class Utility {
|
||||
}
|
||||
|
||||
static getType(value) {
|
||||
if (value === null) {
|
||||
return null
|
||||
}
|
||||
let constructor = value?.constructor;
|
||||
switch (constructor) {
|
||||
case TypeInitialization:
|
||||
@@ -654,8 +686,9 @@ class Utility {
|
||||
*/
|
||||
static encodeString(value, input = false) {
|
||||
return value
|
||||
.replaceAll('"', '\\"') // Escape "
|
||||
.replaceAll("\n", "\\n") // Replace newline with \n
|
||||
.replaceAll("\u00A0", " ") // Replace special space symbol
|
||||
.replaceAll("\n", String.raw`\n`) // Replace newline with \n
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -663,8 +696,9 @@ class Utility {
|
||||
*/
|
||||
static decodeString(value, input = false) {
|
||||
return value
|
||||
.replaceAll(" ", "\u00A0") // Replace special space symbol
|
||||
.replaceAll(String.raw`\n`, "\n") // Replace newline with \n
|
||||
.replaceAll('\\"', '"')
|
||||
.replaceAll("\\n", "\n")
|
||||
.replaceAll(" ", "\u00A0")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -689,33 +723,33 @@ class Utility {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("./IEntity").default} IEntity
|
||||
*/
|
||||
|
||||
class CalculatedType {
|
||||
|
||||
#f
|
||||
|
||||
/**
|
||||
* @param {Function} f
|
||||
*/
|
||||
constructor(f) {
|
||||
this.#f = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {IEntity} entity
|
||||
*/
|
||||
calculate(entity) {
|
||||
return this.f(entity)
|
||||
}
|
||||
/** @typedef {import("../entity/IEntity").default} IEntity */
|
||||
/**
|
||||
* @template {IEntity} T
|
||||
* @typedef {import("./ISerializer").default<T>} ISerializer
|
||||
*/
|
||||
|
||||
class SerializerFactory {
|
||||
|
||||
/** @type {Map<T, ISerializer<T>>} */
|
||||
static #serializers = new Map()
|
||||
|
||||
static registerSerializer(entity, object) {
|
||||
SerializerFactory.#serializers.set(entity, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {IEntity} T
|
||||
* @param {T} entity
|
||||
*/
|
||||
static getSerializer(entity) {
|
||||
return SerializerFactory.#serializers.get(entity)
|
||||
}
|
||||
}
|
||||
|
||||
class IEntity extends Observable {
|
||||
|
||||
static attributes = {}
|
||||
#showAsString = false
|
||||
|
||||
constructor(values) {
|
||||
super();
|
||||
@@ -730,19 +764,25 @@ class IEntity extends Observable {
|
||||
Object.getOwnPropertyNames(properties),
|
||||
Object.getOwnPropertyNames(values ?? {})
|
||||
)) {
|
||||
|
||||
let value = Utility.objectGet(values, [property]);
|
||||
let defaultValue = properties[property];
|
||||
const defaultType = Utility.getType(defaultValue);
|
||||
let defaultType = Utility.getType(defaultValue);
|
||||
if (defaultValue instanceof CalculatedType) {
|
||||
defaultValue = defaultValue.calculate(this);
|
||||
defaultType = Utility.getType(defaultValue);
|
||||
}
|
||||
|
||||
if (!(property in properties)) {
|
||||
console.warn(`Property ${prefix}${property} is not defined in ${this.constructor.name}.attributes`);
|
||||
console.warn(
|
||||
`Property ${prefix}${property} in the serialized data is not defined in ${this.constructor.name}.properties`
|
||||
);
|
||||
} else if (
|
||||
defaultValue != null
|
||||
!(property in values)
|
||||
&& defaultValue !== undefined
|
||||
&& !(defaultValue instanceof TypeInitialization && !defaultValue.showDefault)
|
||||
&& !(property in values)
|
||||
) {
|
||||
console.warn(
|
||||
`${this.constructor.name} adds property ${prefix}${property} not defined in the serialized data`
|
||||
`${this.constructor.name}.properties will add property ${prefix}${property} not defined in the serialized data`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -753,34 +793,34 @@ class IEntity extends Observable {
|
||||
continue
|
||||
}
|
||||
|
||||
/*
|
||||
* The value can either be:
|
||||
* - Array: can contain multiple values, its property is assigned multiple times like (X=1, X="4").
|
||||
* - CalculatedType: the exact type depends on the previous attributes assigned to this entity.
|
||||
* - TypeInitialization: contains the maximum amount of information about the attribute.
|
||||
* - A type: the default value will be default constructed object without arguments.
|
||||
* - A proper value.
|
||||
*/
|
||||
const value = Utility.objectGet(values, [property]);
|
||||
if (value !== undefined) {
|
||||
target[property] = TypeInitialization.sanitize(value, defaultType);
|
||||
// We have a value, need nothing more
|
||||
continue
|
||||
}
|
||||
if (defaultValue instanceof CalculatedType) {
|
||||
defaultValue = defaultValue.calculate(this);
|
||||
defaultType = Utility.getType(defaultValue);
|
||||
// Remember value can still be null
|
||||
if (
|
||||
value?.constructor === String
|
||||
&& defaultValue instanceof TypeInitialization
|
||||
&& defaultValue.serialized
|
||||
&& defaultValue.type !== String
|
||||
) {
|
||||
value = SerializerFactory.getSerializer(defaultValue.type).deserialize(value);
|
||||
}
|
||||
target[property] = TypeInitialization.sanitize(value, Utility.getType(defaultValue));
|
||||
continue // We have a value, need nothing more
|
||||
}
|
||||
|
||||
if (defaultValue instanceof TypeInitialization) {
|
||||
if (!defaultValue.showDefault) {
|
||||
target[property] = undefined; // Declare undefined to preserve the order of attributes
|
||||
continue
|
||||
}
|
||||
defaultValue = defaultValue.value;
|
||||
if (defaultValue.serialized) {
|
||||
defaultValue = "";
|
||||
} else {
|
||||
defaultType = defaultValue.type;
|
||||
defaultValue = defaultValue.value;
|
||||
}
|
||||
}
|
||||
if (defaultValue instanceof Array) {
|
||||
target[property] = [];
|
||||
continue
|
||||
defaultValue = [];
|
||||
}
|
||||
target[property] = TypeInitialization.sanitize(defaultValue, defaultType);
|
||||
}
|
||||
@@ -794,17 +834,6 @@ class IEntity extends Observable {
|
||||
}
|
||||
defineAllAttributes(this, attributes, values);
|
||||
}
|
||||
|
||||
isShownAsString() {
|
||||
return this.#showAsString
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Boolean} v
|
||||
*/
|
||||
setShowAsString(v) {
|
||||
this.#showAsString = v;
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectReferenceEntity extends IEntity {
|
||||
@@ -922,7 +951,7 @@ class KeyBindingEntity extends IEntity {
|
||||
}
|
||||
}
|
||||
|
||||
class LinearColorEntity$1 extends IEntity {
|
||||
class LinearColorEntity extends IEntity {
|
||||
|
||||
static attributes = {
|
||||
R: Number,
|
||||
@@ -969,8 +998,29 @@ class PinReferenceEntity extends IEntity {
|
||||
}
|
||||
}
|
||||
|
||||
class VectorEntity extends IEntity {
|
||||
|
||||
static attributes = {
|
||||
X: Number,
|
||||
Y: Number,
|
||||
Z: Number,
|
||||
}
|
||||
}
|
||||
|
||||
class PinEntity extends IEntity {
|
||||
|
||||
static #typeEntityMap = {
|
||||
"/Script/CoreUObject.LinearColor": LinearColorEntity,
|
||||
"/Script/CoreUObject.Vector": VectorEntity,
|
||||
"bool": Boolean,
|
||||
"exec": String,
|
||||
"name": String,
|
||||
"real": Number,
|
||||
"string": String,
|
||||
}
|
||||
static get typeEntityMap() {
|
||||
return PinEntity.#typeEntityMap
|
||||
}
|
||||
static lookbehind = "Pin"
|
||||
static attributes = {
|
||||
PinId: GuidEntity,
|
||||
@@ -992,12 +1042,16 @@ class PinEntity extends IEntity {
|
||||
bSerializeAsSinglePrecisionFloat: false,
|
||||
},
|
||||
LinkedTo: new TypeInitialization([PinReferenceEntity], false),
|
||||
DefaultValue: new TypeInitialization(
|
||||
new SerializedType([
|
||||
LinearColorEntity$1,
|
||||
]),
|
||||
false
|
||||
),
|
||||
DefaultValue:
|
||||
new CalculatedType(
|
||||
/** @param {PinEntity} pinEntity */
|
||||
pinEntity => new TypeInitialization(
|
||||
PinEntity.typeEntityMap[pinEntity.getType()] ?? String,
|
||||
false,
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
),
|
||||
AutogeneratedDefaultValue: new TypeInitialization(String, false),
|
||||
DefaultObject: new TypeInitialization(ObjectReferenceEntity, false, null),
|
||||
PersistentGuid: GuidEntity,
|
||||
@@ -1009,6 +1063,13 @@ class PinEntity extends IEntity {
|
||||
bOrphanedPin: false,
|
||||
}
|
||||
|
||||
getType() {
|
||||
if (this.PinType.PinCategory == "struct") {
|
||||
return this.PinType.PinSubCategoryObject.path
|
||||
}
|
||||
return this.PinType.PinCategory
|
||||
}
|
||||
|
||||
getDefaultValue() {
|
||||
return this.DefaultValue ?? ""
|
||||
}
|
||||
@@ -1070,13 +1131,6 @@ class PinEntity extends IEntity {
|
||||
return false
|
||||
}
|
||||
|
||||
getType() {
|
||||
if (this.PinType.PinCategory == "struct") {
|
||||
return this.PinType.PinSubCategoryObject.path
|
||||
}
|
||||
return this.PinType.PinCategory
|
||||
}
|
||||
|
||||
getSubCategory() {
|
||||
return this.PinType.PinSubCategoryObject.path
|
||||
}
|
||||
@@ -1160,15 +1214,6 @@ var parsimmon_umd_min = {exports: {}};
|
||||
|
||||
var Parsimmon = /*@__PURE__*/getDefaultExportFromCjs(parsimmon_umd_min.exports);
|
||||
|
||||
class LinearColorEntity extends IEntity {
|
||||
|
||||
static attributes = {
|
||||
X: Number,
|
||||
Y: Number,
|
||||
Z: Number,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("../entity/IEntity").default} IEntity
|
||||
*/
|
||||
@@ -1181,32 +1226,9 @@ class Grammar {
|
||||
|
||||
static getGrammarForType(r, attributeType, defaultGrammar) {
|
||||
if (attributeType instanceof TypeInitialization) {
|
||||
// Unpack TypeInitialization
|
||||
attributeType = attributeType.type;
|
||||
return Grammar.getGrammarForType(r, attributeType, defaultGrammar)
|
||||
}
|
||||
if (attributeType instanceof SerializedType) {
|
||||
const nonStringTypes = attributeType.types.filter(t => t !== String);
|
||||
let result = P.alt(
|
||||
...nonStringTypes.map(t =>
|
||||
Grammar.getGrammarForType(r, t).wrap(P.string('"'), P.string('"')).map(
|
||||
/**
|
||||
* @param {IEntity} entity
|
||||
*/
|
||||
entity => {
|
||||
entity.setShowAsString(true); // Showing as string because it is inside a SerializedType
|
||||
return entity
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
if (nonStringTypes.length < attributeType.types.length) {
|
||||
result = result.or(r.String/*.map(v => {
|
||||
if (attributeType.stringFallback) {
|
||||
console.log("Unrecognized value, fallback on String")
|
||||
}
|
||||
return v
|
||||
})*/); // Separated because it cannot be wrapped into " and "
|
||||
let result = Grammar.getGrammarForType(r, attributeType.type, defaultGrammar);
|
||||
if (attributeType.serialized && !(attributeType.type instanceof String)) {
|
||||
result = result.wrap(P.string('"'), P.string('"'));
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1231,9 +1253,9 @@ class Grammar {
|
||||
return r.InvariantText
|
||||
case PinReferenceEntity:
|
||||
return r.PinReference
|
||||
case LinearColorEntity:
|
||||
case VectorEntity:
|
||||
return r.Vector
|
||||
case LinearColorEntity$1:
|
||||
case LinearColorEntity:
|
||||
return r.LinearColor
|
||||
case FunctionReferenceEntity:
|
||||
return r.FunctionReference
|
||||
@@ -1300,7 +1322,12 @@ class Grammar {
|
||||
|
||||
Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()")
|
||||
|
||||
Boolean = r => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false)
|
||||
Boolean = r => P.alt(
|
||||
P.string("True"),
|
||||
P.string("true"),
|
||||
P.string("False"),
|
||||
P.string("false"),
|
||||
).map(v => v.toLocaleLowerCase() === "true" ? true : false)
|
||||
.desc("either True or False")
|
||||
|
||||
HexDigit = r => P.regex(/[0-9a-fA-f]/).desc("hexadecimal digit")
|
||||
@@ -1403,13 +1430,13 @@ class Grammar {
|
||||
r.Guid, // Goes into pinGuid
|
||||
(objectName, _, pinGuid) => new PinReferenceEntity({
|
||||
objectName: objectName,
|
||||
pinGuid: pinGuid
|
||||
pinGuid: pinGuid,
|
||||
})
|
||||
)
|
||||
|
||||
Vector = r => Grammar.createEntityGrammar(r, LinearColorEntity)
|
||||
Vector = r => Grammar.createEntityGrammar(r, VectorEntity)
|
||||
|
||||
LinearColor = r => Grammar.createEntityGrammar(r, LinearColorEntity$1)
|
||||
LinearColor = r => Grammar.createEntityGrammar(r, LinearColorEntity)
|
||||
|
||||
FunctionReference = r => Grammar.createEntityGrammar(r, FunctionReferenceEntity)
|
||||
|
||||
@@ -1459,7 +1486,7 @@ class Grammar {
|
||||
.string("#")
|
||||
.then(r.HexDigit.times(2).tie().times(3, 4))
|
||||
.trim(P.optWhitespace)
|
||||
.map(([R, G, B, A]) => new LinearColorEntity$1({
|
||||
.map(([R, G, B, A]) => new LinearColorEntity({
|
||||
R: parseInt(R, 16) / 255,
|
||||
G: parseInt(G, 16) / 255,
|
||||
B: parseInt(B, 16) / 255,
|
||||
@@ -1472,12 +1499,12 @@ class Grammar {
|
||||
r.ColorNumber,
|
||||
P.string(",").skip(P.optWhitespace),
|
||||
r.ColorNumber.map(Number),
|
||||
(R, _, G, __, B) => new LinearColorEntity$1({
|
||||
(R, _, G, __, B) => new LinearColorEntity({
|
||||
R: R / 255,
|
||||
G: G / 255,
|
||||
B: B / 255,
|
||||
A: 1,
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
LinearColorFromRGB = r => P.string("rgb").then(
|
||||
@@ -1496,7 +1523,7 @@ class Grammar {
|
||||
r.ColorNumber.map(Number),
|
||||
P.string(",").skip(P.optWhitespace),
|
||||
P.regex(/0?\.\d+|[01]/).map(Number),
|
||||
(R, _, G, __, B, ___, A) => new LinearColorEntity$1({
|
||||
(R, _, G, __, B, ___, A) => new LinearColorEntity({
|
||||
R: R / 255,
|
||||
G: G / 255,
|
||||
B: B / 255,
|
||||
@@ -1516,19 +1543,6 @@ class Grammar {
|
||||
)
|
||||
}
|
||||
|
||||
class SerializerFactory {
|
||||
|
||||
static #serializers = new Map()
|
||||
|
||||
static registerSerializer(entity, object) {
|
||||
SerializerFactory.#serializers.set(entity, object);
|
||||
}
|
||||
|
||||
static getSerializer(entity) {
|
||||
return SerializerFactory.#serializers.get(Utility.getType(entity))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {IEntity} T
|
||||
*/
|
||||
@@ -1558,13 +1572,8 @@ class ISerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
serialize(object, insideString) {
|
||||
insideString ||= object.isShownAsString();
|
||||
let result = this.write(object, insideString);
|
||||
if (object.isShownAsString()) {
|
||||
result = `"${result}"`;
|
||||
}
|
||||
return result
|
||||
serialize(object, insideString, entity = object) {
|
||||
return this.write(entity, object, insideString)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1580,7 +1589,7 @@ class ISerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
write(object, insideString) {
|
||||
write(entity, object, insideString) {
|
||||
throw new Error("Not implemented")
|
||||
}
|
||||
|
||||
@@ -1588,30 +1597,12 @@ class ISerializer {
|
||||
* @param {String[]} fullKey
|
||||
* @param {Boolean} insideString
|
||||
*/
|
||||
writeValue(value, fullKey, insideString) {
|
||||
if (value === null) {
|
||||
return "()"
|
||||
}
|
||||
const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).serialize(v);
|
||||
// This is an exact match (and not instanceof) to hit also primitive types (by accessing value.constructor they are converted to objects automatically)
|
||||
switch (value?.constructor) {
|
||||
case Function:
|
||||
return this.writeValue(value(), fullKey, insideString)
|
||||
case Boolean:
|
||||
return Utility.firstCapital(value.toString())
|
||||
case Number:
|
||||
return value.toString()
|
||||
case String:
|
||||
return insideString
|
||||
? `\\"${Utility.encodeString(value)}\\"`
|
||||
: `"${Utility.encodeString(value)}"`
|
||||
}
|
||||
if (value instanceof Array) {
|
||||
return `(${value.map(v => serialize(v) + ",").join("")})`
|
||||
}
|
||||
if (value instanceof IEntity) {
|
||||
return serialize(value)
|
||||
writeValue(entity, value, fullKey, insideString) {
|
||||
const serializer = SerializerFactory.getSerializer(Utility.getType(value));
|
||||
if (!serializer) {
|
||||
throw new Error("Unknown value type, a serializer must be registered in the SerializerFactory class")
|
||||
}
|
||||
return serializer.write(entity, value, insideString)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1620,7 +1611,7 @@ class ISerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
subWrite(key, object, insideString) {
|
||||
subWrite(entity, key, object, insideString) {
|
||||
let result = "";
|
||||
let fullKey = key.concat("");
|
||||
const last = fullKey.length - 1;
|
||||
@@ -1630,13 +1621,18 @@ class ISerializer {
|
||||
if (value?.constructor === Object) {
|
||||
// Recursive call when finding an object
|
||||
result += (result.length ? this.separator : "")
|
||||
+ this.subWrite(fullKey, value, insideString);
|
||||
} else if (value !== undefined && this.showProperty(object, fullKey, value)) {
|
||||
+ this.subWrite(entity, fullKey, value, insideString);
|
||||
} else if (value !== undefined && this.showProperty(entity, object, fullKey, value)) {
|
||||
const isSerialized = Utility.isSerialized(entity, fullKey);
|
||||
result += (result.length ? this.separator : "")
|
||||
+ this.prefix
|
||||
+ this.attributeKeyPrinter(fullKey)
|
||||
+ this.attributeValueConjunctionSign
|
||||
+ this.writeValue(value, fullKey, insideString);
|
||||
+ (
|
||||
isSerialized
|
||||
? `"${this.writeValue(entity, value, fullKey, true)}"`
|
||||
: this.writeValue(entity, value, fullKey, insideString)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.trailingSeparator && result.length && fullKey.length === 1) {
|
||||
@@ -1646,7 +1642,7 @@ class ISerializer {
|
||||
return result
|
||||
}
|
||||
|
||||
showProperty(object, attributeKey, attributeValue) {
|
||||
showProperty(entity, object, attributeKey, attributeValue) {
|
||||
const attributes = this.entityType.attributes;
|
||||
const attribute = Utility.objectGet(attributes, attributeKey);
|
||||
if (attribute instanceof TypeInitialization) {
|
||||
@@ -1662,7 +1658,7 @@ class ObjectSerializer extends ISerializer {
|
||||
super(ObjectEntity, " ", "\n", false);
|
||||
}
|
||||
|
||||
showProperty(object, attributeKey, attributeValue) {
|
||||
showProperty(entity, object, attributeKey, attributeValue) {
|
||||
switch (attributeKey.toString()) {
|
||||
case "Class":
|
||||
case "Name":
|
||||
@@ -1670,7 +1666,7 @@ class ObjectSerializer extends ISerializer {
|
||||
// Serielized separately
|
||||
return false
|
||||
}
|
||||
return super.showProperty(object, attributeKey, attributeValue)
|
||||
return super.showProperty(entity, object, attributeKey, attributeValue)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1699,9 +1695,9 @@ class ObjectSerializer extends ISerializer {
|
||||
* @param {ObjectEntity} object
|
||||
* @param {Boolean} insideString
|
||||
*/
|
||||
write(object, insideString) {
|
||||
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(object.Name, ["Name"], insideString)}
|
||||
${this.subWrite([], object, insideString)
|
||||
write(entity, object, insideString) {
|
||||
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(entity, object.Name, ["Name"], insideString)}
|
||||
${this.subWrite(entity, [], object, insideString)
|
||||
+ object
|
||||
.CustomProperties.map(pin =>
|
||||
this.separator
|
||||
@@ -3329,7 +3325,6 @@ class IInputPinTemplate extends PinTemplate {
|
||||
* @param {PinElement} pin
|
||||
*/
|
||||
getInputs(pin) {
|
||||
Utility.decodeInputString(pin.entity.DefaultValue);
|
||||
return this.#inputContentElements.map(element =>
|
||||
// Faster than innerText which causes reflow
|
||||
element.innerHTML.replaceAll("<br>", "\n"))
|
||||
@@ -3658,7 +3653,7 @@ class PinElement extends IElement {
|
||||
reflect: true,
|
||||
},
|
||||
color: {
|
||||
type: LinearColorEntity$1,
|
||||
type: LinearColorEntity,
|
||||
converter: {
|
||||
fromAttribute: (value, type) => {
|
||||
return value ? ISerializer.grammar.LinearColorFromAnyColor.parse(value).value : null
|
||||
@@ -5274,8 +5269,8 @@ class GeneralSerializer extends ISerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
write(object, insideString = false) {
|
||||
let result = this.wrap(this.subWrite([], object, insideString));
|
||||
write(entity, object, insideString = false) {
|
||||
let result = this.wrap(this.subWrite(entity, [], object, insideString));
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -5285,16 +5280,19 @@ class GeneralSerializer extends ISerializer {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {IEntity} T
|
||||
* @template {IEntity | Boolean | Number | String} T
|
||||
*/
|
||||
class CustomSerializer extends GeneralSerializer {
|
||||
|
||||
#objectWriter
|
||||
|
||||
/**
|
||||
* @param {(v: T, insideString: Boolean) => String} objectWriter
|
||||
* @param {new () => T} entityType
|
||||
*/
|
||||
constructor(objectWriter, entityType) {
|
||||
super(undefined, entityType);
|
||||
this.objectWriter = objectWriter;
|
||||
this.#objectWriter = objectWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5302,30 +5300,12 @@ class CustomSerializer extends GeneralSerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
write(object, insideString = false) {
|
||||
let result = this.objectWriter(object, insideString);
|
||||
write(entity, object, insideString = false) {
|
||||
let result = this.#objectWriter(object, insideString);
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class PinSerializer extends GeneralSerializer {
|
||||
|
||||
constructor() {
|
||||
super(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String[]} fullKey
|
||||
* @param {Boolean} insideString
|
||||
*/
|
||||
writeValue(value, fullKey, insideString) {
|
||||
if (value?.constructor === String && fullKey.length == 1 && fullKey[0] == "DefaultValue") {
|
||||
return `"${Utility.encodeInputString(value)}"`
|
||||
}
|
||||
return super.writeValue(value, fullKey, insideString)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("../entity/IEntity").default} IEntity
|
||||
*/
|
||||
@@ -5346,11 +5326,10 @@ class ToStringSerializer extends GeneralSerializer {
|
||||
* @param {T} object
|
||||
* @param {Boolean} insideString
|
||||
*/
|
||||
write(object, insideString) {
|
||||
let result = insideString || object.isShownAsString()
|
||||
? `"${object.toString().replaceAll('"', '\\"')}"`
|
||||
: object.toString();
|
||||
return result
|
||||
write(entity, object, insideString) {
|
||||
return !insideString && object.constructor === String
|
||||
? `"${Utility.encodeString(object.toString())}"` // String will have quotes if not inside a string already
|
||||
: Utility.encodeString(object.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5358,6 +5337,44 @@ function initializeSerializerFactory() {
|
||||
|
||||
const bracketsWrapped = v => `(${v})`;
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
null,
|
||||
new CustomSerializer(
|
||||
(nullValue, insideString) => "()",
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
Array,
|
||||
new CustomSerializer(
|
||||
/** @param {Array} array */
|
||||
(array, insideString) =>
|
||||
`(${array
|
||||
.map(v =>
|
||||
SerializerFactory.getSerializer(Utility.getType(v)).serialize(v, insideString) + ","
|
||||
)
|
||||
.join("")
|
||||
})`,
|
||||
Array
|
||||
)
|
||||
);
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
Boolean,
|
||||
new CustomSerializer(
|
||||
/** @param {Boolean} boolean */
|
||||
(boolean, insideString) => boolean
|
||||
? insideString
|
||||
? "true"
|
||||
: "True"
|
||||
: insideString
|
||||
? "false"
|
||||
: "False",
|
||||
Boolean
|
||||
)
|
||||
);
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
FunctionReferenceEntity,
|
||||
new GeneralSerializer(bracketsWrapped, FunctionReferenceEntity)
|
||||
@@ -5380,8 +5397,8 @@ function initializeSerializerFactory() {
|
||||
);
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
LinearColorEntity$1,
|
||||
new GeneralSerializer(bracketsWrapped, LinearColorEntity$1)
|
||||
LinearColorEntity,
|
||||
new GeneralSerializer(bracketsWrapped, LinearColorEntity)
|
||||
);
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
@@ -5389,6 +5406,15 @@ function initializeSerializerFactory() {
|
||||
new GeneralSerializer(v => `${LocalizedTextEntity.lookbehind}(${v})`, LocalizedTextEntity, "", ", ", false, "", _ => "")
|
||||
);
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
Number,
|
||||
new CustomSerializer(
|
||||
/** @param {Number} value */
|
||||
value => value.toString(),
|
||||
Number
|
||||
)
|
||||
);
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
ObjectEntity,
|
||||
new ObjectSerializer()
|
||||
@@ -5409,14 +5435,24 @@ function initializeSerializerFactory() {
|
||||
|
||||
SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity));
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
PinEntity,
|
||||
new GeneralSerializer(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true)
|
||||
);
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
PinReferenceEntity,
|
||||
new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "")
|
||||
);
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
PinEntity,
|
||||
new PinSerializer()
|
||||
String,
|
||||
new CustomSerializer(
|
||||
(value, insideString) => insideString
|
||||
? Utility.encodeString(value)
|
||||
: `"${Utility.encodeString(value)}"`,
|
||||
String
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
4
dist/ueblueprint.min.js
vendored
4
dist/ueblueprint.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,7 +1,9 @@
|
||||
import CalculatedType from "./entity/CalculatedType"
|
||||
import TypeInitialization from "./entity/TypeInitialization"
|
||||
|
||||
/**
|
||||
* @typedef {import("./entity/LinearColorEntity").default} LinearColorEntity
|
||||
* @typedef {import("./entity/IEntity").default} IEntity
|
||||
*/
|
||||
|
||||
export default class Utility {
|
||||
@@ -60,6 +62,26 @@ export default class Utility {
|
||||
return location
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {IEntity}
|
||||
* @param {Object} target Object holding the data
|
||||
* @param {String[]} keys The chained keys to access from object in order to get the value
|
||||
* @param {Boolean} defaultValue Value to return in case from doesn't have it
|
||||
* @returns {any} The value in from corresponding to the keys or defaultValue otherwise
|
||||
*/
|
||||
static isSerialized(entity, keys, propertyDefinition = Utility.objectGet(entity.constructor.attributes, keys)) {
|
||||
if (propertyDefinition instanceof CalculatedType) {
|
||||
return Utility.isSerialized(entity, keys, propertyDefinition.calculate(entity))
|
||||
}
|
||||
if (propertyDefinition instanceof TypeInitialization) {
|
||||
if (propertyDefinition.serialized) {
|
||||
return true
|
||||
}
|
||||
return Utility.isSerialized(entity, keys, propertyDefinition.type)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from an object, gives defaultValue in case of failure
|
||||
* @param {Object} target Object holding the data
|
||||
@@ -128,6 +150,9 @@ export default class Utility {
|
||||
}
|
||||
|
||||
static getType(value) {
|
||||
if (value === null) {
|
||||
return null
|
||||
}
|
||||
let constructor = value?.constructor
|
||||
switch (constructor) {
|
||||
case TypeInitialization:
|
||||
@@ -207,8 +232,9 @@ export default class Utility {
|
||||
*/
|
||||
static encodeString(value, input = false) {
|
||||
return value
|
||||
.replaceAll('"', '\\"') // Escape "
|
||||
.replaceAll("\n", "\\n") // Replace newline with \n
|
||||
.replaceAll("\u00A0", " ") // Replace special space symbol
|
||||
.replaceAll("\n", String.raw`\n`) // Replace newline with \n
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,8 +242,9 @@ export default class Utility {
|
||||
*/
|
||||
static decodeString(value, input = false) {
|
||||
return value
|
||||
.replaceAll(" ", "\u00A0") // Replace special space symbol
|
||||
.replaceAll(String.raw`\n`, "\n") // Replace newline with \n
|
||||
.replaceAll('\\"', '"')
|
||||
.replaceAll("\\n", "\n")
|
||||
.replaceAll(" ", "\u00A0")
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,6 @@ export default class CalculatedType {
|
||||
* @param {IEntity} entity
|
||||
*/
|
||||
calculate(entity) {
|
||||
return this.f(entity)
|
||||
return this.#f(entity)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import CalculatedType from "./CalculatedType"
|
||||
import Observable from "../Observable"
|
||||
import SerializerFactory from "../serialization/SerializerFactory"
|
||||
import TypeInitialization from "./TypeInitialization"
|
||||
import Utility from "../Utility"
|
||||
import CalculatedType from "./CalculatedType"
|
||||
|
||||
export default class IEntity extends Observable {
|
||||
|
||||
static attributes = {}
|
||||
#showAsString = false
|
||||
|
||||
constructor(values) {
|
||||
super()
|
||||
@@ -21,19 +21,25 @@ export default class IEntity extends Observable {
|
||||
Object.getOwnPropertyNames(properties),
|
||||
Object.getOwnPropertyNames(values ?? {})
|
||||
)) {
|
||||
|
||||
let value = Utility.objectGet(values, [property])
|
||||
let defaultValue = properties[property]
|
||||
const defaultType = Utility.getType(defaultValue)
|
||||
let defaultType = Utility.getType(defaultValue)
|
||||
if (defaultValue instanceof CalculatedType) {
|
||||
defaultValue = defaultValue.calculate(this)
|
||||
defaultType = Utility.getType(defaultValue)
|
||||
}
|
||||
|
||||
if (!(property in properties)) {
|
||||
console.warn(`Property ${prefix}${property} is not defined in ${this.constructor.name}.attributes`)
|
||||
console.warn(
|
||||
`Property ${prefix}${property} in the serialized data is not defined in ${this.constructor.name}.properties`
|
||||
)
|
||||
} else if (
|
||||
defaultValue != null
|
||||
!(property in values)
|
||||
&& defaultValue !== undefined
|
||||
&& !(defaultValue instanceof TypeInitialization && !defaultValue.showDefault)
|
||||
&& !(property in values)
|
||||
) {
|
||||
console.warn(
|
||||
`${this.constructor.name} adds property ${prefix}${property} not defined in the serialized data`
|
||||
`${this.constructor.name}.properties will add property ${prefix}${property} not defined in the serialized data`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,34 +50,34 @@ export default class IEntity extends Observable {
|
||||
continue
|
||||
}
|
||||
|
||||
/*
|
||||
* The value can either be:
|
||||
* - Array: can contain multiple values, its property is assigned multiple times like (X=1, X="4").
|
||||
* - CalculatedType: the exact type depends on the previous attributes assigned to this entity.
|
||||
* - TypeInitialization: contains the maximum amount of information about the attribute.
|
||||
* - A type: the default value will be default constructed object without arguments.
|
||||
* - A proper value.
|
||||
*/
|
||||
const value = Utility.objectGet(values, [property])
|
||||
if (value !== undefined) {
|
||||
target[property] = TypeInitialization.sanitize(value, defaultType)
|
||||
// We have a value, need nothing more
|
||||
continue
|
||||
}
|
||||
if (defaultValue instanceof CalculatedType) {
|
||||
defaultValue = defaultValue.calculate(this)
|
||||
defaultType = Utility.getType(defaultValue)
|
||||
// Remember value can still be null
|
||||
if (
|
||||
value?.constructor === String
|
||||
&& defaultValue instanceof TypeInitialization
|
||||
&& defaultValue.serialized
|
||||
&& defaultValue.type !== String
|
||||
) {
|
||||
value = SerializerFactory.getSerializer(defaultValue.type).deserialize(value)
|
||||
}
|
||||
target[property] = TypeInitialization.sanitize(value, Utility.getType(defaultValue))
|
||||
continue // We have a value, need nothing more
|
||||
}
|
||||
|
||||
if (defaultValue instanceof TypeInitialization) {
|
||||
if (!defaultValue.showDefault) {
|
||||
target[property] = undefined // Declare undefined to preserve the order of attributes
|
||||
continue
|
||||
}
|
||||
defaultValue = defaultValue.value
|
||||
if (defaultValue.serialized) {
|
||||
defaultValue = ""
|
||||
} else {
|
||||
defaultType = defaultValue.type
|
||||
defaultValue = defaultValue.value
|
||||
}
|
||||
}
|
||||
if (defaultValue instanceof Array) {
|
||||
target[property] = []
|
||||
continue
|
||||
defaultValue = []
|
||||
}
|
||||
target[property] = TypeInitialization.sanitize(defaultValue, defaultType)
|
||||
}
|
||||
@@ -85,15 +91,4 @@ export default class IEntity extends Observable {
|
||||
}
|
||||
defineAllAttributes(this, attributes, values)
|
||||
}
|
||||
|
||||
isShownAsString() {
|
||||
return this.#showAsString
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Boolean} v
|
||||
*/
|
||||
setShowAsString(v) {
|
||||
this.#showAsString = v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import CalculatedType from "./CalculatedType"
|
||||
import GuidEntity from "./GuidEntity"
|
||||
import IEntity from "./IEntity"
|
||||
import LinearColorEntity from "./LinearColorEntity"
|
||||
@@ -6,9 +7,22 @@ import ObjectReferenceEntity from "./ObjectReferenceEntity"
|
||||
import PinReferenceEntity from "./PinReferenceEntity"
|
||||
import SerializedType from "./SerializedType"
|
||||
import TypeInitialization from "./TypeInitialization"
|
||||
import VectorEntity from "./VectorEntity"
|
||||
|
||||
export default class PinEntity extends IEntity {
|
||||
|
||||
static #typeEntityMap = {
|
||||
"/Script/CoreUObject.LinearColor": LinearColorEntity,
|
||||
"/Script/CoreUObject.Vector": VectorEntity,
|
||||
"bool": Boolean,
|
||||
"exec": String,
|
||||
"name": String,
|
||||
"real": Number,
|
||||
"string": String,
|
||||
}
|
||||
static get typeEntityMap() {
|
||||
return PinEntity.#typeEntityMap
|
||||
}
|
||||
static lookbehind = "Pin"
|
||||
static attributes = {
|
||||
PinId: GuidEntity,
|
||||
@@ -30,12 +44,16 @@ export default class PinEntity extends IEntity {
|
||||
bSerializeAsSinglePrecisionFloat: false,
|
||||
},
|
||||
LinkedTo: new TypeInitialization([PinReferenceEntity], false),
|
||||
DefaultValue: new TypeInitialization(
|
||||
new SerializedType([
|
||||
LinearColorEntity,
|
||||
]),
|
||||
false
|
||||
),
|
||||
DefaultValue:
|
||||
new CalculatedType(
|
||||
/** @param {PinEntity} pinEntity */
|
||||
pinEntity => new TypeInitialization(
|
||||
PinEntity.typeEntityMap[pinEntity.getType()] ?? String,
|
||||
false,
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
),
|
||||
AutogeneratedDefaultValue: new TypeInitialization(String, false),
|
||||
DefaultObject: new TypeInitialization(ObjectReferenceEntity, false, null),
|
||||
PersistentGuid: GuidEntity,
|
||||
@@ -47,6 +65,13 @@ export default class PinEntity extends IEntity {
|
||||
bOrphanedPin: false,
|
||||
}
|
||||
|
||||
getType() {
|
||||
if (this.PinType.PinCategory == "struct") {
|
||||
return this.PinType.PinSubCategoryObject.path
|
||||
}
|
||||
return this.PinType.PinCategory
|
||||
}
|
||||
|
||||
getDefaultValue() {
|
||||
return this.DefaultValue ?? ""
|
||||
}
|
||||
@@ -108,13 +133,6 @@ export default class PinEntity extends IEntity {
|
||||
return false
|
||||
}
|
||||
|
||||
getType() {
|
||||
if (this.PinType.PinCategory == "struct") {
|
||||
return this.PinType.PinSubCategoryObject.path
|
||||
}
|
||||
return this.PinType.PinCategory
|
||||
}
|
||||
|
||||
getSubCategory() {
|
||||
return this.PinType.PinSubCategoryObject.path
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import SerializedType from "./SerializedType"
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
@@ -31,13 +29,22 @@ export default class TypeInitialization {
|
||||
this.#value = v
|
||||
}
|
||||
|
||||
/** @type {Boolean} */
|
||||
#serialized
|
||||
get serialized() {
|
||||
return this.#serialized
|
||||
}
|
||||
set serialized(v) {
|
||||
this.#serialized = v
|
||||
}
|
||||
|
||||
static sanitize(value, targetType) {
|
||||
if (targetType === undefined) {
|
||||
targetType = value?.constructor
|
||||
}
|
||||
if (
|
||||
targetType
|
||||
&& targetType !== SerializedType
|
||||
// value is not of type targetType
|
||||
&& !(value?.constructor === targetType || value instanceof targetType)
|
||||
) {
|
||||
value = new targetType(value)
|
||||
@@ -49,22 +56,25 @@ export default class TypeInitialization {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {(new () => T) | SerializedType | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
|
||||
* @typedef {(new () => T) | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
|
||||
* @param {Constructor|Array<Constructor>} type
|
||||
* @param {Boolean} showDefault
|
||||
* @param {any} value
|
||||
* @param {Boolean} serialized
|
||||
*/
|
||||
constructor(type, showDefault = true, value = undefined) {
|
||||
constructor(type, showDefault = true, value = undefined, serialized = false) {
|
||||
if (value === undefined) {
|
||||
if (type instanceof Array) {
|
||||
value = []
|
||||
} else if (type instanceof SerializedType) {
|
||||
} else if (serialized) {
|
||||
value = ""
|
||||
} else {
|
||||
value = TypeInitialization.sanitize(new type())
|
||||
}
|
||||
}
|
||||
this.#showDefault = showDefault
|
||||
this.#type = type
|
||||
this.#showDefault = showDefault
|
||||
this.#value = value
|
||||
this.#serialized = serialized
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import IEntity from "./IEntity"
|
||||
|
||||
export default class LinearColorEntity extends IEntity {
|
||||
export default class VectorEntity extends IEntity {
|
||||
|
||||
static attributes = {
|
||||
X: Number,
|
||||
|
||||
@@ -5,16 +5,19 @@ import GeneralSerializer from "./GeneralSerializer"
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {IEntity} T
|
||||
* @template {IEntity | Boolean | Number | String} T
|
||||
*/
|
||||
export default class CustomSerializer extends GeneralSerializer {
|
||||
|
||||
#objectWriter
|
||||
|
||||
/**
|
||||
* @param {(v: T, insideString: Boolean) => String} objectWriter
|
||||
* @param {new () => T} entityType
|
||||
*/
|
||||
constructor(objectWriter, entityType) {
|
||||
super(undefined, entityType)
|
||||
this.objectWriter = objectWriter
|
||||
this.#objectWriter = objectWriter
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,8 +25,8 @@ export default class CustomSerializer extends GeneralSerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
write(object, insideString = false) {
|
||||
let result = this.objectWriter(object, insideString)
|
||||
write(entity, object, insideString = false) {
|
||||
let result = this.#objectWriter(object, insideString)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ export default class GeneralSerializer extends ISerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
write(object, insideString = false) {
|
||||
let result = this.wrap(this.subWrite([], object, insideString))
|
||||
write(entity, object, insideString = false) {
|
||||
let result = this.wrap(this.subWrite(entity, [], object, insideString))
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import SerializedType from "../entity/SerializedType"
|
||||
import TypeInitialization from "../entity/TypeInitialization"
|
||||
import Utility from "../Utility"
|
||||
import VectorEntity from "../entity/VectorEntity"
|
||||
import CalculatedType from "../entity/CalculatedType"
|
||||
|
||||
/**
|
||||
* @typedef {import("../entity/IEntity").default} IEntity
|
||||
@@ -30,32 +29,9 @@ export default class Grammar {
|
||||
|
||||
static getGrammarForType(r, attributeType, defaultGrammar) {
|
||||
if (attributeType instanceof TypeInitialization) {
|
||||
// Unpack TypeInitialization
|
||||
attributeType = attributeType.type
|
||||
return Grammar.getGrammarForType(r, attributeType, defaultGrammar)
|
||||
}
|
||||
if (attributeType instanceof SerializedType) {
|
||||
const nonStringTypes = attributeType.types.filter(t => t !== String)
|
||||
let result = P.alt(
|
||||
...nonStringTypes.map(t =>
|
||||
Grammar.getGrammarForType(r, t).wrap(P.string('"'), P.string('"')).map(
|
||||
/**
|
||||
* @param {IEntity} entity
|
||||
*/
|
||||
entity => {
|
||||
entity.setShowAsString(true) // Showing as string because it is inside a SerializedType
|
||||
return entity
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
if (nonStringTypes.length < attributeType.types.length) {
|
||||
result = result.or(r.String/*.map(v => {
|
||||
if (attributeType.stringFallback) {
|
||||
console.log("Unrecognized value, fallback on String")
|
||||
}
|
||||
return v
|
||||
})*/) // Separated because it cannot be wrapped into " and "
|
||||
let result = Grammar.getGrammarForType(r, attributeType.type, defaultGrammar)
|
||||
if (attributeType.serialized && !(attributeType.type instanceof String)) {
|
||||
result = result.wrap(P.string('"'), P.string('"'))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -149,7 +125,12 @@ export default class Grammar {
|
||||
|
||||
Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()")
|
||||
|
||||
Boolean = r => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false)
|
||||
Boolean = r => P.alt(
|
||||
P.string("True"),
|
||||
P.string("true"),
|
||||
P.string("False"),
|
||||
P.string("false"),
|
||||
).map(v => v.toLocaleLowerCase() === "true" ? true : false)
|
||||
.desc("either True or False")
|
||||
|
||||
HexDigit = r => P.regex(/[0-9a-fA-f]/).desc("hexadecimal digit")
|
||||
|
||||
@@ -34,13 +34,8 @@ export default class ISerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
serialize(object, insideString) {
|
||||
insideString ||= object.isShownAsString()
|
||||
let result = this.write(object, insideString)
|
||||
if (object.isShownAsString()) {
|
||||
result = `"${result}"`
|
||||
}
|
||||
return result
|
||||
serialize(object, insideString, entity = object) {
|
||||
return this.write(entity, object, insideString)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +51,7 @@ export default class ISerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
write(object, insideString) {
|
||||
write(entity, object, insideString) {
|
||||
throw new Error("Not implemented")
|
||||
}
|
||||
|
||||
@@ -64,30 +59,12 @@ export default class ISerializer {
|
||||
* @param {String[]} fullKey
|
||||
* @param {Boolean} insideString
|
||||
*/
|
||||
writeValue(value, fullKey, insideString) {
|
||||
if (value === null) {
|
||||
return "()"
|
||||
}
|
||||
const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).serialize(v)
|
||||
// This is an exact match (and not instanceof) to hit also primitive types (by accessing value.constructor they are converted to objects automatically)
|
||||
switch (value?.constructor) {
|
||||
case Function:
|
||||
return this.writeValue(value(), fullKey, insideString)
|
||||
case Boolean:
|
||||
return Utility.firstCapital(value.toString())
|
||||
case Number:
|
||||
return value.toString()
|
||||
case String:
|
||||
return insideString
|
||||
? `\\"${Utility.encodeString(value)}\\"`
|
||||
: `"${Utility.encodeString(value)}"`
|
||||
}
|
||||
if (value instanceof Array) {
|
||||
return `(${value.map(v => serialize(v) + ",").join("")})`
|
||||
}
|
||||
if (value instanceof IEntity) {
|
||||
return serialize(value)
|
||||
writeValue(entity, value, fullKey, insideString) {
|
||||
const serializer = SerializerFactory.getSerializer(Utility.getType(value))
|
||||
if (!serializer) {
|
||||
throw new Error("Unknown value type, a serializer must be registered in the SerializerFactory class")
|
||||
}
|
||||
return serializer.write(entity, value, insideString)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +73,7 @@ export default class ISerializer {
|
||||
* @param {Boolean} insideString
|
||||
* @returns {String}
|
||||
*/
|
||||
subWrite(key, object, insideString) {
|
||||
subWrite(entity, key, object, insideString) {
|
||||
let result = ""
|
||||
let fullKey = key.concat("")
|
||||
const last = fullKey.length - 1
|
||||
@@ -106,13 +83,18 @@ export default class ISerializer {
|
||||
if (value?.constructor === Object) {
|
||||
// Recursive call when finding an object
|
||||
result += (result.length ? this.separator : "")
|
||||
+ this.subWrite(fullKey, value, insideString)
|
||||
} else if (value !== undefined && this.showProperty(object, fullKey, value)) {
|
||||
+ this.subWrite(entity, fullKey, value, insideString)
|
||||
} else if (value !== undefined && this.showProperty(entity, object, fullKey, value)) {
|
||||
const isSerialized = Utility.isSerialized(entity, fullKey)
|
||||
result += (result.length ? this.separator : "")
|
||||
+ this.prefix
|
||||
+ this.attributeKeyPrinter(fullKey)
|
||||
+ this.attributeValueConjunctionSign
|
||||
+ this.writeValue(value, fullKey, insideString)
|
||||
+ (
|
||||
isSerialized
|
||||
? `"${this.writeValue(entity, value, fullKey, true)}"`
|
||||
: this.writeValue(entity, value, fullKey, insideString)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (this.trailingSeparator && result.length && fullKey.length === 1) {
|
||||
@@ -122,7 +104,7 @@ export default class ISerializer {
|
||||
return result
|
||||
}
|
||||
|
||||
showProperty(object, attributeKey, attributeValue) {
|
||||
showProperty(entity, object, attributeKey, attributeValue) {
|
||||
const attributes = this.entityType.attributes
|
||||
const attribute = Utility.objectGet(attributes, attributeKey)
|
||||
if (attribute instanceof TypeInitialization) {
|
||||
|
||||
@@ -9,7 +9,7 @@ export default class ObjectSerializer extends ISerializer {
|
||||
super(ObjectEntity, " ", "\n", false)
|
||||
}
|
||||
|
||||
showProperty(object, attributeKey, attributeValue) {
|
||||
showProperty(entity, object, attributeKey, attributeValue) {
|
||||
switch (attributeKey.toString()) {
|
||||
case "Class":
|
||||
case "Name":
|
||||
@@ -17,7 +17,7 @@ export default class ObjectSerializer extends ISerializer {
|
||||
// Serielized separately
|
||||
return false
|
||||
}
|
||||
return super.showProperty(object, attributeKey, attributeValue)
|
||||
return super.showProperty(entity, object, attributeKey, attributeValue)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,9 +46,9 @@ export default class ObjectSerializer extends ISerializer {
|
||||
* @param {ObjectEntity} object
|
||||
* @param {Boolean} insideString
|
||||
*/
|
||||
write(object, insideString) {
|
||||
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(object.Name, ["Name"], insideString)}
|
||||
${this.subWrite([], object, insideString)
|
||||
write(entity, object, insideString) {
|
||||
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(entity, object.Name, ["Name"], insideString)}
|
||||
${this.subWrite(entity, [], object, insideString)
|
||||
+ object
|
||||
.CustomProperties.map(pin =>
|
||||
this.separator
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import PinEntity from "../entity/PinEntity"
|
||||
import Utility from "../Utility"
|
||||
import GeneralSerializer from "./GeneralSerializer"
|
||||
|
||||
export default class PinSerializer extends GeneralSerializer {
|
||||
|
||||
constructor() {
|
||||
super(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String[]} fullKey
|
||||
* @param {Boolean} insideString
|
||||
*/
|
||||
writeValue(value, fullKey, insideString) {
|
||||
if (value?.constructor === String && fullKey.length == 1 && fullKey[0] == "DefaultValue") {
|
||||
return `"${Utility.encodeInputString(value)}"`
|
||||
}
|
||||
return super.writeValue(value, fullKey, insideString)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,25 @@
|
||||
import Utility from "../Utility"
|
||||
|
||||
/** @typedef {import("../entity/IEntity").default} IEntity */
|
||||
/**
|
||||
* @template {IEntity} T
|
||||
* @typedef {import("./ISerializer").default<T>} ISerializer
|
||||
*/
|
||||
|
||||
export default class SerializerFactory {
|
||||
|
||||
/** @type {Map<T, ISerializer<T>>} */
|
||||
static #serializers = new Map()
|
||||
|
||||
static registerSerializer(entity, object) {
|
||||
SerializerFactory.#serializers.set(entity, object)
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {IEntity} T
|
||||
* @param {T} entity
|
||||
*/
|
||||
static getSerializer(entity) {
|
||||
return SerializerFactory.#serializers.get(Utility.getType(entity))
|
||||
return SerializerFactory.#serializers.get(entity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Utility from "../Utility"
|
||||
import GeneralSerializer from "./GeneralSerializer"
|
||||
|
||||
/**
|
||||
@@ -20,10 +21,9 @@ export default class ToStringSerializer extends GeneralSerializer {
|
||||
* @param {T} object
|
||||
* @param {Boolean} insideString
|
||||
*/
|
||||
write(object, insideString) {
|
||||
let result = insideString || object.isShownAsString()
|
||||
? `"${object.toString().replaceAll('"', '\\"')}"`
|
||||
: object.toString()
|
||||
return result
|
||||
write(entity, object, insideString) {
|
||||
return !insideString && object.constructor === String
|
||||
? `"${Utility.encodeString(object.toString())}"` // String will have quotes if not inside a string already
|
||||
: Utility.encodeString(object.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,52 @@ import ObjectSerializer from "./ObjectSerializer"
|
||||
import PathSymbolEntity from "../entity/PathSymbolEntity"
|
||||
import PinEntity from "../entity/PinEntity"
|
||||
import PinReferenceEntity from "../entity/PinReferenceEntity"
|
||||
import PinSerializer from "./PinSerializer"
|
||||
import SerializerFactory from "./SerializerFactory"
|
||||
import ToStringSerializer from "./ToStringSerializer"
|
||||
import Utility from "../Utility"
|
||||
|
||||
export default function initializeSerializerFactory() {
|
||||
|
||||
const bracketsWrapped = v => `(${v})`
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
null,
|
||||
new CustomSerializer(
|
||||
(nullValue, insideString) => "()",
|
||||
null
|
||||
)
|
||||
)
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
Array,
|
||||
new CustomSerializer(
|
||||
/** @param {Array} array */
|
||||
(array, insideString) =>
|
||||
`(${array
|
||||
.map(v =>
|
||||
SerializerFactory.getSerializer(Utility.getType(v)).serialize(v, insideString) + ","
|
||||
)
|
||||
.join("")
|
||||
})`,
|
||||
Array
|
||||
)
|
||||
)
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
Boolean,
|
||||
new CustomSerializer(
|
||||
/** @param {Boolean} boolean */
|
||||
(boolean, insideString) => boolean
|
||||
? insideString
|
||||
? "true"
|
||||
: "True"
|
||||
: insideString
|
||||
? "false"
|
||||
: "False",
|
||||
Boolean
|
||||
)
|
||||
)
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
FunctionReferenceEntity,
|
||||
new GeneralSerializer(bracketsWrapped, FunctionReferenceEntity)
|
||||
@@ -53,6 +91,15 @@ export default function initializeSerializerFactory() {
|
||||
new GeneralSerializer(v => `${LocalizedTextEntity.lookbehind}(${v})`, LocalizedTextEntity, "", ", ", false, "", _ => "")
|
||||
)
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
Number,
|
||||
new CustomSerializer(
|
||||
/** @param {Number} value */
|
||||
value => value.toString(),
|
||||
Number
|
||||
)
|
||||
)
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
ObjectEntity,
|
||||
new ObjectSerializer()
|
||||
@@ -73,13 +120,23 @@ export default function initializeSerializerFactory() {
|
||||
|
||||
SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity))
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
PinEntity,
|
||||
new GeneralSerializer(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true)
|
||||
)
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
PinReferenceEntity,
|
||||
new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "")
|
||||
)
|
||||
|
||||
SerializerFactory.registerSerializer(
|
||||
PinEntity,
|
||||
new PinSerializer()
|
||||
String,
|
||||
new CustomSerializer(
|
||||
(value, insideString) => insideString
|
||||
? Utility.encodeString(value)
|
||||
: `"${Utility.encodeString(value)}"`,
|
||||
String
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ export default class IInputPinTemplate extends PinTemplate {
|
||||
* @param {PinElement} pin
|
||||
*/
|
||||
getInputs(pin) {
|
||||
Utility.decodeInputString(pin.entity.DefaultValue)
|
||||
return this.#inputContentElements.map(element =>
|
||||
// Faster than innerText which causes reflow
|
||||
element.innerHTML.replaceAll("<br>", "\n"))
|
||||
|
||||
Reference in New Issue
Block a user