├── Tests
├── Resources
│ ├── CommentWithoutDeclaration.xml
│ ├── test_with_Macintosh_LineEnd.xml
│ ├── CommentAfterDeclaration.xml
│ ├── test_with_Unix_LineEnd.xml
│ ├── registry_value.xml
│ ├── registry_value_resorted.xml
│ ├── test_with_umlaut_and_wrong_encoding.xml
│ ├── WIX_config.xml
│ ├── test_with_CDATA.xml
│ ├── single_line_description.xml
│ ├── packages.xml
│ ├── CommentAfterEnd.xml
│ ├── AssemblyDocumentation.xml
│ ├── StyleSheet.xslt
│ ├── test_without_declaration.xml
│ ├── ApplicationManifest.xml
│ ├── VisualBuild_script.xml
│ ├── test.xml
│ ├── Settings_file.xml
│ ├── FxCop_CustomDictionary.xml
│ ├── NuSpec.xml
│ ├── ModuleCatalog.xml
│ ├── EdmxV3.xml
│ ├── NLS1.xml
│ ├── RuleSet.xml
│ ├── Xaml_ResourceDictionary.xml
│ └── NDepend_project.xml
├── Tests.v3.ncrunchproject
├── packages.config
├── ParserTests_NLS.cs
├── Properties
│ └── AssemblyInfo.cs
├── ParserTests_WIX_Config.cs
├── ParserTests_PackagesConfig.cs
├── CharacterPositionFinderTests.cs
├── ParserTests_Manifest.cs
├── ParserTests_CommentAfterDeclaration.cs
├── ParserTests_CommentWithoutDeclaration.cs
├── ParserTests_CommentAfterEnd.cs
├── ParserTests_RegistryValue.cs
├── ParserTests_RuleSet.cs
├── ParserTests_SHFB_Project.cs
├── ParserTests_SettingsFile.cs
├── ParserTests_SingleLineDeclaration.cs
├── ParserTests_WrongEncoding.cs
├── ParserTests_NDepend.cs
├── ParserTests_CSharp_Project.cs
├── ParserTests_ModuleCatalog_Xaml.cs
├── ParserTests_AssemblyDocumentation.cs
├── ParserTests_Xsl.cs
├── ParserTests_LineEnds.cs
├── ParserTests_NuSpec.cs
├── ParserTests_Xlf.cs
├── ParserTests_EdmxV3.cs
├── Tests.ruleset
├── ParserTests_Xaml_ResourceDictionary.cs
├── ParserTests_FxCop.cs
├── ParserTests_VisualBuild_Script.cs
└── ParserTests_CData.cs
├── .gitignore
├── Parser
├── App.config
├── Yaml
│ ├── ParsingError.cs
│ ├── YamlWriter.cs
│ ├── TerminalNode.cs
│ ├── Converters
│ │ ├── CharacterSpanConverter.cs
│ │ ├── ParsingErrorConverter.cs
│ │ └── LocationSpanConverter.cs
│ ├── File.cs
│ ├── LocationSpan.cs
│ ├── Container.cs
│ ├── CharacterSpan.cs
│ ├── ContainerOrTerminalNode.cs
│ └── LineInfo.cs
├── Flavors
│ ├── DocumentInfo.cs
│ ├── XmlFlavorForDotNetCoreMSBuild.cs
│ ├── IXmlFlavor.cs
│ ├── XmlFlavorForWixConfiguration.cs
│ ├── XmlFlavorForNCrunchSolution.cs
│ ├── XmlFlavorForPackagesConfig.cs
│ ├── XmlFlavorForSettings.cs
│ ├── XmlFlavorForWixLocation.cs
│ ├── XmlFlavorForFxCop.cs
│ ├── XmlFlavorForModuleCatalogXaml.cs
│ ├── XmlFlavorForDotCover.cs
│ ├── XmlFlavorForXsdSchemaDefinitions.cs
│ ├── XmlFlavorForRuleSets.cs
│ ├── XmlFlavorForRanorexRepository.cs
│ ├── XmlFlavorForDependencies.cs
│ ├── XmlFlavorForXslTransformations.cs
│ ├── XmlFlavorForVsixManifest.cs
│ ├── XmlFlavorForXlf.cs
│ ├── XmlFlavor.cs
│ ├── XmlFlavorForNLS.cs
│ ├── XmlFlavorForManifest.cs
│ ├── XmlFlavorForAssemblyDocumentation.cs
│ ├── XmlFlavorForRanorexTestSuite.cs
│ ├── XmlFlavorForVisualBuild.cs
│ ├── XmlFlavorFinder.cs
│ ├── XmlFlavorForEdmxV3.cs
│ └── XmlFlavorForNuSpec.cs
├── packages.config
├── NodeType.cs
├── Tracer.cs
├── Resorter.cs
├── Properties
│ └── AssemblyInfo.cs
├── Parser.ruleset
├── Program.cs
└── CommentCleaner.cs
├── XmlSemanticParser.v3.ncrunchsolution
├── codecov.yml
├── appveyor.yml
├── XmlSemanticParser.sln
└── README.md
/Tests/Resources/CommentWithoutDeclaration.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vs/
2 | *.ncrunchsolution.user
3 | /*/obj/
4 | /*/bin/
5 | /packages/
6 | /_NCrunch*/
7 | *.user
8 |
--------------------------------------------------------------------------------
/Tests/Resources/test_with_Macintosh_LineEnd.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/Resources/CommentAfterDeclaration.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Tests/Resources/test_with_Unix_LineEnd.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Tests/Resources/registry_value.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/Resources/registry_value_resorted.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Tests/Resources/test_with_umlaut_and_wrong_encoding.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ÄÖÜ
4 |
5 |
--------------------------------------------------------------------------------
/Tests/Tests.v3.ncrunchproject:
--------------------------------------------------------------------------------
1 |
2 |
3 | True
4 |
5 |
--------------------------------------------------------------------------------
/Parser/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/XmlSemanticParser.v3.ncrunchsolution:
--------------------------------------------------------------------------------
1 |
2 |
3 | True
4 | True
5 |
6 |
--------------------------------------------------------------------------------
/Tests/Resources/WIX_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | some text
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Tests/Resources/test_with_CDATA.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Tests/Resources/single_line_description.xml:
--------------------------------------------------------------------------------
1 | A diagnostic extension for the .NET Compiler Platform ("Roslyn") that checks for different metrics such as Lines of Code or Cyclomatic Complexity; in addition to several maintainability, naming and documentation rules.
2 |
--------------------------------------------------------------------------------
/Tests/Resources/packages.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Tests/Resources/CommentAfterEnd.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/Parser/Yaml/ParsingError.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Serialization;
2 |
3 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml
4 | {
5 | public sealed class ParsingError
6 | {
7 | [YamlMember(Alias = "location")]
8 | public LineInfo Location { get; set; }
9 |
10 | [YamlMember(Alias = "message")]
11 | public string ErrorMessage { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/Tests/Resources/AssemblyDocumentation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | XmlSemanticParser
5 |
6 |
7 |
8 |
9 | Defines the entry point of the assembly.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Tests/Resources/StyleSheet.xslt:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Parser/Flavors/DocumentInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Diagnostics;
3 |
4 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
5 | {
6 | [DebuggerDisplay("Root={RootElement} Namespace={Namespace}")]
7 | public sealed class DocumentInfo
8 | {
9 | public string RootElement { get; set; }
10 |
11 | public string Namespace { get; set; }
12 |
13 | public IReadOnlyCollection> Attributes { get; set; }
14 | }
15 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForDotNetCoreMSBuild.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
4 | {
5 | public class XmlFlavorForDotNetCoreMSBuild : XmlFlavorForMSBuild
6 | {
7 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, ElementNames.Project, StringComparison.Ordinal)
8 | && string.Equals(info.Namespace, "Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase);
9 | }
10 | }
--------------------------------------------------------------------------------
/Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Parser/Flavors/IXmlFlavor.cs:
--------------------------------------------------------------------------------
1 | using System.Xml;
2 |
3 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
4 |
5 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
6 | {
7 | public interface IXmlFlavor
8 | {
9 | bool ParseAttributesEnabled { get; }
10 |
11 | string GetName(XmlReader reader);
12 |
13 | string GetType(XmlReader reader);
14 |
15 | string GetContent(XmlReader reader);
16 |
17 | ContainerOrTerminalNode FinalAdjustAfterParsingComplete(ContainerOrTerminalNode node);
18 | }
19 | }
--------------------------------------------------------------------------------
/Parser/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | # https://docs.codecov.io/docs/codecov-yaml
2 | # https://github.com/codecov/support/wiki/Codecov-Yaml
3 |
4 | coverage:
5 | range: 70..100
6 | round: down
7 | precision: 2
8 |
9 | ignore:
10 | - Tests/
11 |
12 | coverage:
13 | status:
14 | project:
15 | default: off
16 | api:
17 | target: 100%
18 | flags: api
19 |
20 | flags:
21 | api:
22 | paths:
23 | - Parser/
24 | test:
25 | paths:
26 | - Tests/
27 |
28 | codecov:
29 | token: af3796ce-0f24-4014-95b1-c2d6dc80b800
--------------------------------------------------------------------------------
/Tests/Resources/test_without_declaration.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Tests/Resources/ApplicationManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Tests/Resources/VisualBuild_script.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | INSTALLATION_PATH
6 | c:\ProgramData\WAGO Software\e!COCKPIT
7 | 0
8 | Path to installation directory
9 |
10 |
11 | 9
12 | Full
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Tests/Resources/test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Parser/NodeType.cs:
--------------------------------------------------------------------------------
1 | using System.Xml;
2 |
3 | namespace MiKoSolutions.SemanticParsers.Xml
4 | {
5 | public static class NodeType
6 | {
7 | public const string Attribute = nameof(XmlNodeType.Attribute);
8 | public const string Element = nameof(XmlNodeType.Element);
9 | public const string CDATA = nameof(XmlNodeType.CDATA);
10 | public const string XmlDeclaration = nameof(XmlNodeType.XmlDeclaration);
11 | public const string Comment = nameof(XmlNodeType.Comment);
12 | public const string ProcessingInstruction = nameof(XmlNodeType.ProcessingInstruction);
13 | public const string Text = nameof(XmlNodeType.Text);
14 | }
15 | }
--------------------------------------------------------------------------------
/Tests/Resources/Settings_file.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | whatever
10 |
11 |
12 | any stuff
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Tests/Resources/FxCop_CustomDictionary.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | MEF
5 |
6 |
7 |
8 |
9 | setOn
10 |
11 |
12 | Intelli
13 | Mi
14 | Ko
15 | MiKoSolutions
16 | Unsealable
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Tests/Resources/NuSpec.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | My package
7 | 1.0.0.0
8 | Ralf Koban
9 | false
10 | This contains components that are part of my package.
11 | © 2018 MiKo Solutions. All rights reserved
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Tests/Resources/ModuleCatalog.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Parser/Yaml/YamlWriter.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | using MiKoSolutions.SemanticParsers.Xml.Yaml.Converters;
4 |
5 | using YamlDotNet.Serialization;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml
8 | {
9 | public static class YamlWriter
10 | {
11 | public static void Write(TextWriter writer, object graph)
12 | {
13 | var serializer = new SerializerBuilder()
14 | .WithTypeConverter(new CharacterSpanConverter())
15 | .WithTypeConverter(new LocationSpanConverter())
16 | .WithTypeConverter(new ParsingErrorConverter())
17 | .EmitDefaults() // Force even default values to be written, like 0, false.
18 | .Build();
19 | serializer.Serialize(writer, graph);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/Parser/Yaml/TerminalNode.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml
5 | {
6 | public sealed class TerminalNode : ContainerOrTerminalNode
7 | {
8 | [YamlMember(Alias = "span", Order = 4)]
9 | public CharacterSpan Span { get; set; }
10 |
11 | ///
12 | /// Gets the children the node had before it was converted from a to a .
13 | ///
14 | [YamlIgnore]
15 | public List Children { get; } = new List();
16 |
17 | public override CharacterSpan GetTotalSpan() => Span;
18 |
19 | public override TerminalNode ToTerminalNode() => this;
20 | }
21 | }
--------------------------------------------------------------------------------
/Parser/Tracer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace MiKoSolutions.SemanticParsers.Xml
5 | {
6 | public static class Tracer
7 | {
8 | private const string Category = "RKN Semantic";
9 |
10 | public static void Trace(string text) => System.Diagnostics.Trace.WriteLine(text, Category);
11 |
12 | public static void Trace(string text, Exception ex)
13 | {
14 | Trace(text);
15 |
16 | var stackTraceLines = ex.StackTrace?.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty();
17 | foreach (var stackTraceLine in stackTraceLines)
18 | {
19 | Trace(stackTraceLine);
20 | }
21 | }
22 |
23 | public static void Trace(object obj) => System.Diagnostics.Trace.WriteLine(obj, Category);
24 | }
25 | }
--------------------------------------------------------------------------------
/Tests/Resources/EdmxV3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Tests/Resources/NLS1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 2019-03-26
6 |
7 |
8 |
9 | English
10 | German
11 | US English
12 | German - Germany
13 | French - France
14 |
15 |
16 |
17 |
18 |
19 | pressure
20 | Druck
21 |
22 |
23 |
24 | MiKo Solutions
25 |
26 |
27 |
28 | Copyright (c) 2019 MiKo Solutions
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Tests/Resources/RuleSet.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Parser/Yaml/Converters/CharacterSpanConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using YamlDotNet.Core;
4 | using YamlDotNet.Core.Events;
5 | using YamlDotNet.Serialization;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml.Converters
8 | {
9 | public sealed class CharacterSpanConverter : IYamlTypeConverter
10 | {
11 | public bool Accepts(Type type) => type == typeof(CharacterSpan);
12 |
13 | public object ReadYaml(IParser parser, Type type) => throw new NotImplementedException();
14 |
15 | public void WriteYaml(IEmitter emitter, object value, Type type)
16 | {
17 | if (value is CharacterSpan span)
18 | {
19 | emitter.Emit(new SequenceStart(null, null, true, SequenceStyle.Flow));
20 | emitter.Emit(new Scalar(span.Start.ToString()));
21 | emitter.Emit(new Scalar(span.End.ToString()));
22 | emitter.Emit(new SequenceEnd());
23 | }
24 | else
25 | {
26 | throw new NotImplementedException("wrong type");
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/Parser/Yaml/File.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | using YamlDotNet.Serialization;
5 |
6 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml
7 | {
8 | public sealed class File
9 | {
10 | [YamlMember(Alias = "type", Order = 1)]
11 | public string Type { get; } = "file";
12 |
13 | [YamlMember(Alias = "name", Order = 2)]
14 | public string Name { get; set; }
15 |
16 | [YamlMember(Alias = "locationSpan", Order = 3)]
17 | public LocationSpan LocationSpan { get; set; }
18 |
19 | [YamlMember(Alias = "footerSpan", Order = 4)]
20 | public CharacterSpan FooterSpan { get; set; }
21 |
22 | [YamlMember(Alias = "children", Order = 7)]
23 | public List Children { get; } = new List();
24 |
25 | [YamlMember(Alias = "parsingErrorsDetected", Order = 5)]
26 | public bool? ParsingErrorsDetected => ParsingErrors.Any();
27 |
28 | [YamlMember(Alias = "parsingError", Order = 6)]
29 | public List ParsingErrors { get; } = new List();
30 | }
31 | }
--------------------------------------------------------------------------------
/Parser/Yaml/LocationSpan.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 |
4 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml
5 | {
6 | [DebuggerDisplay("Start: {Start}, End: {End}")]
7 | public struct LocationSpan : IEquatable
8 | {
9 | public LocationSpan(LineInfo start, LineInfo end)
10 | {
11 | Start = start;
12 | End = end;
13 | }
14 |
15 | public LineInfo Start { get; }
16 |
17 | public LineInfo End { get; }
18 |
19 | public static bool operator ==(LocationSpan left, LocationSpan right) => Equals(left, right);
20 |
21 | public static bool operator !=(LocationSpan left, LocationSpan right) => !Equals(left, right);
22 |
23 | public bool Equals(LocationSpan other) => Start == other.Start && End == other.End;
24 |
25 | public override bool Equals(object obj) => obj is LocationSpan other && Equals(other);
26 |
27 | public override int GetHashCode()
28 | {
29 | unchecked
30 | {
31 | return (Start.GetHashCode() * 397) ^ End.GetHashCode();
32 | }
33 | }
34 |
35 | public override string ToString() => $"Start: {Start}, End: {End}";
36 | }
37 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForWixConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Xml;
4 |
5 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
6 | {
7 | public sealed class XmlFlavorForWixConfiguration : XmlFlavorForWix
8 | {
9 | public override bool ParseAttributesEnabled => true;
10 |
11 | public override bool Supports(string filePath) => filePath.EndsWith(".wxi", StringComparison.OrdinalIgnoreCase);
12 |
13 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "Include", StringComparison.OrdinalIgnoreCase);
14 |
15 | public override string GetName(XmlReader reader)
16 | {
17 | if (reader.NodeType == XmlNodeType.ProcessingInstruction)
18 | {
19 | var name = reader.LocalName;
20 | var parts = reader.Value.Split('=');
21 | var identifier = parts.Any() ? parts[0].Trim() : null;
22 | return identifier is null ? name : $"{name} '{identifier}'";
23 | }
24 |
25 | return base.GetName(reader);
26 | }
27 |
28 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
29 | }
30 | }
--------------------------------------------------------------------------------
/Parser/Resorter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
5 |
6 | namespace MiKoSolutions.SemanticParsers.Xml
7 | {
8 | public static class Resorter
9 | {
10 | public static void Resort(File file)
11 | {
12 | file.Children.Sort(CompareStartPosition);
13 |
14 | foreach (var node in file.Children)
15 | {
16 | Resort(node.Children);
17 | }
18 | }
19 |
20 | private static void Resort(List nodes)
21 | {
22 | nodes.Sort(CompareStartPosition);
23 |
24 | foreach (var node in nodes.OfType())
25 | {
26 | Resort(node.Children);
27 | }
28 | }
29 |
30 | private static int CompareStartPosition(ContainerOrTerminalNode x, ContainerOrTerminalNode y)
31 | {
32 | var startX = x.LocationSpan.Start;
33 | var startY = y.LocationSpan.Start;
34 |
35 | var result = startX.LineNumber - startY.LineNumber;
36 | if (result == 0)
37 | {
38 | result = startX.LinePosition - startY.LinePosition;
39 | }
40 |
41 | return result;
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForNCrunchSolution.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Xml;
3 |
4 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
5 |
6 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
7 | {
8 | public sealed class XmlFlavorForNCrunchSolution : XmlFlavor
9 | {
10 | public override bool Supports(string filePath) => filePath.EndsWith(".ncrunchsolution", StringComparison.OrdinalIgnoreCase);
11 |
12 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "SolutionConfiguration", StringComparison.OrdinalIgnoreCase);
13 |
14 | public override string GetName(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetName(reader);
15 |
16 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
17 |
18 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node)
19 | {
20 | switch (node.Type)
21 | {
22 | case "SolutionConfiguration":
23 | case "Settings":
24 | case "HotSpotsExclusionList":
25 | case "MetricsExclusionList":
26 | return false;
27 |
28 | default:
29 | return true;
30 | }
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/Parser/Yaml/Container.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | using YamlDotNet.Serialization;
4 |
5 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml
6 | {
7 | public sealed class Container : ContainerOrTerminalNode
8 | {
9 | [YamlMember(Alias = "headerSpan", Order = 4)]
10 | public CharacterSpan HeaderSpan { get; set; }
11 |
12 | [YamlMember(Alias = "footerSpan", Order = 5)]
13 | public CharacterSpan FooterSpan { get; set; }
14 |
15 | [YamlMember(Alias = "children", Order = 6)]
16 | public List Children { get; } = new List();
17 |
18 | public override CharacterSpan GetTotalSpan() => new CharacterSpan(HeaderSpan.Start, FooterSpan.End);
19 |
20 | public override TerminalNode ToTerminalNode()
21 | {
22 | var terminalNode = new TerminalNode
23 | {
24 | Type = Type,
25 | Name = Name,
26 | Content = Content,
27 | LocationSpan = LocationSpan,
28 | Span = GetTotalSpan(),
29 | };
30 | terminalNode.Children.AddRange(Children);
31 |
32 | return terminalNode;
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_NLS.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public sealed class ParserTests_NLS
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "NLS1.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches()
29 | {
30 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "NLS1.xml"));
31 | }
32 |
33 | [Test]
34 | public void Category_string_matches()
35 | {
36 | var node = _objectUnderTest.Children.First(_ => _.Type == "localization").Children.OfType().First(_ => _.Type == "descriptions").Children.First(_ => _.Type == "string");
37 | Assert.That(node.Name, Is.EqualTo("ID_CATEGORY_OF_DEVICE"));
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/Parser/Yaml/CharacterSpan.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml
4 | {
5 | public struct CharacterSpan : IEquatable
6 | {
7 | public static readonly CharacterSpan None = new CharacterSpan(0, -1);
8 |
9 | public CharacterSpan(int start, int end)
10 | {
11 | if (start > end && (start != 0 || end != -1))
12 | {
13 | throw new ArgumentException($"{nameof(start)} should be less than {nameof(end)} but {start} is greater than {end}!", nameof(start));
14 | }
15 |
16 | Start = start;
17 | End = end;
18 | }
19 |
20 | public int Start { get; }
21 |
22 | public int End { get; }
23 |
24 | public static bool operator ==(CharacterSpan left, CharacterSpan right) => Equals(left, right);
25 |
26 | public static bool operator !=(CharacterSpan left, CharacterSpan right) => !Equals(left, right);
27 |
28 | public bool Equals(CharacterSpan other) => Start == other.Start && End == other.End;
29 |
30 | public override bool Equals(object obj) => obj is CharacterSpan other && Equals(other);
31 |
32 | public override int GetHashCode()
33 | {
34 | unchecked
35 | {
36 | return (Start * 397) ^ End;
37 | }
38 | }
39 |
40 | public override string ToString() => $"Span: {Start}, {End}";
41 | }
42 | }
--------------------------------------------------------------------------------
/Parser/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("Parser")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("MiKo Solutions")]
11 | [assembly: AssemblyProduct("XmlSemanticParser")]
12 | [assembly: AssemblyCopyright("Copyright © MiKo Solutions 2018")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("c977cf35-e810-4b3f-a636-d8b56763d090")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("0.0.0.2")]
35 | [assembly: AssemblyFileVersion("0.0.0.2")]
36 |
--------------------------------------------------------------------------------
/Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("Tests")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("MiKo Solutions")]
11 | [assembly: AssemblyProduct("XmlSemanticParser")]
12 | [assembly: AssemblyCopyright("Copyright © MiKo Solutions 2018")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("431219e1-fc29-4d1b-8ed7-92549c2e2cd6")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("1.0.0.0")]
35 | [assembly: AssemblyFileVersion("1.0.0.0")]
36 |
--------------------------------------------------------------------------------
/Parser/Yaml/Converters/ParsingErrorConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using YamlDotNet.Core;
4 | using YamlDotNet.Core.Events;
5 | using YamlDotNet.Serialization;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml.Converters
8 | {
9 | public sealed class ParsingErrorConverter : IYamlTypeConverter
10 | {
11 | public bool Accepts(Type type) => type == typeof(ParsingError);
12 |
13 | public object ReadYaml(IParser parser, Type type) => throw new NotImplementedException();
14 |
15 | public void WriteYaml(IEmitter emitter, object value, Type type)
16 | {
17 | if (value is ParsingError error)
18 | {
19 | emitter.Emit(new MappingStart(null, null, true, MappingStyle.Block));
20 |
21 | // location
22 | emitter.Emit(new Scalar("location"));
23 |
24 | emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Flow));
25 | emitter.Emit(new Scalar(error.Location.LineNumber.ToString()));
26 | emitter.Emit(new Scalar(error.Location.LinePosition.ToString()));
27 | emitter.Emit(new SequenceEnd());
28 |
29 | // message
30 | emitter.Emit(new Scalar("message"));
31 | emitter.Emit(new Scalar(null, null, error.ErrorMessage, ScalarStyle.SingleQuoted, false, true));
32 |
33 | emitter.Emit(new MappingEnd());
34 | }
35 | else
36 | {
37 | throw new NotImplementedException("wrong type");
38 | }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/Parser/Yaml/ContainerOrTerminalNode.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | using YamlDotNet.Serialization;
4 |
5 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml
6 | {
7 | [DebuggerDisplay("Type={Type}, Name={Name}, ClassType={GetType().Name}")]
8 | public abstract class ContainerOrTerminalNode
9 | {
10 | [YamlIgnore]
11 | private string _type;
12 |
13 | [YamlIgnore]
14 | private string _name;
15 |
16 | [YamlIgnore]
17 | private string _content;
18 |
19 | [YamlMember(Alias = "type", Order = 1)]
20 | public string Type
21 | {
22 | get => _type;
23 | set => _type = value is null ? null : string.Intern(value); // performance optimization for large files
24 | }
25 |
26 | [YamlMember(Alias = "name", Order = 2)]
27 | public string Name
28 | {
29 | get => _name;
30 | set => _name = value is null ? null : string.Intern(value); // performance optimization for large files
31 | }
32 |
33 | [YamlMember(Alias = "locationSpan", Order = 3)]
34 | public LocationSpan LocationSpan { get; set; }
35 |
36 | [YamlIgnore]
37 | public string Content
38 | {
39 | get => _content;
40 | set => _content = value is null ? null : string.IsInterned(value) ?? value; // performance optimization for large files (if we have the string interned, we can re-use it)
41 | }
42 |
43 | public abstract CharacterSpan GetTotalSpan();
44 |
45 | public abstract TerminalNode ToTerminalNode();
46 | }
47 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForPackagesConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Xml;
3 |
4 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
5 |
6 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
7 | {
8 | public sealed class XmlFlavorForPackagesConfig : XmlFlavor
9 | {
10 | public override bool ParseAttributesEnabled => false;
11 |
12 | public override bool Supports(string filePath) => filePath.EndsWith(".config", StringComparison.OrdinalIgnoreCase); // do not check for "packages.config" as e.g. TFS might use a temp .config file instead
13 |
14 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "packages", StringComparison.OrdinalIgnoreCase);
15 |
16 | public override string GetName(XmlReader reader)
17 | {
18 | switch (reader.NodeType)
19 | {
20 | case XmlNodeType.Element:
21 | {
22 | return reader.GetAttribute("id") ?? reader.LocalName;
23 | }
24 |
25 | case XmlNodeType.Attribute:
26 | {
27 | return $"{reader.LocalName} '{reader.Value}'";
28 | }
29 |
30 | default:
31 | {
32 | return base.GetName(reader);
33 | }
34 | }
35 | }
36 |
37 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
38 |
39 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => node?.Type == "package";
40 | }
41 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_WIX_Config.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using NUnit.Framework;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml
8 | {
9 | [TestFixture]
10 | public class ParserTests_WIX_Config
11 | {
12 | private Yaml.File _objectUnderTest;
13 | private Yaml.Container _root;
14 |
15 | [SetUp]
16 | public void PrepareTest()
17 | {
18 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
19 | var fileName = Path.Combine(parentDirectory, "Resources", "WIX_config.xml");
20 |
21 | _objectUnderTest = Parser.Parse(fileName);
22 | _root = _objectUnderTest.Children.Single();
23 | }
24 |
25 | [Test]
26 | public void Root_has_correct_amount_of_children()
27 | {
28 | Assert.That(_root.Children, Has.Count.EqualTo(3));
29 | }
30 |
31 | [Test]
32 | public void Defines_have_correct_names()
33 | {
34 | var names = _root.Children.Select(_ => _.Name).ToList();
35 | var foundNames = string.Join(",", names);
36 |
37 | Assert.That(names.Contains("define 'Something'"), foundNames);
38 | Assert.That(names.Contains("define 'Whatever'"), foundNames);
39 | }
40 |
41 | [Test]
42 | public void Property_has_correct_names()
43 | {
44 | var names = _root.Children.Select(_ => _.Name).ToList();
45 | var foundNames = string.Join(",", names);
46 |
47 | Assert.That(names.Contains("some property"), foundNames);
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/Parser/Yaml/Converters/LocationSpanConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using YamlDotNet.Core;
4 | using YamlDotNet.Core.Events;
5 | using YamlDotNet.Serialization;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml.Converters
8 | {
9 | public sealed class LocationSpanConverter : IYamlTypeConverter
10 | {
11 | public bool Accepts(Type type) => type == typeof(LocationSpan);
12 |
13 | public object ReadYaml(IParser parser, Type type) => throw new NotImplementedException();
14 |
15 | public void WriteYaml(IEmitter emitter, object value, Type type)
16 | {
17 | if (value is LocationSpan span)
18 | {
19 | emitter.Emit(new MappingStart(null, null, true, MappingStyle.Flow));
20 |
21 | // start
22 | emitter.Emit(new Scalar("start"));
23 |
24 | emitter.Emit(new SequenceStart(null, null, true, SequenceStyle.Flow));
25 | emitter.Emit(new Scalar(span.Start.LineNumber.ToString()));
26 | emitter.Emit(new Scalar(span.Start.LinePosition.ToString()));
27 | emitter.Emit(new SequenceEnd());
28 |
29 | // end
30 | emitter.Emit(new Scalar("end"));
31 |
32 | emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Flow));
33 | emitter.Emit(new Scalar(span.End.LineNumber.ToString()));
34 | emitter.Emit(new Scalar(span.End.LinePosition.ToString()));
35 | emitter.Emit(new SequenceEnd());
36 |
37 | emitter.Emit(new MappingEnd());
38 | }
39 | else
40 | {
41 | throw new NotImplementedException("wrong type");
42 | }
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_PackagesConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | using NUnit.Framework;
9 |
10 | namespace MiKoSolutions.SemanticParsers.Xml
11 | {
12 | [TestFixture]
13 | public class ParserTests_PackagesConfig
14 | {
15 | private Yaml.File _objectUnderTest;
16 | private Yaml.Container _packages;
17 |
18 | [SetUp]
19 | public void PrepareTest()
20 | {
21 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
22 | var fileName = Path.Combine(parentDirectory, "Resources", "packages.xml");
23 |
24 | _objectUnderTest = Parser.Parse(fileName);
25 | _packages = _objectUnderTest.Children.Single();
26 | }
27 |
28 | [Test]
29 | public void Packages_has_correct_amount_of_children()
30 | {
31 | Assert.That(_packages.Children, Has.Count.EqualTo(3));
32 | }
33 |
34 | [Test]
35 | public void Packages_have_correct_names()
36 | {
37 | var names = _packages.Children.Select(_ => _.Name).ToList();
38 | var foundNames = string.Join(",", names);
39 |
40 | Assert.That(names.Contains("NUnit"), foundNames);
41 | Assert.That(names.Contains("StyleCop.Analyzers"), foundNames);
42 | Assert.That(names.Contains("YamlDotNet"), foundNames);
43 | }
44 |
45 | [Test]
46 | public void RoundTrip_does_not_report_parsing_errors()
47 | {
48 | var builder = new StringBuilder();
49 | using (var writer = new StringWriter(builder))
50 | {
51 | YamlWriter.Write(writer, _objectUnderTest);
52 | }
53 |
54 | Assert.That(builder.ToString(), Does.Contain("parsingErrorsDetected: false"));
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForSettings : XmlFlavor
10 | {
11 | private static readonly HashSet TerminalNodeNames = new HashSet
12 | {
13 | "Setting",
14 | "Value",
15 | };
16 |
17 | public override bool ParseAttributesEnabled => false;
18 |
19 | public override bool Supports(string filePath) => filePath.EndsWith(".settings", StringComparison.OrdinalIgnoreCase);
20 |
21 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "SettingsFile", StringComparison.OrdinalIgnoreCase)
22 | && string.Equals(info.Namespace, "http://schemas.microsoft.com/VisualStudio/2004/01/settings", StringComparison.OrdinalIgnoreCase);
23 |
24 | public override string GetName(XmlReader reader)
25 | {
26 | if (reader.NodeType == XmlNodeType.Element)
27 | {
28 | var name = reader.LocalName;
29 | var identifier = reader.GetAttribute("Name");
30 | return identifier ?? name;
31 | }
32 |
33 | return base.GetName(reader);
34 | }
35 |
36 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
37 |
38 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
39 | }
40 | }
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # Documentation/Reference: https://www.appveyor.com/docs/appveyor-yml/
2 |
3 | # Semantic Versioning http://semver.org/
4 | version: 0.0.{build}
5 |
6 | image: Visual Studio 2017
7 |
8 | # branches to build
9 | branches:
10 | # whitelist
11 | only:
12 | - master
13 |
14 | init:
15 | - git config --global core.autocrlf true
16 |
17 | # environment:
18 |
19 | install:
20 | # - choco install gitversion.portable -pre -y
21 |
22 | # Install NUnit console runner
23 | # - ps: Start-FileDownload $env:NUnitRunnerUri -FileName NUnitInstaller.zip
24 | # - cmd: 7z x NUnitInstaller.zip -y
25 |
26 | # clone directory
27 | clone_folder: c:\projects\XmlSemanticParsers
28 |
29 | # fetch repository as zip archive (when next line is uncommented)
30 | # shallow_clone: true
31 |
32 | # skip specific commits
33 | skip_commits:
34 | files:
35 | - /*.md
36 |
37 | before_build:
38 | - nuget restore "c:\projects\XmlSemanticParsers\XmlSemanticParser.sln"
39 |
40 | # enable patching of AssemblyInfo.* files
41 | assembly_info:
42 | patch: true
43 | file: AssemblyInfo.*
44 | assembly_version: "{version}"
45 | assembly_file_version: "{version}"
46 | assembly_informational_version: "{version}"
47 |
48 | build:
49 | parallel: true
50 | project: XmlSemanticParser.sln
51 | verbosity: minimal
52 |
53 | platform:
54 | - Any CPU
55 |
56 | configuration:
57 | - Debug
58 | # - Release
59 |
60 | test_script:
61 | - .\packages\OpenCover.4.6.519\tools\OpenCover.Console.exe -register:user -target:"nunit3-console.exe" -targetargs:" "".\Tests\bin\Tests.dll"" --workers=16 " -returntargetcode -filter:"+[MiKo*]* -[*Test*]*" -excludebyattribute:*.ExcludeFromCodeCoverage* -excludebyfile:*\*Designer.cs -hideskipped:All -output:"c:\projects\XmlSemanticParsers\CodeCoverage.xml"
62 |
63 | # Codecov Flags (https://docs.codecov.io/v4.3.6/docs/flags)
64 | - .\packages\Codecov.1.1.0\tools\codecov.exe -f "c:\projects\XmlSemanticParsers\CodeCoverage.xml" --flag api
65 |
66 | # cache:
67 |
68 |
--------------------------------------------------------------------------------
/XmlSemanticParser.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27703.2035
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parser", "Parser\Parser.csproj", "{C977CF35-E810-4B3F-A636-D8B56763D090}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Github", "Github", "{9432608C-F798-4550-9FC4-550FE5E5E40B}"
9 | ProjectSection(SolutionItems) = preProject
10 | .gitignore = .gitignore
11 | appveyor.yml = appveyor.yml
12 | codecov.yml = codecov.yml
13 | README.md = README.md
14 | EndProjectSection
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{431219E1-FC29-4D1B-8ED7-92549C2E2CD6}"
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Release|Any CPU = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {C977CF35-E810-4B3F-A636-D8B56763D090}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {C977CF35-E810-4B3F-A636-D8B56763D090}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {C977CF35-E810-4B3F-A636-D8B56763D090}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {C977CF35-E810-4B3F-A636-D8B56763D090}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {431219E1-FC29-4D1B-8ED7-92549C2E2CD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {431219E1-FC29-4D1B-8ED7-92549C2E2CD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {431219E1-FC29-4D1B-8ED7-92549C2E2CD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {431219E1-FC29-4D1B-8ED7-92549C2E2CD6}.Release|Any CPU.Build.0 = Release|Any CPU
32 | EndGlobalSection
33 | GlobalSection(SolutionProperties) = preSolution
34 | HideSolutionNode = FALSE
35 | EndGlobalSection
36 | GlobalSection(ExtensibilityGlobals) = postSolution
37 | SolutionGuid = {0639B1E2-1FED-42DC-B821-5C795D02DC9A}
38 | EndGlobalSection
39 | EndGlobal
40 |
--------------------------------------------------------------------------------
/Parser/Yaml/LineInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using YamlDotNet.Serialization;
4 |
5 | namespace MiKoSolutions.SemanticParsers.Xml.Yaml
6 | {
7 | public struct LineInfo : IEquatable, IComparable
8 | {
9 | public static readonly LineInfo None = new LineInfo(0, -1);
10 |
11 | public LineInfo(int lineNumber, int linePosition)
12 | {
13 | LineNumber = lineNumber;
14 | LinePosition = linePosition;
15 | }
16 |
17 | [YamlMember(Alias = "line")]
18 | public int LineNumber { get; }
19 |
20 | [YamlMember(Alias = "column")]
21 | public int LinePosition { get; }
22 |
23 | public static bool operator ==(LineInfo left, LineInfo right) => Equals(left, right);
24 |
25 | public static bool operator !=(LineInfo left, LineInfo right) => !Equals(left, right);
26 |
27 | public static bool operator <(LineInfo left, LineInfo right) => left.CompareTo(right) < 0;
28 |
29 | public static bool operator >(LineInfo left, LineInfo right) => left.CompareTo(right) > 0;
30 |
31 | public bool Equals(LineInfo other) => LineNumber == other.LineNumber && LinePosition == other.LinePosition;
32 |
33 | public override bool Equals(object obj) => obj is LineInfo other && Equals(other);
34 |
35 | public override int GetHashCode()
36 | {
37 | unchecked
38 | {
39 | return (LineNumber * 397) ^ LinePosition;
40 | }
41 | }
42 |
43 | public int CompareTo(LineInfo other)
44 | {
45 | if (LineNumber < other.LineNumber)
46 | {
47 | return -1;
48 | }
49 |
50 | if (LineNumber > other.LineNumber)
51 | {
52 | return 1;
53 | }
54 |
55 | return LinePosition - other.LinePosition;
56 | }
57 |
58 | public override string ToString() => $"Line: {LineNumber}, Position: {LinePosition}";
59 | }
60 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForWixLocation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForWixLocation : XmlFlavor
10 | {
11 | private static readonly HashSet TerminalNodeNames = new HashSet
12 | {
13 | "String",
14 | "UI",
15 | };
16 |
17 | public override bool ParseAttributesEnabled => false;
18 |
19 | public override bool Supports(string filePath) => filePath.EndsWith(".wxl", StringComparison.OrdinalIgnoreCase);
20 |
21 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "WixLocalization", StringComparison.OrdinalIgnoreCase)
22 | && string.Equals(info.Namespace, "http://schemas.microsoft.com/wix/2006/localization", StringComparison.OrdinalIgnoreCase);
23 |
24 | public override string GetName(XmlReader reader)
25 | {
26 | if (reader.NodeType == XmlNodeType.Element)
27 | {
28 | var name = reader.LocalName;
29 | var identifier = reader.GetAttribute("Id") ?? reader.GetAttribute("Control") ?? reader.GetAttribute("Dialog");
30 | return identifier is null ? name : $"{name} '{identifier}'";
31 | }
32 |
33 | return base.GetName(reader);
34 | }
35 |
36 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
37 |
38 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
39 | }
40 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForFxCop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
9 | {
10 | public sealed class XmlFlavorForFxCop : XmlFlavor
11 | {
12 | private static readonly HashSet TerminalNodeNames = new HashSet
13 | {
14 | "Acronym",
15 | "Term",
16 | "Word",
17 | };
18 |
19 | public override bool ParseAttributesEnabled => false;
20 |
21 | public override bool Supports(string filePath) => filePath.StartsWith("CustomDictionary", StringComparison.OrdinalIgnoreCase) && filePath.EndsWith(".xml", StringComparison.OrdinalIgnoreCase);
22 |
23 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "Dictionary", StringComparison.OrdinalIgnoreCase);
24 |
25 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
26 |
27 | public override ContainerOrTerminalNode FinalAdjustAfterParsingComplete(ContainerOrTerminalNode node)
28 | {
29 | if (node is Container c)
30 | {
31 | var textNode = c.Children.FirstOrDefault(_ => _.Type == NodeType.Text);
32 | if (textNode != null)
33 | {
34 | c.Name = textNode.Content;
35 | }
36 | }
37 |
38 | return base.FinalAdjustAfterParsingComplete(node);
39 | }
40 |
41 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
42 | }
43 | }
--------------------------------------------------------------------------------
/Tests/CharacterPositionFinderTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
5 |
6 | using NUnit.Framework;
7 |
8 | namespace MiKoSolutions.SemanticParsers.Xml
9 | {
10 | [TestFixture]
11 | public sealed class CharacterPositionFinderTests
12 | {
13 | private CharacterPositionFinder _objectUnderTest;
14 |
15 | [SetUp]
16 | public void PrepareTest()
17 | {
18 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
19 | var fileName = Path.Combine(parentDirectory, "Resources", "test.xml");
20 |
21 | _objectUnderTest = CharacterPositionFinder.CreateFrom(fileName, Encoding.Unicode);
22 | }
23 |
24 | [TestCase(3, 10, ExpectedResult = 63)]
25 | [TestCase(18, 12, ExpectedResult = 361)]
26 | public int GetCharacterPosition(int lineNumber, int linePosition) => _objectUnderTest.GetCharacterPosition(new LineInfo(lineNumber, linePosition));
27 |
28 | [TestCase(3, 10, ExpectedResult = 63)]
29 | [TestCase(18, 12, ExpectedResult = 361)]
30 | public int GetCharacterPosition_raw(int lineNumber, int linePosition) => _objectUnderTest.GetCharacterPosition(lineNumber, linePosition);
31 |
32 | [TestCase(3, ExpectedResult = 26)]
33 | [TestCase(18, ExpectedResult = 12)]
34 | public int GetLineLength(int lineNumber) => _objectUnderTest.GetLineLength(lineNumber);
35 |
36 | [TestCase(3, 10, ExpectedResult = 26)]
37 | [TestCase(18, 12, ExpectedResult = 12)]
38 | [TestCase(28, 5, ExpectedResult = 14)]
39 | [TestCase(29, 1, ExpectedResult = 0)]
40 | public int GetLineLength(int lineNumber, int linePosition) => _objectUnderTest.GetLineLength(new LineInfo(lineNumber, linePosition));
41 |
42 | [TestCase(3, 10, 63)]
43 | [TestCase(18, 12, 361)]
44 | public void GetLineInfo(int lineNumber, int linePosition, int characterPosition) => Assert.That(_objectUnderTest.GetLineInfo(characterPosition), Is.EqualTo(new LineInfo(lineNumber, linePosition)));
45 | }
46 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_Manifest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_Manifest
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "ApplicationManifest.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches() => Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "ApplicationManifest.xml"));
29 |
30 | [Test]
31 | public void File_LocationSpan_matches()
32 | {
33 | Assert.Multiple(() =>
34 | {
35 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
36 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(11, 0)), "Wrong end");
37 |
38 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(new CharacterSpan(574, 575)), "Wrong footer");
39 | });
40 | }
41 |
42 | [Test]
43 | public void Root_LocationSpan_matches()
44 | {
45 | Assert.Multiple(() =>
46 | {
47 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
48 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(10, 17)), "Wrong end");
49 |
50 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 268)), "Wrong header");
51 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(557, 573)), "Wrong footer");
52 | });
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_CommentAfterDeclaration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | public class ParserTests_CommentAfterDeclaration
12 | {
13 | private Yaml.File _objectUnderTest;
14 | private Yaml.Container _root;
15 |
16 | [SetUp]
17 | public void PrepareTest()
18 | {
19 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
20 | var fileName = Path.Combine(parentDirectory, "Resources", "CommentAfterDeclaration.xml");
21 |
22 | _objectUnderTest = Parser.Parse(fileName);
23 | _root = _objectUnderTest.Children.Single();
24 | }
25 |
26 | [Test]
27 | public void File_Name_matches() => Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "CommentAfterDeclaration.xml"));
28 |
29 | [Test]
30 | public void File_LocationSpan_matches()
31 | {
32 | Assert.Multiple(() =>
33 | {
34 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
35 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(3, 13)), "Wrong end");
36 |
37 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
38 | });
39 | }
40 |
41 | [Test]
42 | public void Root_LocationSpan_matches()
43 | {
44 | Assert.Multiple(() =>
45 | {
46 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
47 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(3, 13)), "Wrong end");
48 |
49 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 74)), "Wrong header");
50 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(75, 76)), "Wrong footer");
51 | });
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_CommentWithoutDeclaration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | public class ParserTests_CommentWithoutDeclaration
12 | {
13 | private Yaml.File _objectUnderTest;
14 | private Yaml.Container _root;
15 |
16 | [SetUp]
17 | public void PrepareTest()
18 | {
19 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
20 | var fileName = Path.Combine(parentDirectory, "Resources", "CommentWithoutDeclaration.xml");
21 |
22 | _objectUnderTest = Parser.Parse(fileName);
23 | _root = _objectUnderTest.Children.Single();
24 | }
25 |
26 | [Test]
27 | public void File_Name_matches() => Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "CommentWithoutDeclaration.xml"));
28 |
29 | [Test]
30 | public void File_LocationSpan_matches()
31 | {
32 | Assert.Multiple(() =>
33 | {
34 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
35 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(2, 13)), "Wrong end");
36 |
37 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
38 | });
39 | }
40 |
41 | [Test]
42 | public void Root_LocationSpan_matches()
43 | {
44 | Assert.Multiple(() =>
45 | {
46 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
47 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(2, 13)), "Wrong end");
48 |
49 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 33)), "Wrong header");
50 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(34, 35)), "Wrong footer");
51 | });
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_CommentAfterEnd.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | public class ParserTests_CommentAfterEnd
12 | {
13 | private Yaml.File _objectUnderTest;
14 | private Yaml.Container _root;
15 |
16 | [SetUp]
17 | public void PrepareTest()
18 | {
19 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
20 | var fileName = Path.Combine(parentDirectory, "Resources", "CommentAfterEnd.xml");
21 |
22 | _objectUnderTest = Parser.Parse(fileName);
23 | _root = _objectUnderTest.Children.Single();
24 | }
25 |
26 | [Test]
27 | public void File_Name_matches()
28 | {
29 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "CommentAfterEnd.xml"));
30 | }
31 |
32 | [Test]
33 | public void File_LocationSpan_matches()
34 | {
35 | Assert.Multiple(() =>
36 | {
37 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
38 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(10, 0)), "Wrong end");
39 |
40 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(new CharacterSpan(33, 305)), "Wrong footer");
41 | });
42 | }
43 |
44 | [Test]
45 | public void Root_LocationSpan_matches()
46 | {
47 | Assert.Multiple(() =>
48 | {
49 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
50 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(2, 16)), "Wrong end");
51 |
52 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 14)), "Wrong header");
53 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(17, 32)), "Wrong footer");
54 | });
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForModuleCatalogXaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForModuleCatalogXaml : XmlFlavor
10 | {
11 | private const string PrismNamespace = "clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism";
12 |
13 | private const string ModuleInfo = "ModuleInfo";
14 |
15 | private static readonly HashSet TerminalNodeNames = new HashSet
16 | {
17 | ModuleInfo,
18 | };
19 |
20 | public override bool ParseAttributesEnabled => false;
21 |
22 | public override bool Supports(string filePath) => filePath.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase);
23 |
24 | public override bool Supports(DocumentInfo info) =>
25 | string.Equals(info.RootElement, "ModuleCatalog", StringComparison.OrdinalIgnoreCase) &&
26 | string.Equals(info.Namespace, PrismNamespace, StringComparison.OrdinalIgnoreCase);
27 |
28 | public override string GetName(XmlReader reader)
29 | {
30 | if (reader.NodeType == XmlNodeType.Element)
31 | {
32 | var name = reader.LocalName;
33 | switch (name)
34 | {
35 | case ModuleInfo:
36 | {
37 | var identifier = reader.GetAttribute("ModuleName");
38 | return identifier ?? name;
39 | }
40 |
41 | default:
42 | return name;
43 | }
44 | }
45 |
46 | return base.GetName(reader);
47 | }
48 |
49 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
50 |
51 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
52 | }
53 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_RegistryValue.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture("registry_value.xml")]
12 | [TestFixture("registry_value_resorted.xml")]
13 | public class ParserTests_RegistryValue
14 | {
15 | private readonly string _fileName;
16 | private Yaml.File _objectUnderTest;
17 | private Yaml.Container _root;
18 |
19 | public ParserTests_RegistryValue(string fileName) => _fileName = fileName;
20 |
21 | [SetUp]
22 | public void PrepareTest()
23 | {
24 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
25 | var fileName = Path.Combine(parentDirectory, "Resources", _fileName);
26 |
27 | _objectUnderTest = Parser.Parse(fileName);
28 | _root = _objectUnderTest.Children.Single();
29 | }
30 |
31 | [Test]
32 | public void File_Name_matches() => Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + _fileName));
33 |
34 | [Test]
35 | public void File_LocationSpan_matches()
36 | {
37 | Assert.Multiple(() =>
38 | {
39 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
40 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(1, 110)), "Wrong end");
41 |
42 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
43 | });
44 | }
45 |
46 | [Test]
47 | public void Root_LocationSpan_matches()
48 | {
49 | Assert.Multiple(() =>
50 | {
51 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
52 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(1, 110)), "Wrong end");
53 |
54 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 107)), "Wrong header");
55 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(108, 109)), "Wrong footer");
56 | });
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForDotCover.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForDotCover : XmlFlavor
10 | {
11 | private const string Assembly = "Assembly";
12 | private const string Namespace = "Namespace";
13 | private const string Type = "Type";
14 | private const string Method = "Method";
15 |
16 | private static readonly HashSet TerminalNodeNames = new HashSet
17 | {
18 | Method,
19 | };
20 |
21 | public override bool ParseAttributesEnabled => false;
22 |
23 | public override bool Supports(DocumentInfo info) => info.Attributes.Any(_ => _.Key == "DotCoverVersion") &&
24 | info.Attributes.Any(_ => _.Key == "ReportType" && _.Value == "NDependXML");
25 |
26 | public override string GetName(XmlReader reader)
27 | {
28 | if (reader.NodeType == XmlNodeType.Element)
29 | {
30 | var name = reader.LocalName;
31 | var attributeName = GetAttribute(name);
32 | var identifier = string.IsNullOrWhiteSpace(attributeName) ? null : reader.GetAttribute(attributeName);
33 | return identifier ?? name;
34 | }
35 |
36 | return base.GetName(reader);
37 | }
38 |
39 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
40 |
41 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
42 |
43 | private static string GetAttribute(string name)
44 | {
45 | switch (name)
46 | {
47 | case Assembly:
48 | case Namespace:
49 | case Type:
50 | case Method:
51 | return "Name";
52 |
53 | default:
54 | return null;
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Tests/ParserTests_RuleSet.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | using NUnit.Framework;
9 |
10 | namespace MiKoSolutions.SemanticParsers.Xml
11 | {
12 | [TestFixture]
13 | public class ParserTests_RuleSet
14 | {
15 | private Yaml.File _objectUnderTest;
16 | private Yaml.Container _root;
17 |
18 | [SetUp]
19 | public void PrepareTest()
20 | {
21 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
22 | var fileName = Path.Combine(parentDirectory, "Resources", "RuleSet.xml");
23 |
24 | _objectUnderTest = Parser.Parse(fileName);
25 | _root = _objectUnderTest.Children.Single();
26 | }
27 |
28 | [Test]
29 | public void Root_has_correct_name() => Assert.That(_root.Name, Is.EqualTo("Microsoft Managed Recommended Rules"));
30 |
31 | [TestCase("Rules", "Microsoft.Analyzers.ManagedCodeAnalysis")]
32 | [TestCase("Rules", "StyleCop.Analyzers")]
33 | public void Rules_group_is_found_properly(string groupType, string name)
34 | {
35 | var item = _root.Children.OfType().Where(_ => _.Type == groupType).Any(_ => _.Name == name);
36 |
37 | Assert.That(item, Is.True);
38 | }
39 |
40 | [TestCase("Rules", "Microsoft.Analyzers.ManagedCodeAnalysis", "CA1001")]
41 | [TestCase("Rules", "Microsoft.Analyzers.ManagedCodeAnalysis", "CA1009")]
42 | [TestCase("Rules", "StyleCop.Analyzers", "SA1120")]
43 | public void Rule_is_found_properly(string groupType, string name, string ruleName)
44 | {
45 | var item = _root.Children.OfType().Where(_ => _.Type == groupType && _.Name == name).SelectMany(_ => _.Children).Any(_ => _.Name == ruleName);
46 |
47 | Assert.That(item, Is.True);
48 | }
49 |
50 | [Test]
51 | public void RoundTrip_does_not_report_parsing_errors()
52 | {
53 | var builder = new StringBuilder();
54 | using (var writer = new StringWriter(builder))
55 | {
56 | YamlWriter.Write(writer, _objectUnderTest);
57 | }
58 |
59 | Assert.That(builder.ToString(), Does.Contain("parsingErrorsDetected: false"));
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForXsdSchemaDefinitions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
9 | {
10 | public sealed class XmlFlavorForXsdSchemaDefinitions : XmlFlavor
11 | {
12 | private const string Namespace = "http://www.w3.org/2001/XMLSchema";
13 |
14 | private static readonly HashSet TerminalNodeNames = new HashSet
15 | {
16 | "annotation",
17 | "documentation",
18 |
19 | "attribute",
20 | "enumeration",
21 | };
22 |
23 | public override bool ParseAttributesEnabled => false;
24 |
25 | public override bool Supports(string filePath) => filePath.EndsWith(".xsd", StringComparison.OrdinalIgnoreCase);
26 |
27 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "schema", StringComparison.OrdinalIgnoreCase)
28 | && string.Equals(info.Namespace, Namespace, StringComparison.OrdinalIgnoreCase);
29 |
30 | public override string GetName(XmlReader reader)
31 | {
32 | if (reader.NodeType == XmlNodeType.Element)
33 | {
34 | var name = reader.LocalName;
35 | var identifier = GetIdentifier(reader, "name", "value", "id");
36 | return identifier ?? name;
37 | }
38 |
39 | return base.GetName(reader);
40 | }
41 |
42 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
43 |
44 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
45 |
46 | private static string GetIdentifier(XmlReader reader, params string[] attributeNames) => attributeNames.Select(reader.GetAttribute).FirstOrDefault(_ => _ != null);
47 | }
48 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForRuleSets.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForRuleSets : XmlFlavor
10 | {
11 | private const string IncludeAll = "IncludeAll";
12 | private const string RuleSet = "RuleSet";
13 | private const string Rules = "Rules";
14 | private const string Rule = "Rule";
15 |
16 | private static readonly HashSet TerminalNodeNames = new HashSet
17 | {
18 | IncludeAll,
19 | Rule,
20 | };
21 |
22 | public override bool ParseAttributesEnabled => false;
23 |
24 | public override bool Supports(string filePath) => filePath.EndsWith(".ruleset", StringComparison.OrdinalIgnoreCase);
25 |
26 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, RuleSet, StringComparison.OrdinalIgnoreCase);
27 |
28 | public override string GetName(XmlReader reader)
29 | {
30 | if (reader.NodeType == XmlNodeType.Element)
31 | {
32 | var name = reader.LocalName;
33 | var attributeName = GetAttribute(name);
34 | var identifier = string.IsNullOrWhiteSpace(attributeName) ? null : reader.GetAttribute(attributeName);
35 | return identifier ?? name;
36 | }
37 |
38 | return base.GetName(reader);
39 | }
40 |
41 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
42 |
43 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
44 |
45 | private static string GetAttribute(string name)
46 | {
47 | switch (name)
48 | {
49 | case RuleSet: return "Name";
50 | case Rules: return "AnalyzerId";
51 | case Rule: return "Id";
52 | default: return null;
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_SHFB_Project.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_SHFB_Project
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "SFHB_Project.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches() => Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "SFHB_Project.xml"));
29 |
30 | [TestCase("NamespaceSummaries", "NamespaceSummaryItem", "MiKoSolutions.SemanticParsers.Xml")]
31 | [TestCase("DocumentationSources", "DocumentationSource", "my_assembly.xml")]
32 | [TestCase("PlugInConfigurations", "PlugInConfig", "Hierarchical Table of Contents")]
33 | [TestCase("ComponentConfigurations", "ComponentConfig", "Code Block Component")]
34 | [TestCase("ApiFilter", "Filter 'Namespace'", "MiKoSolutions.SemanticParsers.Xml")]
35 | public void Item_is_found_(string groupType, string type, string name)
36 | {
37 | var properties = _root.Children.Where(_ => _.Type == "PropertyGroup").OfType().SelectMany(_ => _.Children);
38 | var nodes = properties.Where(_ => _.Type == groupType).OfType().SelectMany(_ => _.Children);
39 | var item = nodes.Any(_ => _.Type == type && _.Name == name);
40 |
41 | Assert.That(item, Is.True);
42 | }
43 |
44 | [TestCase("NamespaceSummaries", "NamespaceSummaries")]
45 | [TestCase("ApiFilter", "ApiFilter")]
46 | public void Group_is_found_(string groupType, string name)
47 | {
48 | var properties = _root.Children.Where(_ => _.Type == "PropertyGroup").OfType().SelectMany(_ => _.Children);
49 | var item = properties.Where(_ => _.Type == groupType).Any(_ => _.Name == name);
50 |
51 | Assert.That(item, Is.True);
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/Tests/Resources/Xaml_ResourceDictionary.xml:
--------------------------------------------------------------------------------
1 |
2 | Inherit
3 | 5000
4 | 3000
5 | 1000
6 | True
7 | True
8 |
9 |
10 |
11 | ExplicitlyExcluded
12 | ExplicitlyExcluded
13 | ExplicitlyExcluded
14 | ExplicitlyExcluded
15 | ExplicitlyExcluded
16 |
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForRanorexRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForRanorexRepository : XmlFlavor
10 | {
11 | private const string SpecialElement = "basepath";
12 |
13 | private static readonly HashSet TerminalNodeNames = new HashSet
14 | {
15 | SpecialElement,
16 | "codegen",
17 | "icon",
18 | "item",
19 | "var",
20 | };
21 |
22 | public override bool ParseAttributesEnabled => false;
23 |
24 | public override bool Supports(string filePath) => filePath.EndsWith(".rxrep", StringComparison.OrdinalIgnoreCase);
25 |
26 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "repository", StringComparison.OrdinalIgnoreCase);
27 |
28 | public override string GetName(XmlReader reader)
29 | {
30 | if (reader.NodeType == XmlNodeType.Element)
31 | {
32 | return reader.GetAttribute("name") ?? reader.GetAttribute("classname") ?? reader.LocalName;
33 | }
34 |
35 | return base.GetName(reader);
36 | }
37 |
38 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
39 |
40 | public override ContainerOrTerminalNode FinalAdjustAfterParsingComplete(ContainerOrTerminalNode node)
41 | {
42 | if (node is Container c && c.Children.Count == 1 && c.Type == SpecialElement)
43 | {
44 | node.Name = c.Children[0]?.Content?.Trim();
45 | }
46 |
47 | return base.FinalAdjustAfterParsingComplete(node);
48 | }
49 |
50 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
51 | }
52 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForDependencies.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForDependencies : XmlFlavor
10 | {
11 | private static readonly HashSet TerminalNodeNames = new HashSet
12 | {
13 | ElementNames.Dependency,
14 | };
15 |
16 | public override bool ParseAttributesEnabled => false;
17 |
18 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, ElementNames.Dependencies, StringComparison.OrdinalIgnoreCase);
19 |
20 | public override string GetName(XmlReader reader)
21 | {
22 | if (reader.NodeType == XmlNodeType.Element)
23 | {
24 | var name = reader.Name;
25 | var attributeName = GetAttributeName(name);
26 | var identifier = GetAttribute(reader, attributeName);
27 |
28 | return identifier ?? name;
29 | }
30 |
31 | return base.GetName(reader);
32 | }
33 |
34 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
35 |
36 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
37 |
38 | private static string GetAttributeName(string name)
39 | {
40 | switch (name)
41 | {
42 | case ElementNames.Dependency:
43 | return AttributeNames.Name;
44 |
45 | default:
46 | return null;
47 | }
48 | }
49 |
50 | private static string GetAttribute(XmlReader reader, string attributeName) => attributeName is null ? null : reader.GetAttribute(attributeName);
51 |
52 | private static class ElementNames
53 | {
54 | internal const string Dependencies = "Dependencies";
55 | internal const string Dependency = "Dependency";
56 | }
57 |
58 | private static class AttributeNames
59 | {
60 | internal const string Name = "Name";
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForXslTransformations.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
9 | {
10 | public sealed class XmlFlavorForXslTransformations : XmlFlavor
11 | {
12 | private const string Namespace = "http://www.w3.org/1999/XSL/Transform";
13 |
14 | private static readonly HashSet TerminalNodeNames = new HashSet
15 | {
16 | "key",
17 | "output",
18 | "strip-space",
19 | "template",
20 | "apply-templates",
21 | };
22 |
23 | public override bool ParseAttributesEnabled => false;
24 |
25 | public override bool Supports(string filePath) => filePath.EndsWith(".xsl", StringComparison.OrdinalIgnoreCase)
26 | || filePath.EndsWith(".xslt", StringComparison.OrdinalIgnoreCase);
27 |
28 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "stylesheet", StringComparison.OrdinalIgnoreCase)
29 | && string.Equals(info.Namespace, Namespace, StringComparison.OrdinalIgnoreCase);
30 |
31 | public override string GetName(XmlReader reader)
32 | {
33 | if (reader.NodeType == XmlNodeType.Element)
34 | {
35 | var name = reader.LocalName;
36 | var identifier = GetIdentifier(reader, "name", "match");
37 | return identifier ?? name;
38 | }
39 |
40 | return base.GetName(reader);
41 | }
42 |
43 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
44 |
45 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
46 |
47 | private static string GetIdentifier(XmlReader reader, params string[] attributeNames) => attributeNames.Select(reader.GetAttribute).FirstOrDefault(_ => _ != null);
48 | }
49 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_SettingsFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | using NUnit.Framework;
9 |
10 | namespace MiKoSolutions.SemanticParsers.Xml
11 | {
12 | [TestFixture]
13 | public class ParserTests_SettingsFile
14 | {
15 | private Yaml.File _objectUnderTest;
16 | private Yaml.Container _root;
17 |
18 | [SetUp]
19 | public void PrepareTest()
20 | {
21 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
22 | var fileName = Path.Combine(parentDirectory, "Resources", "Settings_file.xml");
23 |
24 | _objectUnderTest = Parser.Parse(fileName);
25 | _root = _objectUnderTest.Children.Single();
26 | }
27 |
28 | [Test]
29 | public void SettingsFile_has_correct_amount_of_children()
30 | {
31 | Assert.That(_root.Children, Has.Count.EqualTo(2));
32 | }
33 |
34 | [Test]
35 | public void Settings_have_correct_names()
36 | {
37 | var names = _root.Children
38 | .OfType().Where(_ => _.Type == "Settings")
39 | .SelectMany(_ => _.Children)
40 | .Select(_ => _.Name)
41 | .ToList();
42 |
43 | var foundNames = string.Join(",", names);
44 |
45 | Assert.That(names.Contains("Some setting"), foundNames);
46 | Assert.That(names.Contains("Some other setting"), foundNames);
47 | }
48 |
49 | [Test]
50 | public void Profiles_have_correct_names()
51 | {
52 | var names = _root.Children
53 | .OfType().Where(_ => _.Type == "Profiles")
54 | .SelectMany(_ => _.Children)
55 | .Select(_ => _.Name)
56 | .ToList();
57 |
58 | var foundNames = string.Join(",", names);
59 |
60 | Assert.That(names.Contains("(Default)"), foundNames);
61 | Assert.That(names.Contains("yet another profile"), foundNames);
62 | }
63 |
64 | [Test]
65 | public void RoundTrip_does_not_report_parsing_errors()
66 | {
67 | var builder = new StringBuilder();
68 | using (var writer = new StringWriter(builder))
69 | {
70 | YamlWriter.Write(writer, _objectUnderTest);
71 | }
72 |
73 | Assert.That(builder.ToString(), Does.Contain("parsingErrorsDetected: false"));
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_SingleLineDeclaration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_SingleLineDeclaration
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "single_line_description.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches()
29 | {
30 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "single_line_description.xml"));
31 | }
32 |
33 | [Test]
34 | public void File_LocationSpan_matches()
35 | {
36 | Assert.Multiple(() =>
37 | {
38 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
39 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(2, 0)), "Wrong end");
40 |
41 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(new CharacterSpan(274, 275)), "Wrong footer");
42 | });
43 | }
44 |
45 | [Test]
46 | public void Root_LocationSpan_matches()
47 | {
48 | Assert.Multiple(() =>
49 | {
50 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 9)), "Wrong start");
51 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(1, 274)), "Wrong end");
52 |
53 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(8, 41)), "Wrong header");
54 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(260, 273)), "Wrong footer");
55 | });
56 | }
57 |
58 | [Test]
59 | public void Text_matches()
60 | {
61 | var text = _root.Children.First(_ => _.Type == NodeType.Text) as TerminalNode;
62 |
63 | Assert.Multiple(() =>
64 | {
65 | Assert.That(text.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 43)), "Wrong start");
66 | Assert.That(text.LocationSpan.End, Is.EqualTo(new LineInfo(1, 260)), "Wrong end");
67 |
68 | Assert.That(text.Span, Is.EqualTo(new CharacterSpan(42, 259)), "Wrong span");
69 | });
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForVsixManifest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
9 | {
10 | public sealed class XmlFlavorForVsixManifest : XmlFlavor
11 | {
12 | private const string Namespace = "http://schemas.microsoft.com/developer/vsx-schema/2011";
13 |
14 | private static readonly HashSet TerminalNodeNames = new HashSet
15 | {
16 | "Identity",
17 | "DisplayName",
18 | "Description",
19 | "InstallationTarget",
20 | "Dependency",
21 | "Asset",
22 | "Prerequisite",
23 | "Preview",
24 | };
25 |
26 | public override bool ParseAttributesEnabled => false;
27 |
28 | public override bool Supports(string filePath) => filePath.EndsWith(".vsixmanifest", StringComparison.OrdinalIgnoreCase);
29 |
30 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "PackageManifest", StringComparison.OrdinalIgnoreCase)
31 | && string.Equals(info.Namespace, Namespace, StringComparison.OrdinalIgnoreCase);
32 |
33 | public override string GetName(XmlReader reader)
34 | {
35 | if (reader.NodeType == XmlNodeType.Element)
36 | {
37 | var name = reader.LocalName;
38 | var identifier = GetIdentifier(reader, "DisplayName", "Id", "Type");
39 | return identifier ?? name;
40 | }
41 |
42 | return base.GetName(reader);
43 | }
44 |
45 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
46 |
47 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
48 |
49 | private static string GetIdentifier(XmlReader reader, params string[] attributeNames) => attributeNames.Select(reader.GetAttribute).FirstOrDefault(_ => _ != null);
50 | }
51 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_WrongEncoding.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_WrongEncoding
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "test_with_umlaut_and_wrong_encoding.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches()
29 | {
30 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "test_with_umlaut_and_wrong_encoding.xml"));
31 | }
32 |
33 | [Test]
34 | public void File_LocationSpan_matches()
35 | {
36 | Assert.Multiple(() =>
37 | {
38 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
39 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(5, 0)), "Wrong end");
40 |
41 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(new CharacterSpan(82, 83)), "Wrong footer");
42 | });
43 | }
44 |
45 | [Test]
46 | public void Root_LocationSpan_matches()
47 | {
48 | Assert.Multiple(() =>
49 | {
50 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
51 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(4, 7)), "Wrong end");
52 |
53 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 48)), "Wrong header");
54 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(75, 81)), "Wrong footer");
55 | });
56 | }
57 |
58 | [Test]
59 | public void Element_matches()
60 | {
61 | var node = _root.Children.OfType().First();
62 |
63 | Assert.Multiple(() =>
64 | {
65 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(3, 1)), "Wrong start");
66 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(3, 26)), "Wrong end");
67 |
68 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(49, 59)), "Wrong header");
69 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(63, 74)), "Wrong footer");
70 | });
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForXlf.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForXlf : XmlFlavor
10 | {
11 | private static readonly HashSet TerminalNodeNames = new HashSet
12 | {
13 | ElementNames.Source,
14 | ElementNames.Target,
15 | ElementNames.TransUnit,
16 | };
17 |
18 | public override bool ParseAttributesEnabled => false;
19 |
20 | public override bool Supports(string filePath) => filePath.EndsWith(".xlf", StringComparison.OrdinalIgnoreCase);
21 |
22 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, ElementNames.XLiff, StringComparison.OrdinalIgnoreCase);
23 |
24 | public override string GetName(XmlReader reader)
25 | {
26 | if (reader.NodeType == XmlNodeType.Element)
27 | {
28 | var name = reader.LocalName;
29 | var attr = GetAttributeName(name);
30 |
31 | if (attr is null)
32 | {
33 | return name;
34 | }
35 |
36 | return reader.GetAttribute(attr);
37 | }
38 |
39 | return base.GetName(reader);
40 | }
41 |
42 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
43 |
44 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
45 |
46 | private string GetAttributeName(string elementName)
47 | {
48 | switch (elementName)
49 | {
50 | case ElementNames.File: return AttributeNames.Original;
51 | case ElementNames.TransUnit: return AttributeNames.Id;
52 | default: return elementName;
53 | }
54 | }
55 |
56 | private static class ElementNames
57 | {
58 | internal const string File = "file";
59 | internal const string TransUnit = "trans-unit";
60 | internal const string Source = "source";
61 | internal const string Target = "target";
62 |
63 | internal const string XLiff = "xliff";
64 | }
65 |
66 | private static class AttributeNames
67 | {
68 | internal const string Original = "original";
69 | internal const string Id = "id";
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_NDepend.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_NDepend
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "NDepend_project.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches()
29 | {
30 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "NDepend_project.xml"));
31 | }
32 |
33 | [Test]
34 | public void File_LocationSpan_matches()
35 | {
36 | Assert.Multiple(() =>
37 | {
38 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
39 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(63, 25)), "Wrong end");
40 |
41 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
42 | });
43 | }
44 |
45 | [Test]
46 | public void Root_LocationSpan_matches()
47 | {
48 | Assert.Multiple(() =>
49 | {
50 | Assert.That(_root.Name, Is.EqualTo("NDepend"));
51 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
52 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(63, 25)), "Wrong end");
53 |
54 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 152)), "Wrong header");
55 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(3931, 3940)), "Wrong footer");
56 | });
57 | }
58 |
59 | [Test]
60 | public void Queries_LocationSpan_matches()
61 | {
62 | var node = _root.Children.OfType().First(_ => _.Name == "Queries");
63 |
64 | Assert.Multiple(() =>
65 | {
66 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(58, 25)), "Wrong start");
67 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(59, 12)), "Wrong end");
68 |
69 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(3555, 3565)), "Wrong header");
70 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(3566, 3577)), "Wrong footer");
71 | });
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public class XmlFlavor : IXmlFlavor
10 | {
11 | private static readonly char[] DirectorySeparators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
12 |
13 | public virtual bool ParseAttributesEnabled => false;
14 |
15 | public virtual bool Supports(string filePath) => filePath.EndsWith(".xml", StringComparison.OrdinalIgnoreCase);
16 |
17 | public virtual bool Supports(DocumentInfo info) => true;
18 |
19 | public virtual string GetName(XmlReader reader)
20 | {
21 | switch (reader.NodeType)
22 | {
23 | case XmlNodeType.Element:
24 | case XmlNodeType.ProcessingInstruction:
25 | case XmlNodeType.Attribute:
26 | {
27 | return reader.Name;
28 | }
29 |
30 | case XmlNodeType.Comment:
31 | case XmlNodeType.XmlDeclaration:
32 | {
33 | return reader.Value;
34 | }
35 |
36 | case XmlNodeType.CDATA:
37 | case XmlNodeType.Text:
38 | default:
39 | {
40 | return string.Empty;
41 | }
42 | }
43 | }
44 |
45 | public virtual string GetType(XmlReader reader)
46 | {
47 | switch (reader.NodeType)
48 | {
49 | case XmlNodeType.Attribute: return NodeType.Attribute;
50 | case XmlNodeType.CDATA: return NodeType.CDATA;
51 | case XmlNodeType.Comment: return NodeType.Comment;
52 | case XmlNodeType.Element: return NodeType.Element;
53 | case XmlNodeType.ProcessingInstruction: return NodeType.ProcessingInstruction;
54 | case XmlNodeType.Text: return NodeType.Text;
55 | case XmlNodeType.XmlDeclaration: return NodeType.XmlDeclaration;
56 |
57 | default: return string.Empty;
58 | }
59 | }
60 |
61 | public virtual string GetContent(XmlReader reader) => reader.Value;
62 |
63 | public virtual ContainerOrTerminalNode FinalAdjustAfterParsingComplete(ContainerOrTerminalNode node)
64 | {
65 | return ShallBeTerminalNode(node)
66 | ? node.ToTerminalNode()
67 | : node;
68 | }
69 |
70 | protected static string GetFileName(string path)
71 | {
72 | // get rid of backslash or slash as we only are interested in the name, not the path
73 | // (and just add 1 and we get rid of situation that index might not be available ;))
74 | return path?.Substring(path.LastIndexOfAny(DirectorySeparators) + 1);
75 | }
76 |
77 | protected virtual bool ShallBeTerminalNode(ContainerOrTerminalNode node) => false;
78 | }
79 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForNLS.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForNLS : XmlFlavor
10 | {
11 | private static readonly HashSet TerminalNodeNames = new HashSet
12 | {
13 | ElementNames.Comment,
14 | ElementNames.Date,
15 | ElementNames.Language,
16 | ElementNames.Locale,
17 | };
18 |
19 | public override bool ParseAttributesEnabled => false;
20 |
21 | public override bool Supports(string filePath) => filePath.EndsWith(".xml", StringComparison.OrdinalIgnoreCase);
22 |
23 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, ElementNames.Localization, StringComparison.OrdinalIgnoreCase);
24 |
25 | public override string GetName(XmlReader reader)
26 | {
27 | if (reader.NodeType == XmlNodeType.Element)
28 | {
29 | var name = reader.LocalName;
30 | var attr = GetAttributeName(name);
31 |
32 | return attr is null ? name : reader.GetAttribute(attr);
33 | }
34 |
35 | return base.GetName(reader);
36 | }
37 |
38 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
39 |
40 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
41 |
42 | private string GetAttributeName(string elementName)
43 | {
44 | switch (elementName)
45 | {
46 | case ElementNames.Language: return AttributeNames.LCID;
47 | case ElementNames.Locale: return AttributeNames.Code;
48 | case ElementNames.String: return AttributeNames.Key;
49 | default: return null;
50 | }
51 | }
52 |
53 | private static class ElementNames
54 | {
55 | internal const string Comment = "comment";
56 | internal const string Date = "date";
57 | internal const string Language = "language";
58 | internal const string Locale = "locale";
59 | internal const string Localization = "localization";
60 | internal const string String = "string";
61 | }
62 |
63 | private static class AttributeNames
64 | {
65 | internal const string LCID = "lcid";
66 | internal const string Code = "code";
67 | internal const string Key = "key";
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_CSharp_Project.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_CSharp_Project
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "CSharp_Project.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches() => Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "CSharp_Project.xml"));
29 |
30 | [TestCase("Reference", "YamlDotNet")]
31 | [TestCase("Compile", "LocationSpanConverter.cs")]
32 | [TestCase("Analyzer", "StyleCop.Analyzers.dll")]
33 | [TestCase("ProjectReference", "Common.csproj")]
34 | [TestCase("BootstrapperPackage", "Microsoft.Windows.Installer.4.5")]
35 | [TestCase("Page", "UserControl.xaml")]
36 | [TestCase("PreBuildEvent", "PreBuildEvent")]
37 | [TestCase("PostBuildEvent", "PostBuildEvent")]
38 | [TestCase("DefineConstants", "'$(TargetFramework)' != 'net20'")]
39 | [TestCase("CppCompile", "Common.cpp")]
40 | [TestCase("OfficialBuildRID", "tizen")]
41 | [TestCase("Folder", "Some\\path/to/wherever")]
42 | [TestCase("Compile", "Class.cs")]
43 | [TestCase("Compile", "something\\*")]
44 | public void Item_is_found_and_truncated_properly(string groupType, string name)
45 | {
46 | var item = _root.Children.OfType().SelectMany(_ => _.Children).Where(_ => _.Type == groupType).Any(_ => _.Name == name);
47 |
48 | Assert.That(item, Is.True);
49 | }
50 |
51 | [TestCase("ItemGroup 'Reference'", "Reference")]
52 | [TestCase("ItemGroup 'None 'Resources''", "None 'Resources'")]
53 | [TestCase("PropertyGroup", "(default)")]
54 | [TestCase("PropertyGroup", "Pre/Post-build events")]
55 | [TestCase("PropertyGroup", "'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'")]
56 | [TestCase("PropertyGroup", "'$(Configuration)|$(Platform)' == 'Release|AnyCPU'")]
57 | public void Group_is_found_and_truncated_properly(string groupType, string name)
58 | {
59 | var item = _root.Children.OfType().Where(_ => _.Type == groupType).Any(_ => _.Name == name);
60 |
61 | Assert.That(item, Is.True);
62 | }
63 |
64 | [TestCase("Import", "dir.props")]
65 | [TestCase("Import", "Microsoft.Common.props")]
66 | [TestCase("Import", "dependencies.props")]
67 | public void TopLevel_Item_is_found_and_truncated_properly(string itemType, string name)
68 | {
69 | var item = _root.Children.Where(_ => _.Type == itemType).Any(_ => _.Name == name);
70 |
71 | Assert.That(item, Is.True);
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_ModuleCatalog_Xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_ModuleCatalog_Xaml
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "ModuleCatalog.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches() => Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "ModuleCatalog.xml"));
29 |
30 | [Test]
31 | public void File_LocationSpan_matches()
32 | {
33 | Assert.Multiple(() =>
34 | {
35 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
36 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(12, 27)), "Wrong end");
37 |
38 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
39 | });
40 | }
41 |
42 | [Test]
43 | public void Root_LocationSpan_matches()
44 | {
45 | Assert.Multiple(() =>
46 | {
47 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
48 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(12, 27)), "Wrong end");
49 |
50 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 395)), "Wrong header");
51 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(660, 730)), "Wrong footer (should include comment at the end)");
52 | });
53 | }
54 |
55 | [TestCase("ModuleOne", 5, 1, 7, 68, 396, 542)]
56 | [TestCase("ModuleTwo", 8, 1, 8, 117, 543, 659)]
57 | public void Element_matches(string name, int startLineNumber, int startLinePos, int endLineNumber, int endLinePos, int startPos, int endPos)
58 | {
59 | var node = _root.Children.Where(_ => _.Type != NodeType.Attribute).OfType().FirstOrDefault(_ => _.Name == name);
60 |
61 | Assert.Multiple(() =>
62 | {
63 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(startLineNumber, startLinePos)), "Wrong start");
64 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(endLineNumber, endLinePos)), "Wrong end");
65 |
66 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(startPos, endPos)), "Wrong span");
67 | });
68 | }
69 |
70 | [Test]
71 | public void No_attributes_are_reported_to_allow_semantic_merge()
72 | {
73 | var hasAttributes = _root.Children.Any(_ => _.Type == NodeType.Attribute);
74 |
75 | Assert.That(hasAttributes, Is.False);
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_AssemblyDocumentation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_AssemblyDocumentation
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "AssemblyDocumentation.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches() => Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "AssemblyDocumentation.xml"));
29 |
30 | [Test]
31 | public void File_LocationSpan_matches()
32 | {
33 | Assert.Multiple(() =>
34 | {
35 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
36 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(13, 6)), "Wrong end");
37 |
38 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
39 | });
40 | }
41 |
42 | [Test]
43 | public void Root_LocationSpan_matches()
44 | {
45 | Assert.Multiple(() =>
46 | {
47 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
48 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(13, 6)), "Wrong end");
49 |
50 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 46)), "Wrong header");
51 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(304, 309)), "Wrong footer");
52 | });
53 | }
54 |
55 | [TestCase("members", "member", "summary", 8, 1, 10, 18, 190, 274)]
56 | public void Element_matches(string groupName, string elementName, string name, int startLineNumber, int startLinePos, int endLineNumber, int endLinePos, int startPos, int endPos)
57 | {
58 | var terminalNodes = _root.Children
59 | .OfType().Where(_ => _.Type == groupName)
60 | .SelectMany(_ => _.Children).Where(_ => _.Type == elementName).OfType()
61 | .SelectMany(_ => _.Children).Where(_ => _.Type == name).OfType();
62 |
63 | var node = terminalNodes.FirstOrDefault();
64 |
65 | Assert.Multiple(() =>
66 | {
67 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(startLineNumber, startLinePos)), "Wrong start");
68 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(endLineNumber, endLinePos)), "Wrong end");
69 |
70 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(startPos, endPos)), "Wrong span");
71 | });
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_Xsl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public sealed class ParserTests_Xsl
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "StyleSheet.xslt");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches()
29 | {
30 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "StyleSheet.xslt"));
31 | }
32 |
33 | [Test]
34 | public void File_LocationSpan_matches()
35 | {
36 | Assert.Multiple(() =>
37 | {
38 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
39 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(13, 0)), "Wrong end");
40 |
41 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(new CharacterSpan(428, 429)), "Wrong footer");
42 | });
43 | }
44 |
45 | [Test]
46 | public void Root_LocationSpan_matches()
47 | {
48 | Assert.Multiple(() =>
49 | {
50 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
51 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(12, 17)), "Wrong end");
52 |
53 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 204)), "Wrong header");
54 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(411, 427)), "Wrong footer");
55 | });
56 | }
57 |
58 | [Test]
59 | public void Output_element_matches()
60 | {
61 | var node = _root.Children.OfType().First();
62 |
63 | Assert.Multiple(() =>
64 | {
65 | Assert.That(node.Type, Is.EqualTo("output"));
66 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(5, 1)), "Wrong start");
67 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(5, 45)), "Wrong end");
68 |
69 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(205, 249)), "Wrong span");
70 | });
71 | }
72 |
73 | [Test]
74 | public void Template_element_matches()
75 | {
76 | var node = _root.Children.OfType().Last();
77 |
78 | Assert.Multiple(() =>
79 | {
80 | Assert.That(node.Type, Is.EqualTo("template"));
81 | Assert.That(node.Name, Is.EqualTo("@* | node()"));
82 |
83 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(6, 1)), "Wrong start");
84 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(11, 21)), "Wrong end");
85 |
86 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(250, 410)), "Wrong span");
87 | });
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Tests/ParserTests_LineEnds.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | using File = System.IO.File;
10 |
11 | namespace MiKoSolutions.SemanticParsers.Xml
12 | {
13 | [TestFixture("test_with_Unix_LineEnd.xml", "\n")]
14 | [TestFixture("test_with_Macintosh_LineEnd.xml", "\r")]
15 | public class ParserTests_LineEnds
16 | {
17 | private readonly string _fileName;
18 | private readonly string _lineBreak;
19 | private Yaml.File _objectUnderTest;
20 | private Yaml.Container _root;
21 |
22 | public ParserTests_LineEnds(string fileName, string lineBreak)
23 | {
24 | _fileName = fileName;
25 | _lineBreak = lineBreak;
26 | }
27 |
28 | [SetUp]
29 | public void PrepareTest()
30 | {
31 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
32 | var fileName = Path.Combine(parentDirectory, "Resources", _fileName);
33 |
34 | // we need to adjust line breaks because Git checkout on AppVeyor (or elsewhere) will adjust the line breaks
35 | var originalContent = File.ReadAllText(fileName);
36 | File.WriteAllText(fileName, originalContent.Replace(Environment.NewLine, _lineBreak));
37 |
38 | _objectUnderTest = Parser.Parse(fileName);
39 | _root = _objectUnderTest.Children.Single();
40 | }
41 |
42 | [Test]
43 | public void File_Name_matches()
44 | {
45 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + _fileName));
46 | }
47 |
48 | [Test]
49 | public void File_LocationSpan_matches()
50 | {
51 | Assert.Multiple(() =>
52 | {
53 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
54 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(5, 0)), "Wrong end");
55 |
56 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(new CharacterSpan(68, 68)), "Wrong footer");
57 | });
58 | }
59 |
60 | [Test]
61 | public void Root_LocationSpan_matches()
62 | {
63 | Assert.Multiple(() =>
64 | {
65 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
66 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(4, 7)), "Wrong end");
67 |
68 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 46)), "Wrong header");
69 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(61, 67)), "Wrong footer");
70 | });
71 | }
72 |
73 | [Test]
74 | public void FirstChild_LocationSpan_matches()
75 | {
76 | var node = _root.Children.OfType().First();
77 |
78 | Assert.Multiple(() =>
79 | {
80 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(3, 1)), "Wrong start");
81 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(3, 14)), "Wrong end");
82 |
83 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(47, 57)), "Wrong header");
84 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(58, 60)), "Wrong footer");
85 | });
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_NuSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | public class ParserTests_NuSpec
12 | {
13 | private Yaml.File _objectUnderTest;
14 | private Yaml.Container _root;
15 |
16 | [SetUp]
17 | public void PrepareTest()
18 | {
19 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
20 | var fileName = Path.Combine(parentDirectory, "Resources", "NuSpec.xml");
21 |
22 | _objectUnderTest = Parser.Parse(fileName);
23 | _root = _objectUnderTest.Children.Single();
24 | }
25 |
26 | [Test]
27 | public void File_Name_matches()
28 | {
29 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "NuSpec.xml"));
30 | }
31 |
32 | [Test]
33 | public void File_LocationSpan_matches()
34 | {
35 | Assert.Multiple(() =>
36 | {
37 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
38 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(16, 10)), "Wrong end");
39 |
40 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
41 | });
42 | }
43 |
44 | [Test]
45 | public void Root_LocationSpan_matches()
46 | {
47 | Assert.Multiple(() =>
48 | {
49 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
50 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(16, 10)), "Wrong end");
51 |
52 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 279)), "Wrong header");
53 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(703, 712)), "Wrong footer");
54 | });
55 | }
56 |
57 | [Test]
58 | public void MetaData_matches()
59 | {
60 | var node = _root.Children.OfType().FirstOrDefault(_ => _.Type == "metadata");
61 |
62 | Assert.Multiple(() =>
63 | {
64 | Assert.That(node.Name, Is.EqualTo("My package"));
65 |
66 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(5, 1)), "Wrong start");
67 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(12, 15)), "Wrong end");
68 |
69 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(280, 293)), "Wrong header");
70 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(607, 621)), "Wrong footer");
71 | });
72 | }
73 |
74 | [Test]
75 | public void MetaData_ID_matches()
76 | {
77 | var node = _root.Children.OfType().Where(_ => _.Type == "metadata").SelectMany(_ => _.Children).OfType().FirstOrDefault(_ => _.Type == "id");
78 |
79 | Assert.Multiple(() =>
80 | {
81 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(6, 1)), "Wrong start");
82 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(6, 25)), "Wrong end");
83 |
84 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(294, 318)), "Wrong span");
85 | });
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # xml-semantic-external-parser
2 | A semantic external parser for XML files that can be used together with [GMaster](https://gmaster.io), [PlasticSCM](https://www.plasticscm.com) or [SemanticMerge](https://semanticmerge.com/).
3 |
4 | How to use it with _**GMaster**_ is documented [here](http://blog.gmaster.io/2018/03/using-external-parsers-with-gmaster.html).
5 |
6 | How to use it with _**SemanticMerge**_ or _**Plastic SCM**_ is described [here](https://users.semanticmerge.com/documentation/external-parsers/external-parsers-guide.shtml) in chapter _"How to invoke Semantic with an external parser"_.
7 |
8 | ## Build status
9 | [](https://ci.appveyor.com/project/RalfKoban/xml-semantic-external-parser/branch/master)
10 | [](https://codecov.io/gh/RalfKoban/xml-semantic-external-parser)
11 |
12 | [](https://ci.appveyor.com/project/RalfKoban/xml-semantic-external-parser/history)
13 |
14 | ## Issues
15 | Please raise issues on [GitHub](https://github.com/RalfKoban/xml-semantic-external-parser/issues).
16 | If you can repeat the issue then please provide a sample to make it easier for me to also repeat it and then implement a fix.
17 |
18 | Please do not hijack unrelated issues, I would rather you create a new issue than add noise to an unrelated issue.
19 |
20 | ## Supported formats
21 |
22 | | Description | File name / extension |
23 | |-------------|-----------------------|
24 | | .NET API reference XML comments | .xml
25 | | .NET Application manifest | .manifest
26 | | .NET Assembly manifest | .manifest
27 | | .NET Configuration | .config
28 | | .NET Settings | .settings
29 | | C# Rule sets | .ruleset
30 | | dotCover (NDepend XML format) | .xml
31 | | Entity Framework Database Schema (V3) | .edmx
32 | | FxCop | _CustomDictionary.xml_
33 | | Microsoft Build Engine (MSBuild) | .proj, .targets, .props, .projitems
34 | | Microsoft Prism (Library 5.0 for WPF) Module catalog | .xaml
35 | | NCrunch Solution | .v3.ncrunchsolution
36 | | [NDepend](https://www.ndepend.com/) | .ndproj, .ndrules
37 | | NuGet Configuration | _packages.config_
38 | | NuGet Manifest | .nuspec
39 | | Publish Profile | .pubxml
40 | | Ranorex (Repositories) | .rxrep
41 | | Ranorex (TestModuleGroups) | .rxtmg
42 | | Ranorex (TestSuites) | .rxtst
43 | | [Sandcastle Help File Builder](https://github.com/EWSoftware/SHFB) Project | .shfbproj
44 | | Visual Build | .bld
45 | | Visual Studio C++ Project | .vcxproj
46 | | Visual Studio C# Project | .csproj
47 | | Visual Studio CIL assembler Project | .ilproj
48 | | Visual Studio Code Sharing App Project | .shproj
49 | | Visual Studio F# Project | .fsproj
50 | | Visual Studio Javascript Project | .jsproj
51 | | Visual Studio Modeling Project | .modelproj
52 | | Visual Studio Native Project | .nativeproj
53 | | Visual Studio Node JS Project | .njsproj
54 | | Visual Studio Python Project | .pyproj
55 | | Visual Studio SQL Server Project | .sqlproj
56 | | Visual Studio Visual Basic Project | .vbproj
57 | | Visual Studio Installer XML | .vsixmanifest
58 | | WPF | .xaml
59 | | [Wix Toolkit](http://wixtoolset.org/) | .wxi, .wxl, .wxs, .wixproj
60 | | XML Localization Interchange File Format | .xlf
61 | | XML | .xml
62 | | XSD | .xsd
63 | | XSL Transformation (XSLT) | .xsl, .xslt
64 |
65 | _**Note:** A semantic parser for .NET resource files (.resx) can be found [here](https://github.com/RalfKoban/resx-semantic-external-parser)._
66 |
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForManifest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForManifest : XmlFlavor
10 | {
11 | private static readonly HashSet NonTerminalNodeNames = new HashSet
12 | {
13 | ElementNames.Assembly,
14 | ElementNames.Dependency,
15 | ElementNames.DependentAssembly,
16 | ElementNames.File,
17 | };
18 |
19 | public override bool ParseAttributesEnabled => false;
20 |
21 | public override bool Supports(string filePath) => filePath.EndsWith(".manifest", StringComparison.OrdinalIgnoreCase);
22 |
23 | public override bool Supports(DocumentInfo info)
24 | {
25 | if (info.RootElement == ElementNames.Assembly)
26 | {
27 | if (info.Namespace is null)
28 | {
29 | return true;
30 | }
31 |
32 | return info.Namespace.EndsWith("urn:schemas-microsoft-com:asm.v1", StringComparison.OrdinalIgnoreCase);
33 | }
34 |
35 | return false;
36 | }
37 |
38 | public override string GetName(XmlReader reader)
39 | {
40 | if (reader.NodeType == XmlNodeType.Element)
41 | {
42 | var name = reader.LocalName;
43 | var attributeName = GetAttributeName(name);
44 | var identifier = reader.GetAttribute(attributeName);
45 | return identifier ?? name;
46 | }
47 |
48 | return base.GetName(reader);
49 | }
50 |
51 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
52 |
53 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => !NonTerminalNodeNames.Contains(node?.Type);
54 |
55 | private static string GetAttributeName(string name)
56 | {
57 | switch (name)
58 | {
59 | case ElementNames.AssemblyIdentity:
60 | case ElementNames.File:
61 | return AttributeNames.Name;
62 |
63 | case ElementNames.ComClass:
64 | return AttributeNames.Clsid;
65 |
66 | default:
67 | return AttributeNames.Name;
68 | }
69 | }
70 |
71 | private static class ElementNames
72 | {
73 | internal const string Assembly = "assembly";
74 | internal const string AssemblyIdentity = "assemblyIdentity";
75 | internal const string ComClass = "comClass";
76 | internal const string Dependency = "dependency";
77 | internal const string DependentAssembly = "dependentAssembly";
78 |
79 | internal const string File = "file";
80 | }
81 |
82 | private static class AttributeNames
83 | {
84 | internal const string Clsid = "clsid";
85 | internal const string Name = "name";
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForAssemblyDocumentation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
9 | {
10 | public sealed class XmlFlavorForAssemblyDocumentation : XmlFlavor
11 | {
12 | private const string Assembly = "assembly";
13 | private const string Overloads = "overloads";
14 | private const string Summary = "summary";
15 | private const string Remarks = "remarks";
16 | private const string Param = "param";
17 | private const string Returns = "returns";
18 | private const string Exception = "exception";
19 | private const string SeeAlso = "seealso";
20 | private const string Example = "example";
21 | private const string Exclude = "exclude";
22 |
23 | private static readonly HashSet TerminalNodeNames = new HashSet
24 | {
25 | Overloads,
26 | Summary,
27 | Remarks,
28 | Param,
29 | Returns,
30 | Exception,
31 | SeeAlso,
32 | Example,
33 | Exclude,
34 | };
35 |
36 | private static readonly Dictionary NameMap = TerminalNodeNames.ToDictionary(_ => _, __ => string.Concat("<", __, ">"));
37 |
38 | public override bool ParseAttributesEnabled => false;
39 |
40 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "doc", StringComparison.OrdinalIgnoreCase);
41 |
42 | public override string GetName(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? GetElementName(reader, reader.LocalName) : base.GetName(reader);
43 |
44 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
45 |
46 | public override ContainerOrTerminalNode FinalAdjustAfterParsingComplete(ContainerOrTerminalNode node)
47 | {
48 | if (node is Container c && c.Type == Assembly)
49 | {
50 | var name = c.Children.FirstOrDefault(_ => _.Name == "name");
51 | if (name != null)
52 | {
53 | c.Children.RemoveAll(_ => _.Name == "name");
54 | c.Name = name.Content;
55 | }
56 | }
57 |
58 | return base.FinalAdjustAfterParsingComplete(node);
59 | }
60 |
61 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
62 |
63 | private static string GetElementName(XmlReader reader, string name) => (reader.GetAttribute("name") ?? reader.GetAttribute("cref")) ?? GetElementNameMapped(name);
64 |
65 | private static string GetElementNameMapped(string name) => NameMap.TryGetValue(name, out var mappedName) ? mappedName : name;
66 | }
67 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_Xlf.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using NUnit.Framework;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml
8 | {
9 | [TestFixture]
10 | public class ParserTests_Xlf
11 | {
12 | private Yaml.File _objectUnderTest;
13 | private Yaml.Container _root;
14 |
15 | [SetUp]
16 | public void PrepareTest()
17 | {
18 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
19 | var fileName = Path.Combine(parentDirectory, "Resources", "xliff1.xlf");
20 |
21 | _objectUnderTest = Parser.Parse(fileName);
22 | _root = _objectUnderTest.Children.Single();
23 | }
24 |
25 | [Test]
26 | public void File_Name_matches()
27 | {
28 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "xliff1.xlf"));
29 | }
30 |
31 | [Test]
32 | public void Bla()
33 | {
34 | var expectedNames = new[]
35 | {
36 | "AppVeyorSettingsUserControl",
37 | "AutoCompileSubModulesPlugin",
38 | "BackgroundFetchPlugin",
39 | "BitbucketPlugin",
40 | "BitbucketPullRequestForm",
41 | "CreateLocalBranchesForm",
42 | "CreateLocalBranchesPlugin",
43 | "DeleteUnusedBranchesForm",
44 | "DeleteUnusedBranchesPlugin",
45 | "FindLargeFilesForm",
46 | "FindLargeFilesPlugin",
47 | "FormGerritChangeSubmitted",
48 | "FormGerritDownload",
49 | "FormGerritPublish",
50 | "FormGitReview",
51 | "FormGitStatistics",
52 | "FormImpact",
53 | "FormPluginInformation",
54 | "GerritPlugin",
55 | "GerritSettings",
56 | "GitFlowForm",
57 | "GitFlowPlugin",
58 | "GitHub3Plugin",
59 | "GitImpactPlugin",
60 | "GitStatisticsPlugin",
61 | "GourcePlugin",
62 | "GourceStart",
63 | "JenkinsSettingsUserControl",
64 | "JiraCommitHintPlugin",
65 | "ProxySwitcherForm",
66 | "ProxySwitcherPlugin",
67 | "ReleaseNotesGeneratorForm",
68 | "ReleaseNotesGeneratorPlugin",
69 | "TeamCitySettingsUserControl",
70 | "TfsSettingsUserControl",
71 | "VstsAndTfsSettingsUserControl",
72 | };
73 |
74 | Assert.That(_root.Children.Count, Is.EqualTo(36));
75 |
76 | var names = _root.Children.Select(_ => _.Name).ToList();
77 | Assert.That(names, Is.EqualTo(expectedNames));
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForRanorexTestSuite.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Xml;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public sealed class XmlFlavorForRanorexTestSuite : XmlFlavor
10 | {
11 | private const string SpecialElement = "reference";
12 |
13 | private static readonly HashSet TerminalNodeNames = new HashSet
14 | {
15 | SpecialElement,
16 | "testcontainer",
17 | "testmodule",
18 | };
19 |
20 | public override bool ParseAttributesEnabled => false;
21 |
22 | public override bool Supports(string filePath) => filePath.EndsWith(".rxtst", StringComparison.OrdinalIgnoreCase) // test suite
23 | || filePath.EndsWith(".rxtmg", StringComparison.OrdinalIgnoreCase); // module group
24 |
25 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "testsuitedoc", StringComparison.OrdinalIgnoreCase)
26 | || string.Equals(info.RootElement, "modulegroupdoc", StringComparison.OrdinalIgnoreCase);
27 |
28 | public override string GetName(XmlReader reader)
29 | {
30 | if (reader.NodeType == XmlNodeType.Element)
31 | {
32 | var name = reader.GetAttribute("name") ?? reader.LocalName;
33 |
34 | switch (reader.Name)
35 | {
36 | case "setup":
37 | case "teardown":
38 | case "testcontainer":
39 | case "testmodule":
40 | {
41 | var id = reader.GetAttribute("id");
42 | return $"{name} ({id})";
43 | }
44 |
45 | default:
46 | return name;
47 | }
48 | }
49 |
50 | return base.GetName(reader);
51 | }
52 |
53 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
54 |
55 | public override ContainerOrTerminalNode FinalAdjustAfterParsingComplete(ContainerOrTerminalNode node)
56 | {
57 | if (node is Container c)
58 | {
59 | switch (c.Type)
60 | {
61 | case SpecialElement:
62 | {
63 | if (c.Children.Count == 1)
64 | {
65 | node.Name = c.Children[0]?.Content?.Trim();
66 | }
67 |
68 | break;
69 | }
70 |
71 | case "flatlistofchildren":
72 | {
73 | for (var index = 0; index < c.Children.Count; index++)
74 | {
75 | var child = c.Children[index];
76 | c.Children[index] = child.ToTerminalNode();
77 | }
78 |
79 | break;
80 | }
81 | }
82 | }
83 |
84 | return base.FinalAdjustAfterParsingComplete(node);
85 | }
86 |
87 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
88 | }
89 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_EdmxV3.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_EdmxV3
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "EdmxV3.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches()
29 | {
30 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "EdmxV3.xml"));
31 | }
32 |
33 | [Test]
34 | public void File_LocationSpan_matches()
35 | {
36 | Assert.Multiple(() =>
37 | {
38 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
39 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(18, 12)), "Wrong end");
40 |
41 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
42 | });
43 | }
44 |
45 | [Test]
46 | public void Root_LocationSpan_matches()
47 | {
48 | Assert.Multiple(() =>
49 | {
50 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
51 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(18, 12)), "Wrong end");
52 |
53 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 125)), "Wrong header");
54 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(947, 958)), "Wrong footer");
55 | });
56 | }
57 |
58 | [Test]
59 | public void RuntimeElement_found()
60 | {
61 | var node = (Container)_root.Children.Single();
62 | Assert.Multiple(() =>
63 | {
64 | Assert.That(node.Type, Is.EqualTo("edmx:Runtime"));
65 |
66 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(3, 1)), "Wrong start");
67 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(17, 19)), "Wrong end");
68 |
69 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(126, 174)), "Wrong header");
70 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(928, 946)), "Wrong footer");
71 | });
72 | }
73 |
74 | [Test]
75 | public void Schema_has_namespace()
76 | {
77 | var runtime = _root.Children.OfType().Single();
78 | var storageModels = runtime.Children.OfType().First();
79 | var node = storageModels.Children.OfType().First();
80 |
81 | Assert.Multiple(() =>
82 | {
83 | Assert.That(node.Type, Is.EqualTo("Schema"));
84 | Assert.That(node.Name, Is.EqualTo("Model.Store"));
85 |
86 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(7, 1)), "Wrong start");
87 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(15, 17)), "Wrong end");
88 |
89 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(228, 573)), "Wrong header");
90 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(884, 900)), "Wrong footer");
91 | });
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Parser/Parser.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/Tests/Tests.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/Tests/ParserTests_Xaml_ResourceDictionary.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | using File = System.IO.File;
10 |
11 | namespace MiKoSolutions.SemanticParsers.Xml
12 | {
13 | [TestFixture]
14 | public class ParserTests_Xaml_ResourceDictionary
15 | {
16 | private Yaml.File _objectUnderTest;
17 | private Yaml.Container _root;
18 |
19 | [SetUp]
20 | public void PrepareTest()
21 | {
22 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
23 | var fileName = Path.Combine(parentDirectory, "Resources", "Xaml_ResourceDictionary.xml");
24 |
25 | // we need to adjust line breaks because Git checkout on AppVeyor (or elsewhere) will adjust the line breaks
26 | var originalContent = File.ReadAllText(fileName);
27 | File.WriteAllText(fileName, originalContent.Replace(Environment.NewLine, "\n"));
28 |
29 | _objectUnderTest = Parser.Parse(fileName);
30 | _root = _objectUnderTest.Children.Single();
31 | }
32 |
33 | [Test]
34 | public void File_Name_matches()
35 | {
36 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "Xaml_ResourceDictionary.xml"));
37 | }
38 |
39 | [Test]
40 | public void File_LocationSpan_matches()
41 | {
42 | Assert.Multiple(() =>
43 | {
44 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
45 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(16, 25)), "Wrong end");
46 |
47 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
48 | });
49 | }
50 |
51 | [Test]
52 | public void Root_LocationSpan_matches()
53 | {
54 | Assert.Multiple(() =>
55 | {
56 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
57 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(16, 25)), "Wrong end");
58 |
59 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 277)), "Wrong header");
60 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(2403, 2427)), "Wrong footer");
61 | });
62 | }
63 |
64 | [TestCase(00, 2, 1, 2, 118, 278, 376, 384, 395)]
65 | [TestCase(01, 3, 1, 3, 219, 396, 599, 604, 614)]
66 | [TestCase(02, 4, 1, 4, 234, 615, 833, 838, 848)]
67 | [TestCase(03, 5, 1, 5, 232, 849, 1065, 1070, 1080)]
68 | [TestCase(04, 6, 1, 6, 166, 1081, 1229, 1234, 1246)]
69 | [TestCase(05, 7, 1, 7, 177, 1247, 1406, 1411, 1423)]
70 | [TestCase(06, 8, 1, 11, 183, 1424, 1582, 1601, 1612)]
71 | [TestCase(07, 12, 1, 12, 241, 1613, 1823, 1842, 1853)]
72 | [TestCase(08, 13, 1, 13, 183, 1854, 2006, 2025, 2036)]
73 | [TestCase(09, 14, 1, 14, 183, 2037, 2189, 2208, 2219)]
74 | [TestCase(10, 15, 1, 15, 183, 2220, 2372, 2391, 2402)]
75 | public void Element_matches(int index, int startLineNumber, int startLinePos, int endLineNumber, int endLinePos, int headerStartPos, int headerEndPos, int footerStartPos, int footerEndPos)
76 | {
77 | var node = _root.Children.Where(_ => _.Type != NodeType.Attribute).OfType().ElementAt(index);
78 |
79 | Assert.Multiple(() =>
80 | {
81 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(startLineNumber, startLinePos)), "Wrong start");
82 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(endLineNumber, endLinePos)), "Wrong end");
83 |
84 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(headerStartPos, footerEndPos)), "Wrong span");
85 | });
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/Tests/Resources/NDepend_project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | .\NDependOut
4 |
5 | MiKoSolutions.Aspects
6 |
7 |
8 | mscorlib
9 | System.Core
10 |
11 |
12 | C:\Windows\Microsoft.NET\Framework\v4.0.30319
13 | C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF
14 |
15 | True
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 1
39 | 1
40 | 0
41 | 0
42 | $ManDay$
43 | 50
44 | EUR
45 | After
46 | 18
47 | 240
48 | 8
49 | 5
50 | 10
51 | 20
52 | 50
53 | 1200000000
54 | 12000000000
55 | 72000000000
56 | 360000000000
57 |
58 |
59 |
60 | The assembly {MiKoSolutions.Aspects} is not in sync with corresponding coverage data.
61 | The analysis found methods in the assembly that have no associated coverage data.
62 | For example, one of this method is {MiKoSolutions.Aspects.Contracts.ValidateArgumentsAttribute.PushAnnotation(MethodBase,Int)}
63 |
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForVisualBuild.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
9 | {
10 | public sealed class XmlFlavorForVisualBuild : XmlFlavor
11 | {
12 | private const string RootElement = "project";
13 | private const string StepElement = "step";
14 | private const string NameElement = "name";
15 | private const string MacroElement = "macro";
16 |
17 | private static readonly HashSet NonTerminalNodeNames = new HashSet
18 | {
19 | RootElement,
20 | "macros",
21 | "steps",
22 | };
23 |
24 | public override bool ParseAttributesEnabled => true;
25 |
26 | public override bool Supports(string filePath) => filePath.EndsWith(".bld", StringComparison.OrdinalIgnoreCase);
27 |
28 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, RootElement, StringComparison.Ordinal)
29 | && info.Namespace is null;
30 |
31 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
32 |
33 | public override ContainerOrTerminalNode FinalAdjustAfterParsingComplete(ContainerOrTerminalNode node)
34 | {
35 | if (node is Container c)
36 | {
37 | switch (node.Type)
38 | {
39 | case MacroElement:
40 | {
41 | FinalAdjustMacroElement(c);
42 | break;
43 | }
44 |
45 | case NameElement:
46 | {
47 | FinalAdjustNameElement(c);
48 | break;
49 | }
50 |
51 | case StepElement:
52 | {
53 | FinalAdjustStepElement(c);
54 | break;
55 | }
56 | }
57 |
58 | // do not report the attributes for the moment as that causes structural issues in SemanticMerge/GMaster (due to gaps between the attributes)
59 | c.Children.RemoveAll(_ => _.Type == NodeType.Attribute);
60 | }
61 |
62 | return base.FinalAdjustAfterParsingComplete(node);
63 | }
64 |
65 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => !NonTerminalNodeNames.Contains(node?.Type);
66 |
67 | private static void FinalAdjustMacroElement(Container c)
68 | {
69 | var name = c.Children.FirstOrDefault(_ => _.Type == NodeType.Attribute && _.Name == "name");
70 | if (name != null)
71 | {
72 | c.Name = name.Content;
73 | }
74 | }
75 |
76 | private static void FinalAdjustNameElement(Container c)
77 | {
78 | var text = c.Children.FirstOrDefault(_ => _.Type == NodeType.Text);
79 | if (text != null)
80 | {
81 | c.Name = text.Content;
82 | }
83 | }
84 |
85 | private static void FinalAdjustStepElement(Container c)
86 | {
87 | var action = c.Children.FirstOrDefault(_ => _.Type == NodeType.Attribute && _.Name == "action");
88 | if (action != null)
89 | {
90 | c.Type = $"{c.Type} '{action.Content}'";
91 | }
92 |
93 | var name = c.Children.FirstOrDefault(_ => _.Type == NameElement);
94 | if (name != null)
95 | {
96 | c.Name = name.Name;
97 | }
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/Parser/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Runtime;
5 | using System.Threading.Tasks;
6 |
7 | using MiKoSolutions.SemanticParsers.Xml.Flavors;
8 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
9 |
10 | using SystemFile = System.IO.File;
11 |
12 | namespace MiKoSolutions.SemanticParsers.Xml
13 | {
14 | public static class Program
15 | {
16 | private static readonly Guid InstanceId = Guid.NewGuid();
17 |
18 | public static async Task Main(string[] args)
19 | {
20 | // check for GMaster or PlasticSCM or SemanticMerge arguments (to allow debugging without the tools)
21 | if (args.Length == 2)
22 | {
23 | var shell = args[0]; // reserved for future usage
24 | var flagFile = args[1];
25 |
26 | SystemFile.WriteAllBytes(flagFile, new byte[] { 0x42 });
27 | }
28 |
29 | var watch = Stopwatch.StartNew();
30 | var gcWatch = Stopwatch.StartNew();
31 | while (true)
32 | {
33 | var inputFile = await Console.In.ReadLineAsync();
34 | if (inputFile == null || "end".Equals(inputFile, StringComparison.OrdinalIgnoreCase))
35 | {
36 | // session is done
37 | Tracer.Trace($"Terminating as session was ended (instance {InstanceId:B})");
38 | return 0;
39 | }
40 |
41 | var encodingToUse = await Console.In.ReadLineAsync();
42 | var outputFile = await Console.In.ReadLineAsync();
43 |
44 | try
45 | {
46 | var parseErrors = false;
47 | try
48 | {
49 | watch.Restart();
50 |
51 | // we find a flavor here, as we want to support different main methods, based on file ending
52 | var flavor = XmlFlavorFinder.Find(inputFile);
53 |
54 | var file = Parser.Parse(inputFile, encodingToUse, flavor);
55 |
56 | using (var writer = SystemFile.CreateText(outputFile))
57 | {
58 | YamlWriter.Write(writer, file);
59 | }
60 |
61 | parseErrors = file.ParsingErrorsDetected == true;
62 | if (parseErrors)
63 | {
64 | var parsingError = file.ParsingErrors[0];
65 | Tracer.Trace(parsingError.ErrorMessage);
66 | Tracer.Trace(parsingError.Location);
67 | }
68 |
69 | // clean-up after big files
70 | if (IsBigFile(inputFile))
71 | {
72 | gcWatch.Restart();
73 |
74 | GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
75 | GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, false, true);
76 |
77 | Tracer.Trace($"Garbage collection took {gcWatch.Elapsed:s\\.fff} secs (instance {InstanceId:B})");
78 | }
79 |
80 | Console.WriteLine(parseErrors ? "KO" : "OK");
81 | }
82 | finally
83 | {
84 | Tracer.Trace($"Parsing took {watch.Elapsed:s\\.fff} secs (instance {InstanceId:B}), errors found: {parseErrors}");
85 | }
86 | }
87 | catch (Exception ex)
88 | {
89 | Tracer.Trace($"Exception: {ex}", ex);
90 |
91 | Console.WriteLine("KO");
92 |
93 | return 0;
94 | }
95 | }
96 | }
97 |
98 | private static bool IsBigFile(string inputFile)
99 | {
100 | var info = new FileInfo(inputFile);
101 |
102 | return info.Exists && info.Length > 10_000_000;
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorFinder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Xml;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
8 | {
9 | public static class XmlFlavorFinder
10 | {
11 | private static readonly Type XmlFlavorType = typeof(XmlFlavor);
12 |
13 | private static readonly XmlFlavor[] Flavors = typeof(XmlFlavorFinder).Assembly
14 | .GetTypes()
15 | .Where(_ => !_.IsAbstract)
16 | .Where(_ => _.IsClass)
17 | .Where(_ => _ != XmlFlavorType) // ignore XML flavor here as that is the fall-back type
18 | .Where(_ => XmlFlavorType.IsAssignableFrom(_))
19 | .Select(_ => _.GetConstructor(Type.EmptyTypes))
20 | .Select(_ => _?.Invoke(null))
21 | .OfType()
22 | .Concat(new[] { new XmlFlavor() }) // add XML flavor here as that is the fall-back type
23 | .ToArray();
24 |
25 | public static IXmlFlavor Find(string filePath)
26 | {
27 | if (new FileInfo(filePath).Length == 0)
28 | {
29 | // empty file
30 | return new XmlFlavor();
31 | }
32 |
33 | // var flavors = Flavors.Where(_ => _.Supports(filePath)).ToList();
34 | // return flavors.Count == 1 ? flavors[0] : GetXmlFlavorForDocument(filePath) ?? new XmlFlavor(); // just in case use XML flavor as fall-back (happens e.g. if XML encoding is wrong and an XmlException gets thrown)
35 |
36 | return GetXmlFlavorForDocument(filePath) ?? new XmlFlavor(); // just in case use XML flavor as fall-back (happens e.g. if XML encoding is wrong and an XmlException gets thrown)
37 | }
38 |
39 | private static IXmlFlavor GetXmlFlavorForDocument(string filePath)
40 | {
41 | var info = GetDocumentInfo(filePath);
42 | return info != null ? Flavors.FirstOrDefault(_ => _.Supports(info)) : null;
43 | }
44 |
45 | private static DocumentInfo GetDocumentInfo(string filePath)
46 | {
47 | try
48 | {
49 | using (var reader = new XmlTextReader(filePath))
50 | {
51 | while (reader.Read())
52 | {
53 | // get first element
54 | if (reader.NodeType == XmlNodeType.Element)
55 | {
56 | var name = reader.LocalName;
57 | var ns = reader.LookupNamespace(reader.Prefix);
58 |
59 | var attributes = new List>();
60 | var attributeCount = reader.AttributeCount;
61 | for (var i = 0; i < attributeCount; i++)
62 | {
63 | reader.MoveToAttribute(i);
64 | var attributeName = reader.LocalName;
65 | var attributeValue = reader.GetAttribute(i);
66 | attributes.Add(new KeyValuePair(attributeName, attributeValue));
67 | }
68 |
69 | return new DocumentInfo
70 | {
71 | RootElement = name,
72 | Namespace = ns,
73 | Attributes = attributes,
74 | };
75 | }
76 | }
77 | }
78 | }
79 | catch (XmlException ex)
80 | {
81 | // root element not contained, so ignore
82 | Tracer.Trace($"While parsing '{filePath}', following {ex.GetType().Name} was thrown: {ex}", ex);
83 | }
84 |
85 | return null;
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForEdmxV3.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
9 | {
10 | public sealed class XmlFlavorForEdmxV3 : XmlFlavor
11 | {
12 | private const string Namespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
13 |
14 | private static readonly HashSet TerminalNodeNames = new HashSet
15 | {
16 | "EntitySet",
17 | "AssociationSet",
18 | //// "Association", // Unsure whether that's necessary
19 | "End",
20 | "OnDelete",
21 | "Key",
22 | "Principal",
23 | "PropertyRef",
24 | "Property",
25 | "NavigationProperty",
26 | "ScalarProperty",
27 | "Dependent",
28 |
29 | "DesignerProperty",
30 | "EntityTypeShape",
31 | "InheritanceConnector",
32 | "AssociationConnector",
33 | "edmx:DesignerProperty",
34 | "edmx:EntityTypeShape",
35 | "edmx:InheritanceConnector",
36 | "edmx:AssociationConnector",
37 | };
38 |
39 | public override bool ParseAttributesEnabled => false;
40 |
41 | public override bool Supports(string filePath) => filePath.EndsWith(".edmx", StringComparison.OrdinalIgnoreCase);
42 |
43 | public override bool Supports(DocumentInfo info) => string.Equals(info.RootElement, "Edmx", StringComparison.OrdinalIgnoreCase)
44 | && string.Equals(info.Namespace, Namespace, StringComparison.OrdinalIgnoreCase);
45 |
46 | public override string GetName(XmlReader reader)
47 | {
48 | if (reader.NodeType == XmlNodeType.Element)
49 | {
50 | var name = reader.Name;
51 | switch (name)
52 | {
53 | case "Schema":
54 | return GetIdentifier(reader, "Namespace") ?? name;
55 |
56 | case "FunctionImportMapping":
57 | return GetIdentifier(reader, "FunctionName") ?? name;
58 |
59 | default:
60 | var identifier = GetIdentifier(reader, "Name", "Role", "TypeName", "StoreEntitySet", "EntityType", "Association");
61 | return identifier ?? name;
62 | }
63 | }
64 |
65 | return base.GetName(reader);
66 | }
67 |
68 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.Name : base.GetType(reader);
69 |
70 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => TerminalNodeNames.Contains(node?.Type);
71 |
72 | private static string GetIdentifier(XmlReader reader, params string[] attributeNames) => attributeNames.Select(reader.GetAttribute).FirstOrDefault(_ => _ != null);
73 | }
74 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_FxCop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_FxCop
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "FxCop_CustomDictionary.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches()
29 | {
30 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "FxCop_CustomDictionary.xml"));
31 | }
32 |
33 | [Test]
34 | public void File_LocationSpan_matches()
35 | {
36 | Assert.Multiple(() =>
37 | {
38 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
39 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(28, 13)), "Wrong end");
40 |
41 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
42 | });
43 | }
44 |
45 | [Test]
46 | public void Root_LocationSpan_matches()
47 | {
48 | Assert.Multiple(() =>
49 | {
50 | Assert.That(_root.Name, Is.EqualTo("Dictionary"));
51 |
52 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
53 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(28, 13)), "Wrong end");
54 |
55 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 13)), "Wrong header");
56 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(617, 629)), "Wrong footer");
57 | });
58 | }
59 |
60 | [Test]
61 | public void Acronyms_LocationSpan_matches()
62 | {
63 | var node = _root.Children.OfType().First(_ => _.Name == "Acronyms");
64 |
65 | Assert.Multiple(() =>
66 | {
67 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(2, 1)), "Wrong start");
68 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(6, 15)), "Wrong end");
69 |
70 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(14, 27)), "Wrong header");
71 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(107, 121)), "Wrong footer");
72 | });
73 | }
74 |
75 | [Test]
76 | public void Words_LocationSpan_matches()
77 | {
78 | var node = _root.Children.OfType().First(_ => _.Name == "Words");
79 |
80 | Assert.Multiple(() =>
81 | {
82 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(7, 1)), "Wrong start");
83 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(27, 12)), "Wrong end");
84 |
85 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(122, 132)), "Wrong header");
86 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(605, 616)), "Wrong footer");
87 | });
88 | }
89 |
90 | [Test]
91 | public void Unrecognized_Words_LocationSpan_matches()
92 | {
93 | var node = _root.Children.OfType().First(_ => _.Name == "Words").Children.OfType().First(_ => _.Name == "Unrecognized");
94 |
95 | Assert.Multiple(() =>
96 | {
97 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(8, 1)), "Wrong start");
98 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(10, 21)), "Wrong end");
99 |
100 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(133, 152)), "Wrong header");
101 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(179, 199)), "Wrong footer");
102 | });
103 | }
104 |
105 | [Test]
106 | public void Word_in_Unrecognized_Words_LocationSpan_matches()
107 | {
108 | var node = _root.Children.OfType().First(_ => _.Name == "Words").Children.OfType().First(_ => _.Name == "Unrecognized").Children.Single() as TerminalNode;
109 |
110 | Assert.Multiple(() =>
111 | {
112 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(9, 1)), "Wrong start");
113 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(9, 26)), "Wrong end");
114 |
115 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(153, 178)), "Wrong span");
116 | });
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/Parser/Flavors/XmlFlavorForNuSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | namespace MiKoSolutions.SemanticParsers.Xml.Flavors
9 | {
10 | public sealed class XmlFlavorForNuSpec : XmlFlavor
11 | {
12 | private static readonly HashSet NonTerminalNodeNames = new HashSet
13 | {
14 | ElementNames.Package,
15 | ElementNames.Metadata,
16 | ElementNames.Id,
17 | ElementNames.Files,
18 | ElementNames.FrameworkAssemblies,
19 | ElementNames.Dependencies,
20 | ElementNames.Group,
21 | ElementNames.References,
22 | };
23 |
24 | public override bool ParseAttributesEnabled => false;
25 |
26 | public override bool Supports(string filePath) => filePath.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase);
27 |
28 | public override bool Supports(DocumentInfo info)
29 | {
30 | if (info.RootElement == ElementNames.Package)
31 | {
32 | if (info.Namespace is null)
33 | {
34 | return true;
35 | }
36 |
37 | return info.Namespace.EndsWith("/nuspec.xsd", StringComparison.OrdinalIgnoreCase);
38 | }
39 |
40 | return false;
41 | }
42 |
43 | public override string GetName(XmlReader reader)
44 | {
45 | if (reader.NodeType == XmlNodeType.Element)
46 | {
47 | var name = reader.LocalName;
48 | var attributeName = GetAttributeName(name);
49 | var identifier = reader.GetAttribute(attributeName);
50 | return identifier ?? name;
51 | }
52 |
53 | return base.GetName(reader);
54 | }
55 |
56 | public override string GetType(XmlReader reader) => reader.NodeType == XmlNodeType.Element ? reader.LocalName : base.GetType(reader);
57 |
58 | public override ContainerOrTerminalNode FinalAdjustAfterParsingComplete(ContainerOrTerminalNode node)
59 | {
60 | if (node is Container c)
61 | {
62 | switch (c.Type)
63 | {
64 | case ElementNames.Id:
65 | // side effect: set content here to be able to get it for MetaData as well
66 | node.Content = c.Children.FirstOrDefault(_ => _.Type == NodeType.Text)?.Content.Trim();
67 |
68 | // ID shall be a terminal node
69 | return node.ToTerminalNode();
70 |
71 | case ElementNames.Metadata:
72 | node.Name = c.Children.FirstOrDefault(_ => _.Type == ElementNames.Id)?.Content.Trim();
73 | break;
74 | }
75 | }
76 |
77 | return base.FinalAdjustAfterParsingComplete(node);
78 | }
79 |
80 | protected override bool ShallBeTerminalNode(ContainerOrTerminalNode node) => !NonTerminalNodeNames.Contains(node?.Type);
81 |
82 | private static string GetAttributeName(string name)
83 | {
84 | switch (name)
85 | {
86 | case ElementNames.Group:
87 | return AttributeNames.TargetFramework;
88 |
89 | case ElementNames.File:
90 | return AttributeNames.Src;
91 |
92 | case ElementNames.Repository:
93 | return AttributeNames.Url;
94 |
95 | default:
96 | return AttributeNames.Id;
97 | }
98 | }
99 |
100 | private static class ElementNames
101 | {
102 | internal const string Dependencies = "dependencies";
103 | internal const string Files = "files";
104 | internal const string File = "file";
105 | internal const string FrameworkAssemblies = "frameworkAssemblies";
106 | internal const string Id = "id";
107 | internal const string Metadata = "metadata";
108 | internal const string Package = "package";
109 | internal const string References = "references";
110 | internal const string Repository = "repository";
111 | internal const string Group = "group";
112 | }
113 |
114 | private static class AttributeNames
115 | {
116 | internal const string Id = "id";
117 | internal const string Src = "src";
118 | internal const string TargetFramework = "targetFramework";
119 | internal const string Url = "url";
120 | }
121 | }
122 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_VisualBuild_Script.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
7 |
8 | using NUnit.Framework;
9 |
10 | namespace MiKoSolutions.SemanticParsers.Xml
11 | {
12 | [TestFixture]
13 | public class ParserTests_VisualBuild_Script
14 | {
15 | private Yaml.File _objectUnderTest;
16 | private Yaml.Container _root;
17 |
18 | [SetUp]
19 | public void PrepareTest()
20 | {
21 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
22 | var fileName = Path.Combine(parentDirectory, "Resources", "VisualBuild_script.xml");
23 |
24 | _objectUnderTest = Parser.Parse(fileName);
25 | _root = _objectUnderTest.Children.Single();
26 | }
27 |
28 | [Test]
29 | public void File_Name_matches()
30 | {
31 | Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "VisualBuild_script.xml"));
32 | }
33 |
34 | [Test]
35 | public void File_LocationSpan_matches()
36 | {
37 | Assert.Multiple(() =>
38 | {
39 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
40 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(18, 10)), "Wrong end");
41 |
42 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
43 | });
44 | }
45 |
46 | [Test]
47 | public void Root_LocationSpan_matches()
48 | {
49 | Assert.Multiple(() =>
50 | {
51 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
52 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(18, 10)), "Wrong end");
53 |
54 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 76)), "Wrong header");
55 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(556, 565)), "Wrong footer");
56 | });
57 | }
58 |
59 | [Test]
60 | public void Steps_LocationSpan_matches()
61 | {
62 | var node = _root.Children.OfType().First();
63 |
64 | Assert.Multiple(() =>
65 | {
66 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(3, 1)), "Wrong start");
67 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(14, 12)), "Wrong end");
68 |
69 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(77, 96)), "Wrong header");
70 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(480, 491)), "Wrong footer");
71 | });
72 | }
73 |
74 | [Test]
75 | public void Step_1_LocationSpan_matches()
76 | {
77 | var node = _root.Children.OfType().First().Children.OfType().First(_ => _.Type.StartsWith("step "));
78 |
79 | Assert.Multiple(() =>
80 | {
81 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(4, 1)), "Wrong start");
82 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(9, 13)), "Wrong end");
83 |
84 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(97, 362)), "Wrong span");
85 | });
86 | }
87 |
88 | [Test]
89 | public void Step_2_LocationSpan_matches()
90 | {
91 | var node = _root.Children.OfType().First().Children.OfType().Last(_ => _.Type.StartsWith("step "));
92 |
93 | Assert.Multiple(() =>
94 | {
95 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(10, 1)), "Wrong start");
96 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(13, 13)), "Wrong end");
97 |
98 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(363, 479)), "Wrong span");
99 | });
100 | }
101 |
102 | [Test]
103 | public void Macro_1_LocationSpan_matches()
104 | {
105 | var container = _root.Children.OfType().Last();
106 | Assert.That(container.Name, Is.EqualTo("macros"));
107 |
108 | var node = container.Children.OfType().First(_ => _.Type.StartsWith("macro"));
109 |
110 | Assert.Multiple(() =>
111 | {
112 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(16, 1)), "Wrong start");
113 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(16, 39)), "Wrong end");
114 |
115 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(504, 542)), "Wrong span");
116 | });
117 | }
118 |
119 | [Test]
120 | public void RoundTrip_does_not_report_parsing_errors()
121 | {
122 | var builder = new StringBuilder();
123 | using (var writer = new StringWriter(builder))
124 | {
125 | YamlWriter.Write(writer, _objectUnderTest);
126 | }
127 |
128 | Assert.That(builder.ToString(), Does.Contain("parsingErrorsDetected: false"));
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/Tests/ParserTests_CData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
6 |
7 | using NUnit.Framework;
8 |
9 | namespace MiKoSolutions.SemanticParsers.Xml
10 | {
11 | [TestFixture]
12 | public class ParserTests_CData
13 | {
14 | private Yaml.File _objectUnderTest;
15 | private Yaml.Container _root;
16 |
17 | [SetUp]
18 | public void PrepareTest()
19 | {
20 | var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
21 | var fileName = Path.Combine(parentDirectory, "Resources", "test_with_CDATA.xml");
22 |
23 | _objectUnderTest = Parser.Parse(fileName);
24 | _root = _objectUnderTest.Children.Single();
25 | }
26 |
27 | [Test]
28 | public void File_Name_matches() => Assert.That(_objectUnderTest.Name, Does.EndWith(Path.DirectorySeparatorChar + "test_with_CDATA.xml"));
29 |
30 | [Test]
31 | public void File_LocationSpan_matches()
32 | {
33 | Assert.Multiple(() =>
34 | {
35 | Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start");
36 | Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(11, 7)), "Wrong end");
37 |
38 | Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(CharacterSpan.None), "Wrong footer");
39 | });
40 | }
41 |
42 | [Test]
43 | public void Root_LocationSpan_matches()
44 | {
45 | Assert.Multiple(() =>
46 | {
47 | Assert.That(_root.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 1)), "Wrong start");
48 | Assert.That(_root.LocationSpan.End, Is.EqualTo(new LineInfo(11, 7)), "Wrong end");
49 |
50 | Assert.That(_root.HeaderSpan, Is.EqualTo(new CharacterSpan(0, 48)), "Wrong header");
51 | Assert.That(_root.FooterSpan, Is.EqualTo(new CharacterSpan(215, 221)), "Wrong footer");
52 | });
53 | }
54 |
55 | [Test]
56 | public void First_Level1_matches()
57 | {
58 | var node = _root.Children.First(_ => _.Name == "level1") as Container;
59 |
60 | Assert.Multiple(() =>
61 | {
62 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(3, 1)), "Wrong start");
63 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(5, 13)), "Wrong end");
64 |
65 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(49, 60)), "Wrong header");
66 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(99, 111)), "Wrong footer");
67 | });
68 | }
69 |
70 | [Test]
71 | public void First_CData_matches()
72 | {
73 | var node = _root.Children.OfType().SelectMany(_ => _.Children).First(_ => _.Type == NodeType.CDATA) as TerminalNode;
74 |
75 | Assert.Multiple(() =>
76 | {
77 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(4, 1)), "Wrong start");
78 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(4, 38)), "Wrong end");
79 |
80 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(61, 98)), "Wrong span");
81 | });
82 | }
83 |
84 | [Test]
85 | public void Second_Level1_matches()
86 | {
87 | var node = _root.Children.Last(_ => _.Name == "level1") as Container;
88 |
89 | Assert.Multiple(() =>
90 | {
91 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(6, 1)), "Wrong start");
92 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(10, 13)), "Wrong end");
93 |
94 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(112, 123)), "Wrong header");
95 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(202, 214)), "Wrong footer");
96 | });
97 | }
98 |
99 | [Test]
100 | public void First_Level2_matches()
101 | {
102 | var node = _root.Children.OfType().SelectMany(_ => _.Children).First(_ => _.Name == "level2") as Container;
103 |
104 | Assert.Multiple(() =>
105 | {
106 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(7, 1)), "Wrong start");
107 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(9, 18)), "Wrong end");
108 |
109 | Assert.That(node.HeaderSpan, Is.EqualTo(new CharacterSpan(124, 135)), "Wrong header");
110 | Assert.That(node.FooterSpan, Is.EqualTo(new CharacterSpan(191, 201)), "Wrong footer");
111 | });
112 | }
113 |
114 | [Test]
115 | public void Last_CData_matches()
116 | {
117 | var node = _root.Children.OfType().SelectMany(_ => _.Children).OfType().SelectMany(_ => _.Children).First(_ => _.Type == NodeType.CDATA) as TerminalNode;
118 |
119 | Assert.Multiple(() =>
120 | {
121 | Assert.That(node.LocationSpan.Start, Is.EqualTo(new LineInfo(7, 13)), "Wrong start");
122 | Assert.That(node.LocationSpan.End, Is.EqualTo(new LineInfo(9, 7)), "Wrong end");
123 |
124 | Assert.That(node.Span, Is.EqualTo(new CharacterSpan(136, 190)), "Wrong span");
125 | });
126 | }
127 | }
128 | }
--------------------------------------------------------------------------------
/Parser/CommentCleaner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using MiKoSolutions.SemanticParsers.Xml.Yaml;
4 |
5 | using Container = MiKoSolutions.SemanticParsers.Xml.Yaml.Container;
6 |
7 | namespace MiKoSolutions.SemanticParsers.Xml
8 | {
9 | public static class CommentCleaner
10 | {
11 | public static void Clean(File file)
12 | {
13 | foreach (var child in file.Children)
14 | {
15 | Clean(child, true);
16 | }
17 | }
18 |
19 | private static void Clean(Container parent, bool initial = false)
20 | {
21 | var children = parent.Children;
22 |
23 | const int first = 0;
24 | var last = children.Count - 1;
25 |
26 | var i = first;
27 | while (i <= last)
28 | {
29 | var child = children[i];
30 | if (child is Container c)
31 | {
32 | Clean(c);
33 | }
34 | else
35 | {
36 | var comment = child as TerminalNode;
37 | if (comment?.Type == NodeType.Comment)
38 | {
39 | if (initial && i == last)
40 | {
41 | // special situation, a comment is the last element before the root footer
42 | // so we simply adjust the footer instead
43 | AdjustLocationSpan(comment, parent);
44 | parent.FooterSpan = new CharacterSpan(comment.Span.Start, parent.FooterSpan.End);
45 | }
46 | else
47 | {
48 | var node = GetNodeToAdjust(parent, comment, i, first, last);
49 |
50 | Adjust(comment, node);
51 | }
52 |
53 | children.Remove(comment);
54 |
55 | last--;
56 | i--;
57 | }
58 | }
59 |
60 | i++;
61 | }
62 | }
63 |
64 | private static ContainerOrTerminalNode GetNodeToAdjust(Container parent, TerminalNode comment, int index, int first, int last)
65 | {
66 | var containers = parent.Children;
67 |
68 | if (index == first && index == last)
69 | {
70 | return parent;
71 | }
72 |
73 | if (index == first)
74 | {
75 | return containers[index + 1];
76 | }
77 |
78 | if (index == last)
79 | {
80 | return containers[index - 1];
81 | }
82 |
83 | var next = containers[index + 1];
84 | var before = containers[index - 1];
85 |
86 | var trailing = comment.LocationSpan.Start.LineNumber == before.LocationSpan.End.LineNumber;
87 | return trailing ? before : next;
88 | }
89 |
90 | private static void Adjust(TerminalNode comment, ContainerOrTerminalNode nodeToAdjust)
91 | {
92 | AdjustLocationSpan(comment, nodeToAdjust);
93 |
94 | if (nodeToAdjust is TerminalNode t)
95 | {
96 | AdjustSpan(comment, t);
97 | }
98 | else if (nodeToAdjust is Container c)
99 | {
100 | AdjustSpan(comment, c);
101 | }
102 | }
103 |
104 | private static void AdjustLocationSpan(TerminalNode comment, ContainerOrTerminalNode nodeToAdjust)
105 | {
106 | var commentStart = comment.LocationSpan.Start;
107 | var commentEnd = comment.LocationSpan.End;
108 |
109 | var nodeStart = nodeToAdjust.LocationSpan.Start;
110 | var nodeEnd = nodeToAdjust.LocationSpan.End;
111 |
112 | var start = nodeStart < commentStart ? nodeStart : commentStart;
113 | var end = nodeEnd < commentEnd ? commentEnd : nodeEnd;
114 |
115 | nodeToAdjust.LocationSpan = new LocationSpan(start, end);
116 | }
117 |
118 | private static void AdjustSpan(TerminalNode comment, TerminalNode nodeToAdjust)
119 | {
120 | var min = Math.Min(nodeToAdjust.Span.Start, comment.Span.Start);
121 | var max = Math.Max(nodeToAdjust.Span.End, comment.Span.End);
122 | nodeToAdjust.Span = new CharacterSpan(min, max);
123 | }
124 |
125 | private static void AdjustSpan(TerminalNode comment, Container nodeToAdjust)
126 | {
127 | var commentStart = comment.Span.Start;
128 | var commentEnd = comment.Span.End;
129 |
130 | var headerStart = nodeToAdjust.HeaderSpan.Start;
131 | var headerEnd = nodeToAdjust.HeaderSpan.End;
132 |
133 | var footerStart = nodeToAdjust.FooterSpan.Start;
134 | var footerEnd = nodeToAdjust.FooterSpan.End;
135 |
136 | if (commentStart > headerEnd && commentStart < footerStart)
137 | {
138 | // comment if after header
139 | nodeToAdjust.HeaderSpan = new CharacterSpan(headerStart, commentEnd);
140 | }
141 | else
142 | {
143 | var min = Math.Min(headerStart, commentStart);
144 | var max = Math.Max(footerEnd, commentEnd);
145 | nodeToAdjust.HeaderSpan = new CharacterSpan(min, headerEnd);
146 | nodeToAdjust.FooterSpan = new CharacterSpan(footerStart, max);
147 | }
148 | }
149 | }
150 | }
--------------------------------------------------------------------------------