├── .gitignore ├── LICENSE ├── LunarParser.sln ├── LunarParser ├── Binary │ ├── BINReader.cs │ └── BINWriter.cs ├── CSV │ ├── CSVReader.cs │ └── CSVWriter.cs ├── DataNode.cs ├── Extensions.cs ├── Formats.cs ├── JSON │ ├── JSONReader.cs │ └── JSONWriter.cs ├── LunarParser.csproj ├── ParserUtils.cs ├── XML │ ├── XMLReader.cs │ └── XMLWriter.cs └── YAML │ ├── YAMLReader.cs │ └── YAMLWriter.cs ├── README.md └── Tests ├── ParserTests.cs ├── Properties └── AssemblyInfo.cs ├── Tests.csproj ├── app.config └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | LunarParser/obj/ 2 | LunarParser/bin/ 3 | .vs/ 4 | Tests/bin/ 5 | Tests/obj/ 6 | packages/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sérgio Flores 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LunarParser.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LunarParser", "LunarParser\LunarParser.csproj", "{33543F22-BCFC-47FB-ACCA-94AE63EDF785}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{77249AA9-D572-457E-BA15-97A17ED39F81}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {33543F22-BCFC-47FB-ACCA-94AE63EDF785}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {33543F22-BCFC-47FB-ACCA-94AE63EDF785}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {33543F22-BCFC-47FB-ACCA-94AE63EDF785}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {33543F22-BCFC-47FB-ACCA-94AE63EDF785}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {77249AA9-D572-457E-BA15-97A17ED39F81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {77249AA9-D572-457E-BA15-97A17ED39F81}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {77249AA9-D572-457E-BA15-97A17ED39F81}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {77249AA9-D572-457E-BA15-97A17ED39F81}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {69175B18-0179-47CF-BB6A-1B4A96C38173} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /LunarParser/Binary/BINReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace LunarLabs.Parser.Binary 6 | { 7 | public class BINReader 8 | { 9 | private static DataNode ReadNode(BinaryReader reader, Dictionary dic) 10 | { 11 | ushort id = reader.ReadUInt16(); 12 | string name = dic[id]; 13 | string value = null; 14 | 15 | ushort len = reader.ReadUInt16(); 16 | if (len > 0) 17 | { 18 | byte[] bytes = reader.ReadBytes(len); 19 | value = System.Text.Encoding.UTF8.GetString(bytes); 20 | } 21 | 22 | int childCount = reader.ReadInt32(); 23 | 24 | var node = DataNode.CreateObject(name); 25 | node.Value = value; 26 | 27 | while (childCount>0) 28 | { 29 | var child = ReadNode(reader, dic); 30 | node.AddNode(child); 31 | childCount--; 32 | } 33 | 34 | return node; 35 | } 36 | 37 | public static DataNode ReadFromBytes(byte[] bytes) 38 | { 39 | var dic = new Dictionary(); 40 | using (var reader = new BinaryReader(new MemoryStream(bytes))) 41 | { 42 | int entryCount = reader.ReadInt32(); 43 | while (entryCount>0) 44 | { 45 | ushort id = reader.ReadUInt16(); 46 | byte len = reader.ReadByte(); 47 | var temp = reader.ReadBytes(len); 48 | 49 | string val = System.Text.Encoding.UTF8.GetString(temp); 50 | 51 | dic[id] = val; 52 | 53 | entryCount--; 54 | } 55 | 56 | return ReadNode(reader, dic); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /LunarParser/Binary/BINWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace LunarLabs.Parser.Binary 6 | { 7 | public class BINWriter 8 | { 9 | private static void GenDic(Dictionary dic, DataNode node) 10 | { 11 | if (!dic.ContainsKey(node.Name)) 12 | { 13 | dic[node.Name] = (ushort)(dic.Count + 1); 14 | } 15 | 16 | foreach (var child in node.Children) 17 | { 18 | GenDic(dic, child); 19 | } 20 | } 21 | 22 | private static void WriteNode(BinaryWriter writer, Dictionary dic, DataNode node) 23 | { 24 | ushort id = dic[node.Name]; 25 | writer.Write(id); 26 | 27 | byte[] bytes = string.IsNullOrEmpty(node.Value) ? null : Encoding.UTF8.GetBytes(node.Value); 28 | ushort len = (byte)(bytes == null ? 0 : bytes.Length); 29 | writer.Write(len); 30 | if (bytes != null) 31 | { 32 | writer.Write(bytes); 33 | } 34 | 35 | int childCount = node.ChildCount; 36 | writer.Write(childCount); 37 | 38 | foreach (var child in node.Children) 39 | { 40 | WriteNode(writer, dic, child); 41 | } 42 | } 43 | 44 | public static byte[] SaveToBytes(DataNode root) 45 | { 46 | var dic = new Dictionary(); 47 | GenDic(dic, root); 48 | 49 | using (var stream = new MemoryStream(1024)) 50 | { 51 | using (var writer = new BinaryWriter(stream)) 52 | { 53 | int entryCount = dic.Count; 54 | writer.Write(entryCount); 55 | foreach (var entry in dic) 56 | { 57 | writer.Write(entry.Value); 58 | var bytes = Encoding.UTF8.GetBytes(entry.Key); 59 | byte len = (byte)bytes.Length; 60 | writer.Write(len); 61 | writer.Write(bytes); 62 | } 63 | 64 | WriteNode(writer, dic, root); 65 | 66 | } 67 | 68 | return stream.ToArray(); 69 | } 70 | 71 | 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /LunarParser/CSV/CSVReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LunarLabs.Parser.CSV 6 | { 7 | public class CSVReader 8 | { 9 | private enum State 10 | { 11 | Header, 12 | Content 13 | } 14 | 15 | public static DataNode ReadFromString(string contents) 16 | { 17 | var root = DataNode.CreateArray(null); 18 | 19 | var header = new List(); 20 | 21 | int index = 0; 22 | var state = State.Header; 23 | char c; 24 | int fieldIndex = 0; 25 | 26 | bool isEscaped = false; 27 | 28 | var content = new StringBuilder(); 29 | 30 | DataNode currentNode = null; 31 | 32 | while (index < contents.Length) 33 | { 34 | c = contents[index]; 35 | 36 | index++; 37 | 38 | switch (state) 39 | { 40 | case State.Header: 41 | { 42 | if (c == ',' || c == '\n') 43 | { 44 | header.Add(content.ToString().Trim()); 45 | content.Length = 0; 46 | } 47 | 48 | switch (c) 49 | { 50 | case ',': { break; } 51 | 52 | case '\n': 53 | { 54 | state = State.Content; 55 | break; 56 | } 57 | 58 | default: 59 | { 60 | content.Append(c); 61 | break; 62 | } 63 | } 64 | break; 65 | } 66 | 67 | case State.Content: 68 | { 69 | if (!isEscaped && (c == ',' || c == '\n')) 70 | { 71 | if (fieldIndex < header.Count) 72 | { 73 | currentNode.AddField(header[fieldIndex], content.ToString()); 74 | } 75 | 76 | content.Length = 0; 77 | 78 | fieldIndex++; 79 | 80 | if (c =='\n') 81 | { 82 | fieldIndex = 0; 83 | currentNode = null; 84 | } 85 | 86 | break; 87 | } 88 | 89 | if (c == '"') 90 | { 91 | if (isEscaped && index(); 18 | foreach (var field in first.Children) 19 | { 20 | if (index > 0 ) 21 | { 22 | content.Append(','); 23 | } 24 | content.Append(field.Name); 25 | header.Add(field.Name); 26 | index++; 27 | } 28 | content.AppendLine(); 29 | 30 | foreach (var item in node.Children) 31 | { 32 | index = 0; 33 | foreach (var fieldName in header) 34 | { 35 | var field = item.GetNodeByName(fieldName); 36 | 37 | if (index > 0) 38 | { 39 | content.Append(','); 40 | } 41 | 42 | if (field != null) 43 | { 44 | bool escape = field.Value.Contains(',') || field.Value.Contains('\n'); 45 | 46 | if (escape) { content.Append('"'); } 47 | content.Append(field.Value); 48 | if (escape) { content.Append('"'); } 49 | } 50 | 51 | index++; 52 | } 53 | 54 | content.AppendLine(); 55 | } 56 | 57 | return content.ToString(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /LunarParser/DataNode.cs: -------------------------------------------------------------------------------- 1 | //#define DATETIME_AS_TIMESTAMPS 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.Xml.Linq; 8 | 9 | namespace LunarLabs.Parser 10 | { 11 | public enum NodeKind 12 | { 13 | Unknown, 14 | Object, 15 | Array, 16 | String, 17 | Numeric, 18 | Boolean, 19 | Null 20 | } 21 | 22 | public class DataNode: IEnumerable 23 | { 24 | protected List _children = new List(); 25 | public IEnumerable Children { get { return _children; } } 26 | 27 | public DataNode Parent { get; private set; } 28 | 29 | public int ChildCount { get { return _children.Count; } } 30 | 31 | public bool HasChildren { get { return _children.Count > 0; } } 32 | 33 | public string Name { get; set; } 34 | public string Value { get; set; } 35 | public NodeKind Kind { get; private set; } 36 | 37 | 38 | private DataNode(NodeKind kind, string name = null, string value = null) 39 | { 40 | this.Kind = kind; 41 | this.Parent = null; 42 | this.Name = name; 43 | this.Value = value; 44 | } 45 | 46 | public IEnumerator GetEnumerator() 47 | { 48 | return _children.GetEnumerator(); 49 | } 50 | 51 | IEnumerator IEnumerable.GetEnumerator() 52 | { 53 | return _children.GetEnumerator(); 54 | } 55 | 56 | public DataNode this[string name] 57 | { 58 | get { 59 | var node = GetNodeByName(name); 60 | if (node == null) 61 | { 62 | return this.AddEmptyNode(name); 63 | } 64 | 65 | return node; 66 | } 67 | } 68 | 69 | public DataNode this[int index] 70 | { 71 | get { return GetNodeByIndex(index); } 72 | } 73 | 74 | public static DataNode CreateString(string name = null) 75 | { 76 | return new DataNode(NodeKind.String, name); 77 | } 78 | 79 | public static DataNode CreateNumber(string name = null) 80 | { 81 | return new DataNode(NodeKind.Numeric, name); 82 | } 83 | 84 | public static DataNode CreateObject(string name = null) 85 | { 86 | return new DataNode(NodeKind.Object, name); 87 | } 88 | 89 | public static DataNode CreateArray(string name = null) 90 | { 91 | return new DataNode(NodeKind.Array, name); 92 | } 93 | 94 | public static DataNode CreateValue(object value) 95 | { 96 | 97 | NodeKind kind; 98 | var val = ConvertValue(value, out kind); 99 | return new DataNode(kind, null, val); 100 | } 101 | 102 | public override string ToString() 103 | { 104 | if (this.ChildCount == 0 && !string.IsNullOrEmpty(this.Value)) 105 | { 106 | return this.Value; 107 | } 108 | 109 | if (!string.IsNullOrEmpty(Name)) 110 | { 111 | return $"{Name}"; 112 | } 113 | 114 | if (this.Parent == null) 115 | { 116 | return "[Root]"; 117 | } 118 | 119 | return "[Null]"; 120 | } 121 | 122 | public bool RemoveNode(DataNode node) 123 | { 124 | if (node == null) 125 | { 126 | return false; 127 | } 128 | 129 | var count = _children.Count; 130 | 131 | _children.Remove(node); 132 | 133 | return (_children.Count < count); 134 | } 135 | 136 | public bool RemoveNodeByName(string name) 137 | { 138 | var node = GetNodeByName(name); 139 | return RemoveNode(node) 140 | ; } 141 | 142 | public bool RemoveNodeByIndex(int index) 143 | { 144 | if (index < 0 || index >= _children.Count) 145 | { 146 | return false; 147 | } 148 | 149 | _children.RemoveAt(index); 150 | 151 | return true; 152 | } 153 | 154 | 155 | public DataNode AddNode(DataNode node) 156 | { 157 | if (node == null) 158 | { 159 | return null; 160 | } 161 | 162 | this._children.Add(node); 163 | node.Parent = this; 164 | return node; 165 | } 166 | 167 | public DataNode AddEmptyNode(string name) 168 | { 169 | var node = DataNode.CreateObject(name); 170 | return AddNode(node); 171 | } 172 | 173 | private static readonly long epochTicks = new DateTime(1970, 1, 1).Ticks; 174 | 175 | public DataNode AddValue(object value) 176 | { 177 | return AddField(null, value); 178 | } 179 | 180 | public DataNode AddField(string name, object value) 181 | { 182 | if (this.Kind != NodeKind.Array && this.Kind != NodeKind.Object) 183 | { 184 | throw new Exception("The kind of this node is not 'object'!"); 185 | } 186 | 187 | if (value is DataNode) 188 | { 189 | throw new Exception("Cannot add a node as a field!"); 190 | } 191 | 192 | NodeKind kind; 193 | string val = ConvertValue(value, out kind); 194 | 195 | var child = new DataNode(kind, name, val); 196 | this.AddNode(child); 197 | return child; 198 | } 199 | 200 | public DataNode SetField(string name, object value) 201 | { 202 | var node = GetNodeByName(name); 203 | 204 | if (node == null) 205 | { 206 | return AddField(name, value); 207 | } 208 | 209 | node.SetValue(value); 210 | 211 | return node; 212 | } 213 | 214 | public void SetValue(object value) 215 | { 216 | NodeKind kind; 217 | this.Value = ConvertValue(value, out kind); 218 | } 219 | 220 | 221 | #if DATETIME_AS_TIMESTAMPS 222 | internal static DateTime FromTimestamp(long unixTimeStamp) 223 | { 224 | // Unix timestamp is seconds past epoch 225 | System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc); 226 | dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime(); 227 | return dtDateTime; 228 | } 229 | 230 | internal static long ToTimestamp(DateTime value) 231 | { 232 | long epoch = (value.Ticks - 621355968000000000) / 10000000; 233 | return epoch; 234 | } 235 | 236 | #endif 237 | 238 | private static string ConvertValue(object value, out NodeKind kind) 239 | { 240 | if (value == null) 241 | { 242 | kind = NodeKind.Null; 243 | return ""; 244 | } 245 | 246 | string val; 247 | 248 | #if DATETIME_AS_TIMESTAMPS 249 | // convert dates to unix timestamps 250 | if (value.GetType() == typeof(DateTime)) 251 | { 252 | val = ToTimestamp(((DateTime)value)).ToString(); 253 | kind = NodeKind.Numeric; 254 | } 255 | else 256 | #endif 257 | if (value is int) 258 | { 259 | val = ((int)value).ToString(CultureInfo.InvariantCulture); 260 | kind = NodeKind.Numeric; 261 | } 262 | else 263 | if (value is uint) 264 | { 265 | val = ((uint)value).ToString(CultureInfo.InvariantCulture); 266 | kind = NodeKind.Numeric; 267 | } 268 | else 269 | if (value is long) 270 | { 271 | val = ((long)value).ToString(CultureInfo.InvariantCulture); 272 | kind = NodeKind.Numeric; 273 | } 274 | else 275 | if (value is ulong) 276 | { 277 | val = ((ulong)value).ToString(CultureInfo.InvariantCulture); 278 | kind = NodeKind.Numeric; 279 | } 280 | else 281 | if (value is byte) 282 | { 283 | val = ((byte)value).ToString(CultureInfo.InvariantCulture); 284 | kind = NodeKind.Numeric; 285 | } 286 | else 287 | if (value is sbyte) 288 | { 289 | val = ((sbyte)value).ToString(CultureInfo.InvariantCulture); 290 | kind = NodeKind.Numeric; 291 | } 292 | else 293 | if (value is short) 294 | { 295 | val = ((short)value).ToString(CultureInfo.InvariantCulture); 296 | kind = NodeKind.Numeric; 297 | } 298 | else 299 | if (value is ushort) 300 | { 301 | val = ((ushort)value).ToString(CultureInfo.InvariantCulture); 302 | kind = NodeKind.Numeric; 303 | } 304 | else 305 | if (value is float) 306 | { 307 | val = ((float)value).ToString(CultureInfo.InvariantCulture); 308 | kind = NodeKind.Numeric; 309 | } 310 | else 311 | if (value is double) 312 | { 313 | val = ((double)value).ToString(CultureInfo.InvariantCulture); 314 | kind = NodeKind.Numeric; 315 | } 316 | else 317 | if (value is decimal) 318 | { 319 | val = ((decimal)value).ToString(CultureInfo.InvariantCulture); 320 | kind = NodeKind.Numeric; 321 | } 322 | else 323 | if (value is bool) 324 | { 325 | val = ((bool)value)?"true":"false"; 326 | kind = NodeKind.Boolean; 327 | } 328 | else 329 | { 330 | val = value.ToString(); 331 | kind = NodeKind.String; 332 | } 333 | 334 | return val; 335 | } 336 | 337 | public bool HasNode(string name, int index = 0) 338 | { 339 | return GetNodeByName(name, index) != null; 340 | } 341 | 342 | // internal auxiliary 343 | private DataNode FindNode(string name, int ndepth, int maxdepth) 344 | { 345 | if (String.Compare(this.Name, name, StringComparison.OrdinalIgnoreCase) == 0) 346 | { 347 | return this; 348 | } 349 | 350 | if (ndepth >= maxdepth) 351 | { 352 | return null; 353 | } 354 | 355 | foreach (DataNode child in _children) 356 | { 357 | DataNode n = child.FindNode(name, ndepth + 1, maxdepth); 358 | if (n != null) 359 | { 360 | return n; 361 | } 362 | } 363 | 364 | return null; 365 | } 366 | 367 | public DataNode FindNode(string name, int maxdepth = 0) 368 | { 369 | return FindNode(name, 0, maxdepth > 0 ? maxdepth : int.MaxValue); 370 | } 371 | 372 | 373 | [Obsolete("GetNode is deprecated, please use GetNodeByName instead.")] 374 | public DataNode GetNode(string name, int index = 0) 375 | { 376 | return GetNodeByName(name, index); 377 | } 378 | 379 | public DataNode GetNodeByName(string name, int index = 0) 380 | { 381 | int n = 0; 382 | 383 | foreach (DataNode child in _children) 384 | { 385 | if (String.Compare(child.Name, name, StringComparison.OrdinalIgnoreCase) == 0) 386 | { 387 | if (n >= index) 388 | { 389 | return child; 390 | } 391 | else 392 | { 393 | n++; 394 | } 395 | 396 | } 397 | } 398 | 399 | return null; 400 | } 401 | 402 | public DataNode GetNodeByIndex(int index) 403 | { 404 | if (index < 0 || index >= _children.Count) 405 | { 406 | return null; 407 | } 408 | 409 | return _children[index]; 410 | } 411 | 412 | #region INT64 413 | public long AsInt64(long defaultValue = 0) 414 | { 415 | long result = defaultValue; 416 | if (long.TryParse(this.Value, out result)) 417 | return result; 418 | 419 | return defaultValue; 420 | } 421 | 422 | public long GetInt64(string name, long defaultValue = 0) 423 | { 424 | DataNode node = this.GetNodeByName(name); 425 | if (node != null) 426 | { 427 | return node.AsInt64(defaultValue); 428 | } 429 | 430 | return defaultValue; 431 | } 432 | 433 | public long GetInt64(int index, long defaultValue = 0) 434 | { 435 | DataNode node = this.GetNodeByIndex(index); 436 | if (node != null) 437 | { 438 | return node.AsInt64(defaultValue); 439 | } 440 | 441 | return defaultValue; 442 | } 443 | #endregion 444 | 445 | #region UINT64 446 | public ulong AsUInt64(ulong defaultValue = 0) 447 | { 448 | ulong result = defaultValue; 449 | if (ulong.TryParse(this.Value, out result)) 450 | return result; 451 | 452 | return defaultValue; 453 | } 454 | 455 | public ulong GetUInt64(string name, ulong defaultValue = 0) 456 | { 457 | DataNode node = this.GetNodeByName(name); 458 | if (node != null) 459 | { 460 | return node.AsUInt64(defaultValue); 461 | } 462 | 463 | return defaultValue; 464 | } 465 | 466 | public ulong GetUInt64(int index, ulong defaultValue = 0) 467 | { 468 | DataNode node = this.GetNodeByIndex(index); 469 | if (node != null) 470 | { 471 | return node.AsUInt64(defaultValue); 472 | } 473 | 474 | return defaultValue; 475 | } 476 | #endregion 477 | 478 | #region INT16 479 | public short AsInt16(short defaultValue = 0) 480 | { 481 | short result = defaultValue; 482 | if (short.TryParse(this.Value, out result)) 483 | return result; 484 | 485 | return defaultValue; 486 | } 487 | 488 | public short GetInt16(string name, short defaultValue = 0) 489 | { 490 | DataNode node = this.GetNodeByName(name); 491 | if (node != null) 492 | { 493 | return node.AsInt16(defaultValue); 494 | } 495 | 496 | return defaultValue; 497 | } 498 | 499 | public short GetInt16(int index, short defaultValue = 0) 500 | { 501 | DataNode node = this.GetNodeByIndex(index); 502 | if (node != null) 503 | { 504 | return node.AsInt16(defaultValue); 505 | } 506 | 507 | return defaultValue; 508 | } 509 | #endregion 510 | 511 | #region UINT16 512 | public ushort AsUInt16(ushort defaultValue = 0) 513 | { 514 | ushort result = defaultValue; 515 | if (ushort.TryParse(this.Value, out result)) 516 | return result; 517 | 518 | return defaultValue; 519 | } 520 | 521 | public ushort GetUInt16(string name, ushort defaultValue = 0) 522 | { 523 | DataNode node = this.GetNodeByName(name); 524 | if (node != null) 525 | { 526 | return node.AsUInt16(defaultValue); 527 | } 528 | 529 | return defaultValue; 530 | } 531 | 532 | public ushort GetUInt16(int index, ushort defaultValue = 0) 533 | { 534 | DataNode node = this.GetNodeByIndex(index); 535 | if (node != null) 536 | { 537 | return node.AsUInt16(defaultValue); 538 | } 539 | 540 | return defaultValue; 541 | } 542 | #endregion 543 | 544 | #region INT32 545 | public int AsInt32(int defaultValue = 0) 546 | { 547 | int result = defaultValue; 548 | if (int.TryParse(this.Value, out result)) 549 | return result; 550 | 551 | return defaultValue; 552 | } 553 | 554 | public int GetInt32(string name, int defaultValue = 0) 555 | { 556 | DataNode node = this.GetNodeByName(name); 557 | if (node != null) 558 | { 559 | return node.AsInt32(defaultValue); 560 | } 561 | 562 | return defaultValue; 563 | } 564 | 565 | public int GetInt32(int index, int defaultValue = 0) 566 | { 567 | DataNode node = this.GetNodeByIndex(index); 568 | if (node != null) 569 | { 570 | return node.AsInt32(defaultValue); 571 | } 572 | 573 | return defaultValue; 574 | } 575 | #endregion 576 | 577 | #region UINT32 578 | public uint AsUInt32(uint defaultValue = 0) 579 | { 580 | uint result = defaultValue; 581 | if (uint.TryParse(this.Value, out result)) 582 | return result; 583 | 584 | return defaultValue; 585 | } 586 | 587 | public uint GetUInt32(string name, uint defaultValue = 0) 588 | { 589 | DataNode node = this.GetNodeByName(name); 590 | if (node != null) 591 | { 592 | return node.AsUInt32(defaultValue); 593 | } 594 | 595 | return defaultValue; 596 | } 597 | #endregion 598 | 599 | #region BYTE 600 | public byte AsByte(byte defaultValue = 0) 601 | { 602 | byte result = defaultValue; 603 | if (byte.TryParse(this.Value, out result)) 604 | return result; 605 | 606 | return defaultValue; 607 | } 608 | 609 | public byte GetByte(string name, byte defaultValue = 0) 610 | { 611 | DataNode node = this.GetNodeByName(name); 612 | if (node != null) 613 | { 614 | return node.AsByte(defaultValue); 615 | } 616 | 617 | return defaultValue; 618 | } 619 | 620 | public byte GetByte(int index, byte defaultValue = 0) 621 | { 622 | DataNode node = this.GetNodeByIndex(index); 623 | if (node != null) 624 | { 625 | return node.AsByte(defaultValue); 626 | } 627 | 628 | return defaultValue; 629 | } 630 | #endregion 631 | 632 | #region SBYTE 633 | public sbyte AsSByte(sbyte defaultValue = 0) 634 | { 635 | sbyte result = defaultValue; 636 | if (sbyte.TryParse(this.Value, out result)) 637 | return result; 638 | 639 | return defaultValue; 640 | } 641 | 642 | public sbyte GetSByte(string name, sbyte defaultValue = 0) 643 | { 644 | DataNode node = this.GetNodeByName(name); 645 | if (node != null) 646 | { 647 | return node.AsSByte(defaultValue); 648 | } 649 | 650 | return defaultValue; 651 | } 652 | 653 | public sbyte GetSByte(int index, sbyte defaultValue = 0) 654 | { 655 | DataNode node = this.GetNodeByIndex(index); 656 | if (node != null) 657 | { 658 | return node.AsSByte(defaultValue); 659 | } 660 | 661 | return defaultValue; 662 | } 663 | #endregion 664 | 665 | #region ENUM 666 | 667 | public T AsEnum(T defaultValue = default(T)) where T : Enum 668 | { 669 | return _AsEnum(defaultValue); 670 | } 671 | 672 | public T _AsEnum(T defaultValue = default(T)) 673 | { 674 | try 675 | { 676 | return (T)Enum.Parse(typeof(T), this.Value, /* ignorecase */ true); 677 | } 678 | catch (Exception) 679 | { 680 | int result = 0; 681 | if (int.TryParse(this.Value, out result)) 682 | { 683 | return (T)(object)result; 684 | } 685 | } 686 | 687 | return defaultValue; 688 | } 689 | 690 | public object _AsEnum(Type type) 691 | { 692 | try 693 | { 694 | return Enum.Parse(type, this.Value, /* ignorecase */ true); 695 | } 696 | catch (Exception) 697 | { 698 | return Enum.Parse(type, "0", /* ignorecase */ true); 699 | } 700 | } 701 | 702 | public T GetEnum(string name, T defaultValue = default(T)) where T : Enum 703 | { 704 | DataNode node = this.GetNodeByName(name); 705 | if (node != null) 706 | { 707 | return node.AsEnum(defaultValue); 708 | } 709 | 710 | return defaultValue; 711 | } 712 | 713 | public T GetEnum(int index, T defaultValue = default(T)) where T : Enum 714 | { 715 | DataNode node = this.GetNodeByIndex(index); 716 | if (node != null) 717 | { 718 | return node.AsEnum(defaultValue); 719 | } 720 | 721 | return defaultValue; 722 | } 723 | #endregion 724 | 725 | #region BOOL 726 | public bool AsBool(bool defaultValue = false) 727 | { 728 | if (this.Value.Equals("1") || string.Equals(this.Value, "true", StringComparison.CurrentCultureIgnoreCase)) 729 | { 730 | return true; 731 | } 732 | 733 | if (this.Value.Equals("0") || string.Equals(this.Value, "false", StringComparison.CurrentCultureIgnoreCase)) 734 | { 735 | return false; 736 | } 737 | 738 | return defaultValue; 739 | } 740 | 741 | public bool GetBool(string name, bool defaultValue = false) 742 | { 743 | DataNode node = this.GetNodeByName(name); 744 | if (node != null) 745 | { 746 | return node.AsBool(defaultValue); 747 | } 748 | 749 | return defaultValue; 750 | } 751 | 752 | public bool GetBool(int index, bool defaultValue = false) 753 | { 754 | DataNode node = this.GetNodeByIndex(index); 755 | if (node != null) 756 | { 757 | return node.AsBool(defaultValue); 758 | } 759 | 760 | return defaultValue; 761 | } 762 | #endregion 763 | 764 | #region FLOAT 765 | public float AsFloat(float defaultValue = 0) 766 | { 767 | float result = defaultValue; 768 | if (float.TryParse(this.Value, NumberStyles.Number, CultureInfo.InvariantCulture.NumberFormat, out result)) 769 | { 770 | return result; 771 | } 772 | 773 | return defaultValue; 774 | } 775 | 776 | public float GetFloat(string name, float defaultValue = 0) 777 | { 778 | DataNode node = this.GetNodeByName(name); 779 | if (node != null) 780 | { 781 | return node.AsFloat(defaultValue); 782 | } 783 | 784 | return defaultValue; 785 | } 786 | 787 | public float GetFloat(int index, float defaultValue = 0) 788 | { 789 | DataNode node = this.GetNodeByIndex(index); 790 | if (node != null) 791 | { 792 | return node.AsFloat(defaultValue); 793 | } 794 | 795 | return defaultValue; 796 | } 797 | #endregion 798 | 799 | #region DOUBLE 800 | public double AsDouble(double defaultValue = 0) 801 | { 802 | double result = defaultValue; 803 | if (double.TryParse(this.Value, NumberStyles.Number, CultureInfo.InvariantCulture.NumberFormat, out result)) 804 | { 805 | return result; 806 | } 807 | 808 | return defaultValue; 809 | } 810 | 811 | public double GetDouble(string name, double defaultValue = 0) 812 | { 813 | DataNode node = this.GetNodeByName(name); 814 | if (node != null) 815 | { 816 | return node.AsDouble(defaultValue); 817 | } 818 | 819 | return defaultValue; 820 | } 821 | 822 | public double GetDouble(int index, double defaultValue = 0) 823 | { 824 | DataNode node = this.GetNodeByIndex(index); 825 | if (node != null) 826 | { 827 | return node.AsDouble(defaultValue); 828 | } 829 | 830 | return defaultValue; 831 | } 832 | #endregion 833 | 834 | #region DECIMAL 835 | public Decimal AsDecimal(decimal defaultValue = 0) 836 | { 837 | decimal result = defaultValue; 838 | if (decimal.TryParse(this.Value, NumberStyles.Number | NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture.NumberFormat, out result)) 839 | { 840 | return result; 841 | } 842 | 843 | return defaultValue; 844 | } 845 | 846 | public Decimal GetDecimal(string name, decimal defaultValue = 0) 847 | { 848 | DataNode node = this.GetNodeByName(name); 849 | if (node != null) 850 | { 851 | return node.AsDecimal(defaultValue); 852 | } 853 | 854 | return defaultValue; 855 | } 856 | 857 | public Decimal GetDecimal(int index, decimal defaultValue = 0) 858 | { 859 | DataNode node = this.GetNodeByIndex(index); 860 | if (node != null) 861 | { 862 | return node.AsDecimal(defaultValue); 863 | } 864 | 865 | return defaultValue; 866 | } 867 | #endregion 868 | 869 | #region STRING 870 | public string AsString(string defaultValue = "") 871 | { 872 | if (this.Value != null) 873 | return this.Value; 874 | 875 | return defaultValue; 876 | } 877 | 878 | public string GetString(string name, string defaultValue = "") 879 | { 880 | DataNode node = this.GetNodeByName(name); 881 | if (node != null) 882 | { 883 | return node.Value; 884 | } 885 | 886 | return defaultValue; 887 | } 888 | 889 | public string GetString(int index, string defaultValue = "") 890 | { 891 | DataNode node = this.GetNodeByIndex(index); 892 | if (node != null) 893 | { 894 | return node.Value; 895 | } 896 | 897 | return defaultValue; 898 | } 899 | #endregion 900 | 901 | #region DATETIME 902 | public DateTime AsDateTime(DateTime defaultValue = default(DateTime)) 903 | { 904 | #if DATETIME_AS_TIMESTAMPS 905 | long ticks; 906 | if (long.TryParse(this.Value, out ticks)) 907 | { 908 | return FromTimestamp(ticks); 909 | } 910 | #endif 911 | DateTime result; 912 | if (DateTime.TryParse(this.Value, out result)) 913 | { 914 | return result; 915 | } 916 | 917 | return defaultValue; 918 | } 919 | 920 | public DateTime GetDateTime(string name, DateTime defaultValue = default(DateTime)) 921 | { 922 | DataNode node = this.GetNodeByName(name); 923 | if (node != null) 924 | { 925 | return node.AsDateTime(defaultValue); 926 | } 927 | 928 | return defaultValue; 929 | } 930 | #endregion 931 | 932 | 933 | #region GENERICS 934 | public T AsObject() 935 | { 936 | var type = typeof(T); 937 | return (T)(object)AsObject(type); 938 | } 939 | 940 | public object AsObject(Type type) 941 | { 942 | if (type == typeof(string)) 943 | { 944 | return this.AsString(); 945 | } 946 | 947 | if (type == typeof(bool)) 948 | { 949 | return this.AsBool(); 950 | } 951 | 952 | if (type == typeof(int)) 953 | { 954 | return this.AsInt32(); 955 | } 956 | 957 | if (type == typeof(uint)) 958 | { 959 | return this.AsUInt32(); 960 | } 961 | 962 | if (type == typeof(DateTime)) 963 | { 964 | return this.AsDateTime(); 965 | } 966 | 967 | if (type == typeof(float)) 968 | { 969 | return this.AsFloat(); 970 | } 971 | 972 | if (type == typeof(double)) 973 | { 974 | return this.AsDouble(); 975 | } 976 | 977 | if (type == typeof(decimal)) 978 | { 979 | return this.AsDecimal(); 980 | } 981 | 982 | if (type == typeof(byte)) 983 | { 984 | return this.AsByte(); 985 | } 986 | 987 | if (type == typeof(sbyte)) 988 | { 989 | return this.AsSByte(); 990 | } 991 | 992 | if (type == typeof(Int64)) 993 | { 994 | return this.AsInt64(); 995 | } 996 | 997 | if (type == typeof(UInt64)) 998 | { 999 | return this.AsUInt64(); 1000 | } 1001 | 1002 | if (type == typeof(short)) 1003 | { 1004 | return this.AsInt16(); 1005 | } 1006 | 1007 | if (type == typeof(ushort)) 1008 | { 1009 | return this.AsUInt16(); 1010 | } 1011 | 1012 | if (type.IsEnum) 1013 | { 1014 | return this._AsEnum(type); 1015 | } 1016 | 1017 | return null; 1018 | } 1019 | 1020 | public T GetObject(string name, T defaultValue) 1021 | { 1022 | DataNode node = this.GetNodeByName(name); 1023 | if (node != null) 1024 | { 1025 | return node.AsObject(); 1026 | } 1027 | 1028 | return defaultValue; 1029 | } 1030 | #endregion 1031 | } 1032 | } 1033 | -------------------------------------------------------------------------------- /LunarParser/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Globalization; 6 | using System.Collections; 7 | 8 | namespace LunarLabs.Parser 9 | { 10 | public static class DataNodeExtensions 11 | { 12 | /// 13 | /// Converts a dictionary to a DataSource 14 | /// 15 | public static DataNode FromDictionary(this IDictionary dic, string name) 16 | { 17 | if (dic == null) 18 | { 19 | return null; 20 | } 21 | 22 | var node = DataNode.CreateObject(name); 23 | foreach (var key in dic.Keys) 24 | { 25 | node.AddField(key.ToString().ToLower(), dic[key]); 26 | } 27 | return node; 28 | } 29 | 30 | /// 31 | /// Converts a DataSource to a Dictionary 32 | /// 33 | public static Dictionary ToDictionary(this DataNode node, string name) 34 | { 35 | if (node == null) 36 | { 37 | return null; 38 | } 39 | 40 | var result = new Dictionary(); 41 | 42 | foreach (var child in node.Children) 43 | { 44 | if (!string.IsNullOrEmpty(child.Value)) 45 | { 46 | result[child.Name] = child.AsObject(); 47 | } 48 | } 49 | return result; 50 | } 51 | 52 | public static DataNode ToDataNode(this IEnumerable obj, string name) 53 | { 54 | var result = DataNode.CreateArray(name); 55 | 56 | foreach (var item in obj) 57 | { 58 | var node = item.ToDataNode(); 59 | result.AddNode(node); 60 | } 61 | 62 | return result; 63 | } 64 | 65 | /// 66 | /// Returns an array with all objects of type T in the children of the node with specified name 67 | /// 68 | public static T[] ToArray(this DataNode node) 69 | { 70 | if (node == null) 71 | { 72 | return new T[]{ }; 73 | } 74 | 75 | var name = typeof(T).Name.ToLower(); 76 | 77 | int count = 0; 78 | foreach (var child in node.Children) 79 | { 80 | if (child.Name == null || child.Name.Equals(name)) 81 | { 82 | count++; 83 | } 84 | } 85 | 86 | var result = new T[count]; 87 | int index = 0; 88 | 89 | foreach (var child in node.Children) 90 | { 91 | if (child.Name == null || child.Name.Equals(name)) 92 | { 93 | result[index] = child.ToObject(); 94 | index++; 95 | } 96 | } 97 | 98 | return result; 99 | } 100 | 101 | public static bool IsPrimitive(this Type type) 102 | { 103 | return type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || type == typeof(ushort) 104 | || type == typeof(int) || type == typeof(uint) || type == typeof(long) || type == typeof(ulong) 105 | || type == typeof(float) || type == typeof(double) || type == typeof(decimal) || type == typeof(bool) 106 | || type == typeof(string) || type == typeof(DateTime); 107 | } 108 | 109 | private static DataNode FromArray(object obj, string arrayName = null) 110 | { 111 | var result = DataNode.CreateArray(arrayName); 112 | 113 | var array = (Array)obj; 114 | var type = array.GetType(); 115 | 116 | if (array != null && array.Length > 0) 117 | { 118 | var itemType = type.GetElementType(); 119 | 120 | for (int i = 0; i < array.Length; i++) 121 | { 122 | var item = array.GetValue(i); 123 | 124 | if (itemType.IsPrimitive()) 125 | { 126 | result.AddValue(item); 127 | } 128 | else 129 | { 130 | var itemNode = item.ToDataNode(null, true); 131 | result.AddNode(itemNode); 132 | } 133 | } 134 | } 135 | 136 | return result; 137 | } 138 | 139 | /// 140 | /// Converts an object to a DataSource 141 | /// 142 | public static DataNode ToDataNode(this object obj, string name = null, bool isArrayElement = false) 143 | { 144 | if (obj == null) 145 | { 146 | return null; 147 | } 148 | 149 | Type type = obj.GetType(); 150 | 151 | if (type.IsArray) 152 | { 153 | return FromArray(obj); 154 | } 155 | else 156 | if (IsPrimitive(type)) 157 | { 158 | throw new Exception("Can't convert primitive type to DataNode"); 159 | } 160 | 161 | TypeInfo info = null; 162 | var fields = Enumerable.Empty(); 163 | 164 | Type currentClass = type; 165 | do 166 | { 167 | var currentInfo = currentClass.GetTypeInfo(); 168 | if (currentClass == type) 169 | { 170 | info = currentInfo; 171 | } 172 | 173 | var temp = currentInfo.DeclaredFields.Where(f => f.IsPublic); 174 | 175 | var fieldArray = temp.ToArray(); 176 | 177 | fields = temp.Concat(fields); 178 | 179 | currentClass = currentInfo.BaseType; 180 | if (currentClass == typeof(object)) 181 | { 182 | break; 183 | } 184 | } while (true); 185 | 186 | 187 | if (name == null && !isArrayElement) 188 | { 189 | name = type.Name.ToLower(); 190 | } 191 | 192 | var result = DataNode.CreateObject(name); 193 | 194 | foreach (var field in fields) 195 | { 196 | var val = field.GetValue(obj); 197 | 198 | var fieldName = field.Name.ToLower(); 199 | var fieldTypeInfo = field.FieldType.GetTypeInfo(); 200 | 201 | if (field.FieldType.IsPrimitive() || fieldTypeInfo.IsEnum) 202 | { 203 | result.AddField(fieldName, val); 204 | } 205 | else 206 | if (fieldTypeInfo.IsArray) 207 | { 208 | var arrayNode = FromArray(val, fieldName); 209 | result.AddNode(arrayNode); 210 | } 211 | else 212 | if (val != null) 213 | { 214 | var node = val.ToDataNode(fieldName); 215 | result.AddNode(node); 216 | } 217 | else 218 | { 219 | result.AddField(fieldName, null); 220 | } 221 | } 222 | 223 | return result; 224 | } 225 | 226 | public static T ToObject(this DataNode node) 227 | { 228 | if (node == null) 229 | { 230 | return default(T); 231 | } 232 | 233 | return (T)node.ToObject(typeof(T)); 234 | } 235 | 236 | /// 237 | /// Converts a DataSource to an Object 238 | /// 239 | public static object ToObject(this DataNode node, Type objectType) 240 | { 241 | if (node == null) 242 | { 243 | return null; 244 | } 245 | 246 | var info = objectType.GetTypeInfo(); 247 | var fields = info.DeclaredFields.Where(f => f.IsPublic); 248 | 249 | var result = Activator.CreateInstance(objectType); 250 | // box result otherwise structs values wont update 251 | object obj = result; 252 | 253 | foreach (var field in fields) 254 | { 255 | if (!node.HasNode(field.Name)) 256 | { 257 | continue; 258 | } 259 | 260 | var fieldType = field.FieldType; 261 | 262 | if (fieldType.IsPrimitive()) 263 | { 264 | var str = node.GetString(field.Name); 265 | 266 | #region TYPES LIST 267 | if (fieldType == typeof(string)) 268 | { 269 | field.SetValue(obj, str); 270 | } 271 | else 272 | if (fieldType == typeof(byte)) 273 | { 274 | byte val; 275 | byte.TryParse(str, out val); 276 | field.SetValue(obj, val); 277 | } 278 | else 279 | if (fieldType == typeof(sbyte)) 280 | { 281 | sbyte val; 282 | sbyte.TryParse(str, out val); 283 | field.SetValue(obj, val); 284 | } 285 | else 286 | if (fieldType == typeof(short)) 287 | { 288 | short val; 289 | short.TryParse(str, out val); 290 | field.SetValue(obj, val); 291 | } 292 | else 293 | if (fieldType == typeof(ushort)) 294 | { 295 | ushort val; 296 | ushort.TryParse(str, out val); 297 | field.SetValue(obj, val); 298 | } 299 | else 300 | if (fieldType == typeof(int)) 301 | { 302 | int val; 303 | int.TryParse(str, out val); 304 | field.SetValue(obj, val); 305 | } 306 | else 307 | if (fieldType == typeof(uint)) 308 | { 309 | uint val; 310 | uint.TryParse(str, out val); 311 | field.SetValue(obj, val); 312 | } 313 | else 314 | if (fieldType == typeof(long)) 315 | { 316 | long val; 317 | long.TryParse(str, out val); 318 | field.SetValue(obj, val); 319 | } 320 | else 321 | if (fieldType == typeof(ulong)) 322 | { 323 | ulong val; 324 | ulong.TryParse(str, out val); 325 | field.SetValue(obj, val); 326 | } 327 | else 328 | if (fieldType == typeof(float)) 329 | { 330 | float val; 331 | float.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture.NumberFormat, out val); 332 | field.SetValue(obj, val); 333 | } 334 | else 335 | if (fieldType == typeof(double)) 336 | { 337 | double val; 338 | double.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture.NumberFormat, out val); 339 | field.SetValue(obj, val); 340 | } 341 | else 342 | if (fieldType == typeof(decimal)) 343 | { 344 | decimal val; 345 | decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture.NumberFormat, out val); 346 | field.SetValue(obj, val); 347 | } 348 | else 349 | if (fieldType == typeof(bool)) 350 | { 351 | bool val; 352 | bool.TryParse(str, out val); 353 | field.SetValue(obj, val); 354 | } 355 | else 356 | { 357 | throw new Exception("Cannot unserialize field of type " + objectType.Name); 358 | } 359 | #endregion 360 | } 361 | else 362 | { 363 | var valNode = node.GetNodeByName(field.Name); 364 | object val = valNode.ToObject(fieldType); 365 | field.SetValue(obj, val); 366 | } 367 | } 368 | 369 | return Convert.ChangeType(obj, objectType); 370 | } 371 | 372 | public static DataNode FromHashSet(this HashSet set, string name) 373 | { 374 | var result = DataNode.CreateArray(name); 375 | 376 | foreach (var item in set) 377 | { 378 | result.AddValue(item); 379 | } 380 | 381 | return result; 382 | } 383 | 384 | public static HashSet ToHashSet(this DataNode node, string name = null) 385 | { 386 | var set = new HashSet(); 387 | 388 | foreach (var entry in node.Children) 389 | { 390 | bool valid; 391 | 392 | if (string.IsNullOrEmpty(name)) 393 | { 394 | valid = true; 395 | } 396 | else 397 | { 398 | valid = entry.Name.Equals(name, StringComparison.OrdinalIgnoreCase); 399 | } 400 | 401 | if (valid) 402 | { 403 | var item = entry.AsObject(); 404 | set.Add(item); 405 | } 406 | } 407 | 408 | return set; 409 | } 410 | 411 | } 412 | 413 | } 414 | -------------------------------------------------------------------------------- /LunarParser/Formats.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | using LunarLabs.Parser.XML; 4 | using LunarLabs.Parser.JSON; 5 | using LunarLabs.Parser.YAML; 6 | using LunarLabs.Parser.Binary; 7 | using LunarLabs.Parser.CSV; 8 | using System; 9 | 10 | namespace LunarLabs.Parser 11 | { 12 | public enum DataFormat 13 | { 14 | Unknown, 15 | BIN, 16 | XML, 17 | JSON, 18 | YAML, 19 | CSV, 20 | } 21 | 22 | public static class DataFormats 23 | { 24 | public static DataFormat GetFormatForExtension(string extension) 25 | { 26 | switch (extension) 27 | { 28 | case ".xml": return DataFormat.XML; 29 | case ".json": return DataFormat.JSON; 30 | case ".yaml": return DataFormat.YAML; 31 | case ".csv": return DataFormat.CSV; 32 | case ".bin": return DataFormat.BIN; 33 | 34 | default: 35 | { 36 | return DataFormat.Unknown; 37 | } 38 | } 39 | } 40 | 41 | public static DataFormat DetectFormat(string content) 42 | { 43 | int i = 0; 44 | while (i 104 | /// Loads a node tree from a file, type is based on filename extension 105 | /// 106 | public static DataNode LoadFromFile(string fileName) 107 | { 108 | if (!File.Exists(fileName)) 109 | { 110 | throw new FileNotFoundException(); 111 | } 112 | 113 | var extension = Path.GetExtension(fileName).ToLower(); 114 | 115 | if (extension.Equals(".bin")) 116 | { 117 | var bytes = File.ReadAllBytes(fileName); 118 | return BINReader.ReadFromBytes(bytes); 119 | } 120 | 121 | var contents = File.ReadAllText(fileName); 122 | 123 | var format = GetFormatForExtension(extension); 124 | 125 | if (format == DataFormat.Unknown) 126 | { 127 | format = DetectFormat(contents); 128 | 129 | if (format == DataFormat.Unknown) 130 | { 131 | throw new Exception("Could not detect format for " + fileName); 132 | } 133 | } 134 | 135 | return LoadFromString(format, contents); 136 | } 137 | 138 | public static void SaveToFile(string fileName, DataNode root) 139 | { 140 | var extension = Path.GetExtension(fileName).ToLower(); 141 | var format = GetFormatForExtension(extension); 142 | 143 | var content = SaveToString(format, root); 144 | File.WriteAllText(fileName, content); 145 | } 146 | 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /LunarParser/JSON/JSONReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace LunarLabs.Parser.JSON 6 | { 7 | public class JSONReader 8 | { 9 | private enum State 10 | { 11 | Type, 12 | Name, 13 | Colon, 14 | Value, 15 | Next 16 | } 17 | 18 | private enum InputMode 19 | { 20 | None, 21 | Text, 22 | Number 23 | } 24 | 25 | public static DataNode ReadFromString(string contents) 26 | { 27 | int index = 0; 28 | var root = ReadNode(contents, ref index, null); 29 | return root; 30 | } 31 | 32 | private static void ReadString(string target, string contents, ref int index) 33 | { 34 | index--; 35 | for (int i = 0; i < target.Length; i++) 36 | { 37 | if (index >= contents.Length) 38 | { 39 | throw new Exception($"JSON parsing exception, unexpected end of data"); 40 | } 41 | 42 | var c = contents[index]; 43 | 44 | if (c != target[i]) 45 | { 46 | throw new Exception($"JSON parsing exception, unexpected character"); 47 | } 48 | 49 | index++; 50 | } 51 | } 52 | 53 | private static DataNode ReadNode(string contents, ref int index, string name) 54 | { 55 | DataNode result = null; 56 | 57 | var state = State.Type; 58 | char c; 59 | var mode = InputMode.None; 60 | 61 | StringBuilder name_content = new StringBuilder(); 62 | StringBuilder value_content = new StringBuilder(); 63 | 64 | int rewind_index = index; 65 | 66 | bool is_escaped = false; 67 | 68 | do 69 | { 70 | bool isWhiteSpace; 71 | bool next = false; 72 | do 73 | { 74 | if (index >= contents.Length) 75 | { 76 | if (state == State.Next) 77 | { 78 | return result; 79 | } 80 | 81 | throw new Exception($"JSON parsing exception, unexpected end of data"); 82 | } 83 | 84 | c = contents[index]; 85 | isWhiteSpace = Char.IsWhiteSpace(c); 86 | 87 | if (!isWhiteSpace) 88 | { 89 | rewind_index = index; 90 | } 91 | 92 | index++; 93 | 94 | 95 | next = (mode == InputMode.None) ? isWhiteSpace : false; 96 | } while (next); 97 | 98 | switch (state) 99 | { 100 | case State.Type: 101 | { 102 | switch (c) 103 | { 104 | case '{': 105 | { 106 | result = DataNode.CreateObject(name); 107 | state = State.Name; 108 | break; 109 | } 110 | 111 | case '[': 112 | { 113 | result = DataNode.CreateArray(name); 114 | state = State.Value; 115 | break; 116 | } 117 | 118 | 119 | default: 120 | { 121 | throw new Exception($"JSON parsing exception at {ParserUtils.GetOffsetError(contents, index)}, unexpected character"); 122 | } 123 | } 124 | break; 125 | } 126 | 127 | case State.Name: 128 | { 129 | if (c == '}' && result.Kind == NodeKind.Object) 130 | { 131 | return result; 132 | } 133 | 134 | switch (c) 135 | { 136 | case '"': 137 | { 138 | if (mode == InputMode.None) 139 | { 140 | mode = InputMode.Text; 141 | name_content.Length = 0; 142 | } 143 | else 144 | { 145 | mode = InputMode.None; 146 | state = State.Colon; 147 | } 148 | break; 149 | } 150 | 151 | default: 152 | { 153 | if (mode == InputMode.Text) 154 | { 155 | name_content.Append(c); 156 | } 157 | else 158 | { 159 | throw new Exception($"JSON parsing exception at {ParserUtils.GetOffsetError(contents, index)}, unexpected character"); 160 | } 161 | break; 162 | } 163 | } 164 | break; 165 | } 166 | 167 | case State.Colon: 168 | { 169 | switch (c) 170 | { 171 | case ':': 172 | { 173 | state = State.Value; 174 | break; 175 | } 176 | 177 | default: 178 | { 179 | throw new Exception($"JSON parsing exception at {ParserUtils.GetOffsetError(contents, index)}, expected collon"); 180 | } 181 | } 182 | break; 183 | } 184 | 185 | case State.Value: 186 | { 187 | if (c == '\\' && !is_escaped) 188 | { 189 | is_escaped = true; 190 | } 191 | else 192 | if (is_escaped) 193 | { 194 | is_escaped = false; 195 | 196 | if (c == 'n') // Newline 197 | { 198 | value_content.Append('\n'); 199 | } 200 | else if (c == 'r') // Carriage return 201 | { 202 | value_content.Append('\r'); 203 | } 204 | else if (c == 't') // Tab 205 | { 206 | value_content.Append('\t'); 207 | } 208 | else if (c == 'b') // Backspace 209 | { 210 | value_content.Append('\b'); 211 | } 212 | else if (c == 'f') // Form feed 213 | { 214 | value_content.Append('\f'); 215 | } 216 | else 217 | { 218 | if (c == 'u') 219 | { 220 | var hex = ""; 221 | for (int i = 0; i < 4; i++) 222 | { 223 | if (index >= contents.Length) 224 | { 225 | throw new Exception($"JSON parsing exception, unexpected end of data"); 226 | } 227 | hex += contents[index]; index++; 228 | } 229 | 230 | ushort unicode_val; 231 | unicode_val = ushort.Parse(hex, System.Globalization.NumberStyles.HexNumber); 232 | 233 | c = (char)unicode_val; 234 | } 235 | 236 | value_content.Append(c); 237 | } 238 | } 239 | else 240 | if (c == 'n' && mode == InputMode.None) 241 | { 242 | ReadString("null", contents, ref index); 243 | result.AddField(name_content.Length == 0 ? null : name_content.ToString(), null); 244 | state = State.Next; 245 | } 246 | else 247 | if (c == 'f' && mode == InputMode.None) 248 | { 249 | ReadString("false", contents, ref index); 250 | result.AddField(name_content.Length == 0 ? null : name_content.ToString(), false); 251 | state = State.Next; 252 | } 253 | else 254 | if (c == 't' && mode == InputMode.None) 255 | { 256 | ReadString("true", contents, ref index); 257 | result.AddField(name_content.Length == 0 ? null : name_content.ToString(), true); 258 | state = State.Next; 259 | } 260 | else 261 | if (c == ']' && mode == InputMode.None && result.Kind == NodeKind.Array) 262 | { 263 | return result; 264 | } 265 | else 266 | switch (c) 267 | { 268 | case '"': 269 | { 270 | if (mode == InputMode.None) 271 | { 272 | mode = InputMode.Text; 273 | value_content.Length = 0; 274 | } 275 | else 276 | { 277 | object value; 278 | 279 | var str = value_content.ToString(); 280 | 281 | if (mode == InputMode.Number) 282 | { 283 | if (str.Contains("e")) 284 | { 285 | // TODO 286 | } 287 | value = str; 288 | } 289 | else 290 | { 291 | value = str; 292 | } 293 | mode = InputMode.None; 294 | 295 | result.AddField(name_content.Length == 0 ? null : name_content.ToString(), value); 296 | state = State.Next; 297 | } 298 | break; 299 | } 300 | 301 | case '[': 302 | case '{': 303 | { 304 | if (mode == InputMode.Text) 305 | { 306 | value_content.Append(c); 307 | } 308 | else 309 | { 310 | index = rewind_index; 311 | var node = ReadNode(contents, ref index, name_content.Length == 0 ? null : name_content.ToString()); 312 | result.AddNode(node); 313 | 314 | state = State.Next; 315 | } 316 | 317 | break; 318 | } 319 | 320 | default: 321 | { 322 | if (mode == InputMode.Text) 323 | { 324 | value_content.Append(c); 325 | } 326 | else 327 | if (char.IsNumber(c) || (c == '.' || c == 'e'|| c == 'E' || c == '-' || c == '+')) 328 | { 329 | if (mode != InputMode.Number) 330 | { 331 | value_content.Length = 0; 332 | mode = InputMode.Number; 333 | } 334 | 335 | if (c == 'E') c = 'e'; 336 | 337 | value_content.Append(c); 338 | } 339 | else 340 | { 341 | if (mode == InputMode.Number) 342 | { 343 | mode = InputMode.None; 344 | 345 | var numStr = value_content.ToString(); 346 | if (numStr.Contains("e")) 347 | { 348 | var num = double.Parse(numStr, NumberStyles.Any, CultureInfo.InvariantCulture); 349 | result.AddField(name_content.Length == 0 ? null : name_content.ToString(), num); 350 | } 351 | else 352 | { 353 | var num = decimal.Parse(numStr, NumberStyles.Any, CultureInfo.InvariantCulture); 354 | result.AddField(name_content.Length == 0 ? null : name_content.ToString(), num); 355 | } 356 | state = State.Next; 357 | 358 | if (c == ',' || c == ']' || c == '}') 359 | { 360 | index = rewind_index; 361 | } 362 | } 363 | else 364 | { 365 | throw new Exception($"JSON parsing exception at {ParserUtils.GetOffsetError(contents, index)}, unexpected character"); 366 | } 367 | 368 | } 369 | break; 370 | } 371 | } 372 | break; 373 | } 374 | 375 | case State.Next: 376 | { 377 | switch (c) 378 | { 379 | case ',': 380 | { 381 | state = result.Kind == NodeKind.Array ? State.Value : State.Name; 382 | break; 383 | } 384 | 385 | case '}': 386 | { 387 | if (result.Kind != NodeKind.Object) 388 | { 389 | throw new Exception($"JSON parsing exception at {ParserUtils.GetOffsetError(contents, index)}, unexpected }}"); 390 | } 391 | 392 | return result; 393 | } 394 | 395 | case ']': 396 | { 397 | if (result.Kind != NodeKind.Array) 398 | { 399 | throw new Exception($"JSON parsing exception at {ParserUtils.GetOffsetError(contents, index)}, unexpected ]"); 400 | } 401 | 402 | return result; 403 | } 404 | 405 | default: 406 | { 407 | throw new Exception($"JSON parsing exception at {ParserUtils.GetOffsetError(contents, index)}, expected collon"); 408 | } 409 | } 410 | break; 411 | } 412 | 413 | } 414 | 415 | } while (true); 416 | } 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /LunarParser/JSON/JSONWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LunarLabs.Parser.JSON 6 | { 7 | public static class JSONWriter 8 | { 9 | private static void Append(DataNode node, DataNode parent, StringBuilder sb, bool genBounds = true) 10 | { 11 | if (node.Name != null && (parent == null || parent.Kind != NodeKind.Array)) 12 | { 13 | sb.Append('"'); 14 | sb.Append(node.Name); 15 | sb.Append("\" : "); 16 | } 17 | 18 | if (node.Value != null) 19 | { 20 | var val = node.Value; 21 | bool shouldEscape = (node.Kind == NodeKind.String); 22 | 23 | if (shouldEscape) 24 | { 25 | val = EscapeJSON(val); 26 | sb.Append("\""); 27 | sb.Append(val); 28 | sb.Append('"'); 29 | } 30 | else 31 | { 32 | sb.Append(val); 33 | } 34 | } 35 | else 36 | { 37 | if (node.Name != null || genBounds) 38 | { 39 | sb.Append(node.Kind == NodeKind.Array ? '[' : '{'); 40 | } 41 | 42 | if (node.Children != null) 43 | { 44 | int index = 0; 45 | foreach (var entry in node.Children) 46 | { 47 | if (index > 0) 48 | { 49 | sb.Append(','); 50 | } 51 | 52 | Append(entry, node, sb, node.Kind == NodeKind.Array); 53 | 54 | index++; 55 | } 56 | } 57 | 58 | if (node.Name != null || genBounds) 59 | { 60 | sb.Append(node.Kind == NodeKind.Array ? ']' : '}'); 61 | } 62 | } 63 | } 64 | 65 | public static string WriteToString(DataNode node) 66 | { 67 | var sb = new StringBuilder(); 68 | if (node.Name != null) sb.Append('{'); 69 | Append(node, null, sb); 70 | if (node.Name != null) sb.Append('}'); 71 | return sb.ToString(); 72 | } 73 | 74 | public static string EscapeJSON(string s) 75 | { 76 | if (s == null || s.Length == 0) 77 | { 78 | return ""; 79 | } 80 | 81 | char c = '\0'; 82 | int i; 83 | int len = s.Length; 84 | StringBuilder sb = new StringBuilder(len + 4); 85 | String t; 86 | 87 | for (i = 0; i < len; i += 1) 88 | { 89 | c = s[i]; 90 | switch (c) 91 | { 92 | case '\\': 93 | case '"': 94 | sb.Append('\\'); 95 | sb.Append(c); 96 | break; 97 | case '/': 98 | sb.Append('\\'); 99 | sb.Append(c); 100 | break; 101 | case '\b': 102 | sb.Append("\\b"); 103 | break; 104 | case '\t': 105 | sb.Append("\\t"); 106 | break; 107 | case '\n': 108 | sb.Append("\\n"); 109 | break; 110 | case '\f': 111 | sb.Append("\\f"); 112 | break; 113 | case '\r': 114 | sb.Append("\\r"); 115 | break; 116 | default: 117 | if (c < ' ') 118 | { 119 | t = "000" + String.Format("X", c); 120 | sb.Append("\\u" + t.Substring(t.Length - 4)); 121 | } 122 | else 123 | { 124 | sb.Append(c); 125 | } 126 | break; 127 | } 128 | } 129 | return sb.ToString(); 130 | } 131 | 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /LunarParser/LunarParser.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1;net461;net471;net48;net6.0 5 | true 6 | Sergio Flores 7 | Lunar Labs 8 | LunarLabs.Parser 9 | Read data from strings in XML/JSON/YAML/CSV formats into a tree structure. 10 | Create data nodes manually and serialize it back to those formats. 11 | https://github.com/relfos/lunar_parser 12 | https://github.com/relfos/lunar_parser 13 | 1.5.7 14 | 1.4.1.0 15 | 16 | 1.4.1.0 17 | xml yaml json parser csv 18 | LunarLabs.Parser 19 | Library 20 | README.md 21 | LICENSE 22 | 23 | 24 | 25 | TRACE;DEBUG;NETSTANDARD1_4;DATETIME_AS_TIMESTAMPS 26 | 27 | 28 | 29 | 30 | True 31 | \ 32 | 33 | 34 | True 35 | \ 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /LunarParser/ParserUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LunarLabs.Parser 6 | { 7 | internal static class ParserUtils 8 | { 9 | public static void GetColumnAndLine(string text, int offset, out int col, out int line) 10 | { 11 | line = 1; 12 | col = 0; 13 | 14 | for (int i=0; i<=offset; i++) 15 | { 16 | if (i>=text.Length ) 17 | { 18 | return; 19 | } 20 | 21 | var c = text[i]; 22 | if (c == '\n') 23 | { 24 | col = 0; 25 | line++; 26 | } 27 | 28 | col++; 29 | } 30 | } 31 | 32 | public static string GetOffsetError(string text, int offset) 33 | { 34 | int col, line; 35 | 36 | GetColumnAndLine(text, offset, out col, out line); 37 | 38 | return $"at line {line}, column {col}"; 39 | } 40 | 41 | public static bool IsNumeric(this string text) { 42 | double val; 43 | return double.TryParse(text, out val); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LunarParser/XML/XMLReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace LunarLabs.Parser.XML 5 | { 6 | public class XMLReader 7 | { 8 | private enum State 9 | { 10 | Next, 11 | TagOpen, 12 | TagClose, 13 | Prolog, 14 | Comment, 15 | AttributeName, 16 | AttributeQuote, 17 | AttributeValue, 18 | NextAttribute, 19 | Content, 20 | CData, 21 | CDataClose, 22 | Escape, 23 | } 24 | 25 | private static bool CDataAt(string contents, int index) 26 | { 27 | var tag = "![CDATA["; 28 | for (int i=0; i= contents.Length) return false; 32 | 33 | var c = contents[ofs]; 34 | 35 | if (c != tag[i]) return false; 36 | } 37 | 38 | return true; 39 | } 40 | 41 | public static DataNode ReadFromString(string contents) 42 | { 43 | int index = 0; 44 | var first = ReadNode(contents, ref index); 45 | var root = DataNode.CreateObject(null); 46 | root.AddNode(first); 47 | return root; 48 | } 49 | 50 | private static DataNode ReadNode(string contents, ref int index) 51 | { 52 | DataNode result = null; 53 | 54 | var state = State.Next; 55 | var prevState = State.Next; 56 | char c; 57 | 58 | var name_content = new StringBuilder(); 59 | var value_content = new StringBuilder(); 60 | var escape_content = new StringBuilder(); 61 | 62 | int rewind_index = index; 63 | 64 | do 65 | { 66 | bool isWhiteSpace; 67 | bool next = false; 68 | bool inside = state == State.Content || state == State.TagOpen || state == State.TagClose || 69 | state == State.AttributeName || state == State.AttributeValue || state == State.CData; 70 | 71 | do 72 | { 73 | if (index >= contents.Length) 74 | { 75 | if (state == State.Next) // no useful data 76 | return null; 77 | throw new Exception($"XML parsing exception, unexpected end of data"); 78 | } 79 | 80 | c = contents[index]; 81 | isWhiteSpace = Char.IsWhiteSpace(c); 82 | 83 | if (!isWhiteSpace) 84 | { 85 | rewind_index = index; 86 | } 87 | 88 | index++; 89 | next = isWhiteSpace && !inside; 90 | } while (next); 91 | 92 | switch (state) 93 | { 94 | case State.Next: 95 | { 96 | switch (c) 97 | { 98 | case '<': 99 | { 100 | state = State.TagOpen; 101 | name_content.Length = 0; 102 | break; 103 | } 104 | 105 | default: 106 | { 107 | throw new Exception($"XML parsingexception at {ParserUtils.GetOffsetError(contents, index)}, unexpected character"); 108 | } 109 | } 110 | break; 111 | } 112 | 113 | case State.TagOpen: 114 | { 115 | switch (c) 116 | { 117 | case '?': 118 | { 119 | if (contents[index - 2] == '<') 120 | { 121 | state = State.Prolog; 122 | } 123 | else 124 | { 125 | name_content.Append(c); 126 | } 127 | break; 128 | } 129 | 130 | case '!': 131 | { 132 | if (index< contents.Length-3 && contents[index - 2] == '<' && contents[index] == '-' && contents[index+1] == '-') 133 | { 134 | state = State.Comment; 135 | prevState = State.Next; 136 | } 137 | else 138 | { 139 | name_content.Append(c); 140 | } 141 | break; 142 | } 143 | 144 | case '/': 145 | { 146 | result = DataNode.CreateObject(name_content.ToString()); 147 | state = State.TagClose; 148 | break; 149 | } 150 | 151 | case '>': 152 | { 153 | result = DataNode.CreateObject(name_content.ToString()); 154 | state = State.Content; 155 | break; 156 | } 157 | 158 | case ' ': 159 | { 160 | result = DataNode.CreateObject(name_content.ToString()); 161 | name_content.Length = 0; 162 | state = State.AttributeName; 163 | break; 164 | } 165 | 166 | default: 167 | { 168 | name_content.Append(c); 169 | break; 170 | } 171 | } 172 | break; 173 | } 174 | 175 | case State.TagClose: 176 | { 177 | switch (c) 178 | { 179 | case '>': 180 | { 181 | // previously created: 182 | // result = DataNode.CreateObject(name_content.ToString()); 183 | return result; 184 | } 185 | default: 186 | { 187 | // TODO: verify that the close tag matches the open tag 188 | // name_content.Append(c); 189 | break; 190 | } 191 | } 192 | break; 193 | } 194 | 195 | case State.AttributeName: 196 | { 197 | switch (c) 198 | { 199 | case '/': 200 | { 201 | state = State.TagClose; 202 | break; 203 | } 204 | 205 | case '=': 206 | { 207 | state = State.AttributeQuote; 208 | break; 209 | } 210 | 211 | default: 212 | { 213 | if (name_content.Length > 0 || !char.IsWhiteSpace(c)) 214 | { 215 | name_content.Append(c); 216 | } 217 | break; 218 | } 219 | } 220 | break; 221 | } 222 | 223 | case State.AttributeQuote: 224 | { 225 | if (c == '"') 226 | { 227 | state = State.AttributeValue; 228 | value_content.Length = 0; 229 | } 230 | else 231 | { 232 | throw new Exception($"XML parsingexception at {ParserUtils.GetOffsetError(contents, index)}, unexpected character"); 233 | } 234 | break; 235 | } 236 | 237 | case State.AttributeValue: 238 | { 239 | switch (c) 240 | { 241 | case '"': 242 | { 243 | result.AddField(name_content.ToString(), value_content.ToString()); 244 | value_content.Length = 0; 245 | state = State.NextAttribute; 246 | break; 247 | } 248 | 249 | case '&': 250 | { 251 | prevState = state; 252 | state = State.Escape; 253 | escape_content.Length = 0; 254 | break; 255 | } 256 | 257 | default: 258 | { 259 | value_content.Append(c); 260 | break; 261 | } 262 | } 263 | 264 | break; 265 | } 266 | 267 | case State.NextAttribute: 268 | { 269 | switch (c) 270 | { 271 | case '/': 272 | { 273 | break; 274 | } 275 | 276 | case '>': 277 | { 278 | if (contents[index-2] == '/') 279 | { 280 | return result; 281 | } 282 | 283 | state = State.Content; 284 | 285 | break; 286 | } 287 | 288 | default: 289 | { 290 | if (char.IsLetter(c)) 291 | { 292 | name_content.Length = 0; 293 | name_content.Append(c); 294 | state = State.AttributeName; 295 | } 296 | else 297 | { 298 | throw new Exception($"XML parsingexception at {ParserUtils.GetOffsetError(contents, index)}, unexpected character"); 299 | } 300 | 301 | break; 302 | } 303 | } 304 | 305 | break; 306 | } 307 | 308 | case State.Prolog: 309 | { 310 | if (c == '>') 311 | { 312 | state = State.Next; 313 | } 314 | break; 315 | } 316 | 317 | case State.Comment: 318 | { 319 | switch (c) 320 | { 321 | case '>': 322 | { 323 | if (contents[index - 2] == '-' && contents[index - 3] == '-') 324 | { 325 | state = prevState; 326 | } 327 | break; 328 | } 329 | } 330 | break; 331 | } 332 | 333 | case State.CData: 334 | { 335 | if (c == ']' && contents[index-2]==c && index') 336 | { 337 | state = State.Content; 338 | value_content.Length--; 339 | index++; 340 | } 341 | else 342 | { 343 | value_content.Append(c); 344 | } 345 | 346 | break; 347 | } 348 | 349 | case State.Escape: 350 | switch (c) 351 | { 352 | case ';': 353 | var escaped = escape_content.ToString(); 354 | 355 | char ch; 356 | 357 | switch (escaped) 358 | { 359 | case "amp": ch = '&'; break; 360 | case "lt": ch = '<'; break; 361 | case "gt": ch = '>'; break; 362 | case "quot": ch = '"'; break; 363 | case "apos": ch = '\''; break; 364 | 365 | default: throw new Exception("Invalid escape code detected: " + escaped); 366 | } 367 | 368 | value_content.Append(ch); 369 | state = prevState; 370 | break; 371 | 372 | default: 373 | escape_content.Append(c); 374 | break; 375 | } 376 | break; 377 | 378 | case State.Content: 379 | { 380 | switch (c) 381 | { 382 | case '&': 383 | prevState = state; 384 | state = State.Escape; 385 | escape_content.Length = 0; 386 | break; 387 | 388 | case '<': 389 | { 390 | if (CDataAt(contents, index)) 391 | { 392 | state = State.CData; 393 | index += 8; 394 | } 395 | else 396 | if (index") 443 | .Replace("&", "&"); 444 | } 445 | 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /LunarParser/XML/XMLWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace LunarLabs.Parser.XML 6 | { 7 | 8 | public class XMLWriter 9 | 10 | { 11 | public static string WriteToString(DataNode node, bool expand = false, bool escape = false, bool allowEmptyNames = false) 12 | { 13 | StringBuilder builder = new StringBuilder(); 14 | 15 | WriteNode(builder, node, 0, expand, escape, allowEmptyNames); 16 | 17 | return builder.ToString(); 18 | } 19 | 20 | private static void WriteNode(StringBuilder buffer, DataNode node, int tabs, bool expand, bool escape, bool allowEmptyNames) 21 | { 22 | if (!allowEmptyNames && string.IsNullOrEmpty(node.Name)) 23 | { 24 | throw new Exception("Node cannot have empty name"); 25 | } 26 | 27 | for (int i = 0; i < tabs; i++) 28 | { 29 | buffer.Append('\t'); 30 | } 31 | 32 | buffer.Append('<'); 33 | buffer.Append(node.Name); 34 | 35 | int processedChildren = 0; 36 | 37 | if (!expand) 38 | { 39 | foreach (DataNode child in node.Children) 40 | { 41 | if (child.Children.Any()) 42 | { 43 | continue; 44 | } 45 | 46 | buffer.Append(' '); 47 | buffer.Append(child.Name); 48 | buffer.Append('='); 49 | buffer.Append('"'); 50 | buffer.Append(EscapeXML(child.Value, escape)); 51 | buffer.Append('"'); 52 | 53 | processedChildren++; 54 | } 55 | } 56 | 57 | var finished = processedChildren == node.ChildCount && node.Value == null; 58 | 59 | if (finished) 60 | { 61 | buffer.Append('/'); 62 | } 63 | buffer.Append('>'); 64 | buffer.AppendLine(); 65 | 66 | if (finished) return; 67 | 68 | if (node.Children.Any()) 69 | { 70 | foreach (DataNode child in node.Children) 71 | { 72 | if (!expand && !child.Children.Any()) 73 | { 74 | continue; 75 | } 76 | 77 | WriteNode(buffer, child, tabs + 1, expand, escape, allowEmptyNames); 78 | } 79 | 80 | if (node.Value != null) 81 | { 82 | if (node.Value.Trim().Length > 0) 83 | { 84 | throw new Exception("Nodes with values cannot have child nodes"); 85 | } 86 | } 87 | } 88 | else 89 | { 90 | buffer.Append(EscapeXML(node.Value, escape)); 91 | } 92 | 93 | for (int i = 0; i < tabs; i++) 94 | { 95 | buffer.Append('\t'); 96 | } 97 | 98 | buffer.Append('<'); 99 | buffer.Append('/'); 100 | buffer.Append(node.Name); 101 | buffer.Append('>'); 102 | 103 | buffer.AppendLine(); 104 | } 105 | 106 | 107 | /* 108 | private static void WriteNode2(StringBuilder buffer, DataNode node, int tabs, bool expand, bool escape) 109 | { 110 | for (int i = 0; i < tabs; i++) 111 | { 112 | buffer.Append('\t'); 113 | } 114 | buffer.Append('<'); 115 | buffer.Append(node.Name); 116 | 117 | int skippedChildren = 0; 118 | int processedChildren = 0; 119 | 120 | foreach (DataNode child in node.Children) 121 | { 122 | if (expand || child.Children.Any()) 123 | { 124 | skippedChildren++; 125 | continue; 126 | } 127 | 128 | buffer.Append(' '); 129 | buffer.Append(child.Name); 130 | buffer.Append('='); 131 | buffer.Append('"'); 132 | buffer.Append(EscapeXML(child.Value, escape)); 133 | buffer.Append('"'); 134 | 135 | processedChildren++; 136 | } 137 | 138 | if (processedChildren > 0) 139 | { 140 | buffer.Append(' '); 141 | } 142 | 143 | var finished = processedChildren == node.ChildCount && node.Value == null; 144 | 145 | if (finished) 146 | { 147 | buffer.Append('/'); 148 | } 149 | buffer.Append('>'); 150 | 151 | if (finished) 152 | { 153 | if (!expand) 154 | { 155 | buffer.AppendLine(); 156 | } 157 | return; 158 | } 159 | 160 | if (processedChildren < node.ChildCount) 161 | { 162 | buffer.AppendLine(); 163 | 164 | foreach (DataNode child in node.Children) 165 | { 166 | if (!expand && !child.Children.Any()) 167 | { 168 | continue; 169 | } 170 | 171 | WriteNode(buffer, child, tabs + 1, expand, escape); 172 | } 173 | 174 | for (int i = 0; i < tabs; i++) 175 | { 176 | buffer.Append('\t'); 177 | } 178 | } 179 | 180 | if (node.Value != null) 181 | { 182 | buffer.Append(EscapeXML(node.Value, escape)); 183 | } 184 | 185 | buffer.Append('<'); 186 | buffer.Append('/'); 187 | buffer.Append(node.Name); 188 | buffer.Append('>'); 189 | buffer.AppendLine(); 190 | }*/ 191 | 192 | private static string EscapeXML(string content, bool escape) 193 | { 194 | if (!escape || string.IsNullOrEmpty(content)) 195 | { 196 | return content; 197 | } 198 | 199 | var sb = new StringBuilder(); 200 | foreach (var ch in content) 201 | { 202 | switch (ch) 203 | { 204 | case '\'': sb.Append("'"); break; 205 | case '"': sb.Append("""); break; 206 | case '<': sb.Append("<"); break; 207 | case '>': sb.Append(">"); break; 208 | case '&': sb.Append("&"); break; 209 | 210 | default: 211 | sb.Append(ch); 212 | break; 213 | } 214 | } 215 | return sb.ToString(); 216 | } 217 | } 218 | 219 | } -------------------------------------------------------------------------------- /LunarParser/YAML/YAMLReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LunarLabs.Parser.YAML 6 | { 7 | public class YAMLReader 8 | { 9 | private enum State 10 | { 11 | Header, 12 | Comment, 13 | Next, 14 | Name, 15 | NewLine, 16 | Idents, 17 | Child, 18 | Content, 19 | } 20 | 21 | public static DataNode ReadFromString(string contents) 22 | { 23 | var lines = contents.Split('\n'); 24 | 25 | int index = 0; 26 | var root = DataNode.CreateArray(null); 27 | ReadNodes(lines, ref index, 0, root); 28 | 29 | return root; 30 | } 31 | 32 | private static void ReadNodes(string[] lines, ref int index, int baseIndents, DataNode parent) 33 | { 34 | int expectedIdents = -1; 35 | 36 | DataNode currentNode = null; 37 | 38 | do 39 | { 40 | if (index >= lines.Length ) 41 | { 42 | return; 43 | } 44 | 45 | int identCount = 0; 46 | var content = lines[index].TrimEnd(); 47 | 48 | if (content.StartsWith("---")) 49 | { 50 | index++; 51 | continue; 52 | } 53 | 54 | for (int i=0; i" || val == "|") 103 | { 104 | bool preserveLineBreaks = val == "|"; 105 | 106 | var sb = new StringBuilder(); 107 | while (index> 10 | 11 | Besides that, each file format requires a different library that works in completly different way. 12 | 13 | Lunar Parser was written with the intent of making parsing of data as easy as possible and in with a way that makes possible to read or write a JSON or XML using exactly the same code for each. 14 | 15 | Get a string and pass it to the proper parser and get data back in a tree structure. 16 | It's also possible to manually create your own data nodes and then serialize them into a string. 17 | And there's also some utility methods to convert back and forth between data nodes and dictionaries, or even serialize an object to a DataNode in a generic way. 18 | 19 | ## Installation 20 | 21 | PM> Install-Package LunarParser 22 | 23 | Since this is a .NET standard package, to use with .NET framework projects please set the target to .NET Framework 4.5 or higher, otherwise Nuget will give you installation errors. 24 | 25 | # Getting Started 26 | 27 | LunarParser supports: 28 | 29 | - .NET Core 30 | - .NET Framework 3.5 and above 31 | - Mono & Xamarin 32 | - UWP 33 | 34 | ## Supported Formats 35 | 36 | - XML 37 | - JSON 38 | - YAML 39 | - CSV 40 | - BIN (custom format) 41 | 42 | # Usage 43 | 44 | Import the package: 45 | 46 | ```c# 47 | using LunarParser; 48 | ``` 49 | 50 | And the formats you want to use, eg: 51 | 52 | ```c# 53 | using LunarParser.XML; 54 | ``` 55 | 56 | Here's some examples. 57 | 58 | ```c# 59 | // reading XML 60 | var root = XMLReader.ReadFromString("Hello world!"); 61 | var msg = root["message"]; 62 | var content = msg.GetString("content"); 63 | Console.WriteLine("Message: " + content); 64 | ``` 65 | 66 | ```c# 67 | // reading JSON 68 | var root = JSONReader.ReadFromString("{\"message\": { \"content\": \"Hello world!\" } }"); 69 | var msg = root["message"]; 70 | var content = msg.GetString("content"); 71 | Console.WriteLine("Message: " + content); 72 | ``` 73 | 74 | ```c# 75 | // writing XML (same could be done for json, using JSONWriter or yaml with YAMLWriter) 76 | var msg = DataSource.CreateObject("message"); 77 | msg.AddField("content", "Hello world!"); 78 | 79 | var xml = XMLWriter.WriteToString(msg); 80 | Console.WriteLine("XML: " + xml); 81 | System.IO.File.WriteAllText("hello.xml", xml); 82 | ``` 83 | 84 | ```c# 85 | // converting a dictionary to a data node 86 | var dic = new Dictionary(); 87 | dic["dog"] = "barf"; 88 | dic["cat"] = "meow"; 89 | dic["fish"] = "blublu"; 90 | var data = dic.ToDataSource(dic); 91 | 92 | // its also easy to iterate on child nodes 93 | foreach (var child in data.Children) { 94 | Console.WriteLine(child.Name + " = " + child.Value); 95 | } 96 | 97 | //same could be done for json, using JSONWriter 98 | var xml = XMLWriter.WriteToString(msg); 99 | Console.WriteLine("XML: " + xml); 100 | 101 | // you can also do the opposite... 102 | dic = data.ToDictionary(); 103 | ``` 104 | 105 | ```c# 106 | // converting a hashset to a data node 107 | var set = new HashSet(); 108 | 109 | set.Add("one"); 110 | set.Add("two"); 111 | set.Add("three"); 112 | 113 | var node = set.FromHashSet("my_hashset_name"); 114 | 115 | // you can also do the opposite... 116 | dic = node.ToHashSet(); 117 | ``` 118 | 119 | 120 | ```c# 121 | // Conversion between formats 122 | var xml = File.ReadAllText("some_file.xml"); 123 | var root = XMLReader.ReadFromString(content); 124 | var json = JSONWriter.WriteToString(root); 125 | 126 | //OR 127 | 128 | // content type is auto-detected based on extension 129 | var root = DataFormats.LoadFromFile("some_file.xml"); 130 | DataFormats.WriteToFile("some_file.json", root); 131 | ``` 132 | 133 | # Notes 134 | 135 | ## DateTime type 136 | 137 | Lunar Parser automatically converts DateTime to UNIX timestamps (and does the opposite when loading). 138 | 139 | So if you serialize it to JSON or XML you will find just a very long number instead of multiple fields. 140 | 141 | This saves space without losing any data down to seconds (miliseconds will be lost, any dates before UNIX epoch will be lost). 142 | 143 | If this behavior is not suitable for your program, please compile Lunar Parser from source, with the DATETIME_AS_TIMESTAMPS conditional symbol removed. 144 | 145 | ## Binary format 146 | 147 | Lunar Parser supports reading and writing data nodes in a custom binary format. 148 | 149 | This format will preserve the tree structure and node values, same as in XML and JSON formats, however it is much faster to parse and write and the file size is smaller. 150 | 151 | Very useful to send data in a tree format over a network connection. 152 | 153 | ## Generic serialization of objects 154 | 155 | In the latest version there is ToObject() / FromDataSource extension methods that allow converting any C# object to a DataSource and back to the same object. 156 | 157 | This works by inspecting public fields with reflection. Properties are currently ignored. 158 | 159 | However note that those methods are still experimental, and currently they won't supported nested objects, but will work fine with simple structs / classes. 160 | 161 | ## XML format 162 | 163 | When saving in XML format, by default XMLWriter will save any node that contains zero childs as an attribute instead of full XML node. 164 | If this is not desirable, pass true as argument to the expand parameter of XMLWriter.WriteToString 165 | 166 | As default, writing XML does not support escaping characters, which can produce invalid XML depending on your input data. 167 | If you need this, please pass true to the parameter "escape" of XMLWriter.WriteToString(). 168 | 169 | ## CSV format 170 | 171 | The CSV format can not handle nested nodes. This is a limitation of the format itself, so be careful if converting from other formats to CSV. 172 | 173 | # Contact 174 | 175 | Let me know if you find bugs or if you have suggestions to improve the code. 176 | 177 | And maybe follow me [@onihunters](https://twitter.com/onihunters) :) -------------------------------------------------------------------------------- /Tests/ParserTests.cs: -------------------------------------------------------------------------------- 1 | using LunarLabs.Parser; 2 | using LunarLabs.Parser.CSV; 3 | using LunarLabs.Parser.JSON; 4 | using LunarLabs.Parser.XML; 5 | using LunarLabs.Parser.YAML; 6 | using NUnit.Framework; 7 | using NUnit.Framework.Legacy; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | 12 | namespace LunarParserTests 13 | { 14 | [TestFixture] 15 | public class ParserTests 16 | { 17 | [Test] 18 | public void TestAddAndRemove() 19 | { 20 | var root = DataNode.CreateArray(null); 21 | root.AddField("hello", "dog"); 22 | ClassicAssert.NotNull(root.ChildCount == 1); 23 | 24 | root.SetField("hello", "cat"); 25 | ClassicAssert.NotNull(root.ChildCount == 1); 26 | 27 | root.RemoveNodeByName("hello"); 28 | ClassicAssert.NotNull(root.ChildCount == 0); 29 | } 30 | 31 | [Test] 32 | public void TestLongPath() 33 | { 34 | var root = DataNode.CreateObject("test"); 35 | 36 | root["test"]["user"]["name"].SetValue("mr.doggo"); 37 | root["test"]["user"]["age"].SetValue(69); 38 | 39 | ClassicAssert.NotNull(root.ChildCount == 1); 40 | 41 | var node = root["test"]["user"]; 42 | ClassicAssert.NotNull(node.ChildCount == 2); 43 | 44 | var name = node.GetString("name"); 45 | ClassicAssert.IsTrue(name == "mr.doggo"); 46 | 47 | var age = node.GetInt32("age"); 48 | ClassicAssert.IsTrue(age == 69); 49 | } 50 | 51 | 52 | #region XML 53 | [Test] 54 | public void TestXMLReader() 55 | { 56 | var xml = "Hello world!"; 57 | 58 | var root = XMLReader.ReadFromString(xml); 59 | ClassicAssert.NotNull(root); 60 | 61 | var msg = root["message"]; 62 | ClassicAssert.NotNull(msg); 63 | 64 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 65 | 66 | var content = msg.GetString("content"); 67 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 68 | 69 | ClassicAssert.IsTrue("Hello world!".Equals(content)); 70 | } 71 | 72 | [Test] 73 | public void TestXMLEscaping() 74 | { 75 | var xml = "Hello&world!"; 76 | 77 | var root = XMLReader.ReadFromString(xml); 78 | ClassicAssert.NotNull(root); 79 | 80 | var msg = root["message"]; 81 | ClassicAssert.NotNull(msg); 82 | 83 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 84 | 85 | var content = msg.Value; 86 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 87 | 88 | var expected = "Hello&world!"; 89 | ClassicAssert.IsTrue(content.Equals(expected)); 90 | 91 | root = DataNode.CreateObject("message"); 92 | ClassicAssert.NotNull(root); 93 | root.Value = expected; 94 | 95 | var xml2 = XMLWriter.WriteToString(root, escape: true).Trim(); 96 | ClassicAssert.IsTrue(xml2.Equals(xml)); 97 | } 98 | 99 | [Test] 100 | public void TestXMLWriter() 101 | { 102 | var root = DataNode.CreateObject("data"); 103 | ClassicAssert.NotNull(root); 104 | 105 | var temp = DataNode.CreateObject("entry"); 106 | temp.AddField("name", "xx"); 107 | root.AddNode(temp); 108 | 109 | var xml = XMLWriter.WriteToString(root); 110 | var expected = "\n\t\n\n"; 111 | expected = expected.Replace("\n", Environment.NewLine); 112 | ClassicAssert.IsTrue(xml == expected); 113 | 114 | xml = XMLWriter.WriteToString(root, true); 115 | expected = "\n\t\n\t\txx\n\t\n\n"; 116 | expected = expected.Replace("\n", Environment.NewLine); 117 | ClassicAssert.IsTrue(xml == expected); 118 | } 119 | 120 | [Test] 121 | public void TestXMLReaderFull() 122 | { 123 | var root = XMLReader.ReadFromString(""); 124 | ClassicAssert.NotNull(root); 125 | 126 | var videos = root["videos"]; 127 | ClassicAssert.NotNull(videos); 128 | 129 | ClassicAssert.IsTrue("videos".Equals(videos.Name)); 130 | ClassicAssert.IsTrue(videos.ChildCount.Equals(2)); 131 | 132 | var content = videos.GetNodeByName("video"); 133 | ClassicAssert.NotNull(content); 134 | ClassicAssert.IsTrue(content.ChildCount.Equals(5)); 135 | } 136 | 137 | [Test] 138 | public void TestXMLComments() 139 | { 140 | var root = XMLReader.ReadFromString("Hello world!"); 141 | ClassicAssert.NotNull(root); 142 | 143 | var msg = root["message"]; 144 | ClassicAssert.NotNull(msg); 145 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 146 | 147 | var content = msg.GetString("content"); 148 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 149 | ClassicAssert.IsTrue("Hello world!".Equals(content)); 150 | } 151 | 152 | [Test] 153 | public void TestXMLEmpty() 154 | { 155 | var root = XMLReader.ReadFromString(""); 156 | ClassicAssert.True(root.ChildCount.Equals(0)); 157 | root = XMLReader.ReadFromString(" "); 158 | ClassicAssert.True(root.ChildCount.Equals(0)); 159 | root = XMLReader.ReadFromString(""); 160 | ClassicAssert.True(root.ChildCount.Equals(0)); 161 | root = XMLReader.ReadFromString(""); 162 | ClassicAssert.True(root.ChildCount.Equals(0)); 163 | root = XMLReader.ReadFromString(""); 164 | ClassicAssert.True(root.ChildCount.Equals(0)); 165 | } 166 | 167 | [Test] 168 | public void TestXMLRoot() 169 | { 170 | var root = XMLReader.ReadFromString(""); 171 | ClassicAssert.NotNull(root); 172 | var msg = root["message"]; 173 | ClassicAssert.NotNull(msg); 174 | ClassicAssert.IsEmpty(msg.Value); 175 | 176 | root = XMLReader.ReadFromString("aaa"); 177 | ClassicAssert.NotNull(root); 178 | msg = root["message"]; 179 | ClassicAssert.NotNull(msg); 180 | ClassicAssert.AreEqual("aaa", msg.Value); 181 | 182 | root = XMLReader.ReadFromString(""); 183 | ClassicAssert.NotNull(root); 184 | msg = root["message"]; 185 | ClassicAssert.NotNull(msg); 186 | ClassicAssert.IsEmpty(msg.Value); 187 | } 188 | 189 | // Valid in XML 190 | [Test] 191 | public void TestXMLCommentsTags() 192 | { 193 | var root = XMLReader.ReadFromString("" + 194 | "" + 195 | "Hello world!" + 196 | " "); 197 | ClassicAssert.NotNull(root); 198 | var msg = root["message"]; 199 | ClassicAssert.NotNull(msg); 200 | 201 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 202 | var content = msg.GetString("content"); 203 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 204 | ClassicAssert.AreEqual("Hello world!", content); 205 | } 206 | 207 | // Not strictly valid in XML, but accepted in HTML and others 208 | [Test] 209 | public void TestXMLCommentsUnbalanced() 210 | { 211 | var root = XMLReader.ReadFromString("" + 212 | " " + 213 | "Hello world!"); 214 | ClassicAssert.NotNull(root); 215 | var msg = root["message"]; 216 | ClassicAssert.NotNull(msg); 217 | ClassicAssert.AreEqual("message", msg.Name); 218 | var content = msg.GetString("content"); 219 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 220 | ClassicAssert.AreEqual("Hello world!", content.Trim()); 221 | } 222 | 223 | [Test] 224 | public void TestXMLProlog() 225 | { 226 | var root = XMLReader.ReadFromString("" + 227 | "Hello world!"); 228 | ClassicAssert.NotNull(root); 229 | ClassicAssert.IsTrue(root.ChildCount.Equals(1)); 230 | 231 | var msg = root["message"]; 232 | ClassicAssert.NotNull(msg); 233 | 234 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 235 | 236 | var content = msg.GetString("content"); 237 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 238 | 239 | ClassicAssert.IsTrue("Hello world!".Equals(content)); 240 | } 241 | 242 | [Test] 243 | public void TestXMLAttributes() 244 | { 245 | var root = XMLReader.ReadFromString(""); 246 | ClassicAssert.NotNull(root); 247 | var msg = root["message"]; 248 | ClassicAssert.NotNull(msg); 249 | 250 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 251 | 252 | var content = msg.GetString("content"); 253 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 254 | 255 | ClassicAssert.IsTrue("Hello world!".Equals(content)); 256 | } 257 | 258 | [Test] 259 | public void TestXMLAttributesIgnored() 260 | { 261 | var root = XMLReader.ReadFromString(" world!\"/>"); 262 | ClassicAssert.NotNull(root); 263 | var msg = root["message"]; 264 | ClassicAssert.NotNull(msg); 265 | 266 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 267 | 268 | var content = msg.GetString("content"); 269 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 270 | 271 | ClassicAssert.IsTrue("Hello /> world!".Equals(content)); 272 | } 273 | 274 | [Test] 275 | public void TestXMLText() 276 | { 277 | var root = XMLReader.ReadFromString("other"); 278 | ClassicAssert.NotNull(root); 279 | var msg = root["message"]; 280 | ClassicAssert.NotNull(msg); 281 | 282 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 283 | 284 | var attr = msg.GetString("attribute"); 285 | ClassicAssert.IsTrue("something".Equals(attr)); 286 | 287 | ClassicAssert.IsTrue("other".Equals(msg.Value)); 288 | } 289 | 290 | [Test] 291 | public void TestXMLShortTag() 292 | { 293 | var root = XMLReader.ReadFromString(""); 294 | ClassicAssert.NotNull(root); 295 | var msg = root["message"]; 296 | ClassicAssert.NotNull(msg); 297 | 298 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 299 | 300 | var attr = msg.GetString("attribute"); 301 | ClassicAssert.IsTrue("something".Equals(attr)); 302 | 303 | ClassicAssert.IsTrue(msg.ChildCount == 2); 304 | 305 | var child = msg.GetNodeByIndex(1); 306 | ClassicAssert.IsNotNull(child); 307 | ClassicAssert.IsTrue("go".Equals(child.Name)); 308 | ClassicAssert.IsTrue(string.IsNullOrEmpty(child.Value)); 309 | } 310 | 311 | [Test] 312 | public void TestXMLCData() 313 | { 314 | string test = String.Format(@" me]]>"); 315 | var root = XMLReader.ReadFromString(test); 316 | var msg = root["message"]; 317 | var content = msg.GetString("content"); 318 | ClassicAssert.IsTrue(content.Equals("test<>me")); 319 | 320 | test = String.Format(@" me]<[]]>"); 321 | root = XMLReader.ReadFromString(test); 322 | msg = root["message"]; 323 | content = msg.GetString("content"); 324 | ClassicAssert.IsTrue(content.Equals("test<>me]<[")); 325 | 326 | test = String.Format(@"![CDATA[testme]]"); 327 | root = XMLReader.ReadFromString(test); 328 | msg = root["message"]; 329 | content = msg.GetString("content"); 330 | ClassicAssert.IsTrue(content.Equals("![CDATA[testme]]")); 331 | 332 | test = String.Format("me\nline2.hello\nthirdline]]>"); 333 | 334 | root = XMLReader.ReadFromString(test); 335 | msg = root["message"]; 336 | content = msg.GetString("content"); 337 | ClassicAssert.IsTrue(content.Equals("line1.test<>me\nline2.hello\nthirdline")); 338 | } 339 | #endregion 340 | 341 | #region JSON 342 | [Test] 343 | public void TestJSONReader() 344 | { 345 | var hello = "The {{Strange}} [[Message]]!"; 346 | var json = "{\"message\": { \"content\": \""+hello+"\"} }"; 347 | 348 | var root = JSONReader.ReadFromString(json); 349 | ClassicAssert.NotNull(root); 350 | 351 | var msg = root["message"]; 352 | ClassicAssert.NotNull(msg); 353 | 354 | // alternate way 355 | msg = root.GetNodeByName("message"); 356 | ClassicAssert.NotNull(msg); 357 | 358 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 359 | 360 | var content = msg.GetString("content"); 361 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 362 | 363 | ClassicAssert.IsTrue(hello.Equals(content)); 364 | } 365 | 366 | [Test] 367 | public void TestJSONReaderEscapedSymbols() 368 | { 369 | var escapedString = "Symbols: \\b \\f \\n \\r \\t \\\\ \\\""; 370 | var unescapedString = "Symbols: \b \f \n \r \t \\ \""; 371 | var json = "{\"message\": { \"content\": \"" + escapedString + "\"} }"; 372 | 373 | var root = JSONReader.ReadFromString(json); 374 | ClassicAssert.NotNull(root); 375 | 376 | var msg = root["message"]; 377 | ClassicAssert.NotNull(msg); 378 | 379 | var content = msg.GetString("content"); 380 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 381 | 382 | ClassicAssert.IsTrue(unescapedString.Equals(content)); 383 | } 384 | 385 | 386 | [Test] 387 | public void TestJSONSimpleValue() 388 | { 389 | var root = DataNode.CreateString("test"); 390 | root.Value = "hello"; 391 | 392 | var json = JSONWriter.WriteToString(root); 393 | 394 | var result = JSONReader.ReadFromString(json); 395 | 396 | ClassicAssert.IsTrue(result.ChildCount == 1); 397 | result = result.GetNodeByIndex(0); 398 | 399 | ClassicAssert.IsTrue(result.Name == root.Name); 400 | ClassicAssert.IsTrue(result.Value == root.Value); 401 | } 402 | 403 | [Test] 404 | public void TestJSONTypes() 405 | { 406 | var root = JSONReader.ReadFromString("{\"message\": { \"number\": 3.14159, \"negative\": -52, \"check\":true, \"item\": null, \"science\":-1.0e-5, \"science_alt\":-2.0e+5} }"); 407 | ClassicAssert.NotNull(root); 408 | 409 | var msg = root["message"]; 410 | ClassicAssert.NotNull(msg); 411 | 412 | // alternate way 413 | msg = root.GetNodeByName("message"); 414 | ClassicAssert.NotNull(msg); 415 | 416 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 417 | 418 | var number = msg.GetFloat("number"); 419 | ClassicAssert.IsTrue(Math.Abs(number - 3.14159) < 0.001f); 420 | ClassicAssert.IsTrue(msg.GetNodeByName("number").Kind == NodeKind.Numeric); 421 | 422 | var negative = msg.GetInt32("negative"); 423 | ClassicAssert.IsTrue(negative == -52); 424 | ClassicAssert.IsTrue(msg.GetNodeByName("negative").Kind == NodeKind.Numeric); 425 | 426 | var check = msg.GetBool("check"); 427 | ClassicAssert.IsTrue(check); 428 | ClassicAssert.IsTrue(msg.GetNodeByName("check").Kind == NodeKind.Boolean); 429 | 430 | var item = msg.GetNodeByName("item"); 431 | ClassicAssert.IsNotNull(item); 432 | ClassicAssert.IsTrue(msg.GetNodeByName("item").Kind == NodeKind.Null); 433 | ClassicAssert.IsTrue(string.IsNullOrEmpty(item.Value)); 434 | 435 | var number2 = msg.GetFloat("science"); 436 | ClassicAssert.IsTrue(Math.Abs(number2 - (-1.0e-5)) < 0.001f); 437 | ClassicAssert.IsTrue(msg.GetNodeByName("science").Kind == NodeKind.Numeric); 438 | } 439 | 440 | [Test] 441 | public void TestJSONArray() 442 | { 443 | var root = JSONReader.ReadFromString("{\"message\": { \"content\": [0, 1, 2, 3]} }"); 444 | ClassicAssert.NotNull(root); 445 | 446 | var msg = root["message"]; 447 | ClassicAssert.NotNull(msg); 448 | 449 | // alternate way 450 | msg = root.GetNodeByName("message"); 451 | ClassicAssert.NotNull(msg); 452 | 453 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 454 | 455 | var content = msg["content"]; 456 | ClassicAssert.IsTrue(content.ChildCount == 4); 457 | 458 | for (int i = 0; i < 4; i++) 459 | { 460 | var number = content.GetNodeByIndex(i); 461 | ClassicAssert.IsTrue(i.ToString().Equals(number.Value)); 462 | } 463 | } 464 | 465 | [Test] 466 | public void TestJSONArrayWriter() 467 | { 468 | var root = DataNode.CreateArray(null); 469 | root.AddField(null, "hello"); 470 | root.AddField(null, "1"); 471 | root.AddField(null, "2"); 472 | 473 | var json = JSONWriter.WriteToString(root); 474 | ClassicAssert.NotNull(json); 475 | 476 | var other = JSONReader.ReadFromString(json); 477 | ClassicAssert.NotNull(other); 478 | 479 | ClassicAssert.IsTrue(other.ChildCount == root.ChildCount); 480 | 481 | for (int i = 0; i < root.ChildCount; i++) 482 | { 483 | var child = root.GetNodeByIndex(i); 484 | var otherChild = other.GetNodeByIndex(i); 485 | 486 | ClassicAssert.NotNull(child); 487 | ClassicAssert.NotNull(otherChild); 488 | 489 | ClassicAssert.IsTrue(child.Name == otherChild.Name); 490 | ClassicAssert.IsTrue(child.Value == otherChild.Value); 491 | } 492 | } 493 | 494 | [Test] 495 | public void TestJSONObjectWriter() 496 | { 497 | var color = new Color(200, 100, 220, 128); 498 | var root = DataNode.CreateObject(null); 499 | root.AddNode(color.ToDataNode()); 500 | 501 | var json = JSONWriter.WriteToString(root); 502 | ClassicAssert.NotNull(json); 503 | 504 | var other = JSONReader.ReadFromString(json); 505 | ClassicAssert.NotNull(other); 506 | 507 | ClassicAssert.IsTrue(other.ChildCount == root.ChildCount); 508 | 509 | for (int i = 0; i < root.ChildCount; i++) 510 | { 511 | var child = root.GetNodeByIndex(i); 512 | var otherChild = other.GetNodeByIndex(i); 513 | 514 | ClassicAssert.NotNull(child); 515 | ClassicAssert.NotNull(otherChild); 516 | 517 | ClassicAssert.IsTrue(child.Name == otherChild.Name); 518 | ClassicAssert.IsTrue(child.Value == otherChild.Value); 519 | } 520 | } 521 | 522 | [Test] 523 | public void TestJSONTyping() 524 | { 525 | var root = DataNode.CreateObject(null); 526 | var val = "0000"; 527 | root.AddField("msg", val); 528 | 529 | var json = JSONWriter.WriteToString(root); 530 | ClassicAssert.NotNull(json); 531 | 532 | var other = JSONReader.ReadFromString(json); 533 | ClassicAssert.NotNull(other); 534 | 535 | ClassicAssert.IsTrue(other.ChildCount == root.ChildCount); 536 | 537 | var otherVal = other.GetString("msg"); 538 | ClassicAssert.IsTrue(otherVal.Equals(val)); 539 | } 540 | #endregion 541 | 542 | #region YAML 543 | [Test] 544 | public void TestYAMLReader() 545 | { 546 | var root = YAMLReader.ReadFromString("---\nmessage:\n content: Hello world!"); 547 | ClassicAssert.NotNull(root); 548 | 549 | var msg = root["message"]; 550 | ClassicAssert.NotNull(msg); 551 | 552 | ClassicAssert.IsTrue("message".Equals(msg.Name)); 553 | 554 | var content = msg.GetString("content"); 555 | ClassicAssert.IsFalse(string.IsNullOrEmpty(content)); 556 | 557 | ClassicAssert.IsTrue("Hello world!".Equals(content)); 558 | } 559 | 560 | [Test] 561 | public void TestYAMLIdentationBlock() 562 | { 563 | var root = YAMLReader.ReadFromString("layout: list\r\ntitle: iDEX Activities\r\nslug: activities\r\ndescription: >\r\n This page is for blogging activities of iDEX."); 564 | ClassicAssert.NotNull(root); 565 | } 566 | #endregion 567 | 568 | #region CSV 569 | private struct Animal 570 | { 571 | public int id; 572 | public string name; 573 | } 574 | 575 | [Test] 576 | public void TestCSVReader() 577 | { 578 | var csv = "id,name\n1,Dog\n2,\"The \"\"Mr\"\"Cat\"\n399,\"Fish,Blue\"\n412,\"Heavy Bird\""; 579 | var root = CSVReader.ReadFromString(csv); 580 | ClassicAssert.NotNull(root); 581 | 582 | ClassicAssert.IsTrue(root.ChildCount == 4); 583 | 584 | var animals = root.ToArray(); 585 | ClassicAssert.IsTrue(animals.Length == 4); 586 | 587 | Animal animal; 588 | 589 | animal = animals[0]; 590 | ClassicAssert.IsTrue(1.Equals(animal.id)); 591 | ClassicAssert.IsTrue("Dog".Equals(animal.name)); 592 | 593 | animal = animals[1]; 594 | ClassicAssert.IsTrue(2.Equals(animal.id)); 595 | ClassicAssert.IsTrue("The \"Mr\"Cat".Equals(animal.name)); 596 | 597 | animal = animals[2]; 598 | ClassicAssert.IsTrue(399.Equals(animal.id)); 599 | ClassicAssert.IsTrue("Fish,Blue".Equals(animal.name)); 600 | 601 | animal = animals[3]; 602 | ClassicAssert.IsTrue(412.Equals(animal.id)); 603 | ClassicAssert.IsTrue("Heavy Bird".Equals(animal.name)); 604 | } 605 | #endregion 606 | 607 | #region DataNode 608 | [Test] 609 | public void TestDateTime() 610 | { 611 | var date = new DateTime(2017, 11, 29, 10, 30, 0); 612 | 613 | var root = DataNode.CreateObject("test"); 614 | ClassicAssert.NotNull(root); 615 | 616 | root.AddField("first", date); 617 | root.AddField("second", date.ToString()); 618 | root.AddField("third", "2017-11-29T10:30:00.000Z"); 619 | 620 | ClassicAssert.IsTrue(root.ChildCount == 3); 621 | 622 | var xml = XMLWriter.WriteToString(root); 623 | ClassicAssert.IsFalse(string.IsNullOrEmpty(xml)); 624 | 625 | root = XMLReader.ReadFromString(xml); 626 | ClassicAssert.NotNull(root); 627 | 628 | var test = root.GetNodeByName("test"); 629 | ClassicAssert.IsTrue("test".Equals(test.Name)); 630 | 631 | var first = test.GetDateTime("first"); 632 | ClassicAssert.IsTrue(first.Equals(date)); 633 | 634 | var second = test.GetDateTime("second"); 635 | ClassicAssert.IsTrue(second.Equals(date)); 636 | 637 | var third = test.GetDateTime("third"); 638 | ClassicAssert.IsTrue(third.Equals(date)); 639 | } 640 | 641 | private struct Color 642 | { 643 | public byte R; 644 | public byte G; 645 | public byte B; 646 | public byte A; 647 | 648 | public Color(byte R, byte G, byte B, byte A = 255) 649 | { 650 | this.R = R; 651 | this.G = G; 652 | this.B = B; 653 | this.A = A; 654 | } 655 | } 656 | 657 | [Test] 658 | public void TestStructs() 659 | { 660 | var color = new Color(128, 200, 64, 255); 661 | 662 | var root = DataNode.CreateObject("test"); 663 | ClassicAssert.NotNull(root); 664 | 665 | var obj = color.ToDataNode(); 666 | ClassicAssert.IsTrue(obj.ChildCount == 4); 667 | 668 | root.AddNode(obj); 669 | 670 | ClassicAssert.IsTrue(root.ChildCount == 1); 671 | 672 | var xml = XMLWriter.WriteToString(root); 673 | ClassicAssert.IsFalse(string.IsNullOrEmpty(xml)); 674 | 675 | root = XMLReader.ReadFromString(xml); 676 | ClassicAssert.NotNull(root); 677 | 678 | var test = root.GetNodeByName("test"); 679 | ClassicAssert.IsTrue("test".Equals(test.Name)); 680 | 681 | var content = test.GetNodeByName("color"); 682 | ClassicAssert.NotNull(content); 683 | ClassicAssert.IsTrue(content.ChildCount == 4); 684 | 685 | var otherColor = content.ToObject(); 686 | 687 | ClassicAssert.IsTrue(otherColor.Equals(color)); 688 | } 689 | 690 | [Test] 691 | public void TestStructArrays() 692 | { 693 | var red = new Color(255, 0, 0, 255); 694 | var green = new Color(0, 255, 0, 255); 695 | var blue = new Color(0, 0, 255, 255); 696 | var white = new Color(255, 255, 255, 255); 697 | var grey = new Color(128, 128, 128, 255); 698 | 699 | var root = DataNode.CreateObject("test"); 700 | ClassicAssert.NotNull(root); 701 | 702 | var colors = new Color[] { red, green, blue, white, grey }; 703 | var temp = colors.ToDataNode("colors"); 704 | ClassicAssert.NotNull(temp); 705 | ClassicAssert.IsTrue(temp.ChildCount == 5); 706 | 707 | root.AddNode(temp); 708 | var xml = XMLWriter.WriteToString(root); 709 | 710 | root = XMLReader.ReadFromString(xml); 711 | 712 | var test = root["test"]; 713 | temp = test["colors"]; 714 | 715 | colors = temp.ToArray(); 716 | ClassicAssert.IsTrue(colors.Length == 5); 717 | 718 | ClassicAssert.IsTrue(colors[0].Equals(red)); 719 | ClassicAssert.IsTrue(colors[1].Equals(green)); 720 | ClassicAssert.IsTrue(colors[2].Equals(blue)); 721 | ClassicAssert.IsTrue(colors[3].Equals(white)); 722 | ClassicAssert.IsTrue(colors[4].Equals(grey)); 723 | } 724 | 725 | private struct ColorGroup 726 | { 727 | public Color foreground; 728 | public Color background; 729 | 730 | public ColorGroup(Color foreground, Color background) 731 | { 732 | this.foreground = foreground; 733 | this.background = background; 734 | } 735 | } 736 | 737 | [Test] 738 | public void TestNestedStructs() 739 | { 740 | var color1 = new Color(128, 200, 64, 255); 741 | var color2 = new Color(230, 130, 60, 100); 742 | var cgroup = new ColorGroup(color1, color2); 743 | 744 | var root = DataNode.CreateObject("test"); 745 | ClassicAssert.NotNull(root); 746 | 747 | var obj = cgroup.ToDataNode(); 748 | ClassicAssert.IsTrue(obj.ChildCount == 2); 749 | 750 | root.AddNode(obj); 751 | 752 | ClassicAssert.IsTrue(root.ChildCount == 1); 753 | 754 | var xml = XMLWriter.WriteToString(root); 755 | ClassicAssert.IsFalse(string.IsNullOrEmpty(xml)); 756 | 757 | root = XMLReader.ReadFromString(xml); 758 | ClassicAssert.NotNull(root); 759 | 760 | var test = root.GetNodeByName("test"); 761 | ClassicAssert.IsTrue("test".Equals(test.Name)); 762 | 763 | var content = test.GetNodeByName("colorgroup"); 764 | ClassicAssert.NotNull(content); 765 | ClassicAssert.IsTrue(content.ChildCount == 2); 766 | 767 | var otherGroup = content.ToObject(); 768 | 769 | ClassicAssert.IsTrue(otherGroup.foreground.Equals(cgroup.foreground)); 770 | ClassicAssert.IsTrue(otherGroup.background.Equals(cgroup.background)); 771 | } 772 | 773 | [Test] 774 | public void TestFindNodes() 775 | { 776 | var root = JSONReader.ReadFromString("{\"root\": { \"number\": 3.14159, \"check\":true, \"item\": {\"base\": \"found me\"} } }"); 777 | ClassicAssert.NotNull(root); 778 | var msg = root["root"]; 779 | ClassicAssert.NotNull(msg); 780 | 781 | // alternate way 782 | var child = root.FindNode("base"); 783 | ClassicAssert.NotNull(child); 784 | ClassicAssert.AreEqual("base", child.Name); 785 | ClassicAssert.AreEqual("found me", child.Value); 786 | } 787 | 788 | [Test] 789 | public void TestAutoDetection() 790 | { 791 | var xml = "Hello world!"; 792 | var json = "{\"message\": { \"content\": \"Hello world!\"} }"; 793 | var yaml = "---\nmessage:\n content: Hello world!"; 794 | 795 | DataFormat format; 796 | 797 | format = DataFormats.DetectFormat(xml); 798 | ClassicAssert.IsTrue(format.Equals(DataFormat.XML)); 799 | 800 | format = DataFormats.DetectFormat(json); 801 | ClassicAssert.IsTrue(format.Equals(DataFormat.JSON)); 802 | 803 | format = DataFormats.DetectFormat(yaml); 804 | ClassicAssert.IsTrue(format.Equals(DataFormat.YAML)); 805 | } 806 | 807 | public enum AnswerKind 808 | { 809 | Yes, 810 | No, 811 | Maybe 812 | } 813 | 814 | [Test] 815 | public void TestEnumParsing() 816 | { 817 | var root = DataNode.CreateObject(); 818 | root.AddField("Answer", "Maybe"); 819 | root.AddField("Other", "1"); 820 | 821 | var answer = root.GetEnum("Answer"); 822 | ClassicAssert.IsTrue(answer == AnswerKind.Maybe); 823 | 824 | var other = root.GetEnum("Other"); 825 | ClassicAssert.IsTrue(other == AnswerKind.No); 826 | } 827 | 828 | [Test] 829 | public void TestAcessors() 830 | { 831 | var dogName = "barry"; 832 | var catName = "bopi"; 833 | 834 | var root = DataNode.CreateObject(); 835 | root.AddField("dog", dogName); 836 | root.AddField("cat", catName); 837 | 838 | string s; 839 | 840 | s = root.GetString("dog"); 841 | ClassicAssert.IsTrue(s == dogName); 842 | 843 | s = root["dog"].Value; 844 | ClassicAssert.IsTrue(s == dogName); 845 | 846 | s = root[0].Value; 847 | ClassicAssert.IsTrue(s == dogName); 848 | 849 | s = root[0].AsString(); 850 | ClassicAssert.IsTrue(s == dogName); 851 | 852 | s = root.GetString("cat"); 853 | ClassicAssert.IsTrue(s == catName); 854 | 855 | s = root["cat"].Value; 856 | ClassicAssert.IsTrue(s == catName); 857 | 858 | s = root[1].Value; 859 | ClassicAssert.IsTrue(s == catName); 860 | 861 | s = root[1].AsString(); 862 | ClassicAssert.IsTrue(s == catName); 863 | } 864 | 865 | [Test] 866 | public void TestTypes() 867 | { 868 | var temp = DataNode.CreateObject("root"); 869 | temp.AddField("a", 123); 870 | temp.AddField("f", 123.456); 871 | temp.AddField("d", "7.65e-6"); 872 | temp.AddField("b", true); 873 | 874 | var json = JSONWriter.WriteToString(temp); 875 | var root = JSONReader.ReadFromString(json); 876 | root = root["root"]; 877 | ClassicAssert.IsNotNull(root); 878 | 879 | ClassicAssert.IsTrue(root["a"].AsInt64() == 123); 880 | ClassicAssert.IsTrue(root.GetInt64("a") == 123); 881 | 882 | ClassicAssert.IsTrue(root["a"].AsUInt32() == 123); 883 | ClassicAssert.IsTrue(root.GetUInt32("a") == 123); 884 | 885 | ClassicAssert.IsTrue(root["a"].AsInt32() == 123); 886 | ClassicAssert.IsTrue(root.GetInt32("a") == 123); 887 | 888 | ClassicAssert.IsTrue(root["a"].AsByte() == 123); 889 | ClassicAssert.IsTrue(root.GetByte("a") == 123); 890 | 891 | ClassicAssert.IsTrue(root["a"].AsSByte() == 123); 892 | ClassicAssert.IsTrue(root.GetSByte("a") == 123); 893 | 894 | ClassicAssert.IsTrue(root["f"].AsFloat() == 123.456f); 895 | ClassicAssert.IsTrue(root.GetFloat("f") == 123.456f); 896 | 897 | ClassicAssert.IsTrue(root["d"].AsDecimal() == 0.00000765m); 898 | ClassicAssert.IsTrue(root.GetDecimal("d") == 0.00000765m); 899 | 900 | ClassicAssert.IsTrue(root["f"].AsDouble() == 123.456); 901 | ClassicAssert.IsTrue(root.GetDouble("f") == 123.456); 902 | 903 | ClassicAssert.IsTrue(root["b"].AsBool() ); 904 | ClassicAssert.IsTrue(root.GetBool("b")); 905 | 906 | ClassicAssert.IsTrue(root["f"].AsString() == "123.456"); 907 | ClassicAssert.IsTrue(root.GetString("f") == "123.456"); 908 | } 909 | 910 | [Test] 911 | public void TestDefaults() 912 | { 913 | var root = DataNode.CreateObject(); 914 | root.AddField("something", "5"); 915 | root.AddField("other", "1"); 916 | root.AddField("maybe", "yes"); 917 | 918 | string s; 919 | 920 | s = root.GetString("maybe", "no"); 921 | ClassicAssert.IsTrue(s == "yes"); 922 | 923 | s = root.GetString("never", "no"); 924 | ClassicAssert.IsTrue(s == "no"); 925 | 926 | bool b; 927 | 928 | b = root.GetBool("other"); 929 | ClassicAssert.IsTrue(b); 930 | 931 | b = root.GetBool("missing"); 932 | ClassicAssert.IsFalse(b); 933 | 934 | b = root.GetBool("missing", true); 935 | ClassicAssert.IsTrue(b); 936 | 937 | b = root.GetBool("something"); 938 | ClassicAssert.IsFalse(b); 939 | } 940 | 941 | [Test] 942 | public void TestArray() 943 | { 944 | var root = DataNode.CreateArray(); 945 | root.AddNode(DataNode.CreateValue("first")); 946 | root.AddValue("second"); 947 | root.AddField(null, "third"); 948 | 949 | ClassicAssert.IsTrue(root.ChildCount == 3); 950 | 951 | string s; 952 | 953 | s = root.GetNodeByIndex(0).AsString(); 954 | ClassicAssert.IsTrue(s.Equals("first")); 955 | 956 | s = root.GetNodeByIndex(1).AsString(); 957 | ClassicAssert.IsTrue(s.Equals("second")); 958 | 959 | s = root.GetNodeByIndex(2).AsString(); 960 | ClassicAssert.IsTrue(s.Equals("third")); 961 | } 962 | 963 | [Test] 964 | public void TestAsObject() 965 | { 966 | var root = DataNode.CreateObject("temp"); 967 | root.AddField("hello", "world"); 968 | root.AddField("number", "4"); 969 | root.AddField("bool", true); 970 | 971 | ClassicAssert.IsTrue(root.ChildCount == 3); 972 | 973 | var s = root.GetObject("hello", ""); 974 | ClassicAssert.IsTrue(s.Equals("world")); 975 | 976 | var n = root.GetObject("number", 0); 977 | ClassicAssert.IsTrue(n == 4); 978 | 979 | var b = root.GetObject("bool", false); 980 | ClassicAssert.IsTrue(b == true); 981 | } 982 | 983 | [Test] 984 | public void TestNodeToDictionary() 985 | { 986 | var dic = new Dictionary(); 987 | 988 | dic.Add("one", 1); 989 | dic.Add("two", 2); 990 | dic.Add("three", 3); 991 | 992 | var root = dic.FromDictionary("temp"); 993 | 994 | ClassicAssert.IsTrue(root.ChildCount == dic.Count); 995 | 996 | foreach (var entry in dic) 997 | { 998 | var val = root.GetInt32(entry.Key); 999 | 1000 | ClassicAssert.IsTrue(val == entry.Value); 1001 | } 1002 | 1003 | var other = root.ToDictionary("temp"); 1004 | ClassicAssert.IsTrue(other.Count == dic.Count); 1005 | 1006 | foreach (var entry in dic) 1007 | { 1008 | var val = other[entry.Key]; 1009 | 1010 | ClassicAssert.IsTrue(val == entry.Value); 1011 | } 1012 | } 1013 | 1014 | [Test] 1015 | public void TestNodeToHashSet() 1016 | { 1017 | var set = new HashSet(); 1018 | 1019 | set.Add("one"); 1020 | set.Add("two"); 1021 | set.Add("three"); 1022 | 1023 | var root = set.FromHashSet("temp"); 1024 | 1025 | ClassicAssert.IsTrue(root.ChildCount == set.Count); 1026 | 1027 | foreach (var entry in set) 1028 | { 1029 | ClassicAssert.IsTrue(root.Children.Any(x => x.Value == entry)); 1030 | } 1031 | 1032 | var other = root.ToHashSet(); 1033 | ClassicAssert.IsTrue(other.Count == set.Count); 1034 | 1035 | foreach (var entry in set) 1036 | { 1037 | ClassicAssert.IsTrue(other.Contains(entry)); 1038 | } 1039 | 1040 | var node = root.GetNodeByIndex(1); 1041 | ClassicAssert.IsTrue(root.RemoveNode(node)); 1042 | ClassicAssert.IsTrue(root.ChildCount == set.Count - 1); 1043 | } 1044 | 1045 | #endregion 1046 | } 1047 | } 1048 | -------------------------------------------------------------------------------- /Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("77249aa9-d572-457e-ba15-97a17ed39f81")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 9 | Library 10 | Properties 11 | Tests 12 | Tests 13 | v4.8 14 | 512 15 | 16 | 17 | 18 | {77249AA9-D572-457E-BA15-97A17ED39F81} 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\packages\NUnit.4.3.2\lib\net462\nunit.framework.dll 40 | 41 | 42 | ..\packages\NUnit.4.3.2\lib\net462\nunit.framework.legacy.dll 43 | 44 | 45 | 46 | ..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll 47 | 48 | 49 | 50 | ..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll 51 | 52 | 53 | 54 | ..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll 55 | 56 | 57 | ..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll 58 | 59 | 60 | ..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | {33543f22-bcfc-47fb-acca-94ae63edf785} 76 | LunarParser 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Tests/app.config: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------