├── 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 | [![Build status](https://ci.appveyor.com/api/projects/status/9dnbofw2gpedfiaa?svg=true)](https://ci.appveyor.com/project/RalfKoban/xml-semantic-external-parser/branch/master) 10 | [![codecov](https://codecov.io/gh/RalfKoban/xml-semantic-external-parser/branch/master/graph/badge.svg)](https://codecov.io/gh/RalfKoban/xml-semantic-external-parser) 11 | 12 | [![Build history](https://buildstats.info/appveyor/chart/RalfKoban/xml-semantic-external-parser)](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 | } --------------------------------------------------------------------------------