コード例 #1
0
        /// <summary>
        /// Attempts to solve cases where the length of the kanji reading matches the length of the
        /// kana reading.
        /// </summary>
        protected override IEnumerable <FuriganaSolution> DoSolve(FuriganaResourceSet r, VocabEntry v)
        {
            if (v.KanjiReading.Length == v.KanaReading.Length)
            {
                List <FuriganaPart> parts = new List <FuriganaPart>();
                for (int i = 0; i < v.KanjiReading.Length; i++)
                {
                    if (r.GetKanji(v.KanjiReading[i]) != null)
                    {
                        parts.Add(new FuriganaPart(v.KanaReading[i].ToString(), i));
                    }
                    else if (!KanaHelper.IsAllKana(v.KanjiReading[i].ToString()))
                    {
                        // Our character is not a kanji and apparently not a kana either.
                        // Stop right there. It's probably a trap.
                        yield break;
                    }
                    else
                    {
                        if (!KanaHelper.AreEquivalent(v.KanjiReading[i].ToString(), v.KanaReading[i].ToString()))
                        {
                            // We are reading kana characters that are not equivalent. Stop.
                            yield break;
                        }
                    }
                }

                if (parts.Any())
                {
                    yield return(new FuriganaSolution(v, parts));
                }
            }
        }
コード例 #2
0
 /// <summary>
 /// Attempts to solve furigana when the kanji reading only has one character.
 /// </summary>
 protected override IEnumerable <FuriganaSolution> DoSolve(FuriganaResourceSet r, VocabEntry v)
 {
     if (v.KanjiReading.Length == 1 && !KanaHelper.IsAllKana(v.KanjiReading))
     {
         yield return(new FuriganaSolution(v, new FuriganaPart(v.KanaReading, 0, 0)));
     }
 }
コード例 #3
0
        /// <summary>
        /// Given a kanji, finds and returns all potential readings that it could take in a string.
        /// </summary>
        /// <param name="k">Kanji to evaluate.</param>
        /// <param name="isFirstChar">Set to true if this kanji is the first character of the string
        /// that the kanji is found in.</param>
        /// <param name="isLastChar">Set to true if this kanji is the last character of the string
        /// that the kanji is found in.</param>
        /// <param name="useNanori">Set to true to use nanori readings as well.</param>
        /// <returns>A list containing all potential readings that the kanji could take.</returns>
        public static List <string> GetPotentialKanjiReadings(Kanji k, bool isFirstChar, bool isLastChar, bool useNanori)
        {
            List <string> output = new List <string>();

            foreach (string reading in (useNanori ? k.ReadingsWithNanori : k.Readings))
            {
                string r = reading.Replace("-", string.Empty);
                if (!KanaHelper.IsAllKatakana(r))
                {
                    r = r.Replace("ー", string.Empty);
                }

                string[] dotSplit = r.Split('.');
                if (dotSplit.Count() == 1)
                {
                    output.Add(r);
                }
                else if (dotSplit.Count() == 2)
                {
                    output.Add(dotSplit[0]);
                    output.Add(r.Replace(".", string.Empty));

                    if (AfterDotKunYomiTransformDictionary.ContainsKey(dotSplit[1]))
                    {
                        string newTerm    = AfterDotKunYomiTransformDictionary[dotSplit[1]];
                        string newReading = r.Replace(".", string.Empty);
                        newReading  = newReading.Substring(0, newReading.Length - dotSplit[1].Length);
                        newReading += newTerm;
                        output.Add(newReading);
                    }

                    if (dotSplit[1].Length >= 2 && dotSplit[1][1] == 'る')
                    {
                        // Add variant without the ending る.
                        string newReading = r.Replace(".", string.Empty);
                        newReading = newReading.Substring(0, newReading.Length - 1);
                        output.Add(newReading);
                    }
                }
                else
                {
                    throw new Exception(string.Format("Weird reading: {0} for kanji {1}.", reading, k.Character));
                }
            }

            // Add final small tsu rendaku
            if (!isLastChar)
            {
                output.AddRange(GetSmallTsuRendaku(output));
            }

            // Rendaku
            if (!isFirstChar)
            {
                output.AddRange(GetAllRendaku(output));
            }

            return(output.Distinct().ToList());
        }
コード例 #4
0
 /// <summary>
 /// Called when the main filter is validated.
 /// </summary>
 private void OnSendMainFilter()
 {
     if (MainFilterMode != KanjiFilterModeEnum.Meaning)
     {
         // Change input to kana when searching for a reading.
         MainFilter = KanaHelper.RomajiToKana(MainFilter);
     }
 }
コード例 #5
0
 /// <summary>
 /// Called when the filter mode is changed.
 /// </summary>
 private void OnFilterModeChanged()
 {
     if (MainFilterMode != KanjiFilterModeEnum.Meaning)
     {
         // Change main filter to kana when searching for a reading.
         MainFilter = KanaHelper.RomajiToKana(MainFilter);
     }
 }
コード例 #6
0
        /// <summary>
        /// Recursive method that, given an input string and a number of cuts
        /// wanted, finds and returns all the cut possibilities.
        /// </summary>
        /// <param name="input">Input string to cut.</param>
        /// <param name="cutCount">Number of cuts to make.</param>
        /// <returns>The exhaustive list of possible cuts.</returns>
        private static IEnumerable <string> GetCuts(string input, int cutCount)
        {
            // Recursive exit condition.
            if (cutCount == 1)
            {
                // If we have only one cut to make, the only possible cut is our input string itself.

                if (input.Length <= MaxKanaPerKanji)
                {
                    // Performance fix.
                    // Return nothing if the final cut is more than xx characters long,
                    // because a single kanji associated with xx hiragana is not gonna happen anyway.
                    yield return(input);
                }
            }
            else
            {
                // When we have more than one cut to make...
                string firstCut = string.Empty;
                while (input.Length >= cutCount)
                {
                    // Pop the first character of the input string to the firstCut.
                    firstCut += input.First();
                    input     = input.Remove(0, 1);

                    // Performance fix. Check the characters to see if it is worth going on.
                    if ((firstCut.Length == 1 && ImpossibleCutStart.Contains(firstCut.First())) ||
                        (firstCut.Length == 2 && KanaHelper.IsAllKatakana(firstCut)))
                    {
                        break;
                    }

                    // Recursively call this method with our input (keep in mind its first character has been removed)
                    // and with one less cut, because our currentString is the first cut in our context.
                    foreach (string subcut in GetCuts(input, cutCount - 1))
                    {
                        // This gives us all possible cuts after our currentString cut.
                        // Output the results.
                        yield return(string.Format("{0}{1}{2}", firstCut, SeparatorHelper.FuriganaSeparator, subcut));
                    }

                    // If, after removing its first character, our input string is now too short,
                    // i.e. we could not produce any valid cut with a firstCut longer than it is now,
                    // stop looping. We're done.

                    // Example: for (がんばる, 3), we are stopping when firstCut = がん, because a
                    // firstCut of がんば couldn't produce any valid output, considering that we want
                    // 2 more cuts and there's only 1 character left.

                    if (firstCut.Length >= MaxKanaPerKanji)
                    {
                        // Performance fix. Exit the loop if our first cut is too long.
                        break;
                    }
                }
            }
        }
コード例 #7
0
        public void HalfKana_ToFullKana_Works()
        {
            string half = "ニッポン";
            string full = "ニッポン";

            string actual = KanaHelper.ToFullKana(half);

            Assert.AreEqual(full, actual);
        }
コード例 #8
0
        /// <summary>
        /// Called when the filter mode is changed.
        /// </summary>
        private void OnFilterModeChanged()
        {
            if (MainFilterMode != KanjiFilterModeEnum.Meaning)
            {
                // Change main filter to kana when searching for a reading.
                MainFilter = KanaHelper.RomajiToKana(MainFilter);
            }

            if (!string.IsNullOrWhiteSpace(MainFilter))
            {
                DoFilterChange();
            }
        }
コード例 #9
0
        private IEnumerable <object> GetReadingList(string readingsString, KanaTypeEnum kanaType)
        {
            string[] readings = readingsString.Split(new char[] { MultiValueFieldHelper.ValueSeparator }, StringSplitOptions.RemoveEmptyEntries);

            foreach (string reading in readings)
            {
                yield return(new KanjiReading()
                {
                    HiraganaReading = KanaHelper.ToHiragana(reading.Trim()),
                    ModifiedReading = GetModifiedReading(kanaType, reading.Trim())
                });
            }
        }
コード例 #10
0
        /// <summary>
        /// Submits the current answer.
        /// </summary>
        private void SubmitAnswer()
        {
            if (string.IsNullOrEmpty(CurrentAnswer) ||
                CurrentQuestion == null)
            {
                // Do not evaluate an empty answer.
                return;
            }

            // Convert the answer to kana if necessary.
            if (CurrentQuestion.Question == SrsQuestionEnum.Reading)
            {
                CurrentAnswer = KanaHelper.RomajiToKana(CurrentAnswer);
            }

            // Determine if the answer is correct.
            bool success = IsAnswerCorrect();

            // Set the review state accordingly.
            // Other operations will be executed when we move on to another question.
            ReviewState = success ? SrsReviewStateEnum.Success : SrsReviewStateEnum.Failure;

            // Play audio if auto play enabled.
            if (CurrentQuestion.Question == SrsQuestionEnum.Reading &&
                CurrentQuestionGroup.IsVocab &&
                ((success && Properties.Settings.Default.AudioAutoplayMode.ShouldPlayOnSuccess()) ||
                 (!success && Properties.Settings.Default.AudioAutoplayMode.ShouldPlayOnFailure())))
            {
                AudioBusiness.PlayVocabAudio(CurrentQuestionGroup.Audio);
            }

            // If we have a failure: trigger the timer.
            if (ReviewState == SrsReviewStateEnum.Failure)
            {
                _canSubmit = false;
                DispatcherTimer timer = new DispatcherTimer();
                timer.Interval = FailureSubmitDelay;
                timer.Tick    += OnFailureSubmitTimerTick;
                timer.Start();
            }
            // If it was a success on the last question of the group, allow level up/down preview.
            else if (CurrentQuestionGroup.GetUnansweredQuestions().Count() == 1)
            {
                SrsLevelStore.Instance.IssueWhenLoaded(
                    () => {
                    DispatcherHelper.Invoke(() => {
                        PreviewNextLevel = GetUpdatedLevel(CurrentQuestionGroup);
                    });
                });
            }
        }
コード例 #11
0
        /// <summary>
        /// Subpart of TryReading. Attempts to find a match between the current kanji reading character
        /// and the current kana reading character. If found, iterates on TryReading.
        /// </summary>
        private IEnumerable <FuriganaSolution> ReadAsKana(FuriganaResourceSet r, VocabEntry v,
                                                          int currentIndexKanji, int currentIndexKana, List <FuriganaPart> currentCut, char c)
        {
            char kc = v.KanaReading[currentIndexKana];

            if (c == kc || KanaHelper.ToHiragana(c.ToString()) == KanaHelper.ToHiragana(kc.ToString()))
            {
                // What we are reading in the kanji reading matches the kana reading.
                // We can iterate with the same cut (no added furigana) because we are reading kana.
                foreach (FuriganaSolution result in TryReading(r, v, currentIndexKanji + 1, currentIndexKana + 1, currentCut))
                {
                    yield return(result);
                }
            }
        }
コード例 #12
0
        /// <summary>
        /// Overrides the TextInput event handler to alter the text being typed
        /// if needed.
        /// </summary>
        protected override void OnTextInput(TextCompositionEventArgs e)
        {
            lock (_textLock)
            {
                base.OnTextInput(e);

                if (IsKanaInput)
                {
                    int caretIndexBefore = this.CaretIndex;
                    int lengthBefore     = Text.Length;
                    Text            = KanaHelper.RomajiToKana(Text, true);
                    this.CaretIndex = caretIndexBefore + (Text.Length - lengthBefore);
                }
            }
        }
コード例 #13
0
        private string GetModifiedReading(KanaTypeEnum kanaType, string readingString)
        {
            switch (kanaType)
            {
            case KanaTypeEnum.Hiragana:
                return(KanaHelper.ToHiragana(readingString));

            case KanaTypeEnum.Katakana:
                return(KanaHelper.ToKatakana(readingString));

            case KanaTypeEnum.Romaji:
                return(KanaHelper.ToRomaji(readingString).ToLower());

            default:
                return(null);
            }
        }
コード例 #14
0
        /// <summary>
        /// Validates the input.
        /// </summary>
        private void ValidateInput()
        {
            if (IsKanaInput)
            {
                Text = KanaHelper.RomajiToKana(Text);
            }

            if (Text != _lastValidatedValue)
            {
                _lastValidatedValue = Text;
                if (ValidationCommand != null &&
                    ValidationCommand.CanExecute(ValidationCommandParameter))
                {
                    ValidationCommand.Execute(ValidationCommandParameter);
                }
            }
        }
コード例 #15
0
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is string && parameter is KanjiReadingToListConversionType)
            {
                // Get the conversion type and deduce the associated reading type.
                KanjiReadingToListConversionType conversionType
                    = (KanjiReadingToListConversionType)parameter;
                KanaTypeEnum kanaType = (conversionType ==
                                         KanjiReadingToListConversionType.KunYomi ?
                                         Properties.Settings.Default.KunYomiReadingType
                    : conversionType == KanjiReadingToListConversionType.OnYomi ?
                                         Properties.Settings.Default.OnYomiReadingType
                        : Properties.Settings.Default.NanoriReadingType);

                List <KanjiReading> readingList = new List <KanjiReading>();
                if (!string.IsNullOrWhiteSpace(value.ToString()))
                {
                    string[] readings = value.ToString().Split(
                        new char[] { MultiValueFieldHelper.ValueSeparator },
                        StringSplitOptions.RemoveEmptyEntries);

                    foreach (string reading in readings)
                    {
                        readingList.Add(new KanjiReading()
                        {
                            HiraganaReading = KanaHelper.ToHiragana(reading.Trim()),
                            ModifiedReading = GetModifiedReading(kanaType, reading.Trim())
                        });
                    }
                }
                return(readingList);
            }
            else
            {
                throw new ArgumentException(
                          "This converter takes a reading string as a value and a "
                          + "matching conversion type as a parameter.");
            }
        }
コード例 #16
0
ファイル: KanjiEtl.cs プロジェクト: reline/JmdictFurigana
        /// <summary>
        /// Reads and returns Kanji models.
        /// </summary>
        public IEnumerable <Kanji> Execute()
        {
            // Load the supplement file.
            List <Kanji> supplementaryKanji = new List <Kanji>();

            foreach (string line in File.ReadAllLines(PathHelper.SupplementaryKanjiPath))
            {
                if (string.IsNullOrWhiteSpace(line) || line.First() == ';')
                {
                    continue;
                }

                char     c        = line.First();
                string[] split    = line.Split(SeparatorHelper.FileFieldSeparator);
                string[] readings = split[1].Split(SeparatorHelper.FileReadingSeparator);

                supplementaryKanji.Add(new Kanji()
                {
                    Character          = c,
                    Readings           = readings.ToList(),
                    ReadingsWithNanori = readings.ToList(),
                    IsRealKanji        = false
                });
            }

            // Load the KanjiDic2 file.
            XDocument xdoc = XDocument.Load(PathHelper.KanjiDic2Path);

            // Browse kanji nodes.
            foreach (XElement xkanji in xdoc.Root.Elements(XmlNode_Character))
            {
                // For each kanji node, read values.
                Kanji kanji = new Kanji();

                // Read the kanji character.
                kanji.Character = xkanji.Element(XmlNode_Literal).Value.First();

                // In the reading/meaning node...
                XElement xreadingMeaning = xkanji.Element(XmlNode_ReadingMeaning);
                if (xreadingMeaning != null)
                {
                    // Browse the reading group...
                    XElement xrmGroup = xreadingMeaning.Element(XmlNode_ReadingMeaningGroup);
                    if (xrmGroup != null)
                    {
                        // Read the readings and add them to the readings of the kanji.
                        foreach (XElement xreading in xrmGroup.Elements(XmlNode_Reading)
                                 .Where(x => x.Attribute(XmlAttribute_ReadingType).Value == XmlAttributeValue_OnYomiReading ||
                                        x.Attribute(XmlAttribute_ReadingType).Value == XmlAttributeValue_KunYomiReading))
                        {
                            kanji.Readings.Add(KanaHelper.ToHiragana(xreading.Value));
                        }
                    }
                }

                // See if there's a supplementary entry for this kanji.
                Kanji supp = supplementaryKanji.FirstOrDefault(k => k.Character == kanji.Character);
                if (supp != null)
                {
                    // Supplementary entry found. Remove it from the list and add its readings to our current entry.
                    kanji.Readings.AddRange(supp.Readings);
                    supplementaryKanji.Remove(supp);
                }

                // Read the nanori readings
                var nanoriReadings = xreadingMeaning?.Elements(XmlNode_Nanori).Select(n => n.Value).ToList() ?? new List <string>();
                kanji.ReadingsWithNanori = kanji.Readings.Union(nanoriReadings).Distinct().ToList();

                // Return the kanji read and go to the next kanji node.
                yield return(kanji);

                xkanji.RemoveAll();
            }

            // Return the remaining supplementary kanji as new kanji.
            foreach (Kanji k in supplementaryKanji)
            {
                yield return(k);
            }
        }
コード例 #17
0
 public void OnValidate()
 {
     ReadingFilter = KanaHelper.RomajiToKana(ReadingFilter);
     RaiseFilterChanged();
 }
コード例 #18
0
        /// <summary>
        /// Attempts to solve furigana by reading the kana string and attributing kanji a reading based
        /// not on the readings of the kanji, but on the kana characters that come up.
        /// </summary>
        protected override IEnumerable <FuriganaSolution> DoSolve(FuriganaResourceSet r, VocabEntry v)
        {
            // Basically, we are reading the kanji reading character by character, eating the kana from
            // the kana reading and associating each kanji the piece of kana that comes next.
            // The thing is, we are taking advantage that kanji readings cannot start with certain
            // kana (ん and the small characters).
            // If we just stumbled upon a kanji and the next characters of the kana string are of these
            // impossible start kana, we can automatically associate them with the kanji.
            // Now this will work only for a number of vocab, but it does significantly improve the results.
            // It is especially good for 2-characters compounds that use unusual readings.

            /// Example: 阿呆陀羅 (あほんだら)
            /// Read the あ for 阿;
            /// Read the ほ for 呆;
            /// Read the ん: it's an impossible start character, so it goes with 呆 as well;
            /// Read the だ for 陀;
            /// Read the ら for 羅.

            string kana = v.KanaReading;
            List <FuriganaPart> furigana = new List <FuriganaPart>();

            for (int i = 0; i < v.KanjiReading.Length; i++)
            {
                if (kana.Length == 0)
                {
                    // We still have characters to browse in our kanji reading, but
                    // there are no more kana to consume. Cannot solve.
                    yield break;
                }

                char c = v.KanjiReading[i];
                // Check for special expressions
                bool foundExpression = false;
                for (int j = v.KanjiReading.Length - 1; j >= i; j--)
                {
                    string            lookup     = v.KanjiReading.Substring(i, (j - i) + 1);
                    SpecialExpression expression = r.GetExpression(lookup);
                    if (expression != null)
                    {
                        // We found an expression.
                        foreach (SpecialReading expressionReading in ReadingExpander.GetPotentialSpecialReadings(
                                     expression, i == 0, j == v.KanjiReading.Length - 1))
                        {
                            if (kana.Length >= expressionReading.KanaReading.Length &&
                                kana.Substring(0, expressionReading.KanaReading.Length) == expressionReading.KanaReading)
                            {
                                // The reading matches.
                                // Eat the kana chain.
                                furigana.AddRange(expressionReading.Furigana.Furigana
                                                  .Select(fp => new FuriganaPart(fp.Value, fp.StartIndex + i, fp.EndIndex + i)));
                                kana            = kana.Substring(expressionReading.KanaReading.Length);
                                i               = j;
                                foundExpression = true;
                                break;
                            }
                        }

                        if (foundExpression)
                        {
                            break;
                        }
                    }
                }

                if (foundExpression)
                {
                    continue;
                }

                // Normal process: eat the first character of our kana string.
                string eaten = kana.First().ToString();
                kana = kana.Substring(1);
                Kanji k = r.GetKanji(c);
                if (k != null)
                {
                    // On a kanji case, also eat consecutive "impossible start characters"
                    // (ん, ょ, ゃ, ゅ, っ)
                    while (kana.Length > 0 && ImpossibleCutStart.Contains(kana.First()))
                    {
                        eaten += kana.First();
                        kana   = kana.Substring(1);
                    }

                    furigana.Add(new FuriganaPart(eaten, i));
                }
                else if (!KanaHelper.IsAllKana(c.ToString()))
                {
                    // The character is neither a kanji or a kana.
                    // Cannot solve.
                    yield break;
                }
                else
                {
                    if (eaten != c.ToString())
                    {
                        // The character browsed is a kana but is not the
                        // character that we just ate. We made a mistake
                        // in one of the kanji readings, meaning that we...
                        // Cannot solve.
                        yield break;
                    }
                }
            }

            if (kana.Length == 0)
            {
                // We consumed the whole kana string.
                // The case is solved.
                yield return(new FuriganaSolution(v, furigana));
            }
        }
コード例 #19
0
        /// <summary>
        /// Checks if the solution is correctly solved for the given coupling of vocab and furigana.
        /// </summary>
        /// <param name="v">Vocab to check.</param>
        /// <param name="furigana">Furigana to check.</param>
        /// <returns>True if the furigana covers all characters of the vocab reading without
        /// overlapping.</returns>
        public static bool Check(VocabEntry v, List <FuriganaPart> furigana)
        {
            // There are three conditions to check:
            // 1. Furigana parts are not overlapping: for any given index in the kanji reading string,
            // there is between 0 and 1 matching furigana parts.
            // 2. All non-kana characters are covered by a furigana part.
            // 3. Reconstituting the kana reading from the kanji reading using the furigana parts when
            // available will give the kana reading of the vocab entry.

            // Keep in mind things like 真っ青 admit a correct "0-2:まっさお" solution. There can be
            // furigana parts covering kana.

            // Check condition 1.
            if (Enumerable.Range(0, v.KanjiReading.Length).Any(i => furigana.Count(f => i >= f.StartIndex && i <= f.EndIndex) > 1))
            {
                // There are multiple furigana parts that are appliable for a given index.
                // This constitutes an overlap and results in the check being negative.
                // Condition 1 failed.
                return(false);
            }

            // Now try to reconstitute the reading using the furigana parts.
            // This will allow us to test both 2 and 3.
            StringBuilder reconstitutedReading = new StringBuilder();

            for (int i = 0; i < v.KanjiReading.Length; i++)
            {
                // Try to find a matching part.
                FuriganaPart matchingPart = furigana.FirstOrDefault(f => i >= f.StartIndex && i <= f.EndIndex);
                if (matchingPart != null)
                {
                    // We have a matching part. Add the furigana string to the reconstituted reading.
                    reconstitutedReading.Append(matchingPart.Value);

                    // Advance i to the end index and continue.
                    i = matchingPart.EndIndex;
                    continue;
                }

                // Characters that are not covered by a furigana part should be kana.
                char c = v.KanjiReading[i];
                if (KanaHelper.IsAllKana(c.ToString()))
                {
                    // It is kana. Add the character to the reconstituted reading.
                    reconstitutedReading.Append(c);
                }
                else
                {
                    // This is not kana and this is not covered by any furigana part.
                    // The solution is not complete and is therefore not valid.
                    // Condition 2 failed.
                    return(false);
                }
            }

            // Our reconstituted reading should be the same as the kana reading of the vocab.
            if (!KanaHelper.AreEquivalent(reconstitutedReading.ToString(), v.KanaReading))
            {
                // It is different. Something is not correct in the furigana reading values.
                // Condition 3 failed.
                return(false);
            }

            // Nothing has failed. Everything is good.
            return(true);
        }
コード例 #20
0
        /// <summary>
        /// Reads the KanjiDic2 file and outputs kanji entities parsed from the file.
        /// </summary>
        /// <returns>Kanji entities parsed from the file.</returns>
        private IEnumerable <KanjiEntity> ReadKanjiDic2()
        {
            // Load the KanjiDic2 file.
            XDocument xdoc = XDocument.Load(PathHelper.KanjiDic2Path);

            // Browse kanji nodes.
            foreach (XElement xkanji in xdoc.Root.Elements(XmlNode_Character))
            {
                // For each kanji node, read values.
                KanjiEntity kanji = new KanjiEntity();

                // Read the kanji character.
                kanji.Character = xkanji.Element(XmlNode_Literal).Value;

                // In the code point node...
                XElement xcodePoint = xkanji.Element(XmlNode_CodePoint);
                if (xcodePoint != null)
                {
                    // Try to read the unicode character value.
                    XElement xunicode = xcodePoint.Elements(XmlNode_CodePointValue)
                                        .Where(x => x.ReadAttributeString(XmlAttribute_CodePointType) == XmlAttributeValue_CodePointUnicode)
                                        .FirstOrDefault();

                    if (xunicode != null)
                    {
                        string unicodeValueString = xunicode.Value;
                        int    intValue           = 0;
                        if (int.TryParse(unicodeValueString, System.Globalization.NumberStyles.HexNumber, ParsingHelper.DefaultCulture, out intValue))
                        {
                            kanji.UnicodeValue = intValue;
                        }
                    }
                }

                // In the misc node...
                XElement xmisc = xkanji.Element(XmlNode_Misc);
                if (xmisc != null)
                {
                    // Try to read the grade, stroke count, frequency and JLPT level.
                    // Update: JLPT level is outdated in this file. Now using the JLPTKanjiList.
                    XElement xgrade       = xmisc.Element(XmlNode_Grade);
                    XElement xstrokeCount = xmisc.Element(XmlNode_StrokeCount);
                    XElement xfrequency   = xmisc.Element(XmlNode_Frequency);
                    //XElement xjlpt = xmisc.Element(XmlNode_JlptLevel);

                    if (xgrade != null)
                    {
                        kanji.Grade = ParsingHelper.ParseShort(xgrade.Value);
                    }
                    if (xstrokeCount != null)
                    {
                        kanji.StrokeCount = ParsingHelper.ParseShort(xstrokeCount.Value);
                    }
                    if (xfrequency != null)
                    {
                        kanji.NewspaperRank = ParsingHelper.ParseInt(xfrequency.Value);
                    }
                    //if (xjlpt != null) kanji.JlptLevel = ParsingHelper.ParseShort(xjlpt.Value);
                }

                // Find the JLPT level using the dictionary.
                if (_jlptDictionary.ContainsKey(kanji.Character))
                {
                    kanji.JlptLevel = _jlptDictionary[kanji.Character];
                }

                // Find the frequency rank using the dictionary.
                if (_frequencyRankDictionary.ContainsKey(kanji.Character))
                {
                    kanji.MostUsedRank = _frequencyRankDictionary[kanji.Character];
                }

                // Find the WaniKani level using the dictionary.
                if (_waniKaniDictionary.ContainsKey(kanji.Character))
                {
                    kanji.WaniKaniLevel = _waniKaniDictionary[kanji.Character];
                }

                // In the reading/meaning node...
                XElement xreadingMeaning = xkanji.Element(XmlNode_ReadingMeaning);
                if (xreadingMeaning != null)
                {
                    // Read the nanori readings.
                    kanji.Nanori = string.Empty;
                    foreach (XElement xnanori in xreadingMeaning.Elements(XmlNode_Nanori))
                    {
                        kanji.Nanori += xnanori.Value + MultiValueFieldHelper.ValueSeparator;
                    }
                    kanji.Nanori = kanji.Nanori.Trim(MultiValueFieldHelper.ValueSeparator);

                    // Browse the reading group...
                    XElement xrmGroup = xreadingMeaning.Element(XmlNode_ReadingMeaningGroup);
                    if (xrmGroup != null)
                    {
                        // Read the on'yomi readings.
                        kanji.OnYomi = string.Empty;
                        foreach (XElement xonYomi in xrmGroup.Elements(XmlNode_Reading)
                                 .Where(x => x.Attribute(XmlAttribute_ReadingType).Value == XmlAttributeValue_OnYomiReading))
                        {
                            kanji.OnYomi += xonYomi.Value + MultiValueFieldHelper.ValueSeparator;
                        }
                        kanji.OnYomi = KanaHelper.ToHiragana(kanji.OnYomi.Trim(MultiValueFieldHelper.ValueSeparator));

                        // Read the kun'yomi readings.
                        kanji.KunYomi = string.Empty;
                        foreach (XElement xkunYomi in xrmGroup.Elements(XmlNode_Reading)
                                 .Where(x => x.Attribute(XmlAttribute_ReadingType).Value == XmlAttributeValue_KunYomiReading))
                        {
                            kanji.KunYomi += xkunYomi.Value + MultiValueFieldHelper.ValueSeparator;
                        }
                        kanji.KunYomi = kanji.KunYomi.Trim(MultiValueFieldHelper.ValueSeparator);

                        // Browse the meanings...
                        foreach (XElement xmeaning in xrmGroup.Elements(XmlNode_Meaning))
                        {
                            // Get the language and meaning.
                            XAttribute xlanguage = xmeaning.Attribute(XmlAttribute_MeaningLanguage);
                            string     language  = xlanguage != null?xlanguage.Value.ToLower() : null;

                            string meaning = xmeaning.Value;

                            if (xlanguage == null || language.ToLower() == "en")
                            {
                                // Build a meaning.
                                KanjiMeaning kanjiMeaning = new KanjiMeaning()
                                {
                                    Kanji = kanji, Language = language, Meaning = meaning
                                };

                                // Add the meaning to the kanji.
                                kanji.Meanings.Add(kanjiMeaning);
                            }
                        }
                    }
                }

                // Return the kanji read and go to the next kanji node.
                yield return(kanji);

                xkanji.RemoveAll();
            }
        }