/// <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)); } } }
/// <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))); } }
/// <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()); }
/// <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); } }
/// <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); } }
/// <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; } } } }
public void HalfKana_ToFullKana_Works() { string half = "ニッポン"; string full = "ニッポン"; string actual = KanaHelper.ToFullKana(half); Assert.AreEqual(full, actual); }
/// <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(); } }
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()) }); } }
/// <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); }); }); } }
/// <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); } } }
/// <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); } } }
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); } }
/// <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); } } }
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."); } }
/// <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); } }
public void OnValidate() { ReadingFilter = KanaHelper.RomajiToKana(ReadingFilter); RaiseFilterChanged(); }
/// <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)); } }
/// <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); }
/// <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(); } }