This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit 71fcc19af0010baba7f7ccea4d6c91e29addde89
Author: Shad Storhaug <s...@shadstorhaug.com>
AuthorDate: Sat Nov 23 05:00:44 2019 +0700

    Ported Lucene.Net.Analysis.Morfologik + tests
---
 Lucene.Net.sln                                     |   16 +-
 build/Dependencies.props                           |    3 +
 .../publish-test-results-for-test-projects.yml     |   20 +
 build/build.ps1                                    |   12 +
 global.json                                        |    9 +-
 .../Lucene.Net.Analysis.Morfologik.csproj          |   68 ++
 .../Morfologik/MorfologikAnalyzer.cs               |   79 ++
 .../Morfologik/MorfologikFilter.cs                 |  183 +++
 .../Morfologik/MorfologikFilterFactory.cs          |  105 ++
 .../IMorphosyntacticTagsAttribute.cs               |   44 +
 .../MorphosyntacticTagsAttribute.cs                |  105 ++
 src/Lucene.Net.Analysis.Morfologik/Uk/README       |   11 +
 .../Uk/UkrainianMorfologikAnalyzer.cs              |  177 +++
 .../Uk/mapping_uk.txt                              |   19 +
 .../Uk/stopwords.txt                               | 1269 ++++++++++++++++++++
 src/Lucene.Net.Analysis.Morfologik/Uk/tagset.txt   |  170 +++
 .../Uk/ukrainian.dict                              |  Bin 0 -> 6929502 bytes
 .../Uk/ukrainian.info                              |   10 +
 .../Properties/AssemblyInfo.cs                     |    2 +
 .../Lucene.Net.Tests.Analysis.Morfologik.csproj    |   47 +
 .../Morfologik/TestMorfologikAnalyzer.cs           |  235 ++++
 .../Morfologik/TestMorfologikFilterFactory.cs      |  107 ++
 .../Morfologik/custom-dictionary.dict              |  Bin 0 -> 90 bytes
 .../Morfologik/custom-dictionary.info              |   24 +
 .../Morfologik/custom-dictionary.input             |    2 +
 .../Uk/TestUkrainianAnalyzer.cs                    |   92 ++
 26 files changed, 2804 insertions(+), 5 deletions(-)

diff --git a/Lucene.Net.sln b/Lucene.Net.sln
index 192f434..92655ec 100644
--- a/Lucene.Net.sln
+++ b/Lucene.Net.sln
@@ -193,9 +193,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = 
"Lucene.Net.Tests.TestFramew
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = 
"Lucene.Net.TestFramework.MSTest", 
"src\Lucene.Net.TestFramework.MSTest\Lucene.Net.TestFramework.MSTest.csproj", 
"{48520313-3B78-40D9-AE34-4864BFADF747}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = 
"Lucene.Net.Analysis.OpenNLP", 
"src\Lucene.Net.Analysis.OpenNLP\Lucene.Net.Analysis.OpenNLP.csproj", 
"{CC2CE069-5BBB-429E-8510-7C3FBA8069D5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = 
"Lucene.Net.Analysis.OpenNLP", 
"src\Lucene.Net.Analysis.OpenNLP\Lucene.Net.Analysis.OpenNLP.csproj", 
"{CC2CE069-5BBB-429E-8510-7C3FBA8069D5}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = 
"Lucene.Net.Tests.Analysis.OpenNLP", 
"src\Lucene.Net.Tests.Analysis.OpenNLP\Lucene.Net.Tests.Analysis.OpenNLP.csproj",
 "{88D6D124-711D-4232-AD70-F22AB6AF9EA1}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = 
"Lucene.Net.Tests.Analysis.OpenNLP", 
"src\Lucene.Net.Tests.Analysis.OpenNLP\Lucene.Net.Tests.Analysis.OpenNLP.csproj",
 "{88D6D124-711D-4232-AD70-F22AB6AF9EA1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = 
"Lucene.Net.Analysis.Morfologik", 
"src\Lucene.Net.Analysis.Morfologik\Lucene.Net.Analysis.Morfologik.csproj", 
"{17C7E54C-7A95-46A5-9905-90F68D349F3F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = 
"Lucene.Net.Tests.Analysis.Morfologik", 
"src\Lucene.Net.Tests.Analysis.Morfologik\Lucene.Net.Tests.Analysis.Morfologik.csproj",
 "{435F91AD-8BA4-4376-904C-385A165C1AF0}"
 EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -451,6 +455,14 @@ Global
                {88D6D124-711D-4232-AD70-F22AB6AF9EA1}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
                {88D6D124-711D-4232-AD70-F22AB6AF9EA1}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
                {88D6D124-711D-4232-AD70-F22AB6AF9EA1}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {17C7E54C-7A95-46A5-9905-90F68D349F3F}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
+               {17C7E54C-7A95-46A5-9905-90F68D349F3F}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {17C7E54C-7A95-46A5-9905-90F68D349F3F}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
+               {17C7E54C-7A95-46A5-9905-90F68D349F3F}.Release|Any CPU.Build.0 
= Release|Any CPU
+               {435F91AD-8BA4-4376-904C-385A165C1AF0}.Debug|Any CPU.ActiveCfg 
= Debug|Any CPU
+               {435F91AD-8BA4-4376-904C-385A165C1AF0}.Debug|Any CPU.Build.0 = 
Debug|Any CPU
+               {435F91AD-8BA4-4376-904C-385A165C1AF0}.Release|Any 
CPU.ActiveCfg = Release|Any CPU
+               {435F91AD-8BA4-4376-904C-385A165C1AF0}.Release|Any CPU.Build.0 
= Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
diff --git a/build/Dependencies.props b/build/Dependencies.props
index 6852efd..c9be589 100644
--- a/build/Dependencies.props
+++ b/build/Dependencies.props
@@ -43,6 +43,9 @@
     <MicrosoftCSharpPackageVersion>4.4.0</MicrosoftCSharpPackageVersion>
     
<MicrosoftExtensionsDependencyModelPackageVersion>2.0.0</MicrosoftExtensionsDependencyModelPackageVersion>
     
<MicrosoftNETTestSdkPackageVersion>16.2.0</MicrosoftNETTestSdkPackageVersion>
+    <MorfologikFsaPackageVersion>2.1.6-alpha-0001</MorfologikFsaPackageVersion>
+    
<MorfologikPolishPackageVersion>$(MorfologikFsaPackageVersion)</MorfologikPolishPackageVersion>
+    
<MorfologikStemmingPackageVersion>$(MorfologikFsaPackageVersion)</MorfologikStemmingPackageVersion>
     
<MSTestTestFrameworkPackageVersion>2.0.0</MSTestTestFrameworkPackageVersion>
     
<MSTestTestAdapterPackageVersion>$(MSTestTestFrameworkPackageVersion)</MSTestTestAdapterPackageVersion>
     
<NETStandardLibrary16PackageVersion>1.6.1</NETStandardLibrary16PackageVersion>
diff --git a/build/azure-templates/publish-test-results-for-test-projects.yml 
b/build/azure-templates/publish-test-results-for-test-projects.yml
index 1b61c3c..cdc53aa 100644
--- a/build/azure-templates/publish-test-results-for-test-projects.yml
+++ b/build/azure-templates/publish-test-results-for-test-projects.yml
@@ -91,6 +91,26 @@ steps:
     testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
     testResultsFileName: '${{ parameters.testResultsFileName }}'
 
+# Special case: Doesn't support .netcoreapp1.1
+- template: publish-test-results.yml
+  parameters:
+    framework: 'netcoreapp2.1'
+    testProjectName: 'Lucene.Net.Tests.Analysis.Morfologik'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results.yml
+  parameters:
+    framework: 'net451'
+    testProjectName: 'Lucene.Net.Tests.Analysis.Morfologik'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+
 - template: publish-test-results-for-target-frameworks.yml
   parameters:
     testProjectName: 'Lucene.Net.Tests._A-D'
diff --git a/build/build.ps1 b/build/build.ps1
index d9725b6..e31d1a1 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -220,6 +220,11 @@ task Publish -depends Compile -description "This task uses 
dotnet publish to pac
                                        continue
                                }
 
+                               # Special case - Morfologik doesn't support 
.NET Standard 1.x
+                               if 
($projectName.Contains("Tests.Analysis.Morfologik") -and 
($framework.StartsWith("netcoreapp1."))) {
+                                       continue
+                               }
+
                                $logPath = "$outDirectory/$framework"
                                $outputPath = "$logPath/$projectName"
 
@@ -304,6 +309,13 @@ task Test -depends InstallSDK, UpdateLocalSDKVersion, 
Restore -description "This
                                continue
                        }
 
+                       # Special case - Morfologik doesn't support .NET 
Standard 1.x
+                       if ($testName.Contains("Tests.Analysis.Morfologik") 
-and ($framework.StartsWith("netcoreapp1."))) {
+                               $totalProjects--
+                               $remainingProjects--
+                               continue
+                       }
+
                        Write-Host "  Next Project in Queue: $testName, 
Framework: $framework" -ForegroundColor Yellow
 
                        # Pause if we have queued too many parallel jobs
diff --git a/global.json b/global.json
index ce063d2..cccadab 100644
--- a/global.json
+++ b/global.json
@@ -1,3 +1,6 @@
-{
-    "sources": [ "src" ]
-}
\ No newline at end of file
+{
+  "sources": [ "src" ],
+  "sdk": {
+    "version": "2.2.300"
+  }
+}
diff --git 
a/src/Lucene.Net.Analysis.Morfologik/Lucene.Net.Analysis.Morfologik.csproj 
b/src/Lucene.Net.Analysis.Morfologik/Lucene.Net.Analysis.Morfologik.csproj
new file mode 100644
index 0000000..10b82ad
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Lucene.Net.Analysis.Morfologik.csproj
@@ -0,0 +1,68 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <Import Project="$(SolutionDir)build/NuGet.props" />
+
+  <PropertyGroup>
+    <TargetFrameworks>netstandard2.0</TargetFrameworks>
+    <TargetFrameworks 
Condition="$([MSBuild]::IsOsPlatform('Windows'))">$(TargetFrameworks);net451</TargetFrameworks>
+    <PackageTargetFallback Condition=" '$(TargetFramework)' == 
'netstandard1.6' ">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
+
+    <AssemblyTitle>Lucene.Net.Analysis.Morfologik</AssemblyTitle>
+    <RootNamespace>Lucene.Net.Analysis</RootNamespace>
+    <Description>Japanese Morphological Analyzer for the Lucene.Net full-text 
search engine library from The Apache Software Foundation.</Description>
+    <PackageTags>$(PackageTags);analysis;japanese</PackageTags>
+    
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
+    <NoWarn>$(NoWarn);1591;1573</NoWarn>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <None Remove="Uk\mapping_uk.txt" />
+    <None Remove="Uk\README" />
+    <None Remove="Uk\stopwords.txt" />
+    <None Remove="Uk\tagset.txt" />
+    <None Remove="Uk\ukrainian.dict" />
+    <None Remove="Uk\ukrainian.info" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="..\CommonAssemblyKeys.cs" 
Link="Properties\CommonAssemblyKeys.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <EmbeddedResource Include="Uk\mapping_uk.txt" />
+    <EmbeddedResource Include="Uk\README" />
+    <EmbeddedResource Include="Uk\stopwords.txt" />
+    <EmbeddedResource Include="Uk\tagset.txt" />
+    <EmbeddedResource Include="Uk\ukrainian.dict" />
+    <EmbeddedResource Include="Uk\ukrainian.info" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Morfologik.Fsa" 
Version="$(MorfologikFsaPackageVersion)" />
+    <PackageReference Include="Morfologik.Polish" 
Version="$(MorfologikPolishPackageVersion)" />
+    <PackageReference Include="Morfologik.Stemming" 
Version="$(MorfologikStemmingPackageVersion)" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference 
Include="..\Lucene.Net.Analysis.Common\Lucene.Net.Analysis.Common.csproj" />
+    <ProjectReference Include="..\Lucene.Net\Lucene.Net.csproj" />
+  </ItemGroup>
+
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
+    <DebugType>portable</DebugType>
+  </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
+    <DebugType>portable</DebugType>
+  </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'net45' ">
+    <DebugType>full</DebugType>
+  </PropertyGroup>
+
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
+    <Reference Include="System" />
+    <Reference Include="Microsoft.CSharp" />
+  </ItemGroup>
+
+</Project>
diff --git 
a/src/Lucene.Net.Analysis.Morfologik/Morfologik/MorfologikAnalyzer.cs 
b/src/Lucene.Net.Analysis.Morfologik/Morfologik/MorfologikAnalyzer.cs
new file mode 100644
index 0000000..9147da2
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Morfologik/MorfologikAnalyzer.cs
@@ -0,0 +1,79 @@
+// Lucene version compatibility level 8.2.0
+using Lucene.Net.Analysis.Standard;
+using Lucene.Net.Util;
+using Morfologik.Stemming;
+using Morfologik.Stemming.Polish;
+using System.IO;
+
+namespace Lucene.Net.Analysis.Morfologik
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// <see cref="Analyzer"/> using Morfologik library.
+    /// <para/>
+    /// See: <a href="http://morfologik.blogspot.com/";>Morfologik project 
page</a>
+    /// </summary>
+    /// <since>4.0.0</since>
+    public class MorfologikAnalyzer : Analyzer
+    {
+        private readonly Dictionary dictionary;
+        private readonly LuceneVersion version;
+
+        /// <summary>
+        /// Builds an analyzer with an explicit <see cref="Dictionary"/> 
resource.
+        /// <para/>
+        /// See: <a 
href="https://github.com/morfologik/";>https://github.com/morfologik/</a>
+        /// </summary>
+        /// <param name="version">Lucene compatibility version</param>
+        /// <param name="dictionary">A prebuilt automaton with inflected and 
base word forms.</param>
+        public MorfologikAnalyzer(LuceneVersion version, Dictionary dictionary)
+        {
+            this.version = version;
+            this.dictionary = dictionary;
+        }
+
+        /// <summary>
+        /// Builds an analyzer with the default Morfologik's Polish dictionary.
+        /// </summary>
+        /// <param name="version">Lucene compatibility version</param>
+        public MorfologikAnalyzer(LuceneVersion version)
+            : this(version, new PolishStemmer().Dictionary)
+        {
+        }
+
+        /// <summary>
+        /// Creates a <see cref="TokenStreamComponents"/>
+        /// which tokenizes all the text in the provided <paramref 
name="reader"/>.
+        /// </summary>
+        /// <param name="fieldName">Ignored field name.</param>
+        /// <param name="reader">Source of tokens.</param>
+        /// <returns>A <see cref="TokenStreamComponents"/>
+        /// built from a <see cref="StandardTokenizer"/> filtered with
+        /// <see cref="MorfologikFilter"/>.</returns>
+        protected override TokenStreamComponents CreateComponents(string 
fieldName, TextReader reader)
+        {
+            Tokenizer src = new StandardTokenizer(this.version, reader);
+
+            return new TokenStreamComponents(
+                src,
+                new MorfologikFilter(src, dictionary));
+        }
+
+    }
+}
diff --git a/src/Lucene.Net.Analysis.Morfologik/Morfologik/MorfologikFilter.cs 
b/src/Lucene.Net.Analysis.Morfologik/Morfologik/MorfologikFilter.cs
new file mode 100644
index 0000000..5562c8d
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Morfologik/MorfologikFilter.cs
@@ -0,0 +1,183 @@
+// Lucene version compatibility level 8.2.0
+using Lucene.Net.Analysis.Morfologik.TokenAttributes;
+using Lucene.Net.Analysis.TokenAttributes;
+using Lucene.Net.Support;
+using Lucene.Net.Util;
+using Morfologik.Stemming;
+using Morfologik.Stemming.Polish;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Lucene.Net.Analysis.Morfologik
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// <see cref="TokenFilter"/> using Morfologik library to transform input 
tokens into lemma and
+    /// morphosyntactic (POS) tokens. Applies to Polish only.
+    /// <para/>
+    /// MorfologikFilter contains a <see 
cref="MorphosyntacticTagsAttribute"/>, which provides morphosyntactic
+    /// annotations for produced lemmas. See the Morfologik documentation for 
details.
+    /// </summary>
+    public class MorfologikFilter : TokenFilter
+    {
+        private readonly ICharTermAttribute termAtt;
+        private readonly IMorphosyntacticTagsAttribute tagsAtt;
+        private readonly IPositionIncrementAttribute posIncrAtt;
+        private readonly IKeywordAttribute keywordAttr;
+
+        private readonly CharsRef scratch = new CharsRef();
+
+        private State current;
+        private readonly TokenStream input;
+        private readonly IStemmer stemmer;
+
+        private IList<WordData> lemmaList;
+        private readonly List<StringBuilder> tagsList = new 
List<StringBuilder>();
+
+        private int lemmaListIndex;
+
+        /// <summary>
+        /// Creates a filter with the default (Polish) dictionary.
+        /// </summary>
+        /// <param name="input">Input token stream.</param>
+        public MorfologikFilter(TokenStream input)
+            : this(input, new PolishStemmer().Dictionary)
+        {
+        }
+
+        /// <summary>
+        /// Creates a filter with a given dictionary.
+        /// </summary>
+        /// <param name="input">Input token stream.</param>
+        /// <param name="dict"><see cref="Dictionary"/> to use for 
stemming.</param>
+        public MorfologikFilter(TokenStream input, Dictionary dict)
+            : base(input)
+        {
+            this.termAtt = AddAttribute<ICharTermAttribute>();
+            this.tagsAtt = AddAttribute<IMorphosyntacticTagsAttribute>();
+            this.posIncrAtt = AddAttribute<IPositionIncrementAttribute>();
+            this.keywordAttr = AddAttribute<IKeywordAttribute>();
+
+            this.input = input;
+            this.stemmer = new DictionaryLookup(dict);
+            this.lemmaList = new List<WordData>();
+        }
+
+        /// <summary>
+        /// A regex used to split lemma forms.
+        /// </summary>
+        private readonly static Regex lemmaSplitter = new Regex("\\+|\\|", 
RegexOptions.Compiled);
+
+        private void PopNextLemma()
+        {
+            // One tag (concatenated) per lemma.
+            WordData lemma = lemmaList[lemmaListIndex++];
+            termAtt.SetEmpty().Append(lemma.GetStem().ToString());
+            var tag = lemma.GetTag();
+            if (tag != null)
+            {
+                string[] tags = lemmaSplitter.Split(tag.ToString());
+                for (int i = 0; i < tags.Length; i++)
+                {
+                    if (tagsList.Count <= i)
+                    {
+                        tagsList.Add(new StringBuilder());
+                    }
+                    StringBuilder buffer = tagsList[i];
+                    buffer.Length = 0;
+                    buffer.Append(tags[i]);
+                }
+                tagsAtt.Tags = tagsList.SubList(0, tags.Length);
+            }
+            else
+            {
+                tagsAtt.Tags = Collections.EmptyList<StringBuilder>();
+            }
+        }
+
+        /// <summary>
+        /// Lookup a given surface form of a token and update
+        /// <see cref="lemmaList"/> and <see cref="lemmaListIndex"/> 
accordingly.
+        /// </summary>
+        private bool LookupSurfaceForm(string token)
+        {
+            lemmaList = this.stemmer.Lookup(token);
+            lemmaListIndex = 0;
+            return lemmaList.Count > 0;
+        }
+
+        /// <summary>Retrieves the next token (possibly from the list of 
lemmas).</summary>
+        public override sealed bool IncrementToken()
+        {
+            if (lemmaListIndex < lemmaList.Count)
+            {
+                RestoreState(current);
+                posIncrAtt.PositionIncrement = 0;
+                PopNextLemma();
+                return true;
+            }
+            else if (this.input.IncrementToken())
+            {
+                if (!keywordAttr.IsKeyword &&
+                    (LookupSurfaceForm(termAtt.ToString()) || 
LookupSurfaceForm(ToLowercase(termAtt.ToString()))))
+                {
+                    current = CaptureState();
+                    PopNextLemma();
+                }
+                else
+                {
+                    tagsAtt.Clear();
+                }
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        /// <summary>Convert to lowercase in-place.</summary>
+        private string ToLowercase(string chs)
+        {
+            int length = chs.Length;
+            scratch.Length = length;
+            scratch.Grow(length);
+
+            char[] buffer = scratch.Chars;
+            for (int i = 0; i < length;)
+            {
+                i += Character.ToChars(
+                    Character.ToLower(Character.CodePointAt(chs, i)), buffer, 
i);
+            }
+
+            return scratch.ToString();
+        }
+
+        /// <summary>Resets stems accumulator and hands over to 
superclass.</summary>
+        public override void Reset()
+        {
+            lemmaListIndex = 0;
+            lemmaList = new List<WordData>();
+            tagsList.Clear();
+            base.Reset();
+        }
+    }
+}
diff --git 
a/src/Lucene.Net.Analysis.Morfologik/Morfologik/MorfologikFilterFactory.cs 
b/src/Lucene.Net.Analysis.Morfologik/Morfologik/MorfologikFilterFactory.cs
new file mode 100644
index 0000000..5feef4a
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Morfologik/MorfologikFilterFactory.cs
@@ -0,0 +1,105 @@
+// Lucene version compatibility level 8.2.0
+using Lucene.Net.Analysis.Util;
+using Morfologik.Stemming;
+using Morfologik.Stemming.Polish;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Lucene.Net.Analysis.Morfologik
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// Filter factory for <see cref="MorfologikFilter"/>.
+    /// <para/>
+    /// An explicit resource name of the dictionary (<c>".dict"</c>) can be 
+    /// provided via the <code>dictionary</code> attribute, as the example 
below demonstrates:
+    /// <code>
+    /// &lt;fieldType name="text_mylang" class="solr.TextField" 
positionIncrementGap="100"&gt;
+    ///   &lt;analyzer&gt;
+    ///     &lt;tokenizer class="solr.WhitespaceTokenizerFactory"/&gt;
+    ///     &lt;filter class="solr.MorfologikFilterFactory" 
dictionary="mylang.dict" /&gt;
+    ///   &lt;/analyzer&gt;
+    /// &lt;/fieldType&gt;
+    /// </code>
+    /// <para/>
+    /// If the dictionary attribute is not provided, the Polish dictionary is 
loaded
+    /// and used by default.
+    /// <para/>
+    /// See: <a href="http://morfologik.blogspot.com/";>Morfologik web site</a>
+    /// </summary>
+    /// <since>4.0.0</since>
+    public class MorfologikFilterFactory : TokenFilterFactory, 
IResourceLoaderAware
+    {
+        /// <summary>Dictionary resource attribute (should have <c>".dict"</c> 
suffix), loaded from <see cref="IResourceLoader"/>.</summary>
+        public const string DICTIONARY_ATTRIBUTE = "dictionary";
+
+        /// <summary><see cref="DICTIONARY_ATTRIBUTE"/> value passed to <see 
cref="Inform(IResourceLoader)"/>.</summary>
+        private readonly string resourceName;
+
+        /// <summary>Loaded <see cref="Dictionary"/>, initialized on <see 
cref="Inform(IResourceLoader)"/>.</summary>
+        private Dictionary dictionary;
+
+        /// <summary>Creates a new <see 
cref="MorfologikFilterFactory"/></summary>
+        public MorfologikFilterFactory(IDictionary<string, string> args)
+            : base(args)
+        {
+            // Be specific about no-longer-supported dictionary attribute.
+            string DICTIONARY_RESOURCE_ATTRIBUTE = "dictionary-resource";
+            string dictionaryResource = Get(args, 
DICTIONARY_RESOURCE_ATTRIBUTE);
+            if (!string.IsNullOrEmpty(dictionaryResource))
+            {
+                throw new ArgumentException("The " + 
DICTIONARY_RESOURCE_ATTRIBUTE + " attribute is no "
+                    + "longer supported. Use the '" + DICTIONARY_ATTRIBUTE + 
"' attribute instead (see LUCENE-6833).");
+            }
+
+            resourceName = Get(args, DICTIONARY_ATTRIBUTE);
+
+            if (args.Count != 0)
+            {
+                throw new ArgumentException("Unknown parameters: " + args);
+            }
+        }
+
+        public virtual void Inform(IResourceLoader loader)
+        {
+            if (resourceName == null)
+            {
+                // Get the dictionary lazily, does not hold up memory.
+                this.dictionary = new PolishStemmer().Dictionary;
+            }
+            else
+            {
+                using (Stream dict = loader.OpenResource(resourceName))
+                using (Stream meta = 
loader.OpenResource(DictionaryMetadata.GetExpectedMetadataFileName(resourceName)))
+                {
+                    this.dictionary = Dictionary.Read(dict, meta);
+                }
+            }
+        }
+
+        public override TokenStream Create(TokenStream ts)
+        {
+            if (this.dictionary == null)
+                throw new ArgumentException("MorfologikFilterFactory was not 
fully initialized.");
+
+            return new MorfologikFilter(ts, dictionary);
+        }
+    }
+}
diff --git 
a/src/Lucene.Net.Analysis.Morfologik/Morfologik/TokenAttributes/IMorphosyntacticTagsAttribute.cs
 
b/src/Lucene.Net.Analysis.Morfologik/Morfologik/TokenAttributes/IMorphosyntacticTagsAttribute.cs
new file mode 100644
index 0000000..e537403
--- /dev/null
+++ 
b/src/Lucene.Net.Analysis.Morfologik/Morfologik/TokenAttributes/IMorphosyntacticTagsAttribute.cs
@@ -0,0 +1,44 @@
+// Lucene version compatibility level 8.2.0
+using Lucene.Net.Util;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Lucene.Net.Analysis.Morfologik.TokenAttributes
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// Morfologik provides morphosyntactic annotations for
+    /// surface forms. For the exact format and description of these,
+    /// see the project's documentation.
+    /// </summary>
+    public interface IMorphosyntacticTagsAttribute : IAttribute
+    {
+        /// <summary>
+        /// Gets or sets the POS tag of the term. A single word may have 
multiple POS tags,
+        /// depending on the interpretation (context disambiguation is 
typically needed
+        /// to determine which particular tag is appropriate).
+        /// <para/>
+        /// The default value (no-value) is null. Returns a list of POS tags 
corresponding to current lemma.
+        /// </summary>
+        IList<StringBuilder> Tags { get; set; }
+
+        /// <summary>Clear to default value.</summary>
+        void Clear();
+    }
+}
diff --git 
a/src/Lucene.Net.Analysis.Morfologik/Morfologik/TokenAttributes/MorphosyntacticTagsAttribute.cs
 
b/src/Lucene.Net.Analysis.Morfologik/Morfologik/TokenAttributes/MorphosyntacticTagsAttribute.cs
new file mode 100644
index 0000000..2e15a3d
--- /dev/null
+++ 
b/src/Lucene.Net.Analysis.Morfologik/Morfologik/TokenAttributes/MorphosyntacticTagsAttribute.cs
@@ -0,0 +1,105 @@
+// Lucene version compatibility level 8.2.0
+using Lucene.Net.Util;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Lucene.Net.Analysis.Morfologik.TokenAttributes
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// Morphosyntactic annotations for surface forms.
+    /// </summary>
+    /// <seealso cref="IMorphosyntacticTagsAttribute"/>
+    public class MorphosyntacticTagsAttribute : Attribute, 
IMorphosyntacticTagsAttribute
+#if FEATURE_CLONEABLE
+        , ICloneable
+#endif
+    {
+        /// <summary>Initializes this attribute with no tags</summary>
+        public MorphosyntacticTagsAttribute() { }
+
+        /// <summary>
+        /// A list of potential tag variants for the current token.
+        /// </summary>
+        private IList<StringBuilder> tags;
+
+        /// <summary>
+        /// Gets or sets the POS tag of the term. If you need a copy of this 
char sequence, copy
+        /// its contents (and clone <see cref="StringBuilder"/>s) because it 
changes with
+        /// each new term to avoid unnecessary memory allocations.
+        /// </summary>
+        public virtual IList<StringBuilder> Tags
+        {
+            get => tags;
+            set => tags = value;
+        }
+
+
+        public override void Clear()
+        {
+            tags = null;
+        }
+
+
+        public override bool Equals(object other)
+        {
+            if (other is IMorphosyntacticTagsAttribute)
+            {
+                return Equal(this.Tags, 
((IMorphosyntacticTagsAttribute)other).Tags);
+            }
+            return false;
+        }
+
+        private bool Equal(object l1, object l2)
+        {
+            return l1 == null ? (l2 == null) : (l1.Equals(l2));
+        }
+
+        public override int GetHashCode()
+        {
+            return this.tags == null ? 0 : tags.GetHashCode();
+        }
+
+        public override void CopyTo(IAttribute target)
+        {
+            List<StringBuilder> cloned = null;
+            if (tags != null)
+            {
+                cloned = new List<StringBuilder>(tags.Count);
+                foreach (StringBuilder b in tags)
+                {
+                    cloned.Add(new StringBuilder(b.ToString()));
+                }
+            }
+            ((IMorphosyntacticTagsAttribute)target).Tags = cloned;
+        }
+
+        public override object Clone()
+        {
+            MorphosyntacticTagsAttribute cloned = new 
MorphosyntacticTagsAttribute();
+            this.CopyTo(cloned);
+            return cloned;
+        }
+
+        public override void ReflectWith(IAttributeReflector reflector)
+        {
+            reflector.Reflect(typeof(MorphosyntacticTagsAttribute), "tags", 
tags);
+        }
+    }
+}
diff --git a/src/Lucene.Net.Analysis.Morfologik/Uk/README 
b/src/Lucene.Net.Analysis.Morfologik/Uk/README
new file mode 100644
index 0000000..f11d845
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Uk/README
@@ -0,0 +1,11 @@
+ukrainian.dict is a binary dictionary file for morphological analysis in 
fsa_morph program
+(see 
http://www.eti.pg.gda.pl/katedry/kiw/pracownicy/Jan.Daciuk/personal/fsa.html).
+
+See tagset.txt for description of the tags.
+
+This dictionary is currently under development and is based on dict_uk project 
(https://github.com/brown-uk/dict_uk)
+
+Note: to better fit into full-text search model this dictionary has all word 
forms in lower case but keeps lemmas for proper nouns in upper case.
+Also letter ґ was normalized to г.
+
+Licensed under GPL/LGPL, CC BY-NC-SA 4.0, and Apache License 2.0.
diff --git 
a/src/Lucene.Net.Analysis.Morfologik/Uk/UkrainianMorfologikAnalyzer.cs 
b/src/Lucene.Net.Analysis.Morfologik/Uk/UkrainianMorfologikAnalyzer.cs
new file mode 100644
index 0000000..e38e974
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Uk/UkrainianMorfologikAnalyzer.cs
@@ -0,0 +1,177 @@
+// Lucene version compatibility level 8.2.0
+using Lucene.Net.Analysis.CharFilters;
+using Lucene.Net.Analysis.Core;
+using Lucene.Net.Analysis.Miscellaneous;
+using Lucene.Net.Analysis.Morfologik;
+using Lucene.Net.Analysis.Standard;
+using Lucene.Net.Analysis.Util;
+using Lucene.Net.Support;
+using Lucene.Net.Util;
+using Morfologik.Stemming;
+using System;
+using System.IO;
+using System.Text;
+
+namespace Lucene.Net.Analysis.Uk
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// A dictionary-based <see cref="Analyzer"/> for Ukrainian.
+    /// </summary>
+    /// <since>6.2.0</since>
+    public sealed class UkrainianMorfologikAnalyzer : StopwordAnalyzerBase
+    {
+        private readonly CharArraySet stemExclusionSet;
+
+        /// <summary>File containing default Ukrainian stopwords.</summary>
+        public const string DEFAULT_STOPWORD_FILE = "stopwords.txt";
+
+        /// <summary>
+        /// Returns an unmodifiable instance of the default stop words set.
+        /// </summary>
+        /// <returns>Default stop words set.</returns>
+        public static CharArraySet DefaultStopSet => 
DefaultSetHolder.DEFAULT_STOP_SET;
+
+        /// <summary>
+        /// Atomically loads the <see cref="DEFAULT_STOP_SET"/> in a lazy 
fashion once the outer class
+        /// accesses the static final set the first time.
+        /// </summary>
+        private static class DefaultSetHolder
+        {
+            internal static readonly CharArraySet DEFAULT_STOP_SET = 
LoadDefaultSet(); // LUCENENET: Avoid static constructors (see 
https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
+
+            private static CharArraySet LoadDefaultSet()
+            {
+                try
+                {
+                    return 
WordlistLoader.GetSnowballWordSet(IOUtils.GetDecodingReader(typeof(UkrainianMorfologikAnalyzer),
+                        DEFAULT_STOPWORD_FILE, Encoding.UTF8),
+#pragma warning disable 612, 618
+                        LuceneVersion.LUCENE_CURRENT);
+#pragma warning restore 612, 618
+                }
+#pragma warning disable 168
+                catch (IOException ex)
+#pragma warning restore 168
+                {
+                    // default set should always be present as it is part of 
the
+                    // distribution (JAR)
+                    throw new Exception("Unable to load default stopword set");
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Builds an analyzer with the default stop words: <see 
cref="DEFAULT_STOPWORD_FILE"/>.
+        /// </summary>
+        /// <param name="matchVersion"><see cref="LuceneVersion"/> to 
match.</param>
+        public UkrainianMorfologikAnalyzer(LuceneVersion matchVersion)
+            : this(matchVersion, DefaultSetHolder.DEFAULT_STOP_SET)
+        {
+        }
+
+        /// <summary>
+        /// Builds an analyzer with the given stop words.
+        /// </summary>
+        /// <param name="matchVersion"><see cref="LuceneVersion"/> to 
match.</param>
+        /// <param name="stopwords">A stopword set.</param>
+        public UkrainianMorfologikAnalyzer(LuceneVersion matchVersion, 
CharArraySet stopwords)
+            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+        {
+        }
+
+        /// <summary>
+        /// Builds an analyzer with the given stop words. If a non-empty stem 
exclusion set is
+        /// provided this analyzer will add a <see 
cref="SetKeywordMarkerFilter"/> before
+        /// stemming.
+        /// </summary>
+        /// <param name="matchVersion"><see cref="LuceneVersion"/> to 
match.</param>
+        /// <param name="stopwords">A stopword set.</param>
+        /// <param name="stemExclusionSet">A set of terms not to be 
stemmed.</param>
+        public UkrainianMorfologikAnalyzer(LuceneVersion matchVersion, 
CharArraySet stopwords, CharArraySet stemExclusionSet)
+                    : base(matchVersion, stopwords)
+        {
+            this.stemExclusionSet = 
CharArraySet.UnmodifiableSet(CharArraySet.Copy(matchVersion, stemExclusionSet));
+        }
+
+        protected override TextReader InitReader(string fieldName, TextReader 
reader)
+        {
+            NormalizeCharMap.Builder builder = new NormalizeCharMap.Builder();
+            // different apostrophes
+            builder.Add("\u2019", "'");
+            builder.Add("\u2018", "'");
+            builder.Add("\u02BC", "'");
+            builder.Add("`", "'");
+            builder.Add("´", "'");
+            // ignored characters
+            builder.Add("\u0301", "");
+            builder.Add("\u00AD", "");
+            builder.Add("ґ", "г");
+            builder.Add("Ґ", "Г");
+
+            NormalizeCharMap normMap = builder.Build();
+            reader = new MappingCharFilter(normMap, reader);
+            return reader;
+        }
+
+        /// <summary>
+        /// Creates a <see cref="TokenStreamComponents"/>
+        /// which tokenizes all the text in the provided <see 
cref="TextReader"/>.
+        /// </summary>
+        /// <param name="fieldName"></param>
+        /// <param name="reader"></param>
+        /// <returns>A <see cref="TokenStreamComponents"/> built from a <see 
cref="StandardTokenizer"/>
+        /// filtered with <see cref="LowerCaseFilter"/>, <see 
cref="StopFilter"/>, <see cref="SetKeywordMarkerFilter"/>
+        /// if a stem exclusion set is provided and <see 
cref="MorfologikFilter"/> on the Ukrainian dictionary.</returns>
+        protected override TokenStreamComponents CreateComponents(string 
fieldName, TextReader reader)
+        {
+            Tokenizer source = new StandardTokenizer(m_matchVersion, reader);
+            TokenStream result = new LowerCaseFilter(m_matchVersion, source);
+            result = new StopFilter(m_matchVersion, result, m_stopwords);
+
+            if (stemExclusionSet.Count > 0)
+            {
+                result = new SetKeywordMarkerFilter(result, stemExclusionSet);
+            }
+
+            result = new MorfologikFilter(result, GetDictionary());
+            return new TokenStreamComponents(source, result);
+        }
+
+        private static Dictionary GetDictionary()
+        {
+            try
+            {
+                Type type = typeof(UkrainianMorfologikAnalyzer);
+                // LUCENENET NOTE: In Lucene, this was downloaded from Maven 
as a dependency
+                // (see 
https://search.maven.org/search?q=a:morfologik-ukrainian-search). However, we 
are embedding the file in .NET.
+                // Since it doesn't appear to be updated frequently, this 
should be okay.
+                string dictFile = "ukrainian.dict";
+                using (var dictStream = 
type.Assembly.FindAndGetManifestResourceStream(type, dictFile))
+                using (var metadataStream = 
type.Assembly.FindAndGetManifestResourceStream(type, 
DictionaryMetadata.GetExpectedMetadataFileName(dictFile)))
+                    return Dictionary.Read(dictStream, metadataStream);
+            }
+            catch (IOException e)
+            {
+                throw new Exception(e.ToString(), e);
+            }
+        }
+    }
+}
diff --git a/src/Lucene.Net.Analysis.Morfologik/Uk/mapping_uk.txt 
b/src/Lucene.Net.Analysis.Morfologik/Uk/mapping_uk.txt
new file mode 100644
index 0000000..1142604
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Uk/mapping_uk.txt
@@ -0,0 +1,19 @@
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# This map normalizes some characters used in Ukrainian text
+"\u2019" => "'"
+"\u02BC" => "'"
+
+# Remove accent
+"\u0301" => ""
diff --git a/src/Lucene.Net.Analysis.Morfologik/Uk/stopwords.txt 
b/src/Lucene.Net.Analysis.Morfologik/Uk/stopwords.txt
new file mode 100644
index 0000000..651776b
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Uk/stopwords.txt
@@ -0,0 +1,1269 @@
+а
+аби
+абиде
+абиколи
+абикуди
+абихто
+абикого
+абикому
+абиким
+абичий
+абичийого
+абичиєму
+абичийому
+абичиїм
+абичия
+абичиєї
+абичиїй
+абичию
+абичиєю
+абичиє
+абичиї
+абичиїх
+абичиїми
+абищо
+абичого
+абичому
+абичим
+абиякий
+абиякого
+абиякому
+абияким
+абиякім
+абияка
+абиякої
+абиякій
+абияку
+абиякою
+абияке
+абиякі
+абияких
+абиякими
+або
+абощо
+авжеж
+авось
+ага
+адже
+аж
+ажень
+але
+амінь
+ані
+аніде
+аніж
+анізащо
+анікогісінько
+аніколи
+аніскільки
+аніхто
+анікого
+анікому
+аніким
+анічогісінько
+аніщо
+анічого
+анічому
+анічим
+аніякий
+аніякого
+аніякому
+аніяким
+аніякім
+аніяка
+аніякої
+аніякій
+аніяку
+аніякою
+аніяке
+аніякі
+аніяких
+аніякими
+аніякісенький
+аніякісенького
+аніякісенькому
+аніякісеньким
+аніякісенькім
+аніякісенька
+аніякісенької
+аніякісенькій
+аніякісеньку
+аніякісенькою
+аніякісеньке
+аніякісенькі
+аніякісеньких
+аніякісенькими
+аніякісінький
+аніякісінького
+аніякісінькому
+аніякісіньким
+аніякісінькім
+аніякісінька
+аніякісінької
+аніякісінькій
+аніякісіньку
+аніякісінькою
+аніякісіньке
+аніякісінькі
+аніякісіньких
+аніякісінькими
+ану
+ато
+атож
+ач
+ачей
+аякже
+б
+ба
+багато
+багатьох
+багатьом
+багатьма
+без
+би
+біля
+бо
+бодай
+бути
+будь
+будьмо
+будьте
+є
+єси
+суть
+буду
+будеш
+буде
+будем
+будемо
+будете
+будуть
+був
+була
+було
+були
+буцім
+буцімто
+в
+ваш
+вашого
+вашому
+вашим
+вашім
+ваша
+вашої
+вашій
+вашу
+вашою
+ваше
+ваші
+ваших
+вашими
+ввесь
+всього
+всьому
+всім
+вся
+всієї
+всій
+всю
+всією
+все
+всі
+всіх
+всіма
+вві
+весь
+вздовж
+ви
+вас
+вам
+вами
+ві
+від
+відколи
+відповідно
+відтепер
+відтоді
+він
+його
+нього
+йому
+ним
+нім
+ньому
+власне
+властиво
+внаслідок
+вона
+її
+неї
+їй
+нею
+ній
+вони
+їх
+них
+їм
+ними
+воно
+вподовж
+впоперек
+впродовж
+всілякий
+всілякого
+всілякому
+всіляким
+всілякім
+всіляка
+всілякої
+всілякій
+всіляку
+всілякою
+всіляке
+всілякі
+всіляких
+всілякими
+вслід
+всупереч
+всюди
+всякий
+всякого
+всякому
+всяким
+всякім
+всяка
+всякої
+всякій
+всяку
+всякою
+всяке
+всякі
+всяких
+всякими
+всяк
+втім
+гаразд
+ге
+геть
+де
+дедалі
+деінде
+декілька
+декількох
+декільком
+декількома
+деколи
+декотрий
+декотрого
+декотрому
+декотрим
+декотрім
+декотра
+декотрої
+декотрій
+декотру
+декотрою
+декотре
+декотрі
+декотрих
+декотрими
+десь
+дехто
+декого
+декому
+деким
+декім
+дечий
+дечийого
+дечиєму
+дечийому
+дечиїм
+дечия
+дечиєї
+дечиїй
+дечию
+дечиєю
+дечиє
+дечиї
+дечиїх
+дечиїми
+дещо
+дечого
+дечому
+дечим
+дечім
+деякий
+деякого
+деякому
+деяким
+деякім
+деяка
+деякої
+деякій
+деяку
+деякою
+деяке
+деякі
+деяких
+деякими
+для
+до
+довкола
+доки
+допіру
+допоки
+досі
+дотепер
+доти
+еге
+ж
+же
+жодний
+жодного
+жодному
+жодним
+жоднім
+жодна
+жодної
+жодній
+жодну
+жодною
+жодне
+жодні
+жодних
+жодними
+жоден
+жоднісінький
+жоднісінького
+жоднісінькому
+жоднісіньким
+жоднісінькім
+жоднісінька
+жоднісінької
+жоднісінькій
+жоднісіньку
+жоднісінькою
+жоднісіньке
+жоднісінькі
+жоднісіньких
+жоднісінькими
+з
+за
+завгодно
+завдяки
+завжди
+завше
+задля
+залежно
+замість
+заради
+зараз
+зате
+зверху
+звідки
+звідкилясь
+звідкись
+звідкіль
+звідкіля
+звідкілясь
+звідси
+звідсіль
+звідсіля
+звідти
+звідтіль
+звідтіля
+звідусіль
+звідусюди
+звідціля
+здовж
+ззаду
+зі
+зо
+зсередини
+ич
+і
+ібн
+із
+ізсередини
+інакше
+інакший
+інакшого
+інакшому
+інакшим
+інакшім
+інакша
+інакшої
+інакшій
+інакшу
+інакшою
+інакші
+інакших
+інакшими
+інколи
+іноді
+інше
+іншого
+іншому
+іншим
+інший
+іншім
+інша
+іншої
+іншій
+іншу
+іншою
+інші
+інших
+іншими
+іще
+їхній
+їхнього
+їхньому
+їхнім
+їхня
+їхньої
+їхню
+їхньою
+їхнє
+їхні
+їхніх
+їхніми
+й
+кілька
+кількох
+кільком
+кількома
+кінець
+кожний
+кожного
+кожному
+кожним
+кожнім
+кожна
+кожної
+кожній
+кожну
+кожною
+кожне
+кожні
+кожних
+кожними
+кожен
+кожнісінький
+кожнісінького
+кожнісінькому
+кожнісіньким
+кожнісінькім
+кожнісінька
+кожнісінької
+кожнісінькій
+кожнісіньку
+кожнісінькою
+кожнісіньке
+кожнісінькі
+кожнісіньких
+кожнісінькими
+коли
+колись
+коло
+котрий
+котрого
+котрому
+котрим
+котрім
+котра
+котрої
+котрій
+котру
+котрою
+котре
+котрі
+котрих
+котрими
+котрийсь
+котрогось
+котромусь
+котримось
+котримсь
+котрімсь
+котрась
+котроїсь
+котрійсь
+котрусь
+котроюсь
+котресь
+котрісь
+котрихось
+котрихсь
+котримись
+край
+крізь
+крім
+круг
+кругом
+куди
+кудись
+кудою
+ледве
+ледь
+лиш
+лише
+лишень
+мерсі
+ми
+нас
+нам
+нами
+між
+мій
+мого
+моєму
+моїм
+моя
+моєї
+моїй
+мою
+моєю
+моє
+мої
+моїх
+моїми
+мов
+мовби
+мовбито
+могти
+можіть
+можу
+можеш
+може
+можем
+можемо
+можете
+можуть
+міг
+могла
+могло
+могли
+можна
+на
+навіть
+навіщо
+навіщось
+навколо
+навкруг
+навпаки
+навперейми
+навпроти
+над
+наді
+надо
+наперед
+напередодні
+наперекір
+напереріз
+наприкінці
+напроти
+насеред
+насупроти
+нате
+наче
+начеб
+начебто
+наш
+нашого
+нашому
+нашим
+нашім
+наша
+нашої
+нашій
+нашу
+нашою
+наше
+наші
+наших
+нашими
+не
+неабищо
+неабичого
+неабичому
+неабичим
+небагато
+небагатьох
+небагатьом
+небагатьма
+невважаючи
+невже
+незважаючи
+немов
+немовби
+немовбито
+неначе
+неначебто
+нехай
+нижче
+ні
+ніби
+нібито
+ніде
+ніж
+нізащо
+нізвідки
+нізвідкіля
+ніким
+нікогісінько
+нікого
+ніколи
+нікому
+нікотрий
+нікотрого
+нікотрому
+нікотрим
+нікотрім
+нікотра
+нікотрої
+нікотрій
+нікотру
+нікотрою
+нікотре
+нікотрі
+нікотрих
+нікотрими
+нікуди
+нінащо
+ніскільки
+ніхто
+нічий
+нічийого
+нічиєму
+нічийому
+нічиїм
+нічия
+нічиєї
+нічиїй
+нічию
+нічиєю
+нічиє
+нічиї
+нічиїх
+нічиїми
+нічийний
+нічийного
+нічийному
+нічийним
+нічийнім
+нічийна
+нічийної
+нічийній
+нічийну
+нічийною
+нічийне
+нічийні
+нічийних
+нічийними
+нічим
+нічого
+нічому
+ніщо
+ніяк
+ніякий
+ніякого
+ніякому
+ніяким
+ніякім
+ніяка
+ніякої
+ніякій
+ніяку
+ніякою
+ніяке
+ніякі
+ніяких
+ніякими
+ніякісінький
+ніякісінького
+ніякісінькому
+ніякісіньким
+ніякісінькім
+ніякісінька
+ніякісінької
+ніякісінькій
+ніякісіньку
+ніякісінькою
+ніякісіньке
+ніякісінькі
+ніякісіньких
+ніякісінькими
+но
+ну
+нумо
+нумте
+о
+об
+обабіч
+обік
+обіч
+од
+один
+одного
+одному
+одним
+однім
+одна
+однієї
+одної
+одній
+одну
+однією
+одною
+одне
+одно
+одні
+одних
+одними
+однак
+одначе
+окрай
+окрім
+округ
+округи
+он
+онде
+онно
+оно
+опісля
+опріч
+осе
+осісьо
+оскільки
+ось
+от
+отак
+отакий
+отакого
+отакому
+отаким
+отакім
+отака
+отакої
+отакій
+отаку
+отакою
+отаке
+отакі
+отаких
+отакими
+отакісінький
+отакісінького
+отакісінькому
+отакісіньким
+отакісінькім
+отакісінька
+отакісінької
+отакісінькій
+отакісіньку
+отакісінькою
+отакісіньке
+отакісінькі
+отакісіньких
+отакісінькими
+отам
+отже
+ото
+отож
+отой
+отого
+отому
+отим
+отім
+ота
+отієї
+отої
+отій
+оту
+отією
+отою
+оте
+оті
+отих
+отими
+отсе
+оттак
+отто
+отут
+оце
+оцей
+оцього
+оцьому
+оцим
+оцім
+оця
+оцієї
+оцій
+оцю
+оцією
+оці
+оцих
+оцими
+пак
+перед
+перетакий
+перетакого
+перетакому
+перетаким
+перетакім
+перетака
+перетакої
+перетакій
+перетаку
+перетакою
+перетаке
+перетакі
+перетаких
+перетакими
+під
+підо
+після
+по
+побік
+побіч
+поблизу
+поверх
+повз
+повздовж
+повсюди
+повсюдно
+подекуди
+подеякий
+подеякого
+подеякому
+подеяким
+подеякім
+подеяка
+подеякої
+подеякій
+подеяку
+подеякою
+подеяке
+подеякі
+подеяких
+подеякими
+подовж
+поза
+позад
+позаду
+позатой
+позатого
+позатому
+позатим
+позатім
+позата
+позатієї
+позатої
+позатій
+позату
+позатією
+позатою
+позате
+позаті
+позатих
+позатими
+позаяк
+поздовж
+поки
+покіль
+покрай
+поміж
+понад
+понадо
+понижче
+поперед
+попереду
+поперек
+попід
+попліч
+попри
+попросту
+поруч
+поряд
+посеред
+посередині
+потім
+поуз
+прецінь
+при
+притому
+причім
+причому
+про
+проміж
+просто
+проте
+проти
+протягом
+раз
+раніше
+сам
+самого
+самому
+самим
+самім
+сама
+самої
+самій
+саму
+самою
+саме
+само
+сами
+самі
+самих
+самими
+самий
+свій
+свого
+своєму
+своїм
+своя
+своєї
+своїй
+свою
+своєю
+своє
+свої
+своїх
+своїми
+се
+себе
+собі
+собою
+себто
+серед
+сиріч
+скільки
+скількох
+скільком
+скількома
+скількись
+скількохось
+скількохсь
+скількомось
+скількомсь
+скількомась
+скрізь
+спереду
+справді
+стільки
+стількох
+стільком
+стількома
+супроти
+супротив
+сюди
+сякий
+сякого
+сякому
+сяким
+сякім
+сяка
+сякої
+сякій
+сяку
+сякою
+сяке
+сякі
+сяких
+сякими
+та
+так
+такенний
+такенного
+такенному
+такенним
+такеннім
+такенна
+такенної
+такенній
+такенну
+такенною
+такенне
+такенні
+такенних
+такенними
+таки
+такий
+такого
+такому
+таким
+такім
+така
+такої
+такій
+таку
+такою
+таке
+такі
+таких
+такими
+такісінький
+такісінького
+такісінькому
+такісіньким
+такісінькім
+такісінька
+такісінької
+такісінькій
+такісіньку
+такісінькою
+такісіньке
+такісінькі
+такісіньких
+такісінькими
+також
+там
+тамки
+тамтой
+тамтого
+тамтому
+тамтим
+тамтім
+тамта
+тамтієї
+тамтої
+тамтій
+тамту
+тамтією
+тамтою
+тамте
+тамті
+тамтих
+тамтими
+твій
+твого
+твоєму
+твоїм
+твоя
+твоєї
+твоїй
+твою
+твоєю
+твоє
+твої
+твоїх
+твоїми
+те
+того
+тому
+тим
+тім
+теє
+теж
+тепер
+теперечки
+ти
+тебе
+тобі
+тобою
+тільки
+то
+тобто
+тоді
+тож
+той
+тієї
+тої
+тій
+ту
+тією
+тою
+ті
+тих
+тими
+тощо
+туди
+тудою
+тут
+тутеньки
+тутечки
+тутки
+у
+ув
+увесь
+усього
+усьому
+усім
+уся
+усієї
+усій
+усю
+усією
+усе
+усі
+усіх
+усіма
+уві
+угу
+уздовж
+унаслідок
+уподовж
+упоперек
+упродовж
+усілякий
+усілякого
+усілякому
+усіляким
+усілякім
+усіляка
+усілякої
+усілякій
+усіляку
+усілякою
+усіляке
+усілякі
+усіляких
+усілякими
+услід
+усупереч
+усюди
+усякий
+усякого
+усякому
+усяким
+усякім
+усяка
+усякої
+усякій
+усяку
+усякою
+усяке
+усякі
+усяких
+усякими
+усяк
+утім
+хай
+хіба
+хоч
+хоча
+хто
+кого
+кому
+ким
+кім
+хтось
+когось
+комусь
+кимось
+кимсь
+кімось
+кімсь
+це
+цього
+цьому
+цим
+цім
+цебто
+цей
+ця
+цієї
+цій
+цю
+цією
+ці
+цих
+цими
+чень
+через
+чи
+чий
+чийого
+чиєму
+чийому
+чиїм
+чия
+чиєї
+чиїй
+чию
+чиєю
+чиє
+чиї
+чиїх
+чиїми
+чийсь
+чийогось
+чиємусь
+чийомусь
+чиїмось
+чиїмсь
+чиясь
+чиєїсь
+чиїйсь
+чиюсь
+чиєюсь
+чиєсь
+чиїсь
+чиїхось
+чиїхсь
+чиїмись
+чому
+чомусь
+чортзна
+шляхом
+ще
+що
+чого
+віщо
+чим
+чім
+щоб
+щоби
+щодо
+щойно
+щоправда
+щось
+чогось
+віщось
+чимось
+чимсь
+чімось
+чімсь
+я
+мене
+мені
+мною
+як
+якби
+який
+якого
+якому
+яким
+якім
+яка
+якої
+якій
+яку
+якою
+яке
+які
+яких
+якими
+якийсь
+якогось
+якомусь
+якимось
+якимсь
+якімсь
+якась
+якоїсь
+якійсь
+якусь
+якоюсь
+якесь
+якісь
+якихось
+якихсь
+якимись
+якось
+якраз
+якщо
\ No newline at end of file
diff --git a/src/Lucene.Net.Analysis.Morfologik/Uk/tagset.txt 
b/src/Lucene.Net.Analysis.Morfologik/Uk/tagset.txt
new file mode 100644
index 0000000..bc5371e
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Uk/tagset.txt
@@ -0,0 +1,170 @@
+Теги:
+
+[КЛ] - ключ леми (тег, який розрізняє різні леми з омонімів)
+
+noun    іменник
+    [КЛ] anim       істота
+    [КЛ] fname      ім'я
+    [КЛ] lname      прізвище
+    [КЛ] patr       по батькові
+    [КЛ] inanim     неістота
+    [КЛ] unanim     невизначена категорія істота/неістота (бактерія)
+         prop       власна назва
+
+verb    дієслово
+    [КЛ] imperf/perf недоконаний вид
+    [КЛ] perf доконаний вид
+    [КЛ] rev  зворотна форма (дієслова) (тег є неявним ключем, оскільки лема 
на -ся завжди відрізняється від прямого дієслова)
+
+    inf інфінітив
+    futr  майбутній час
+    past  минулий час
+    pres  теперішній час
+    impr    наказова форма
+    impers безособова форма
+
+    1       1-а особа
+    2       2-а особа
+    3       3-а особа
+
+    TODO: Ще немає:
+        tran    перехідне
+        intran  непрехідне
+
+
+adj     прикметник
+    compb    базова форма
+    compr    порівняльна форма
+    super    найвища форма
+    short    короткі форми прикметників
+
+    adjp    дієприкметник: (:&adjp - лише дієприкметник; :&_adjp - 
дієприкметник і прикметник)
+        actv   активний
+        pasv   пасивний
+        imperf недоконаний вид
+        perf   доконаний вид
+
+    (past/pres є в коментарях сирців для більшості дієприкметників, але наразі 
не використовується)
+
+adj/adjp:
+    v_zna:rinanim   знахідний для неістот (лише ч.р.)
+    v_zna:ranim     знахідний для істот (лише ч.р.)
+    uncontr         нестягнені
+
+adv     прислівник
+    compb    базова форма
+    compr    порівняльна форма
+    super    найвища форма
+
+advp    дієприслівник
+    [КЛ] perf
+    [КЛ] imperf
+
+prep    прийменник
+    Вимагає відмінка:
+        rv_rod
+        rv_dav
+        rv_zna
+        rv_oru
+        rv_mis
+
+conj    сполучник
+    subord підрядний
+    coord сурядний
+
+part    частка
+
+intj    вигук
+
+numr    числівник
+
+foreign невідмінювані запозичені слова невизначеної частини мови (Альгемайне, 
Юнайтед тощо)
+
+noninfl     невідмінювані частини (най-най, брутто, екстра...)
+
+
+Спільні для noun/adj/adjp:
+    Відмінки:
+        v_naz   називний
+        v_rod   родовий
+        v_dav   давальний
+        v_zna   знахідний
+        v_oru   орудний
+        v_mis   місцевий
+        v_kly   кличний
+        nv    не відмінюється
+        np    без множини (TODO: проставлено не всюди)
+        ns    без однини (TODO: проставлено не всюди)
+
+
+
+Спільні для noun/adj/adjp/verb
+    p  множина
+    s  однина
+
+    Рід:
+        m  чоловічий
+        f  жіночий
+        n  середній
+
+
+Додаткові теги:
+
+    abbr  абревіатура
+    bad   покруч
+    subst просторічна форма
+    rare  рідковживане/діалектичне/застаріле
+    coll  розмовне слово/розмовна форма
+    slang сленг
+    alt   альтернативне написання (не за чинним правописом)
+
+    :xp[1-9] омоніми, що відрізняються парадигмою відмінювання (напр. бар - 
р.в. бару, бар - р.в. бара)
+    # в коментарях також :xv[1-9] омоніми, що відрізняються семантично (напр. 
глупий (дурний, має вищий ступінь глупіший) і глупий - глупа ніч, без 
порівняльних форм)
+
+
+    v-u   паралельні форми на в-/у- (для правил милозвучності, вимкнено за 
уставою)
+
+
+Додаткові теги класів слів (після &):
+     &adjp — слова, що є дієприкметниками
+     &_adjp — слова, що є і прикметниками і дієприкметниками
+[КЛ] &pron - наразі всі займенники мають теги відповідних частин мови 
(noun/adj/adv), але всі мають додатковий тег &pron
+        (тег &pron разом з наступним класифікатором стає ключем леми)
+     &numr - слова, що є порядковими числівниками
+     &_numr - слова, що є і прикметниками і порядковими числівниками або і 
іменниками і кількісними числівниками
+     &insert - може бути вставним словом
+     &predic - може бути предикативом
+
+
+Теги займенників:
+    pers  особовий
+    refl  зворотний
+    pos   присвійний
+    dem   вказівний
+    def   означальний
+    int   питальний
+    rel   відносний
+    neg   заперечний
+    ind   неозначений
+    gen   узагальнювальний
+    emph  підсилювальний
+
+
+
+Динамічні теги (відсутні в словнику, їх проставляє модуль тегування LT):
+    number - число
+    date - дата
+    time - час
+
+
+Теги, яких немає, але які теоретично нескладно додати:
+    noun:
+        common gender
+    verb:
+        dual form (imperf+perf)
+    adj:
+        qualitative (має порівняльні форми) / relative (не має порівняльних)
+    adjp:
+        past/pres
+    advp:
+        past/pres
diff --git a/src/Lucene.Net.Analysis.Morfologik/Uk/ukrainian.dict 
b/src/Lucene.Net.Analysis.Morfologik/Uk/ukrainian.dict
new file mode 100644
index 0000000..49b1655
Binary files /dev/null and 
b/src/Lucene.Net.Analysis.Morfologik/Uk/ukrainian.dict differ
diff --git a/src/Lucene.Net.Analysis.Morfologik/Uk/ukrainian.info 
b/src/Lucene.Net.Analysis.Morfologik/Uk/ukrainian.info
new file mode 100644
index 0000000..b5331be
--- /dev/null
+++ b/src/Lucene.Net.Analysis.Morfologik/Uk/ukrainian.info
@@ -0,0 +1,10 @@
+#
+# Dictionary properties.
+#
+
+fsa.dict.separator=+
+fsa.dict.encoding=utf-8
+
+fsa.dict.encoder=SUFFIX
+
+fsa.dict.speller.ignore-diacritics=false
diff --git a/src/Lucene.Net.TestFramework.NUnit/Properties/AssemblyInfo.cs 
b/src/Lucene.Net.TestFramework.NUnit/Properties/AssemblyInfo.cs
index fe8b490..942dec1 100644
--- a/src/Lucene.Net.TestFramework.NUnit/Properties/AssemblyInfo.cs
+++ b/src/Lucene.Net.TestFramework.NUnit/Properties/AssemblyInfo.cs
@@ -54,6 +54,8 @@ using System.Runtime.InteropServices;
 [assembly: InternalsVisibleTo("Lucene.Net.Tests._U-Z, PublicKey=" + 
AssemblyKeys.PublicKey)]
 [assembly: InternalsVisibleTo("Lucene.Net.Tests.Analysis.Common, PublicKey=" + 
AssemblyKeys.PublicKey)]
 [assembly: InternalsVisibleTo("Lucene.Net.Tests.Analysis.Kuromoji, PublicKey=" 
+ AssemblyKeys.PublicKey)]
+[assembly: InternalsVisibleTo("Lucene.Net.Tests.Analysis.Morfologik, 
PublicKey=" + AssemblyKeys.PublicKey)]
+[assembly: InternalsVisibleTo("Lucene.Net.Tests.Analysis.OpenNLP, PublicKey=" 
+ AssemblyKeys.PublicKey)]
 [assembly: InternalsVisibleTo("Lucene.Net.Tests.Analysis.Phonetic, PublicKey=" 
+ AssemblyKeys.PublicKey)]
 [assembly: InternalsVisibleTo("Lucene.Net.Tests.Analysis.SmartCn, PublicKey=" 
+ AssemblyKeys.PublicKey)]
 [assembly: InternalsVisibleTo("Lucene.Net.Tests.Analysis.Stempel, PublicKey=" 
+ AssemblyKeys.PublicKey)]
diff --git 
a/src/Lucene.Net.Tests.Analysis.Morfologik/Lucene.Net.Tests.Analysis.Morfologik.csproj
 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Lucene.Net.Tests.Analysis.Morfologik.csproj
new file mode 100644
index 0000000..0005bb8
--- /dev/null
+++ 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Lucene.Net.Tests.Analysis.Morfologik.csproj
@@ -0,0 +1,47 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <Import Project="$(SolutionDir)TestTargetFramework.props" />
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.1;net451</TargetFrameworks>
+
+    <AssemblyTitle>Lucene.Net.Tests.Analysis.Morfologik</AssemblyTitle>
+    <RootNamespace>Lucene.Net.Analysis</RootNamespace>
+    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
+    <RuntimeIdentifiers>win7-x86;win7-x64</RuntimeIdentifiers>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <None Remove="Morfologik\custom-dictionary.dict" />
+    <None Remove="Morfologik\custom-dictionary.info" />
+    <None Remove="Morfologik\custom-dictionary.input" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <EmbeddedResource Include="Morfologik\custom-dictionary.dict" />
+    <EmbeddedResource Include="Morfologik\custom-dictionary.info" />
+    <EmbeddedResource Include="Morfologik\custom-dictionary.input" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference 
Include="..\Lucene.Net.Analysis.Morfologik\Lucene.Net.Analysis.Morfologik.csproj"
 />
+    <ProjectReference 
Include="..\Lucene.Net.TestFramework.NUnit\Lucene.Net.TestFramework.NUnit.csproj"
 />
+  </ItemGroup>
+
+  <Import Project="$(SolutionDir)build/TestReferences.Common.targets" />
+
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
+    <DefineConstants>$(DefineConstants);NETSTANDARD</DefineConstants>
+    <DebugType>portable</DebugType>
+  </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'net451' ">
+    <DebugType>full</DebugType>
+  </PropertyGroup>
+
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net451' ">
+    <Reference Include="System" />
+    <Reference Include="Microsoft.CSharp" />
+  </ItemGroup>
+
+</Project>
diff --git 
a/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/TestMorfologikAnalyzer.cs 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/TestMorfologikAnalyzer.cs
new file mode 100644
index 0000000..96faa72
--- /dev/null
+++ 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/TestMorfologikAnalyzer.cs
@@ -0,0 +1,235 @@
+// Lucene version compatibility level 8.2.0
+using Lucene.Net.Analysis.Miscellaneous;
+using Lucene.Net.Analysis.Morfologik.TokenAttributes;
+using Lucene.Net.Analysis.Standard;
+using Lucene.Net.Analysis.TokenAttributes;
+using Lucene.Net.Analysis.Util;
+using Lucene.Net.Support;
+using NUnit.Framework;
+using System;
+using System.IO;
+using System.Text;
+
+namespace Lucene.Net.Analysis.Morfologik
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// TODO: The tests below rely on the order of returned lemmas, which is 
probably not good. 
+    /// </summary>
+    public class TestMorfologikAnalyzer : BaseTokenStreamTestCase
+    {
+        private Analyzer getTestAnalyzer()
+        {
+            return new MorfologikAnalyzer(TEST_VERSION_CURRENT);
+        }
+
+        /** Test stemming of single tokens with Morfologik library. */
+        [Test]
+        public void TestSingleTokens()
+        {
+            Analyzer a = getTestAnalyzer();
+            AssertAnalyzesTo(a, "a", new String[] { "a" });
+            AssertAnalyzesTo(a, "liście", new String[] { "liście", "liść", 
"list", "lista" });
+            AssertAnalyzesTo(a, "danych", new String[] { "dany", "dana", 
"dane", "dać" });
+            AssertAnalyzesTo(a, "ęóąśłżźćń", new String[] { "ęóąśłżźćń" });
+            a.Dispose();
+        }
+
+        /** Test stemming of multiple tokens and proper term metrics. */
+        [Test]
+        public void TestMultipleTokens()
+        {
+            Analyzer a = getTestAnalyzer();
+            AssertAnalyzesTo(
+                a,
+                "liście danych",
+                new String[] { "liście", "liść", "list", "lista", "dany", 
"dana", "dane", "dać" },
+                new int[] { 0, 0, 0, 0, 7, 7, 7, 7 },
+                new int[] { 6, 6, 6, 6, 13, 13, 13, 13 },
+                new int[] { 1, 0, 0, 0, 1, 0, 0, 0 });
+
+            AssertAnalyzesTo(
+                a,
+                "T. Gl\u00FCcksberg",
+                new String[] { "tom", "tona", "Gl\u00FCcksberg" },
+                new int[] { 0, 0, 3 },
+                new int[] { 1, 1, 13 },
+                new int[] { 1, 0, 1 });
+            a.Dispose();
+        }
+
+        private void dumpTokens(String input)
+        {
+            using (Analyzer a = getTestAnalyzer())
+            using (TokenStream ts = a.GetTokenStream("dummy", input))
+            {
+                ts.Reset();
+
+                IMorphosyntacticTagsAttribute attribute = 
ts.GetAttribute<IMorphosyntacticTagsAttribute>();
+                ICharTermAttribute charTerm = 
ts.GetAttribute<ICharTermAttribute>();
+                while (ts.IncrementToken())
+                {
+                    Console.WriteLine(charTerm.ToString() + " => " + 
Collections.ToString(attribute.Tags));
+                }
+                ts.End();
+            }
+        }
+
+        /** Test reuse of MorfologikFilter with leftover stems. */
+        [Test]
+        public void TestLeftoverStems()
+        {
+            Analyzer a = getTestAnalyzer();
+            using (TokenStream ts_1 = a.GetTokenStream("dummy", "liście"))
+            {
+                ICharTermAttribute termAtt_1 = 
ts_1.GetAttribute<ICharTermAttribute>();
+                ts_1.Reset();
+                ts_1.IncrementToken();
+                assertEquals("first stream", "liście", termAtt_1.ToString());
+                ts_1.End();
+            }
+
+            using (TokenStream ts_2 = a.GetTokenStream("dummy", "danych"))
+            {
+                ICharTermAttribute termAtt_2 = 
ts_2.GetAttribute<ICharTermAttribute>();
+                ts_2.Reset();
+                ts_2.IncrementToken();
+                assertEquals("second stream", "dany", termAtt_2.toString());
+                ts_2.End();
+            }
+            a.Dispose();
+        }
+
+        /** Test stemming of mixed-case tokens. */
+        [Test]
+        public void TestCase()
+        {
+            Analyzer a = getTestAnalyzer();
+
+            AssertAnalyzesTo(a, "AGD", new String[] { "AGD", "artykuły 
gospodarstwa domowego" });
+            AssertAnalyzesTo(a, "agd", new String[] { "artykuły gospodarstwa 
domowego" });
+
+            AssertAnalyzesTo(a, "Poznania", new String[] { "Poznań" });
+            AssertAnalyzesTo(a, "poznania", new String[] { "poznanie", 
"poznać" });
+
+            AssertAnalyzesTo(a, "Aarona", new String[] { "Aaron" });
+            AssertAnalyzesTo(a, "aarona", new String[] { "aarona" });
+
+            AssertAnalyzesTo(a, "Liście", new String[] { "liście", "liść", 
"list", "lista" });
+            a.Dispose();
+        }
+
+        private void assertPOSToken(TokenStream ts, String term, params 
String[] tags)
+        {
+            ts.IncrementToken();
+            assertEquals(term, 
ts.GetAttribute<ICharTermAttribute>().ToString());
+
+            TreeSet<String> actual = new TreeSet<String>();
+            TreeSet<String> expected = new TreeSet<String>();
+            foreach (StringBuilder b in 
ts.GetAttribute<IMorphosyntacticTagsAttribute>().Tags)
+            {
+                actual.add(b.toString());
+            }
+            foreach (String s in tags)
+            {
+                expected.add(s);
+            }
+
+            if (!expected.equals(actual))
+            {
+                Console.WriteLine("Expected:\n" + expected);
+                Console.WriteLine("Actual:\n" + actual);
+                assertEquals(expected, actual);
+            }
+        }
+
+        /** Test morphosyntactic annotations. */
+        [Test]
+        public void TestPOSAttribute()
+        {
+            using (Analyzer a = getTestAnalyzer())
+            using (TokenStream ts = a.GetTokenStream("dummy", "liście"))
+            {
+                ts.Reset();
+                assertPOSToken(ts, "liście",
+                  "subst:sg:acc:n2",
+                  "subst:sg:nom:n2",
+                  "subst:sg:voc:n2");
+
+                assertPOSToken(ts, "liść",
+                  "subst:pl:acc:m3",
+                  "subst:pl:nom:m3",
+                  "subst:pl:voc:m3");
+
+                assertPOSToken(ts, "list",
+                  "subst:sg:loc:m3",
+                  "subst:sg:voc:m3");
+
+                assertPOSToken(ts, "lista",
+                  "subst:sg:dat:f",
+                  "subst:sg:loc:f");
+                ts.End();
+            }
+        }
+
+        private class MockMorfologikAnalyzer : MorfologikAnalyzer
+        {
+            public MockMorfologikAnalyzer()
+                : base(TEST_VERSION_CURRENT)
+            { }
+
+            protected override TokenStreamComponents CreateComponents(string 
fieldName, TextReader reader)
+            {
+                CharArraySet keywords = new CharArraySet(TEST_VERSION_CURRENT, 
1, false);
+                keywords.add("liście");
+
+                Tokenizer src = new StandardTokenizer(TEST_VERSION_CURRENT, 
reader);
+                TokenStream result = new SetKeywordMarkerFilter(src, keywords);
+                result = new MorfologikFilter(result);
+
+                return new TokenStreamComponents(src, result);
+            }
+        }
+
+        /** */
+        [Test]
+        public void TestKeywordAttrTokens()
+        {
+            Analyzer a = new MockMorfologikAnalyzer();
+
+            AssertAnalyzesTo(
+              a,
+                  "liście danych",
+                  new String[] { "liście", "dany", "dana", "dane", "dać" },
+                  new int[] { 0, 7, 7, 7, 7 },
+                  new int[] { 6, 13, 13, 13, 13 },
+                  new int[] { 1, 1, 0, 0, 0 });
+            a.Dispose();
+        }
+
+        /** blast some random strings through the analyzer */
+        [Test]
+        public void TestRandom()
+        {
+            Analyzer a = getTestAnalyzer();
+            CheckRandomData(Random, a, 1000 * RANDOM_MULTIPLIER);
+            a.Dispose();
+        }
+    }
+}
diff --git 
a/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/TestMorfologikFilterFactory.cs
 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/TestMorfologikFilterFactory.cs
new file mode 100644
index 0000000..7a8449e
--- /dev/null
+++ 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/TestMorfologikFilterFactory.cs
@@ -0,0 +1,107 @@
+// Lucene version compatibility level 8.2.0
+using Lucene.Net.Analysis.Util;
+using Lucene.Net.Support;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Lucene.Net.Analysis.Morfologik
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// Test for <see cref="MorfologikFilterFactory"/>
+    /// </summary>
+    public class TestMorfologikFilterFactory : BaseTokenStreamTestCase
+    {
+        private class ForbidResourcesLoader : IResourceLoader
+        {
+            public Type FindType(string cname)
+            {
+                throw new NotSupportedException();
+            }
+
+            public T NewInstance<T>(string cname)
+            {
+                throw new NotSupportedException();
+            }
+
+            public Stream OpenResource(string resource)
+            {
+                throw new NotSupportedException();
+            }
+        }
+
+        [Test]
+        public void TestDefaultDictionary()
+        {
+            StringReader reader = new StringReader("rowery bilety");
+            MorfologikFilterFactory factory = new 
MorfologikFilterFactory(Collections.EmptyMap<String, String>());
+            factory.Inform(new ForbidResourcesLoader());
+            TokenStream stream = new MockTokenizer(reader); 
//whitespaceMockTokenizer(reader);
+            stream = factory.Create(stream);
+            AssertTokenStreamContents(stream, new String[] { "rower", "bilet" 
});
+        }
+
+        [Test]
+        public void TestExplicitDictionary()
+        {
+            IResourceLoader loader = new 
ClasspathResourceLoader(typeof(TestMorfologikFilterFactory));
+
+            StringReader reader = new StringReader("inflected1 inflected2");
+            IDictionary<String, String> @params = new HashMap<string, 
string>();
+            @params[MorfologikFilterFactory.DICTIONARY_ATTRIBUTE] = 
"custom-dictionary.dict";
+            MorfologikFilterFactory factory = new 
MorfologikFilterFactory(@params);
+            factory.Inform(loader);
+            TokenStream stream = new MockTokenizer(reader); // 
whitespaceMockTokenizer(reader);
+            stream = factory.Create(stream);
+            AssertTokenStreamContents(stream, new String[] { "lemma1", 
"lemma2" });
+        }
+
+        [Test]
+        public void TestMissingDictionary()
+        {
+            IResourceLoader loader = new 
ClasspathResourceLoader(typeof(TestMorfologikFilterFactory));
+
+            IOException expected = 
NUnit.Framework.Assert.Throws<IOException>(() =>
+            {
+                IDictionary<String, String> @params = new HashMap<String, 
String>();
+                @params[MorfologikFilterFactory.DICTIONARY_ATTRIBUTE] = 
"missing-dictionary-resource.dict";
+                MorfologikFilterFactory factory = new 
MorfologikFilterFactory(@params);
+                factory.Inform(loader);
+            });
+
+            assertTrue(expected.Message.Contains("Resource not found"));
+        }
+
+        /** Test that bogus arguments result in exception */
+        [Test]
+        public void TestBogusArguments()
+        {
+            ArgumentException expected = 
NUnit.Framework.Assert.Throws<ArgumentException>(() =>
+            {
+                HashMap<String, String> @params = new HashMap<String, 
String>();
+                @params["bogusArg"] = "bogusValue";
+                new MorfologikFilterFactory(@params);
+            });
+
+            assertTrue(expected.Message.Contains("Unknown parameters"));
+        }
+    }
+}
diff --git 
a/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/custom-dictionary.dict 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/custom-dictionary.dict
new file mode 100644
index 0000000..e157303
Binary files /dev/null and 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/custom-dictionary.dict 
differ
diff --git 
a/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/custom-dictionary.info 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/custom-dictionary.info
new file mode 100644
index 0000000..53796c0
--- /dev/null
+++ b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/custom-dictionary.info
@@ -0,0 +1,24 @@
+#
+# An example stemming dictionary file for Morfologik filter.
+#
+# Compile with Morfologik-stemming, see
+# https://github.com/morfologik/morfologik-stemming/wiki/Examples
+#
+
+# Author of the dictionary.
+fsa.dict.author=Acme Inc.
+
+# Date the dictionary data was assembled (not compilation time!).
+fsa.dict.created=2015/10/08 09:16:00
+
+# The license for the dictionary data.
+fsa.dict.license=ASL 2.0
+
+# Character encoding inside the automaton (and input file).
+fsa.dict.encoding=UTF-8
+
+# field separator (lemma;inflected;tag)
+fsa.dict.separator=;
+
+# type of base/lemma compression encoding before automaton compression.
+fsa.dict.encoder=INFIX
\ No newline at end of file
diff --git 
a/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/custom-dictionary.input 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/custom-dictionary.input
new file mode 100644
index 0000000..b6e07f9
--- /dev/null
+++ 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Morfologik/custom-dictionary.input
@@ -0,0 +1,2 @@
+lemma1;inflected1;tag1
+lemma2;inflected2;tag2
\ No newline at end of file
diff --git 
a/src/Lucene.Net.Tests.Analysis.Morfologik/Uk/TestUkrainianAnalyzer.cs 
b/src/Lucene.Net.Tests.Analysis.Morfologik/Uk/TestUkrainianAnalyzer.cs
new file mode 100644
index 0000000..5e8e414
--- /dev/null
+++ b/src/Lucene.Net.Tests.Analysis.Morfologik/Uk/TestUkrainianAnalyzer.cs
@@ -0,0 +1,92 @@
+// Lucene version compatibility level 8.2.0
+using NUnit.Framework;
+using System;
+
+namespace Lucene.Net.Analysis.Uk
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// Test case for <see cref="UkrainianAnalyzer"/>.
+    /// </summary>
+    public class TestUkrainianAnalyzer : BaseTokenStreamTestCase
+    {
+        /** Check that UkrainianAnalyzer doesn't discard any numbers */
+        [Test]
+        public void TestDigitsInUkrainianCharset()
+        {
+            UkrainianMorfologikAnalyzer ra = new 
UkrainianMorfologikAnalyzer(TEST_VERSION_CURRENT);
+            AssertAnalyzesTo(ra, "text 1000", new String[] { "text", "1000" });
+            ra.Dispose();
+        }
+
+        [Test]
+        public void TestReusableTokenStream()
+        {
+            Analyzer a = new UkrainianMorfologikAnalyzer(TEST_VERSION_CURRENT);
+            AssertAnalyzesTo(a, "Ця п'єса, у свою чергу, рухається по 
емоційно-напруженому колу за ритм-енд-блюзом.",
+                                 new String[] { "п'єса", "черга", "рухатися", 
"емоційно", "напружений", "кола", "коло", "кіл", "ритм", "енд", "блюз" });
+            a.Dispose();
+        }
+
+        [Test]
+        public void TestSpecialCharsTokenStream()
+        {
+            Analyzer a = new UkrainianMorfologikAnalyzer(TEST_VERSION_CURRENT);
+            AssertAnalyzesTo(a, "м'яса м'я\u0301са м\u02BCяса м\u2019яса 
м\u2018яса м`яса",
+                     new String[] { "м'ясо", "м'ясо", "м'ясо", "м'ясо", 
"м'ясо", "м'ясо" });
+            a.Dispose();
+        }
+
+        [Test]
+        public void TestCapsTokenStream()
+        {
+            Analyzer a = new UkrainianMorfologikAnalyzer(TEST_VERSION_CURRENT);
+            AssertAnalyzesTo(a, "Цих Чайковського і Ґете.",
+                     new String[] { "Чайковське", "Чайковський", "Гете" });
+            a.Dispose();
+        }
+
+        [Test]
+        public void TestCharNormalization()
+        {
+            Analyzer a = new UkrainianMorfologikAnalyzer(TEST_VERSION_CURRENT);
+            AssertAnalyzesTo(a, "Ґюмрі та Гюмрі.",
+                     new String[] { "Гюмрі", "Гюмрі" });
+            a.Dispose();
+        }
+
+        [Test]
+        public void TestSampleSentence()
+        {
+            Analyzer a = new UkrainianMorfologikAnalyzer(TEST_VERSION_CURRENT);
+            AssertAnalyzesTo(a, "Це — проект генерування словника з тегами 
частин мови для української мови.",
+                     new String[] { "проект", "генерування", "словник", "тег", 
"частина", "мова", "українська", "український", "Українська", "мова" });
+            a.Dispose();
+        }
+
+        /** blast some random strings through the analyzer */
+        [Test]
+        public void TestRandomStrings()
+        {
+            Analyzer analyzer = new 
UkrainianMorfologikAnalyzer(TEST_VERSION_CURRENT);
+            CheckRandomData(Random, analyzer, 1000 * RANDOM_MULTIPLIER);
+            analyzer.Dispose();
+        }
+    }
+}

Reply via email to