blob: 28abc1b809099655d3c6e0a9183ae3d753ef71aa [file] [log] [blame]
Constantin Ziesche857c7ab2020-02-25 11:24:51 +01001/*******************************************************************************
2* Copyright (c) 2020 Robert Bosch GmbH
3* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
4*
5* This program and the accompanying materials are made available under the
6* terms of the Eclipse Public License 2.0 which is available at
7* http://www.eclipse.org/legal/epl-2.0
8*
9* SPDX-License-Identifier: EPL-2.0
10*******************************************************************************/
11using BaSyx.Models.Core.AssetAdministrationShell.Generics;
12using BaSyx.Models.Core.AssetAdministrationShell.Generics.SubmodelElementTypes;
13using BaSyx.Models.Core.AssetAdministrationShell.Implementations;
14using BaSyx.Models.Core.AssetAdministrationShell.References;
15using BaSyx.Models.Core.AssetAdministrationShell.Semantics;
16using BaSyx.Models.Core.Common;
17using BaSyx.Models.Export.Converter;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010018using BaSyx.Models.Export.EnvironmentSubmodelElements;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010019using BaSyx.Models.Extensions.Semantics.DataSpecifications;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010020using Newtonsoft.Json;
21using Newtonsoft.Json.Converters;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010022using NLog;
23using System;
24using System.Collections.Generic;
25using System.IO;
26using System.Linq;
27using System.Reflection;
28using System.Runtime.Serialization;
29using System.Xml;
30using System.Xml.Schema;
31using System.Xml.Serialization;
32
33namespace BaSyx.Models.Export
34{
35 [DataContract]
36 [XmlType(AnonymousType = true, Namespace = AAS_NAMESPACE)]
37 [XmlRoot(ElementName = "aasenv", Namespace = AAS_NAMESPACE, IsNullable = false)]
38 public class AssetAdministrationShellEnvironment_V2_0
39 {
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010040 public const string AAS_NAMESPACE = "http://www.admin-shell.io/aas/2/0";
41 public const string IEC61360_NAMESPACE = "http://www.admin-shell.io/IEC61360/2/0";
42 public const string ABAC_NAMESPACE = "http://www.admin-shell.io/aas/abac/2/0";
Constantin Ziesche02817f12020-08-04 21:40:43 +020043 public const string AAS_XSD_FILENAME = "aas-spec-v2.0/AAS.xsd";
44 public const string IEC61360_XSD_FILENAME = "aas-spec-v2.0/IEC61360.xsd";
45 public const string ABAC_XSD_FILENAME = "aas-spec-v2.0/AAS_ABAC.xsd";
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010046
47 [DataMember(EmitDefaultValue = false, IsRequired = true, Name = "assetAdministrationShells", Order = 0)]
48 [XmlIgnore, JsonIgnore]
49 public List<IAssetAdministrationShell> AssetAdministrationShells { get; }
50
51 [DataMember(EmitDefaultValue = false, IsRequired = true, Name = "assets", Order = 1)]
52 [XmlIgnore, JsonIgnore]
53 public List<IAsset> Assets { get; }
54
55 [DataMember(EmitDefaultValue = false, IsRequired = true, Name = "submodels", Order = 2)]
56 [XmlIgnore, JsonIgnore]
57 public List<ISubmodel> Submodels { get; }
58
59 [DataMember(EmitDefaultValue = false, IsRequired = true, Name = "conceptDescriptions", Order = 3)]
60 [XmlIgnore, JsonIgnore]
61 public List<IConceptDescription> ConceptDescriptions { get; }
62
63 [JsonProperty("assetAdministrationShells")]
64 [XmlArray("assetAdministrationShells")]
65 [XmlArrayItem("assetAdministrationShell")]
66 public List<EnvironmentAssetAdministationShell_V2_0> EnvironmentAssetAdministationShells { get; set; }
67
68 [JsonProperty("assets")]
69 [XmlArray("assets")]
70 [XmlArrayItem("asset")]
71 public List<EnvironmentAsset_V2_0> EnvironmentAssets { get; set; }
72
73 [JsonProperty("submodels")]
74 [XmlArray("submodels")]
75 [XmlArrayItem("submodel")]
76 public List<EnvironmentSubmodel_V2_0> EnvironmentSubmodels { get; set; }
77
78 [JsonProperty("conceptDescriptions")]
79 [XmlArray("conceptDescriptions")]
80 [XmlArrayItem("conceptDescription")]
81 public List<EnvironmentConceptDescription_V2_0> EnvironmentConceptDescriptions { get; set; }
82
83 [IgnoreDataMember]
84 [XmlIgnore]
85 public Dictionary<string, IFile> SupplementalFiles;
86
87 private string ContentRoot = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
88
89 public static JsonSerializerSettings JsonSettings;
90 private static Logger logger = LogManager.GetCurrentClassLogger();
91
92 public static XmlReaderSettings XmlSettings;
93
94 static AssetAdministrationShellEnvironment_V2_0()
95 {
96 JsonSettings = new JsonSerializerSettings()
97 {
98 Formatting = Newtonsoft.Json.Formatting.Indented,
99 DefaultValueHandling = DefaultValueHandling.Include,
100 NullValueHandling = NullValueHandling.Ignore
101 };
102 JsonSettings.Converters.Add(new StringEnumConverter());
103
104 XmlSettings = new XmlReaderSettings();
105 XmlSettings.ValidationType = ValidationType.Schema;
106 XmlSettings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
107 XmlSettings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
108 XmlSettings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
109 XmlSettings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
110 XmlSettings.Schemas.Add(AAS_NAMESPACE, AAS_XSD_FILENAME);
111 XmlSettings.Schemas.Add(IEC61360_NAMESPACE, IEC61360_XSD_FILENAME);
112 XmlSettings.Schemas.Add(ABAC_NAMESPACE, ABAC_XSD_FILENAME);
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100113 }
114
115 [JsonConstructor]
116 protected AssetAdministrationShellEnvironment_V2_0()
117 {
118 AssetAdministrationShells = new List<IAssetAdministrationShell>();
119 Submodels = new List<ISubmodel>();
120 Assets = new List<IAsset>();
121 ConceptDescriptions = new List<IConceptDescription>();
122 SupplementalFiles = new Dictionary<string, IFile>();
123
124 EnvironmentAssetAdministationShells = new List<EnvironmentAssetAdministationShell_V2_0>();
125 EnvironmentAssets = new List<EnvironmentAsset_V2_0>();
126 EnvironmentSubmodels = new List<EnvironmentSubmodel_V2_0>();
127 EnvironmentConceptDescriptions = new List<EnvironmentConceptDescription_V2_0>();
128 }
129
130 public AssetAdministrationShellEnvironment_V2_0(params IAssetAdministrationShell[] assetAdministrationShells) : this()
131 {
132 foreach (var aas in assetAdministrationShells)
133 AddAssetAdministationShell(aas);
134
135 ConvertToEnvironment();
136 }
137
138 public void AddAssetAdministationShell(IAssetAdministrationShell aas)
139 {
140 AssetAdministrationShells.Add(aas);
141 Assets.Add(aas.Asset);
142 if (aas.Submodels?.Count > 0)
143 {
144 Submodels.AddRange(aas.Submodels);
145 foreach (var submodel in aas.Submodels)
146 {
147 ExtractAndClearConceptDescriptions(submodel.SubmodelElements);
148 ExtractSupplementalFiles(submodel.SubmodelElements);
149 ResetConstraints(submodel.SubmodelElements);
Constantin Ziesche635c2e32020-02-27 18:11:07 +0100150 DeleteEvents(submodel.SubmodelElements);
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100151 }
152 }
153 }
154
155 private void ConvertToEnvironment()
156 {
157 foreach (var asset in Assets)
158 {
159 EnvironmentAsset_V2_0 envAsset = new EnvironmentAsset_V2_0()
160 {
161 Administration = asset.Administration,
162 AssetIdentificationModelReference = asset.AssetIdentificationModel?.ToEnvironmentReference_V2_0(),
163 Category = asset.Category,
164 Description = asset.Description,
165 Identification = asset.Identification,
166 IdShort = asset.IdShort,
167 Kind = asset.Kind,
168 Parent = asset.Parent?.First?.Value
169 };
170 EnvironmentAssets.Add(envAsset);
171 }
172 foreach (var conceptDescription in ConceptDescriptions)
173 {
174 EmbeddedDataSpecification_V2_0 embeddedDataSpecification = null;
175 var dataSpecification = conceptDescription.EmbeddedDataSpecifications?.FirstOrDefault();
176 if (dataSpecification != null && dataSpecification.DataSpecificationContent is DataSpecificationIEC61360Content dataSpecificationContent)
177 {
178 embeddedDataSpecification = new EmbeddedDataSpecification_V2_0()
179 {
Constantin Ziesche02817f12020-08-04 21:40:43 +0200180 DataSpecification = dataSpecification.HasDataSpecification?.ToEnvironmentReference_V2_0(),
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100181 DataSpecificationContent = new DataSpecificationContent_V2_0()
182 {
183 DataSpecificationIEC61360 = dataSpecificationContent.ToEnvironmentDataSpecificationIEC61360_V2_0()
184 }
185 };
186 }
187
188 EnvironmentConceptDescription_V2_0 environmentConceptDescription = new EnvironmentConceptDescription_V2_0()
189 {
190 Administration = conceptDescription.Administration,
191 Category = conceptDescription.Category,
192 Description = conceptDescription.Description,
193 Identification = conceptDescription.Identification,
194 IdShort = conceptDescription.IdShort,
195 Parent = conceptDescription.Parent?.First?.Value,
196 IsCaseOf = conceptDescription.IsCaseOf?.ToList()?.ConvertAll(c => c.ToEnvironmentReference_V2_0()),
197 EmbeddedDataSpecification = embeddedDataSpecification
198 };
199
Constantin Ziesche635c2e32020-02-27 18:11:07 +0100200 if(EnvironmentConceptDescriptions.Find(m => m.Identification.Id == conceptDescription.Identification.Id) == null)
201 EnvironmentConceptDescriptions.Add(environmentConceptDescription);
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100202 }
203 foreach (var assetAdministrationShell in AssetAdministrationShells)
204 {
205 EnvironmentAssetAdministationShell_V2_0 environmentAssetAdministationShell = new EnvironmentAssetAdministationShell_V2_0()
206 {
207 Administration = assetAdministrationShell.Administration,
208 Category = assetAdministrationShell.Category,
209 Description = assetAdministrationShell.Description,
210 IdShort = assetAdministrationShell.IdShort,
211 Identification = assetAdministrationShell.Identification,
212 Parent = assetAdministrationShell.Parent?.First?.Value,
213 AssetReference = assetAdministrationShell.Asset?.ToEnvironmentReference_V2_0(),
214 Views = null,
215 ConceptDictionaries = null
216 };
217 environmentAssetAdministationShell.SubmodelReferences = new List<EnvironmentReference_V2_0>();
218 foreach (var submodel in assetAdministrationShell.Submodels)
219 environmentAssetAdministationShell.SubmodelReferences.Add(submodel.ToEnvironmentReference_V2_0());
220
221 EnvironmentAssetAdministationShells.Add(environmentAssetAdministationShell);
222 }
223 foreach (var submodel in Submodels)
224 {
225 EnvironmentSubmodel_V2_0 environmentSubmodel = new EnvironmentSubmodel_V2_0()
226 {
227 Administration = submodel.Administration,
228 Category = submodel.Category,
229 Description = submodel.Description,
230 Identification = submodel.Identification,
231 IdShort = submodel.IdShort,
232 Kind = submodel.Kind,
233 Parent = submodel.Parent?.First?.Value,
234 Qualifier = null,
235 SemanticId = submodel.SemanticId?.ToEnvironmentReference_V2_0()
236 };
237
238 environmentSubmodel.SubmodelElements = new List<EnvironmentSubmodelElement_V2_0>();
239 foreach (var submodelElement in submodel.SubmodelElements)
240 environmentSubmodel.SubmodelElements.Add(submodelElement.ToEnvironmentSubmodelElement_V2_0());
241
242
243 EnvironmentSubmodels.Add(environmentSubmodel);
244 }
245 }
246
247
Constantin Ziesche635c2e32020-02-27 18:11:07 +0100248 private void DeleteEvents(IElementContainer<ISubmodelElement> submodelElements)
249 {
250 var eventsToDelete = submodelElements.Where(s => s.ModelType == ModelType.Event || s.ModelType == ModelType.BasicEvent).ToList();
251 foreach (var eventable in eventsToDelete)
252 submodelElements.Remove(eventable);
253 }
254
255
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100256 private void ExtractSupplementalFiles(IEnumerable<ISubmodelElement> submodelElements)
257 {
258 foreach (var smElement in submodelElements)
259 {
260 if (smElement is Core.AssetAdministrationShell.Implementations.SubmodelElementTypes.File file)
261 {
262 string filePath = ContentRoot + file.Value.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
263 if (System.IO.File.Exists(filePath))
264 {
265 string destinationPath = file.Value;
266 if (!destinationPath.StartsWith(AASX.AASX_FOLDER))
267 destinationPath = AASX.AASX_FOLDER + destinationPath;
268
269 file.Value = destinationPath;
270 SupplementalFiles.Add(filePath, file);
271 }
272 }
273 else if (smElement.ModelType == ModelType.SubmodelElementCollection)
274 ExtractSupplementalFiles((smElement as Core.AssetAdministrationShell.Implementations.SubmodelElementTypes.SubmodelElementCollection).Value);
275 }
276 }
277
278 private void ExtractAndClearConceptDescriptions(IEnumerable<ISubmodelElement> submodelElements)
279 {
280 foreach (var smElement in submodelElements)
281 {
282 if (smElement.ConceptDescription != null)
283 {
284 ConceptDescriptions.Add(smElement.ConceptDescription);
285 (smElement as SubmodelElement).SemanticId = new Reference(new Key(KeyElements.ConceptDescription, smElement.ConceptDescription.Identification.IdType, smElement.ConceptDescription.Identification.Id, true));
286 (smElement as SubmodelElement).ConceptDescription = null;
287 (smElement as SubmodelElement).EmbeddedDataSpecifications = null;
288 }
Constantin Ziesche635c2e32020-02-27 18:11:07 +0100289 if (smElement.ModelType == ModelType.SubmodelElementCollection)
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100290 ExtractAndClearConceptDescriptions((smElement as Core.AssetAdministrationShell.Implementations.SubmodelElementTypes.SubmodelElementCollection).Value);
291 }
292 }
293
294 public void SetContentRoot(string contentRoot) => ContentRoot = contentRoot;
295
296 private void ResetConstraints(IEnumerable<ISubmodelElement> submodelElements)
297 {
298 foreach (var smElement in submodelElements)
299 {
300 if (smElement.Constraints?.Count > 0)
301 (smElement as SubmodelElement).Constraints = null;
302 if (smElement is IOperation operation)
303 {
304 if (operation.InputVariables?.Count > 0)
305 ResetConstraints((smElement as IOperation).InputVariables.ToElementContainer());
306 if (operation.OutputVariables?.Count > 0)
307 ResetConstraints((smElement as IOperation).OutputVariables.ToElementContainer());
308 }
309 else if (smElement.ModelType == ModelType.SubmodelElementCollection)
310 ResetConstraints((smElement as Core.AssetAdministrationShell.Implementations.SubmodelElementTypes.SubmodelElementCollection).Value);
311 }
312 }
313
314 public void WriteEnvironment_V2_0(ExportType exportType, string filePath) => WriteEnvironment_V2_0(this, exportType, filePath);
315
316 public static void WriteEnvironment_V2_0(AssetAdministrationShellEnvironment_V2_0 environment, ExportType exportType, string filePath)
317 {
318 if (environment == null)
319 return;
320
321 switch (exportType)
322 {
323 case ExportType.Json:
324 try
325 {
326 string serialized = JsonConvert.SerializeObject(environment, JsonSettings);
327 System.IO.File.WriteAllText(filePath, serialized);
328 }
329 catch (Exception e)
330 {
331 logger.Error(e);
332 }
333 break;
334 case ExportType.Xml:
335 try
336 {
337 using (StreamWriter writer = new StreamWriter(filePath))
338 {
339 XmlSerializer serializer = new XmlSerializer(typeof(AssetAdministrationShellEnvironment_V2_0), AAS_NAMESPACE);
340 XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
341 namespaces.Add("xsi", XmlSchema.InstanceNamespace);
342 namespaces.Add("aas", AAS_NAMESPACE);
343 namespaces.Add("IEC61360", IEC61360_NAMESPACE);
344 serializer.Serialize(writer, environment, namespaces);
345 }
346
347 }
348 catch (Exception e)
349 {
350 logger.Error(e);
351 }
352 break;
353 default:
354 break;
355 }
356 }
357
358
359 public static AssetAdministrationShellEnvironment_V2_0 ReadEnvironment_V2_0(Stream stream, ExportType exportType)
360 {
361 AssetAdministrationShellEnvironment_V2_0 env = null;
362
363 try
364 {
365 switch (exportType)
366 {
367 case ExportType.Xml:
368 {
369 XmlSerializer serializer = new XmlSerializer(typeof(AssetAdministrationShellEnvironment_V2_0), AAS_NAMESPACE);
370
371 using (XmlReader reader = XmlReader.Create(stream, XmlSettings))
372 env = (AssetAdministrationShellEnvironment_V2_0)serializer.Deserialize(reader);
373 }
374 break;
375 case ExportType.Json:
376 {
377 using (StreamReader reader = new StreamReader(stream))
378 env = JsonConvert.DeserializeObject<AssetAdministrationShellEnvironment_V2_0>(reader.ReadToEnd(), JsonSettings);
379 }
380 break;
381 default:
382 throw new InvalidOperationException(exportType + " not supported");
383 }
384
385 ConvertToAssetAdministrationShell(env);
386 return env;
387 }
388 catch (Exception e)
389 {
390 logger.Error(e, "Failed to read environment - Exception: " + e.Message);
391 return null;
392 }
393 }
394
395
396 public static AssetAdministrationShellEnvironment_V2_0 ReadEnvironment_V2_0(string filePath)
397 {
398 if (string.IsNullOrEmpty(filePath))
399 throw new ArgumentNullException(filePath);
400 if (!System.IO.File.Exists(filePath))
401 throw new ArgumentException(filePath + " does not exist");
402
403 AssetAdministrationShellEnvironment_V2_0 env = null;
404
405 string fileExtension = Path.GetExtension(filePath);
406 ExportType exportType;
407 switch (fileExtension)
408 {
409 case ".xml":
410 exportType = ExportType.Xml;
411 break;
412 case ".json":
413 exportType = ExportType.Json;
414 break;
415 default:
416 throw new InvalidOperationException(fileExtension + " not supported");
417 }
418
419 using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read))
420 env = ReadEnvironment_V2_0(file, exportType);
421
422 if (env == null)
423 return null;
424
425 ConvertToAssetAdministrationShell(env);
426
427 return env;
428 }
429
430 private static void ConvertToAssetAdministrationShell(AssetAdministrationShellEnvironment_V2_0 environment)
431 {
432 foreach (var envAsset in environment.EnvironmentAssets)
433 {
434 Asset asset = new Asset
435 {
436 Administration = envAsset.Administration,
437 Category = envAsset.Category,
438 Description = envAsset.Description,
439 Identification = envAsset.Identification,
440 IdShort = envAsset.IdShort,
441 Kind = envAsset.Kind,
442 AssetIdentificationModel = envAsset.AssetIdentificationModelReference?.ToReference_V2_0<ISubmodel>()
443 };
444 environment.Assets.Add(asset);
445 }
446 foreach (var envConceptDescription in environment.EnvironmentConceptDescriptions)
447 {
448 ConceptDescription conceptDescription = new ConceptDescription()
449 {
450 Administration = envConceptDescription.Administration,
451 Category = envConceptDescription.Category,
452 Description = envConceptDescription.Description,
453 Identification = envConceptDescription.Identification,
454 IdShort = envConceptDescription.IdShort,
455 IsCaseOf = envConceptDescription.IsCaseOf?.ConvertAll(c => c.ToReference_V2_0()),
456 EmbeddedDataSpecifications = (envConceptDescription.EmbeddedDataSpecification?.DataSpecificationContent?.DataSpecificationIEC61360 != null) ? new List<DataSpecificationIEC61360>() : null
457 };
458 if (conceptDescription.EmbeddedDataSpecifications != null)
459 {
460 DataSpecificationIEC61360 dataSpecification = envConceptDescription
461 .EmbeddedDataSpecification
462 .DataSpecificationContent
463 .DataSpecificationIEC61360
464 .ToDataSpecificationIEC61360();
465
466 (conceptDescription.EmbeddedDataSpecifications as List<DataSpecificationIEC61360>).Add(dataSpecification);
467 }
468 environment.ConceptDescriptions.Add(conceptDescription);
469 }
470 foreach (var envSubmodel in environment.EnvironmentSubmodels)
471 {
472 Submodel submodel = new Submodel()
473 {
474 Administration = envSubmodel.Administration,
475 Category = envSubmodel.Category,
476 Description = envSubmodel.Description,
477 Identification = envSubmodel.Identification,
478 IdShort = envSubmodel.IdShort,
479 Kind = envSubmodel.Kind,
480 Parent = string.IsNullOrEmpty(envSubmodel.Parent) ? null :
481 new Reference(
482 new Key(KeyElements.AssetAdministrationShell, KeyType.IRI, envSubmodel.Parent, true)),
483 SemanticId = envSubmodel.SemanticId?.ToReference_V2_0(),
484 ConceptDescription = null,
485 };
486 List<ISubmodelElement> smElements = envSubmodel.SubmodelElements.ConvertAll(c => c.submodelElement?.ToSubmodelElement(environment.ConceptDescriptions));
487 foreach (var smElement in smElements)
488 submodel.SubmodelElements.Add(smElement);
489
490 environment.Submodels.Add(submodel);
491 }
492 foreach (var envAssetAdministrationShell in environment.EnvironmentAssetAdministationShells)
493 {
494 AssetAdministrationShell assetAdministrationShell = new AssetAdministrationShell()
495 {
496 Administration = envAssetAdministrationShell.Administration,
497 Category = envAssetAdministrationShell.Category,
498 DerivedFrom = envAssetAdministrationShell.DerivedFrom?.ToReference_V2_0<IAssetAdministrationShell>(),
499 Description = envAssetAdministrationShell.Description,
500 Identification = envAssetAdministrationShell.Identification,
501 IdShort = envAssetAdministrationShell.IdShort
502 };
503
504 IAsset asset = environment.Assets.Find(a => a.Identification.Id == envAssetAdministrationShell.AssetReference?.Keys?.FirstOrDefault()?.Value);
505 assetAdministrationShell.Asset = asset;
506
507 foreach (var envSubmodelRef in envAssetAdministrationShell.SubmodelReferences)
508 {
509 ISubmodel submodel = environment.Submodels.Find(s => s.Identification.Id == envSubmodelRef.Keys?.FirstOrDefault()?.Value);
510 if (submodel != null)
511 assetAdministrationShell.Submodels.Add(submodel);
512 }
513
514 environment.AssetAdministrationShells.Add(assetAdministrationShell);
515 }
516 }
517
518 private static void ValidationCallBack(object sender, ValidationEventArgs args)
519 {
520 if (args.Severity == XmlSeverityType.Warning)
521 logger.Warn("Validation warning: " + args.Message);
522 else
523 logger.Error("Validation error: " + args.Message + " | LineNumber: " + args.Exception.LineNumber + " | LinePosition: " + args.Exception.LinePosition);
524
525 }
526 }
527}