Last active
March 25, 2024 08:25
-
-
Save Lachee/5f80fb5cb2be99dad9fc1ae5915d8263 to your computer and use it in GitHub Desktop.
Script that can read and parse Unity YAML files into basic trees.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Lachee.Utilities.Serialization; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace Lachee.Utilities.Serialization | |
{ | |
public interface IUPropertyCollection | |
{ | |
bool Add(UProperty property); | |
} | |
/// <summary>Basic data structure</summary> | |
public class UNode { } | |
/// <summary>Stores a key pair</summary> | |
public class UProperty | |
{ | |
public string name; | |
public UNode value; | |
} | |
/// <summary>Stores a single value</summary> | |
public class UValue : UNode | |
{ | |
public string value = ""; | |
public UValue() { } | |
public UValue(string value) | |
{ | |
this.value = value; | |
} | |
public override string ToString() | |
=> value; | |
} | |
/// <summary>Stores an array of values/objects</summary> | |
public class UArray : UNode, IUPropertyCollection | |
{ | |
public List<UNode> items = new List<UNode>(); | |
public bool Add(UProperty property) | |
=> Add(property.value); | |
public bool Add(UNode node) | |
{ | |
items.Add(node); | |
return true; | |
} | |
} | |
/// <summary>Stores a map of names and properties</summary> | |
public class UObject : UNode, IUPropertyCollection | |
{ | |
public Dictionary<string, UProperty> properties = new Dictionary<string, UProperty>(); | |
public bool Add(UProperty property) | |
=> properties.TryAdd(property.name, property); | |
} | |
/// <summary>A single asset/class</summary> | |
public sealed class UComponent : UNode, IUPropertyCollection | |
{ | |
public long fileID = -1; | |
public UClassID classID = UClassID.Object; | |
public string TypeName => _property?.name; | |
public UObject Component => (UObject)_property?.value; | |
public UProperty Property => _property; | |
private UProperty _property; | |
public bool Add(UProperty property) | |
{ | |
if (!string.IsNullOrEmpty(TypeName)) | |
return false; | |
_property = property; | |
return true; | |
} | |
public override string ToString() | |
=> $"{classID} &{fileID}"; | |
} | |
public enum UClassID | |
{ | |
Object = 0, | |
GameObject = 1, | |
Component = 2, | |
LevelGameManager = 3, | |
Transform = 4, | |
TimeManager = 5, | |
GlobalGameManager = 6, | |
Behaviour = 8, | |
GameManager = 9, | |
AudioManager = 11, | |
InputManager = 13, | |
EditorExtension = 18, | |
Physics2DSettings = 19, | |
Camera = 20, | |
Material = 21, | |
MeshRenderer = 23, | |
Renderer = 25, | |
Texture = 27, | |
Texture2D = 28, | |
OcclusionCullingSettings = 29, | |
GraphicsSettings = 30, | |
MeshFilter = 33, | |
OcclusionPortal = 41, | |
Mesh = 43, | |
Skybox = 45, | |
QualitySettings = 47, | |
Shader = 48, | |
TextAsset = 49, | |
Rigidbody2D = 50, | |
Collider2D = 53, | |
Rigidbody = 54, | |
PhysicsManager = 55, | |
Collider = 56, | |
Joint = 57, | |
CircleCollider2D = 58, | |
HingeJoint = 59, | |
PolygonCollider2D = 60, | |
BoxCollider2D = 61, | |
PhysicsMaterial2D = 62, | |
MeshCollider = 64, | |
BoxCollider = 65, | |
CompositeCollider2D = 66, | |
EdgeCollider2D = 68, | |
CapsuleCollider2D = 70, | |
ComputeShader = 72, | |
AnimationClip = 74, | |
ConstantForce = 75, | |
TagManager = 78, | |
AudioListener = 81, | |
AudioSource = 82, | |
AudioClip = 83, | |
RenderTexture = 84, | |
CustomRenderTexture = 86, | |
Cubemap = 89, | |
Avatar = 90, | |
AnimatorController = 91, | |
RuntimeAnimatorController = 93, | |
ScriptMapper = 94, | |
Animator = 95, | |
TrailRenderer = 96, | |
DelayedCallManager = 98, | |
TextMesh = 102, | |
RenderSettings = 104, | |
Light = 108, | |
CGProgram = 109, | |
BaseAnimationTrack = 110, | |
Animation = 111, | |
MonoBehaviour = 114, | |
MonoScript = 115, | |
MonoManager = 116, | |
Texture3D = 117, | |
NewAnimationTrack = 118, | |
Projector = 119, | |
LineRenderer = 120, | |
Flare = 121, | |
Halo = 122, | |
LensFlare = 123, | |
FlareLayer = 124, | |
HaloLayer = 125, | |
NavMeshProjectSettings = 126, | |
Font = 128, | |
PlayerSettings = 129, | |
NamedObject = 130, | |
PhysicMaterial = 134, | |
SphereCollider = 135, | |
CapsuleCollider = 136, | |
SkinnedMeshRenderer = 137, | |
FixedJoint = 138, | |
BuildSettings = 141, | |
AssetBundle = 142, | |
CharacterController = 143, | |
CharacterJoint = 144, | |
SpringJoint = 145, | |
WheelCollider = 146, | |
ResourceManager = 147, | |
PreloadData = 150, | |
ConfigurableJoint = 153, | |
TerrainCollider = 154, | |
TerrainData = 156, | |
LightmapSettings = 157, | |
WebCamTexture = 158, | |
EditorSettings = 159, | |
EditorUserSettings = 162, | |
AudioReverbFilter = 164, | |
AudioHighPassFilter = 165, | |
AudioChorusFilter = 166, | |
AudioReverbZone = 167, | |
AudioEchoFilter = 168, | |
AudioLowPassFilter = 169, | |
AudioDistortionFilter = 170, | |
SparseTexture = 171, | |
AudioBehaviour = 180, | |
AudioFilter = 181, | |
WindZone = 182, | |
Cloth = 183, | |
SubstanceArchive = 184, | |
ProceduralMaterial = 185, | |
ProceduralTexture = 186, | |
Texture2DArray = 187, | |
CubemapArray = 188, | |
OffMeshLink = 191, | |
OcclusionArea = 192, | |
Tree = 193, | |
NavMeshAgent = 195, | |
NavMeshSettings = 196, | |
ParticleSystem = 198, | |
ParticleSystemRenderer = 199, | |
ShaderVariantCollection = 200, | |
LODGroup = 205, | |
BlendTree = 206, | |
Motion = 207, | |
NavMeshObstacle = 208, | |
SortingGroup = 210, | |
SpriteRenderer = 212, | |
Sprite = 213, | |
CachedSpriteAtlas = 214, | |
ReflectionProbe = 215, | |
Terrain = 218, | |
LightProbeGroup = 220, | |
AnimatorOverrideController = 221, | |
CanvasRenderer = 222, | |
Canvas = 223, | |
RectTransform = 224, | |
CanvasGroup = 225, | |
BillboardAsset = 226, | |
BillboardRenderer = 227, | |
SpeedTreeWindAsset = 228, | |
AnchoredJoint2D = 229, | |
Joint2D = 230, | |
SpringJoint2D = 231, | |
DistanceJoint2D = 232, | |
HingeJoint2D = 233, | |
SliderJoint2D = 234, | |
WheelJoint2D = 235, | |
ClusterInputManager = 236, | |
BaseVideoTexture = 237, | |
NavMeshData = 238, | |
AudioMixer = 240, | |
AudioMixerController = 241, | |
AudioMixerGroupController = 243, | |
AudioMixerEffectController = 244, | |
AudioMixerSnapshotController = 245, | |
PhysicsUpdateBehaviour2D = 246, | |
ConstantForce2D = 247, | |
Effector2D = 248, | |
AreaEffector2D = 249, | |
PointEffector2D = 250, | |
PlatformEffector2D = 251, | |
SurfaceEffector2D = 252, | |
BuoyancyEffector2D = 253, | |
RelativeJoint2D = 254, | |
FixedJoint2D = 255, | |
FrictionJoint2D = 256, | |
TargetJoint2D = 257, | |
LightProbes = 258, | |
LightProbeProxyVolume = 259, | |
SampleClip = 271, | |
AudioMixerSnapshot = 272, | |
AudioMixerGroup = 273, | |
AssetBundleManifest = 290, | |
RuntimeInitializeOnLoadManager = 300, | |
UnityConnectSettings = 310, | |
AvatarMask = 319, | |
PlayableDirector = 320, | |
VideoPlayer = 328, | |
VideoClip = 329, | |
ParticleSystemForceField = 330, | |
SpriteMask = 331, | |
WorldAnchor = 362, | |
OcclusionCullingData = 363, | |
PrefabInstance = 1001, | |
EditorExtensionImpl = 1002, | |
AssetImporter = 1003, | |
AssetDatabaseV1 = 1004, | |
Mesh3DSImporter = 1005, | |
TextureImporter = 1006, | |
ShaderImporter = 1007, | |
ComputeShaderImporter = 1008, | |
AudioImporter = 1020, | |
HierarchyState = 1026, | |
AssetMetaData = 1028, | |
DefaultAsset = 1029, | |
DefaultImporter = 1030, | |
TextScriptImporter = 1031, | |
SceneAsset = 1032, | |
NativeFormatImporter = 1034, | |
MonoImporter = 1035, | |
LibraryAssetImporter = 1038, | |
ModelImporter = 1040, | |
FBXImporter = 1041, | |
TrueTypeFontImporter = 1042, | |
EditorBuildSettings = 1045, | |
InspectorExpandedState = 1048, | |
AnnotationManager = 1049, | |
PluginImporter = 1050, | |
EditorUserBuildSettings = 1051, | |
IHVImageFormatImporter = 1055, | |
AnimatorStateTransition = 1101, | |
AnimatorState = 1102, | |
HumanTemplate = 1105, | |
AnimatorStateMachine = 1107, | |
PreviewAnimationClip = 1108, | |
AnimatorTransition = 1109, | |
SpeedTreeImporter = 1110, | |
AnimatorTransitionBase = 1111, | |
SubstanceImporter = 1112, | |
LightmapParameters = 1113, | |
LightingDataAsset = 1120, | |
SketchUpImporter = 1124, | |
BuildReport = 1125, | |
PackedAssets = 1126, | |
VideoClipImporter = 1127, | |
Int = 100000, | |
Bool = 100001, | |
Float = 100002, | |
MonoObject = 100003, | |
Collision = 100004, | |
Vector3f = 100005, | |
RootMotionData = 100006, | |
Collision2D = 100007, | |
AudioMixerLiveUpdateFloat = 100008, | |
AudioMixerLiveUpdateBool = 100009, | |
Polygon2D = 100010, | |
Void = 100011, | |
TilemapCollider2D = 19719996, | |
AssetImporterLog = 41386430, | |
VFXRenderer = 73398921, | |
SerializableManagedRefTestClass = 76251197, | |
Grid = 156049354, | |
ScenesUsingAssets = 156483287, | |
ArticulationBody = 171741748, | |
Preset = 181963792, | |
EmptyObject = 277625683, | |
IConstraint = 285090594, | |
TestObjectWithSpecialLayoutOne = 293259124, | |
AssemblyDefinitionReferenceImporter = 294290339, | |
SiblingDerived = 334799969, | |
TestObjectWithSerializedMapStringNonAlignedStruct = 342846651, | |
SubDerived = 367388927, | |
AssetImportInProgressProxy = 369655926, | |
PluginBuildInfo = 382020655, | |
EditorProjectAccess = 426301858, | |
PrefabImporter = 468431735, | |
TestObjectWithSerializedArray = 478637458, | |
TestObjectWithSerializedAnimationCurve = 478637459, | |
TilemapRenderer = 483693784, | |
ScriptableCamera = 488575907, | |
SpriteAtlasAsset = 612988286, | |
SpriteAtlasDatabase = 638013454, | |
AudioBuildInfo = 641289076, | |
CachedSpriteAtlasRuntimeData = 644342135, | |
RendererFake = 646504946, | |
AssemblyDefinitionReferenceAsset = 662584278, | |
BuiltAssetBundleInfoSet = 668709126, | |
SpriteAtlas = 687078895, | |
RayTracingShaderImporter = 747330370, | |
RayTracingShader = 825902497, | |
LightingSettings = 850595691, | |
PlatformModuleSetup = 877146078, | |
VersionControlSettings = 890905787, | |
AimConstraint = 895512359, | |
VFXManager = 937362698, | |
VisualEffectSubgraph = 994735392, | |
VisualEffectSubgraphOperator = 994735403, | |
VisualEffectSubgraphBlock = 994735404, | |
Prefab = 1001480554, | |
LocalizationImporter = 1027052791, | |
Derived = 1091556383, | |
PropertyModificationsTargetTestObject = 1111377672, | |
ReferencesArtifactGenerator = 1114811875, | |
AssemblyDefinitionAsset = 1152215463, | |
SceneVisibilityState = 1154873562, | |
LookAtConstraint = 1183024399, | |
SpriteAtlasImporter = 1210832254, | |
MultiArtifactTestImporter = 1223240404, | |
GameObjectRecorder = 1268269756, | |
LightingDataAssetParent = 1325145578, | |
PresetManager = 1386491679, | |
TestObjectWithSpecialLayoutTwo = 1392443030, | |
StreamingManager = 1403656975, | |
LowerResBlitTexture = 1480428607, | |
StreamingController = 1542919678, | |
TestObjectVectorPairStringBool = 1628831178, | |
GridLayout = 1742807556, | |
AssemblyDefinitionImporter = 1766753193, | |
ParentConstraint = 1773428102, | |
FakeComponent = 1803986026, | |
PositionConstraint = 1818360608, | |
RotationConstraint = 1818360609, | |
ScaleConstraint = 1818360610, | |
Tilemap = 1839735485, | |
PackageManifest = 1896753125, | |
PackageManifestImporter = 1896753126, | |
TerrainLayer = 1953259897, | |
SpriteShapeRenderer = 1971053207, | |
NativeObjectType = 1977754360, | |
TestObjectWithSerializedMapStringBool = 1981279845, | |
SerializableManagedHost = 1995898324, | |
VisualEffectAsset = 2058629509, | |
VisualEffectImporter = 2058629510, | |
VisualEffectResource = 2058629511, | |
VisualEffectObject = 2059678085, | |
VisualEffect = 2083052967, | |
LocalizationAsset = 2083778819, | |
ScriptedImporter = 2089858483, | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Text; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
namespace Lachee.Utilities.Serialization | |
{ | |
/// <summary> | |
/// Simple Parser for Unity YAML files. Able to produce basic tree structures, it is suitable for raw manipulation of the data but not serialization. | |
/// </summary> | |
public sealed class UYAMLParser | |
{ | |
internal const string COMPONENT_HEADER = "--- !u!"; | |
private IUPropertyCollection _curObject; | |
private UProperty _curProperty; | |
private Stack<IUPropertyCollection> _objects; | |
private int _spt; | |
private int _indentLevel; | |
private int _prevIndentLevel; | |
private UYAMLParser() | |
{ | |
_spt = 0; | |
_objects = new Stack<IUPropertyCollection>(); | |
Reset(); | |
} | |
/// <summary>Resets the state of the parser</summary> | |
private void Reset() | |
{ | |
_curObject = null; | |
_curProperty = null; | |
_objects.Clear(); | |
_indentLevel = 0; | |
_prevIndentLevel = 0; | |
} | |
/// <summary>Parses the given UYAML content</summary> | |
public static List<UComponent> Parse(string content) | |
{ | |
int offset; | |
int nextOffset; | |
string block; | |
// Get to first chunk | |
offset = content.IndexOf(COMPONENT_HEADER); | |
if (offset == -1) | |
throw new System.InvalidOperationException("There was no blocks found"); | |
List<UComponent> components = new List<UComponent>(); | |
UYAMLParser parser = new UYAMLParser(); | |
do | |
{ | |
nextOffset = content.IndexOf(COMPONENT_HEADER, offset + COMPONENT_HEADER.Length); | |
if (nextOffset == -1) | |
block = content.Substring(offset); | |
else | |
block = content.Substring(offset, nextOffset - offset-1); | |
var component = parser.ParseComponent(block); | |
components.Add(component); | |
offset = nextOffset; | |
} while (offset >= 0); | |
return components; | |
} | |
private UComponent ParseComponent(string content) | |
{ | |
Reset(); | |
int offset; | |
int nextOffset; | |
string line; | |
// Get to the first line | |
offset = content.IndexOf(COMPONENT_HEADER); | |
if (offset == -1) | |
throw new System.InvalidOperationException("The block is missing the content header"); | |
do | |
{ | |
nextOffset = content.IndexOf('\n', offset)+1; | |
if (nextOffset <= 0) line = content.Substring(offset); | |
else line = content.Substring(offset, nextOffset - offset -1); | |
ParseLine(line); | |
offset = nextOffset; | |
} while (offset > 0); | |
while (_objects.TryPop(out var node)) | |
{ | |
if (node is UComponent comp) | |
return comp; | |
} | |
return null; | |
} | |
private void ParseLine(string content) | |
{ | |
if (string.IsNullOrWhiteSpace(content)) | |
return; | |
string line; | |
bool isArrayEntry = false; | |
_prevIndentLevel = _indentLevel; | |
_indentLevel = Tabulate(content, out line); | |
if (_spt < 1) // Update the indentation level based of the first item we meet | |
{ | |
_spt = _indentLevel; | |
_indentLevel = Tabulate(content, out line); | |
} | |
// No block yet, expecting a new def | |
if (_curObject == null) | |
{ | |
if (!line.StartsWith(COMPONENT_HEADER)) | |
throw new ParseException($"Expecting a new block, but got '{line}' instead."); | |
string[] segs = line.Split(' '); | |
if (segs.Length != 3) | |
throw new ParseException("Expecting 3 parts in the block header."); | |
_curObject = new UComponent() | |
{ | |
classID = (UClassID) int.Parse(segs[1].Substring(3)), | |
fileID = long.Parse(segs[2].Substring(1)) | |
}; | |
return; | |
} | |
// Start of Array | |
if (line[0] == '-') | |
{ | |
line = line.Trim('-', '\t', ' '); | |
_indentLevel++; | |
isArrayEntry = true; | |
} | |
// Start of Object | |
int indentDiff = _indentLevel - _prevIndentLevel; | |
if (indentDiff == 1) // Something wrong with this logic when adding item,s to array | |
{ | |
if (isArrayEntry) | |
{ | |
// We have increased our indentation and we have started with a -, | |
// that means we starting an array and the parent property needs to be converted | |
// into an array. | |
// We need to convert the current property to an array | |
var arr = new UArray(); | |
_objects.Push(_curObject); | |
_curProperty.value = arr; | |
_curObject = arr; | |
} | |
else | |
{ | |
// Our indentation level has increased so we are making a new object | |
var obj = new UObject(); | |
_objects.Push(_curObject); | |
_curProperty.value = obj; | |
_curObject = obj; | |
} | |
} | |
else if (indentDiff < 0) | |
{ | |
while (_objects.Count > _indentLevel) | |
_curObject = _objects.Pop(); | |
} | |
else if (indentDiff != 0) | |
{ | |
throw new ParseException("Indentation grew/shrunk too rapidly. Expected only a change of either 0 or 1."); | |
} | |
// Seperate the parts | |
string[] parts = line.Split(':', 2); | |
if (parts.Length == 1) | |
{ | |
if (_curObject is UArray arr) | |
arr.Add(ParseValue(line)); | |
else | |
throw new ParseException("Cannot add key-less values to an object"); | |
} | |
else | |
{ | |
_curProperty = new UProperty(); | |
if (parts.Length != 2) | |
throw new ParseException($"Cannot find property name in '{line}'"); | |
_curProperty.name = parts[0].Trim(); | |
_curProperty.value = ParseValue(parts[1]); | |
if (isArrayEntry) | |
{ | |
if (_curObject is not UArray && _objects.Peek() is UArray) | |
_curObject = _objects.Pop(); | |
if (_curObject is UArray arr) | |
{ | |
// Create a new object to put this property into | |
var obj = new UObject(); | |
_objects.Push(_curObject); | |
_curObject = obj; | |
arr.Add(obj); | |
} | |
else | |
{ | |
throw new ParseException("Adding a new array item, but could not get an array to put it in."); | |
} | |
} | |
// Attempt to push the current property into the current object | |
if (_curProperty != null && !_curObject.Add(_curProperty)) | |
throw new ParseException($"Failed to add the property '{_curProperty.name}'"); | |
} | |
#if false | |
// If we are in the middle of an array, we need to either add to the | |
// array or build the items | |
if (_curObject is UArray curArray) | |
{ | |
// Seperate the parts | |
bool isProperty = line.IndexOf(':') > 0; | |
bool isInlineShape = line[0] == '{' || line[0] == '['; | |
if (!isProperty && !isInlineShape) // A single value, we will push it to the array directly | |
{ | |
if (!isArrayEntry) | |
throw new ParseException("Array Values must begin with -"); | |
curArray.Add(ParseValue(line)); | |
} | |
else if (isInlineShape) | |
{ | |
if (!isArrayEntry) | |
throw new ParseException("Array Values must begin with -"); | |
curArray.Add(ParseValue(line)); | |
} | |
else // An object for a value | |
{ | |
if (isArrayEntry) // Its a new item, so create the item and push it to the array. | |
{ | |
var item = new UObject(); | |
curArray.Add(item); | |
} | |
string[] parts = line.Split(':'); | |
if (parts.Length == 2) // If we have a name: value, add it to the last array item. | |
{ | |
var lastItem = curArray.items[curArray.items.Count - 1]; | |
if (lastItem is IUPropertyCollection propertyCollection) | |
{ | |
var property = new UProperty() | |
{ | |
name = parts[0].Trim(), | |
value = ParseValue(parts[1]) | |
}; | |
if (!propertyCollection.Add(property)) | |
throw new ParseException($"Duplicate property found '{property.name}'"); | |
} | |
else | |
{ | |
throw new ParseException("Previous array item was not a property collection. Cannot add new properties."); | |
} | |
} | |
} | |
} | |
else // Adding a property to the previous object. | |
{ | |
// Seperate the parts | |
string[] parts = line.Split(':', 2); | |
_curProperty = new UProperty(); | |
if (parts.Length != 2) | |
throw new ParseException($"Cannot find property name in '{line}'"); | |
_curProperty.name = parts[0].Trim(); | |
_curProperty.value = ParseValue(parts[1]); | |
// Attempt to push the current property into the current object | |
if (_curProperty != null && !_curObject.Add(_curProperty)) | |
throw new ParseException($"Duplicate property found '{_curProperty.name}'"); | |
} | |
#endif | |
} | |
private UNode ParseValue(string value) | |
{ | |
string[] parts; | |
string content = value.Trim(); | |
if (content.Length == 0) | |
return new UValue(); | |
if (content[0] == '{' && content[content.Length - 1] == '}') { | |
UObject objValue = new UObject(); | |
if (content.Length > 2) | |
{ | |
parts = content.Split(','); | |
foreach (var part in parts) | |
{ | |
string[] sParts = part.Trim('{', '}', ' ').Split(':', 2); | |
if (sParts.Length != 2) | |
throw new ParseException("Cannot parse non-key values inside a inline object!"); | |
objValue.Add(new UProperty() | |
{ | |
name = sParts[0].Trim(), | |
value = ParseValue(sParts[1]) | |
}); | |
} | |
} | |
return objValue; | |
} | |
if (content[0] == '[' && content[content.Length - 1] == ']') { | |
UArray arrayValue = new UArray(); | |
if (content.Length > 2) | |
{ | |
parts = content.Split(','); | |
foreach (var part in parts) | |
arrayValue.Add(new UValue(part.Trim('[', ']', ' '))); | |
} | |
return arrayValue; | |
} | |
if (content[0] == '"' && content[content.Length - 1] == '"') | |
content = content.Trim('"').Replace("\\\"", "\""); | |
return new UValue(content); | |
} | |
private int Tabulate(string content, out string line) | |
{ | |
int spaces; | |
for (spaces = 0; spaces < content.Length; spaces++) | |
{ | |
if (content[spaces] != ' ' && content[spaces] != '\t') | |
break; | |
} | |
line = content.TrimStart(' ', '\t', '\n', '\r'); | |
return spaces / Math.Max(_spt, 1); | |
} | |
} | |
/// <summary>Represents errors that occure during parsing</summary> | |
public sealed class ParseException : System.Exception | |
{ | |
public ParseException(string message) : base(message) { } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Text; | |
namespace Lachee.Utilities.Serialization | |
{ | |
public class UYAMLWriter | |
{ | |
private const string DEFAULT_EOL = "\r\n"; | |
private StringBuilder builder = new StringBuilder(); | |
private string eol = DEFAULT_EOL; | |
public int IndentationSpaces { get; set; } = 2; | |
public bool InlineObjects { get; set; } = true; | |
public bool InlineComplexObjects { get; set; } = false; | |
public bool InlineArrays { get; set; } = true; | |
public bool InlineComplexArrays { get; set; } = false; | |
public void AddComponets(IEnumerable<UComponent> components) | |
{ | |
foreach (var component in components) | |
AddComponent(component); | |
} | |
public void AddComponent(UComponent component) | |
{ | |
builder.Append(UYAMLParser.COMPONENT_HEADER) | |
.Append((int)component.classID) | |
.Append(" &") | |
.Append(component.fileID) | |
.Append(eol); | |
AppendProperty(component.Property, 0, false, false); | |
} | |
private void AppendProperty(UProperty property, int indent, bool arrayItem, bool skipIndent) | |
{ | |
int indentLevel = indent; | |
int indentSize = IndentationSpaces; | |
string name = string.IsNullOrEmpty(property.name) ? "" : property.name + ":"; | |
if (arrayItem) | |
{ | |
name = "-"; | |
indentLevel -= 1; | |
} | |
if (skipIndent) | |
indentSize = 0; | |
switch (property.value) | |
{ | |
default: break; | |
case UValue uValue: | |
builder.Append(' ', indentLevel * indentSize).Append(name).Append(" ").Append(uValue.value); | |
builder.Append(eol); | |
break; | |
case UObject uObject: | |
if (CanInline(uObject) && !arrayItem) | |
{ | |
builder.Append(' ', indentLevel * indentSize).Append(name).Append(" {"); | |
eol = ""; | |
bool first = true; | |
foreach (var kp in uObject.properties) | |
{ | |
if (!first) builder.Append(", "); | |
AppendProperty(kp.Value, 0, false, true); | |
first = false; | |
} | |
eol = DEFAULT_EOL; | |
builder.Append('}').Append(eol); | |
} | |
else | |
{ | |
bool first = true; | |
builder.Append(' ', indentLevel * indentSize).Append(name); | |
builder.Append(arrayItem ? ' ' : eol); // This inlines the first time of an array | |
foreach (var kp in uObject.properties) | |
{ | |
AppendProperty(kp.Value, indentLevel + 1, false, arrayItem && first); | |
first = false; | |
} | |
} | |
break; | |
case UArray uArray: | |
if (CanInline(uArray)) | |
{ | |
builder.Append(' ', indentLevel * indentSize).Append(name).Append(" ["); | |
eol = ""; | |
bool first = true; | |
for (int i = 0; i < uArray.items.Count; i++) | |
{ | |
if (!first) builder.Append(","); | |
AppendProperty(new UProperty() { value = uArray.items[i] }, 0, false, true); | |
first = false; | |
} | |
eol = DEFAULT_EOL; | |
builder.Append(']').Append(eol); | |
} | |
else | |
{ | |
builder.Append(' ', indentLevel * indentSize).Append(name); | |
builder.Append(eol); | |
for (int i = 0; i < uArray.items.Count; i++) | |
AppendProperty(new UProperty() { value = uArray.items[i] }, indentLevel + 1, true, false); | |
} | |
break; | |
} | |
} | |
private bool CanInline(UObject obj) | |
{ | |
if (!InlineObjects) | |
return false; | |
if (obj.properties.Count == 0) | |
return true; | |
foreach(var kp in obj.properties) | |
{ | |
if (InlineComplexObjects) | |
{ | |
if (kp.Value.value is UObject o && !CanInline(o)) | |
return false; | |
if (kp.Value.value is UArray a && !CanInline(a)) | |
return false; | |
} | |
else | |
{ | |
if (kp.Value.value is not UValue) | |
return false; | |
} | |
} | |
return true; | |
} | |
private bool CanInline(UArray arr) | |
{ | |
if (!InlineArrays) | |
return false; | |
if (arr.items.Count == 0) | |
return true; | |
foreach(var item in arr.items) | |
{ | |
if (InlineComplexArrays) | |
{ | |
if (item is UObject o && !CanInline(o)) | |
return false; | |
if (item is UArray a && !CanInline(a)) | |
return false; | |
} | |
else | |
{ | |
if (item is not UValue) | |
return false; | |
} | |
} | |
return true; | |
} | |
public override string ToString() | |
=> $"%YAML 1.1{DEFAULT_EOL}%TAG !u! tag:unity3d.com,2011:{DEFAULT_EOL}" + builder.ToString(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated version can be found https://github.com/Lachee/unity-utilities/tree/master/Runtime/UYAML