diff --git a/RAE.Demo/Program.cs b/RAE.Demo/Program.cs index af55f07..d4444e5 100644 --- a/RAE.Demo/Program.cs +++ b/RAE.Demo/Program.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using RAE.Models; namespace RAE.Demo { @@ -11,6 +12,7 @@ class Program static Func[] functions = { + FetchWordsAsync, GetKeysAsync, SearchWordAsync, WordOfTheDayAsync, @@ -37,6 +39,23 @@ static async Task Main() Console.ReadLine(); } + static async Task FetchWordsAsync() + { + string[] wordsToFetch = { "y", "tonto", "niño", "en", "manada" }; + + foreach (string wordToFetch in wordsToFetch) + { + IList words = await drae.FetchWordsAsync(wordToFetch); + + foreach (IWord word in words) + { + Console.WriteLine(word); + } + + Console.WriteLine(); + } + } + static async Task GetKeysAsync() { string query = "w"; @@ -48,32 +67,30 @@ static async Task GetKeysAsync() static async Task SearchWordAsync() { string query = "a"; - IList words = await drae.SearchWordAsync(query, false); + IEntry[] entries = await drae.SearchWordAsync(query, false); - Console.WriteLine($"SearchWord ({query}): {string.Join(", ", words.Select(w => $"{w.Content} ({w.Id})"))}"); + Console.WriteLine($"SearchWord ({query}): {string.Join(", ", entries)}"); } static async Task WordOfTheDayAsync() { - Word word = await drae.GetWordOfTheDayAsync(); + IEntry word = await drae.GetWordOfTheDayAsync(); Console.WriteLine($"Word of the day: {word} ({word.Id})"); } static async Task GetRandomWorldAsync() { - Word word = await drae.GetRandomWordAsync(); + IWord word = await drae.GetRandomWordAsync(); Console.WriteLine($"A random word: {word}"); } static async Task FetchRandomWorldAsync() { - Word word = await drae.GetRandomWordAsync(); - string[] definitions = await drae.FetchWordByIdAsync(word.Id); + IWord word = await drae.GetRandomWordAsync(); - Console.WriteLine($"Definitions of {word.Content}:"); - Array.ForEach(definitions, Console.WriteLine); + Console.WriteLine(word); } static async Task GetWordsStartWithAsync() diff --git a/RAE.Demo/RAE.Demo.csproj b/RAE.Demo/RAE.Demo.csproj index 8cbc543..a7c0b5c 100644 --- a/RAE.Demo/RAE.Demo.csproj +++ b/RAE.Demo/RAE.Demo.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.0 + net7.0 diff --git a/RAE.NET.sln b/RAE.NET.sln index 2e93e1b..38d0efc 100644 --- a/RAE.NET.sln +++ b/RAE.NET.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29519.87 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33829.357 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RAE", "RAE\RAE.csproj", "{F214F029-5904-44EB-8C99-B84821558369}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RAE.Demo", "RAE.Demo\RAE.Demo.csproj", "{66258F90-04F3-49B6-B8E0-98A82838D6B5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAE.Tests", "RAE.Tests\RAE.Tests.csproj", "{AE13E843-1DA4-4507-9FE2-F7FCDF0EB899}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {66258F90-04F3-49B6-B8E0-98A82838D6B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {66258F90-04F3-49B6-B8E0-98A82838D6B5}.Release|Any CPU.ActiveCfg = Release|Any CPU {66258F90-04F3-49B6-B8E0-98A82838D6B5}.Release|Any CPU.Build.0 = Release|Any CPU + {AE13E843-1DA4-4507-9FE2-F7FCDF0EB899}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE13E843-1DA4-4507-9FE2-F7FCDF0EB899}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE13E843-1DA4-4507-9FE2-F7FCDF0EB899}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE13E843-1DA4-4507-9FE2-F7FCDF0EB899}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RAE.Tests/DRAE.cs b/RAE.Tests/DRAE.cs new file mode 100644 index 0000000..1fc2e9b --- /dev/null +++ b/RAE.Tests/DRAE.cs @@ -0,0 +1,160 @@ +namespace RAE.Tests; + +public class DRAE +{ + private RAE.DRAE Drae { get; } = new RAE.DRAE(); + + [Theory] + [InlineData("-ato")] + [InlineData("en")] + [InlineData("en-")] + [InlineData("manada")] + [InlineData("niño")] + [InlineData("tonto")] + [InlineData("y")] + public async Task FetchWordsExist(string word) + { + IList words = await Drae.FetchWordsAsync(word); + + Assert.NotNull(words); + Assert.NotEmpty(words); + } + + [Theory] + [InlineData("asdadasdada")] + public async Task FetchWordsNoExist(string word) + { + IList words = await Drae.FetchWordsAsync(word); + + Assert.NotNull(words); + Assert.Empty(words); + } + + [Theory] + [InlineData("SPSNBuG")] //pelo + [InlineData("R9dHZWB")] //ordenador + public async Task FetchWordByIdExist(string wordId) + { + IWord word = await Drae.FetchWordByIdAsync(wordId); + + Assert.NotNull(word); + } + + [Theory] + [InlineData("sadafasda")] + public async Task FetchWordByIdNoExist(string wordId) + { + IWord word = await Drae.FetchWordByIdAsync(wordId); + + Assert.Null(word); + } + + [Fact] + public async Task GetAllWords() + { + string[] allWords = await Drae.GetAllWordsAsync(); + + Assert.NotNull(allWords); + Assert.NotEmpty(allWords); + } + + [Theory] + [InlineData("a")] + [InlineData("la")] + public async Task GetKeysExist(string query) + { + string[] keys = await Drae.GetKeysAsync(query); + + Assert.NotNull(keys); + Assert.NotEmpty(keys); + } + + [Theory] + [InlineData("asdasdsad")] + public async Task GetKeysNoExist(string query) + { + string[] keys = await Drae.GetKeysAsync(query); + + Assert.NotNull(keys); + Assert.Empty(keys); + } + + [Fact] + public async Task GetRandomWord() + { + IWord word = await Drae.GetRandomWordAsync(); + + Assert.NotNull(word); + } + + [Fact] + public async Task GetWordOfTheDay() + { + IEntry word = await Drae.GetWordOfTheDayAsync(); + + Assert.NotNull(word); + } + + [Theory] + [InlineData("a")] + [InlineData("la")] + public async Task GetWordsStartWithExist(string query) + { + string[] words = await Drae.GetWordsStartWithAsync(query); + + Assert.NotNull(words); + Assert.NotEmpty(words); + } + + [Theory] + [InlineData("aadadsadasdc")] + public async Task GetWordsStartWithNoExist(string query) + { + string[] words = await Drae.GetWordsStartWithAsync(query); + + Assert.NotNull(words); + Assert.Empty(words); + } + + [Theory] + [InlineData("a")] + [InlineData("la")] + public async Task GetWordsContainExist(string query) + { + string[] words = await Drae.GetWordsContainAsync(query); + + Assert.NotNull(words); + Assert.NotEmpty(words); + } + + [Theory] + [InlineData("dfsdfsdfsfdsa")] + public async Task GetWordsContainNoExist(string query) + { + string[] words = await Drae.GetWordsContainAsync(query); + + Assert.NotNull(words); + Assert.Empty(words); + } + + [Theory] + [InlineData("avión")] + [InlineData("planeta")] + public async Task SearchWordExist(string word) + { + IEntry[] entries = await Drae.SearchWordAsync(word, true); + + Assert.NotNull(entries); + Assert.NotEmpty(entries); + } + + [Theory] + [InlineData("sdfsfsdfsfssdfsdfs")] + public async Task SearchWordNoExist(string word) + { + IEntry[] entries = await Drae.SearchWordAsync(word, true); + + Assert.NotNull(entries); + Assert.Empty(entries); + } +} \ No newline at end of file diff --git a/RAE.Tests/RAE.Tests.csproj b/RAE.Tests/RAE.Tests.csproj new file mode 100644 index 0000000..8dad6c6 --- /dev/null +++ b/RAE.Tests/RAE.Tests.csproj @@ -0,0 +1,29 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/RAE.Tests/Usings.cs b/RAE.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/RAE.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/RAE/DRAE.cs b/RAE/DRAE.cs index 6900631..87912d3 100644 --- a/RAE/DRAE.cs +++ b/RAE/DRAE.cs @@ -1,12 +1,14 @@ using System.Collections.Generic; using System.Threading.Tasks; +using RAE.Models; +using RAE.Services; namespace RAE { public class DRAE { - private RAEAPI _raeAPI; - private ListaPalabrasAPI _listaPalabrasAPI; + private readonly RAEAPI _raeAPI; + private readonly ListaPalabrasAPI _listaPalabrasAPI; public DRAE() { @@ -15,20 +17,42 @@ public DRAE() } /// - /// Get definitions of a word. - /// Obtiene las definiciones de una palabra. + /// Gets the information of all occurrences related to a word. + /// Obtiene la información de todas las ocurrencias relacionadas con una palabra. + /// + /// + /// The word to search. + /// La palabra a buscar. + /// + public async Task FetchWordsAsync(string word) + { + IEntry[] entries = await _raeAPI.SearchWordAsync(word, true); + List result = new List(entries.Length); + + foreach (IEntry entry in entries) + { + IWord wordData = await _raeAPI.FetchWordByIdAsync(entry.Id); + result.Add(wordData); + } + + return result.ToArray(); + } + + /// + /// Gets the information related to a word. + /// Obtiene la información relacionada con una palabra. /// /// /// The id of the word to search. /// El id de la palabra a buscar. /// - public Task FetchWordByIdAsync(string wordId) + public async Task FetchWordByIdAsync(string wordId) { - return _raeAPI.FetchWordByIdAsync(wordId); + return await _raeAPI.FetchWordByIdAsync(wordId); } /// - /// Get all the words that exist. + /// Gets all the words that exist. /// Obtiene todas las palabras que existen. /// public Task GetAllWordsAsync() @@ -37,7 +61,7 @@ public Task GetAllWordsAsync() } /// - /// Get keywords from another. + /// Gets keywords from another. /// Obtiene palabras claves a partir de otra. /// /// @@ -50,19 +74,19 @@ public Task GetKeysAsync(string query) } /// - /// Get a random word. + /// Gets a random word. /// Obtiene una palabra aleatoria. /// - public Task GetRandomWordAsync() + public Task GetRandomWordAsync() { return _raeAPI.GetRandomWordAsync(); } /// - /// Get the word of the day. + /// Gets the word of the day. /// Obtiene la palabra del día. /// - public Task GetWordOfTheDayAsync() + public Task GetWordOfTheDayAsync() { return _raeAPI.GetWordOfTheDayAsync(); } @@ -105,7 +129,7 @@ public Task GetWordsContainAsync(string query) /// If true it will take secondary entries. /// Si es verdadero cogerá entradas secundarias. /// - public Task> SearchWordAsync(string word, bool allGroups = true) + public Task SearchWordAsync(string word, bool allGroups = true) { return _raeAPI.SearchWordAsync(word, allGroups); } diff --git a/RAE/IDefinition.cs b/RAE/IDefinition.cs new file mode 100644 index 0000000..daec09e --- /dev/null +++ b/RAE/IDefinition.cs @@ -0,0 +1,14 @@ +namespace RAE +{ + public interface IDefinition + { + WordType Type { get; } + WordGenre? Genre { get; } + WordLanguage? Language { get; } + bool IsObsolete { get; } + string Content { get; } + string[] Regions { get; } + string[] Examples { get; } + string[] OtherData { get; } + } +} diff --git a/RAE/IEntry.cs b/RAE/IEntry.cs new file mode 100644 index 0000000..f715dda --- /dev/null +++ b/RAE/IEntry.cs @@ -0,0 +1,8 @@ +namespace RAE +{ + public interface IEntry + { + string Id { get; } + string Content { get; } + } +} diff --git a/RAE/IWord.cs b/RAE/IWord.cs new file mode 100644 index 0000000..1db50ef --- /dev/null +++ b/RAE/IWord.cs @@ -0,0 +1,11 @@ +using RAE.Models; + +namespace RAE +{ + public interface IWord : IEntry + { + string Origin { get; } + string[] Values { get; } + IDefinition[] Definitions { get; } + } +} diff --git a/RAE/Models/Definition.cs b/RAE/Models/Definition.cs new file mode 100644 index 0000000..b45a24c --- /dev/null +++ b/RAE/Models/Definition.cs @@ -0,0 +1,44 @@ +using System.Text; + +namespace RAE.Models +{ + internal class Definition : IDefinition + { + public WordType Type { get; private set; } + public WordGenre? Genre { get; private set; } + public WordLanguage? Language { get; private set; } + public bool IsObsolete { get; private set; } + public string Content { get; private set; } + public string[] Regions { get; private set; } + public string[] Examples { get; private set; } + public string[] OtherData { get; private set; } + + internal Definition(WordType type, WordGenre? genre, WordLanguage? language, bool isObsolete, + string content, string[] origins, string[] examples) + { + Type = type; + Genre = genre; + Language = language; + IsObsolete = isObsolete; + Content = content; + Regions = origins; + Examples = examples; + } + + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.Append(Type); + if (Genre.HasValue) stringBuilder.Append(", " + Genre.Value); + if (Language.HasValue) stringBuilder.Append(", " + Language.Value); + if (IsObsolete) stringBuilder.Append(", Obsolete"); + if (Regions != null && Regions.Length > 0) stringBuilder.Append(", " + string.Join(", ", Regions)); + if (OtherData != null && OtherData.Length > 0) stringBuilder.Append(", " + string.Join(", ", OtherData)); + stringBuilder.Append(" => " + Content); + if (Examples != null && Examples.Length > 0) stringBuilder.Append(" (" + string.Join(" ", Examples) + ")"); + + return stringBuilder.ToString(); + } + } +} diff --git a/RAE/Word.cs b/RAE/Models/Entry.cs similarity index 62% rename from RAE/Word.cs rename to RAE/Models/Entry.cs index 5b79911..f8713af 100644 --- a/RAE/Word.cs +++ b/RAE/Models/Entry.cs @@ -1,12 +1,11 @@ -namespace RAE +namespace RAE.Models { - public class Word + internal class Entry : IEntry { public string Id { get; private set; } - public string Content { get; private set; } - public Word(string id, string content) + internal Entry(string id, string content) { Id = id; Content = content; @@ -14,7 +13,7 @@ public Word(string id, string content) public override string ToString() { - return Content; + return $"#{Id} => {Content}"; } } } diff --git a/RAE/Models/Word.cs b/RAE/Models/Word.cs new file mode 100644 index 0000000..4712d83 --- /dev/null +++ b/RAE/Models/Word.cs @@ -0,0 +1,38 @@ +using System.Text; + +namespace RAE.Models +{ + internal class Word : Entry, IWord + { + public string Origin { get; private set; } + public string[] Values { get; set; } + public IDefinition[] Definitions { get; private set; } + + internal Word(string id, string content, string origin, string[] values, IDefinition[] definitions) : base(id, content) + { + Origin = origin; + Values = values; + Definitions = definitions; + } + + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append(base.ToString()); + stringBuilder.Append($" ({string.Join(", ", Values)})"); + if (!string.IsNullOrEmpty(Origin)) stringBuilder.Append($" {{{Origin}}}"); + stringBuilder.AppendLine(); + + if (Definitions != null) + { + for (int i = 0; i < Definitions.Length; i++) + { + stringBuilder.Append($"\t{i + 1}. "); + stringBuilder.AppendLine(Definitions[i].ToString()); + } + } + + return stringBuilder.ToString(); + } + } +} diff --git a/RAE/RAE.csproj b/RAE/RAE.csproj index a7cc72c..ecead02 100644 --- a/RAE/RAE.csproj +++ b/RAE/RAE.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 true RAE.NET RAE.NET @@ -11,7 +11,7 @@ MIT https://github.com/josago97/RAE.NET RAE, Diccionario, España, Español, Lengua, Dictionary, Spain, Spanish, Language - 1.0.3 + 1.1.0 diff --git a/RAE/ListaPalabrasAPI.cs b/RAE/Services/ListaPalabrasAPI.cs similarity index 86% rename from RAE/ListaPalabrasAPI.cs rename to RAE/Services/ListaPalabrasAPI.cs index df1b5bb..9434aaa 100644 --- a/RAE/ListaPalabrasAPI.cs +++ b/RAE/Services/ListaPalabrasAPI.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using HtmlAgilityPack; -namespace RAE +namespace RAE.Services { internal class ListaPalabrasAPI { @@ -29,7 +29,7 @@ public async Task GetAllWordsAsync() { result.AddRange(words); } - + return result.ToArray(); } @@ -80,12 +80,12 @@ private async Task LoadPageAsync(string url, HttpContent content) private string[] GetWords(HtmlDocument document) { - string[] words = document.GetElementbyId("columna_resultados_generales") - .SelectNodes("descendant::*[@id='palabra_resultado']") - .Select(node => node.InnerText.Trim(' ', '\n')) - .ToArray(); + HtmlNode resultNode = document.GetElementbyId("columna_resultados_generales"); + HtmlNodeCollection wordNodes = resultNode.SelectNodes("descendant::*[@id='palabra_resultado']"); - return words; + return wordNodes == null + ? new string[0] + : wordNodes.Select(node => node.InnerText.Trim(' ', '\n')).ToArray(); } } } diff --git a/RAE/Services/RAEAPI.Utils.cs b/RAE/Services/RAEAPI.Utils.cs new file mode 100644 index 0000000..1f5cdef --- /dev/null +++ b/RAE/Services/RAEAPI.Utils.cs @@ -0,0 +1,206 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Xml; +using RAE.Models; + +namespace RAE.Services +{ + internal partial class RAEAPI + { + private static readonly string[] ALLOW_ABBR_DEFINITION = { "U." }; + + private static readonly Dictionary GENRES = new Dictionary() + { + { "m.", WordGenre.Male }, + { "f.", WordGenre.Female }, + { "f. y m.", WordGenre.Both } + }; + + private static readonly Dictionary LANGUAGES = new Dictionary() + { + { "coloq.", WordLanguage.Colloquial }, + { "rur.", WordLanguage.Rural } + }; + + private static readonly Dictionary TYPES = new Dictionary() + { + { "adj.", WordType.Adjective }, + { "adv.", WordType.Adverb }, + { "art.", WordType.Article }, + { "conj.", WordType.Conjunction }, + { "interj.", WordType.Interjection }, + { "pref.", WordType.Prefix }, + { "prep.", WordType.Preposition }, + { "pron.", WordType.Pronoun }, + { "suf.", WordType.Suffix }, + { "verb.", WordType.Verb } + }; + + private string HtmlDecode(string content) + { + return WebUtility.HtmlDecode(content); + } + + private XmlDocument ParseToDocument(string xml) + { + XmlDocument document = new XmlDocument(); + document.LoadXml($"{xml}"); + + return document; + } + + private Word GetWord(string xml) + { + if (string.IsNullOrEmpty(xml)) return null; + + XmlDocument document = ParseToDocument(xml); + + string id = document.SelectSingleNode("//article").Attributes["id"].Value; + XmlElement header = (XmlElement)document.SelectSingleNode("//header"); + string content = header.SelectSingleNode("./node()[not(self::sup)]").InnerText; + string[] values = header.GetAttribute("title").Replace("Definición de ", "").Split(", "); + string origin = BuildString(document.SelectNodes(".//p[@class='n2' or @class='n4']/node()[not(self::sup)]")); + Definition[] definitions = GetDefinitions(document.DocumentElement); + + return new Word(id, content, origin, values, definitions); + } + + private Definition[] GetDefinitions(XmlElement document) + { + List result = new List(); + XmlNodeList definitions = document.SelectNodes("//p[contains(@class, 'j')]"); + + foreach (XmlElement definition in definitions) + { + result.Add(GetDefinition(definition)); + } + + return result.ToArray(); + } + + private Definition GetDefinition(XmlElement document) + { + WordType type = default; + WordGenre? genre = null; + WordLanguage? language = null; + bool isObsolete = false; + List extraData = new List(); + + XmlNodeList dataNodes = document.SelectNodes("./*/preceding-sibling::abbr"); + + foreach (XmlElement dataNode in dataNodes) + { + string[] dataValues = dataNode.InnerText.Split(' '); + bool includedExtraData = false; + + foreach (string dataValue in dataValues) + { + if (GENRES.TryGetValue(dataValue, out WordGenre genreAux)) + { + genre = genreAux; + type = WordType.Noun; + } + else if (LANGUAGES.TryGetValue(dataValue, out WordLanguage languageAux)) + language = languageAux; + else if (TYPES.TryGetValue(dataValue, out WordType typeAux)) + type = typeAux; + else if (dataValue == "desus.") + isObsolete = true; + else if (!includedExtraData) + { + extraData.Add(dataNode.GetAttribute("title")); + includedExtraData = true; + } + } + } + + string content = GetDefinitionContent(document); + string[] origins = GetDefinitionOrigins(document); + string[] examples = GetDefinitionExamples(document); + + return new Definition(type, genre, language, isObsolete, content, origins, examples); + } + + private string GetDefinitionContent(XmlElement document) + { + string startElementXpath = ".//*[not(self::abbr)]/preceding-sibling::abbr[1]"; + XmlElement startElement = (XmlElement)document.SelectSingleNode(startElementXpath); + + string nodesXpath = "./following-sibling::node()[not(@class='h')]"; + XmlNodeList nodes = startElement.SelectNodes(nodesXpath); + + string result = BuildString(nodes); + + if (result.Contains('‖')) + result = result.Replace("‖ ", ""); + else if (ALLOW_ABBR_DEFINITION.Contains(startElement.InnerText)) + result = startElement.GetAttribute("title") + ' ' + result; + + return result; + } + + private string[] GetDefinitionOrigins(XmlElement document) + { + List result = new List(); + XmlNodeList nodes = document.SelectNodes("./abbr[@class='c']"); + + foreach (XmlElement node in nodes) + { + string[] origins = node.GetAttribute("title") + .Replace(" y", ",") + .Split(", "); + + result.AddRange(origins); + } + + return result.ToArray(); + } + + private string[] GetDefinitionExamples(XmlElement document) + { + List result = new List(); + XmlNodeList nodes = document.SelectNodes("./*[@class='h']"); + + foreach (XmlElement node in nodes) + { + XmlNodeList exampleNodes = node.SelectNodes("./node()"); + string example = BuildString(exampleNodes); + + result.Add(example); + } + + return result.ToArray(); + } + + private string BuildString(XmlNodeList nodes) + { + StringBuilder stringBuilder = new StringBuilder(); + + for (int i = 0; i < nodes.Count; i++) + { + XmlNode node = nodes[i]; + + if (node is XmlElement element) + { + if (element.HasAttribute("title")) + stringBuilder.Append(element.GetAttribute("title")); + else + stringBuilder.Append(element.InnerText); + + if (i == nodes.Count - 1 && stringBuilder[^1] != '.') + stringBuilder.Append('.'); + else if (i < nodes.Count - 1 && nodes[i + 1] is XmlElement) + stringBuilder.Append(' '); + } + else + { + stringBuilder.Append(node.InnerText); + } + } + + return stringBuilder.ToString().Trim(); + } + } +} diff --git a/RAE/RAEAPI.cs b/RAE/Services/RAEAPI.cs similarity index 58% rename from RAE/RAEAPI.cs rename to RAE/Services/RAEAPI.cs index f6317ce..917259e 100644 --- a/RAE/RAEAPI.cs +++ b/RAE/Services/RAEAPI.cs @@ -1,14 +1,12 @@ using System.Collections.Generic; -using System.Linq; -using System.Net; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; -using System.Xml; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using RAE.Models; -namespace RAE +namespace RAE.Services { /* Based on information obtained from: @@ -16,7 +14,7 @@ namespace RAE https://devhub.io/repos/mgp25-RAE-API https://github.com/mgp25/RAE-API */ - internal class RAEAPI + internal partial class RAEAPI { private const string URLBASE = "https://dle.rae.es/data"; private const string TOKEN = "cDY4MkpnaFMzOmFHZlVkQ2lFNDM0"; @@ -28,18 +26,11 @@ public RAEAPI() _httpClient = new ScraperHttpClient(TOKEN); } - public async Task FetchWordByIdAsync(string wordId) + public async Task FetchWordByIdAsync(string wordId) { - var response = await _httpClient.GetStringAsync($"{URLBASE}/fetch?id={wordId}"); + string response = await _httpClient.GetStringAsync($"{URLBASE}/fetch?id={wordId}"); - MatchCollection matches = Regex.Matches(response, "

.*?

"); - - string[] definitions = matches.Cast() - .Select(match => Regex.Replace(match.Value, "<.*?>", "")) - .Select(HtmlDecode) - .ToArray(); - - return definitions; + return GetWord(response); } public async Task GetKeysAsync(string query) @@ -52,19 +43,14 @@ public async Task GetKeysAsync(string query) return keys; } - public async Task GetRandomWordAsync() + public async Task GetRandomWordAsync() { string response = await _httpClient.GetStringAsync($"{URLBASE}/random"); - XmlDocument xml = new XmlDocument(); - xml.LoadXml($"{response}"); - - string id = xml.DocumentElement.SelectSingleNode("//article").Attributes["id"].Value; - string content = xml.DocumentElement.SelectSingleNode("//header").InnerText; - return new Word(id, content); + return GetWord(response); } - public async Task GetWordOfTheDayAsync() + public async Task GetWordOfTheDayAsync() { string response = await _httpClient.GetStringAsync($"{URLBASE}/wotd?callback="); JObject jobject = JObject.Parse(response); @@ -72,14 +58,14 @@ public async Task GetWordOfTheDayAsync() string id = jobject.Value("id"); string content = jobject.Value("header"); - return new Word(id, HtmlDecode(content)); + return new Entry(id, HtmlDecode(content)); } - public async Task> SearchWordAsync(string word, bool allGroups = true) + public async Task SearchWordAsync(string word, bool allGroups = true) { string response = await _httpClient.GetStringAsync($"{URLBASE}/search?w={word}"); JToken jtoken = JToken.Parse(response); - List words = new List(); + List words = new List(); foreach (JToken jWord in jtoken.SelectToken("res")) { @@ -92,16 +78,11 @@ public async Task> SearchWordAsync(string word, bool allGroups = true Match contentMatch = Regex.Match(content, @"\b[\p{L}\p{M}/-]+"); if (contentMatch.Success) content = contentMatch.Value; - words.Add(new Word(id, content)); + words.Add(new Entry(id, content)); } } - return words; - } - - private string HtmlDecode(string content) - { - return WebUtility.HtmlDecode(content); + return words.ToArray(); } } } diff --git a/RAE/ScraperHttpClient.cs b/RAE/Services/ScraperHttpClient.cs similarity index 94% rename from RAE/ScraperHttpClient.cs rename to RAE/Services/ScraperHttpClient.cs index 7f66356..3a88bea 100644 --- a/RAE/ScraperHttpClient.cs +++ b/RAE/Services/ScraperHttpClient.cs @@ -1,7 +1,7 @@ using System.Net.Http; using System.Net.Http.Headers; -namespace RAE +namespace RAE.Services { internal class ScraperHttpClient : HttpClient { diff --git a/RAE/WordGenre.cs b/RAE/WordGenre.cs new file mode 100644 index 0000000..a589096 --- /dev/null +++ b/RAE/WordGenre.cs @@ -0,0 +1,20 @@ +namespace RAE +{ + public enum WordGenre + { + /// + /// Femenino + /// + Female, + + /// + /// Masculino + /// + Male, + + /// + /// Femenino y Masculino + /// + Both + } +} diff --git a/RAE/WordLanguage.cs b/RAE/WordLanguage.cs new file mode 100644 index 0000000..b929b96 --- /dev/null +++ b/RAE/WordLanguage.cs @@ -0,0 +1,15 @@ +namespace RAE +{ + public enum WordLanguage + { + /// + /// Coloquial + /// + Colloquial, + + /// + /// Rural + /// + Rural + } +} diff --git a/RAE/WordType.cs b/RAE/WordType.cs new file mode 100644 index 0000000..0166adb --- /dev/null +++ b/RAE/WordType.cs @@ -0,0 +1,60 @@ +namespace RAE +{ + public enum WordType + { + /// + /// Adjetivo + /// + Adjective, + + /// + /// Adverbio + /// + Adverb, + + /// + /// Artículo + /// + Article, + + /// + /// Conjunción + /// + Conjunction, + + /// + /// Interjección + /// + Interjection, + + /// + /// Sustantivo + /// + Noun, + + /// + /// Prefijo + /// + Prefix, + + /// + /// Preposición + /// + Preposition, + + /// + /// Pronombre + /// + Pronoun, + + /// + /// Sufijo + /// + Suffix, + + /// + /// Verbo + /// + Verb, + } +}