/* * Copyright (c) 2014-2017, Eren Okka * Copyright (c) 2016-2017, Paul Miller * Copyright (c) 2017-2018, Tyler Bratton * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System.Text; namespace AnitomySharp; /// /// Utility class to assist in the parsing. /// public class ParserHelper(Parser parser) { private static readonly System.Buffers.SearchValues s_myChars = System.Buffers.SearchValues.Create("xX\u00D7"); private const string Dashes = "-\u2010\u2011\u2012\u2013\u2014\u2015"; private const string DashesWithSpace = " -\u2010\u2011\u2012\u2013\u2014\u2015"; private static readonly Dictionary s_ordinals = new() { {"1st", "1"}, {"First", "1"}, {"2nd", "2"}, {"Second", "2"}, {"3rd", "3"}, {"Third", "3"}, {"4th", "4"}, {"Fourth", "4"}, {"5th", "5"}, {"Fifth", "5"}, {"6th", "6"}, {"Sixth", "6"}, {"7th", "7"}, {"Seventh", "7"}, {"8th", "8"}, {"Eighth", "8"}, {"9th", "9"}, {"Ninth", "9"} }; /// /// Returns whether or not the result matches the category. /// public bool IsTokenCategory(int result, Token.TokenCategory category) { return Token.InListRange(result, parser.Tokens) && parser.Tokens[result].Category == category; } /// /// Returns whether or not the str is a CRC string. /// public static bool IsCrc32(string str) { return str.Length == 8 && StringHelper.IsHexadecimalString(str); } /// /// Returns whether or not the character is a dash character /// public static bool IsDashCharacter(char c) { return Dashes.Contains(c.ToString()); } /// /// Returns a number from an original (e.g. 2nd) /// private static string GetNumberFromOrdinal(string str) { return string.IsNullOrEmpty(str) ? string.Empty : s_ordinals.GetValueOrDefault(str, ""); } /// /// Returns the index of the first digit in the str; -1 otherwise. /// public static int IndexOfFirstDigit(string str) { if (string.IsNullOrEmpty(str)) return -1; for (int i = 0; i < str.Length; i++) { if (char.IsDigit(str, i)) { return i; } } return -1; } /// /// Returns whether or not the str is a resolution. /// public static bool IsResolution(string str) { if (string.IsNullOrEmpty(str)) return false; const int minWidthSize = 3; const int minHeightSize = 3; switch (str.Length) { case >= minWidthSize + 1 + minHeightSize: { int pos = str.AsSpan().IndexOfAny(s_myChars); if (pos == -1 || pos < minWidthSize || pos > str.Length - (minHeightSize + 1)) return false; return !str.Where((t, i) => i != pos && !char.IsDigit(t)).Any(); } case < minHeightSize + 1: return false; default: { if (char.ToLower(str[^1]) != 'p') return false; for (int i = 0; i < str.Length - 1; i++) { if (!char.IsDigit(str[i])) return false; } return true; } } } /// /// Returns whether or not the category is searchable. /// public static bool IsElementCategorySearchable(ElementCategory category) { return category switch { ElementCategory.ElementAnimeSeasonPrefix or ElementCategory.ElementAnimeType or ElementCategory.ElementAudioTerm or ElementCategory.ElementDeviceCompatibility or ElementCategory.ElementEpisodePrefix or ElementCategory.ElementFileChecksum or ElementCategory.ElementLanguage or ElementCategory.ElementOther or ElementCategory.ElementReleaseGroup or ElementCategory.ElementReleaseInformation or ElementCategory.ElementReleaseVersion or ElementCategory.ElementSource or ElementCategory.ElementSubtitles or ElementCategory.ElementVideoResolution or ElementCategory.ElementVideoTerm or ElementCategory.ElementVolumePrefix => true, _ => false }; } /// /// Returns whether the category is singular. /// public static bool IsElementCategorySingular(ElementCategory category) { return category switch { ElementCategory.ElementAnimeSeason or ElementCategory.ElementAnimeType or ElementCategory.ElementAudioTerm or ElementCategory.ElementDeviceCompatibility or ElementCategory.ElementEpisodeNumber or ElementCategory.ElementLanguage or ElementCategory.ElementOther or ElementCategory.ElementReleaseInformation or ElementCategory.ElementSource or ElementCategory.ElementVideoTerm => false, _ => false }; } /// /// Returns whether or not a token at the current pos is isolated(surrounded by braces). /// public bool IsTokenIsolated(int pos) { int prevToken = Token.FindPrevToken(parser.Tokens, pos, Token.TokenFlag.FlagNotDelimiter); if (!IsTokenCategory(prevToken, Token.TokenCategory.Bracket)) return false; int nextToken = Token.FindNextToken(parser.Tokens, pos, Token.TokenFlag.FlagNotDelimiter); return IsTokenCategory(nextToken, Token.TokenCategory.Bracket); } /// /// Finds and sets the anime season keyword. /// public bool CheckAndSetAnimeSeasonKeyword(Token token, int currentTokenPos) { int previousToken = Token.FindPrevToken(parser.Tokens, currentTokenPos, Token.TokenFlag.FlagNotDelimiter); if (Token.InListRange(previousToken, parser.Tokens)) { string number = GetNumberFromOrdinal(parser.Tokens[previousToken].Content); if (!string.IsNullOrEmpty(number)) { SetAnimeSeason(parser.Tokens[previousToken], token, number); return true; } } int nextToken = Token.FindNextToken(parser.Tokens, currentTokenPos, Token.TokenFlag.FlagNotDelimiter); if (!Token.InListRange(nextToken, parser.Tokens) || !StringHelper.IsNumericString(parser.Tokens[nextToken].Content)) return false; SetAnimeSeason(token, parser.Tokens[nextToken], parser.Tokens[nextToken].Content); return true; void SetAnimeSeason(Token first, Token second, string content) { parser.Elements.Add(new Element(ElementCategory.ElementAnimeSeason, content)); first.Category = Token.TokenCategory.Identifier; second.Category = Token.TokenCategory.Identifier; } } /// /// A Method to find the correct volume/episode number when prefixed (i.e. Vol.4). /// /// the category we're searching for /// the current token position /// the token /// true if we found the volume/episode number public bool CheckExtentKeyword(ElementCategory category, int currentTokenPos, Token token) { int nToken = Token.FindNextToken(parser.Tokens, currentTokenPos, Token.TokenFlag.FlagNotDelimiter); if (!IsTokenCategory(nToken, Token.TokenCategory.Unknown)) return false; if (IndexOfFirstDigit(parser.Tokens[nToken].Content) != 0) return false; switch (category) { case ElementCategory.ElementEpisodeNumber: if (!parser.ParseNumber.MatchEpisodePatterns(parser.Tokens[nToken].Content, parser.Tokens[nToken])) { parser.ParseNumber.SetEpisodeNumber(parser.Tokens[nToken].Content, parser.Tokens[nToken], false); } break; case ElementCategory.ElementVolumeNumber: if (!parser.ParseNumber.MatchVolumePatterns(parser.Tokens[nToken].Content, parser.Tokens[nToken])) { parser.ParseNumber.SetVolumeNumber(parser.Tokens[nToken].Content, parser.Tokens[nToken], false); } break; } token.Category = Token.TokenCategory.Identifier; return true; } public void BuildElement(ElementCategory category, bool keepDelimiters, List tokens) { var element = new StringBuilder(); for (int i = 0; i < tokens.Count; i++) { var token = tokens[i]; switch (token.Category) { case Token.TokenCategory.Unknown: element.Append(token.Content); token.Category = Token.TokenCategory.Identifier; break; case Token.TokenCategory.Bracket: element.Append(token.Content); break; case Token.TokenCategory.Delimiter: string delimiter = ""; if (!string.IsNullOrEmpty(token.Content)) { delimiter = token.Content[0].ToString(); } if (keepDelimiters) { element.Append(delimiter); } else if (Token.InListRange(i, tokens)) { switch (delimiter) { case ",": case "&": element.Append(delimiter); break; default: element.Append(' '); break; } } break; } } if (!keepDelimiters) { element = new StringBuilder(element.ToString().Trim(DashesWithSpace.ToCharArray())); } if (!string.IsNullOrEmpty(element.ToString())) { parser.Elements.Add(new Element(category, element.ToString())); } } }