├── .gitignore ├── Parser ├── App.config ├── Parser.cs ├── Parser.csproj ├── Parser.csproj.DotSettings ├── Parser.ruleset ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Yaml │ ├── CharacterSpan.cs │ ├── Container.cs │ ├── ContainerOrTerminalNode.cs │ ├── File.cs │ ├── IntendedString.cs │ ├── LineInfo.cs │ ├── LocationSpan.cs │ ├── ParsingError.cs │ └── TerminalNode.cs └── packages.config ├── README.md ├── ResXSemanticParser.sln ├── ResXSemanticParser.sln.DotSettings ├── ResXSemanticParser.v3.ncrunchsolution ├── Screenshots ├── ResX_SemanticDiff.png ├── ResX_TextDiff.png └── ResX_VisualDiff.png ├── Tests ├── ParserAdditionalTests.cs ├── ParserTests.cs ├── Properties │ └── AssemblyInfo.cs ├── Tests.csproj ├── Tests.ruleset ├── packages.config ├── test.resx ├── test2.Designer.cs └── test2.resx ├── appveyor.yml └── codecov.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs/ 2 | *.ncrunchsolution.user 3 | /*/obj/ 4 | /*/bin/ 5 | /packages/ 6 | /_NCrunch*/ 7 | *.user 8 | -------------------------------------------------------------------------------- /Parser/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Parser/Parser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml; 5 | using System.Xml.Linq; 6 | 7 | using MiKoSolutions.SemanticParsers.ResX.Yaml; 8 | 9 | using File = System.IO.File; 10 | 11 | namespace MiKoSolutions.SemanticParsers.ResX 12 | { 13 | public static class Parser 14 | { 15 | private static readonly char LineEndingCR = '\r'; 16 | private static readonly char LineEndingLF = '\n'; 17 | private static readonly char[] LineEndings = { LineEndingCR, LineEndingLF }; 18 | 19 | public static bool TryParse(string path, out string yamlContent) 20 | { 21 | var parsingFine = TryParseFile(path, out Yaml.File file); 22 | yamlContent = file.ToYamlString(); 23 | 24 | return parsingFine; 25 | } 26 | 27 | public static bool TryParseFile(string path, out Yaml.File yamlContent) 28 | { 29 | var allText = File.ReadAllText(path); 30 | 31 | if (string.IsNullOrWhiteSpace(allText)) 32 | { 33 | yamlContent = new Yaml.File 34 | { 35 | Name = path, 36 | LocationSpan = new LocationSpan(new LineInfo(0, 0), new LineInfo(0, 0)), 37 | FooterSpan = new CharacterSpan(0, -1), 38 | }; 39 | 40 | return true; 41 | } 42 | 43 | return CreateYaml(path, allText, out yamlContent); 44 | } 45 | 46 | private static bool CreateYaml(string path, string allText, out Yaml.File yamlContent) 47 | { 48 | var lines = LineSplitter.SplitLines(allText); 49 | 50 | var file = YamlFile(lines, path); 51 | 52 | XDocument document = null; 53 | try 54 | { 55 | document = XDocument.Parse(allText, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); 56 | } 57 | catch (Exception ex) 58 | { 59 | file.ParsingErrors.Add(new ParsingError { ErrorMessage = ex.Message }); 60 | } 61 | 62 | var parsingFine = file.ParsingErrors.Count == 0; 63 | if (parsingFine) 64 | { 65 | var root = YamlRoot(lines, allText); 66 | 67 | // adjust footer 68 | var footerStart = root.FooterSpan.End + 1; 69 | var footerEnd = allText.Length - 1; 70 | if (footerStart < footerEnd) 71 | { 72 | file.FooterSpan = new CharacterSpan(footerStart, footerEnd); 73 | } 74 | 75 | YamlInfrastructureCommentAndSchema(root, lines, allText); 76 | 77 | file.Children.Add(root); 78 | root.Children.AddRange(Yaml("resheader", document, lines)); 79 | root.Children.AddRange(Yaml("data", document, lines)); 80 | root.Children.AddRange(Yaml("metadata", document, lines)); 81 | root.Children.AddRange(Yaml("assembly", document, lines)); 82 | 83 | // sort based on span 84 | root.Children.Sort(new AscendingSpanComparer()); 85 | } 86 | 87 | yamlContent = file; 88 | 89 | return parsingFine; 90 | } 91 | 92 | private static Yaml.File YamlFile(string[] lines, string fileName) 93 | { 94 | return new Yaml.File 95 | { 96 | Name = fileName, 97 | LocationSpan = new LocationSpan(new LineInfo(1, 0), new LineInfo(lines.Length, lines.Last().Length)), 98 | FooterSpan = new CharacterSpan(0, -1), 99 | }; 100 | } 101 | 102 | private static Container YamlRoot(string[] lines, string allText) 103 | { 104 | const string TAG = "root"; 105 | const string STARTTAG = "<" + TAG + ">"; 106 | const string ENDTAG = ""; 107 | 108 | var endLine = GetLastLineInfo(ENDTAG, lines); 109 | 110 | var headerSpan = GetFirstCharacterSpan(STARTTAG, allText); 111 | 112 | // adjust root header to include XML header 113 | headerSpan = new CharacterSpan(0, headerSpan.End); 114 | 115 | var footerSpan = GetLastCharacterSpan(ENDTAG, allText); 116 | 117 | return new Container 118 | { 119 | Type = TAG, 120 | Name = TAG, 121 | LocationSpan = new LocationSpan(YamlLine(1, 1), endLine), 122 | HeaderSpan = headerSpan, 123 | FooterSpan = footerSpan, 124 | }; 125 | } 126 | 127 | private static void YamlInfrastructureCommentAndSchema(Container parent, string[] lines, string allText) 128 | { 129 | const string ENDTAG = ""; 130 | 131 | if (allText.LastIndexOf(ENDTAG, StringComparison.OrdinalIgnoreCase) >= 0) 132 | { 133 | var startLineComment = GetFirstLineInfo(" 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | Image1.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | 125 | some string 126 | 127 | 128 | some multiline string A 129 | some multiline string B 130 | some multiline string C 131 | 132 | 133 | 134 | some string 135 | 136 | 137 | some string 138 | 139 | 140 | anything 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /Tests/test2.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace MiKoSolutions.SemanticParsers.ResX { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class test2 { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal test2() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MiKoSolutions.SemanticParsers.ResX.test2", typeof(test2).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to something. 65 | /// 66 | internal static string String1 { 67 | get { 68 | return ResourceManager.GetString("String1", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to more text 74 | ///with line break. 75 | /// 76 | internal static string String2 { 77 | get { 78 | return ResourceManager.GetString("String2", resourceCulture); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/test2.resx: -------------------------------------------------------------------------------- 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 | text/microsoft-resx 51 | 52 | 53 | 2.0 54 | 55 | 56 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 57 | 58 | 59 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 60 | 61 | 62 | something 63 | 64 | 65 | more text 66 | with line break 67 | Some comment 68 | 69 | -------------------------------------------------------------------------------- /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\ResXSemanticParsers 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\ResXSemanticParsers\ResXSemanticParser.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: ResXSemanticParser.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\ResXSemanticParsers\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\ResXSemanticParsers\CodeCoverage.xml" --flag api 65 | 66 | # cache: 67 | 68 | -------------------------------------------------------------------------------- /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: e2924b31-58b4-44d7-8093-b315286bf58f --------------------------------------------------------------------------------