/// <summary> /// Преобразование кирилицы в латиницу и обратно /// </summary> /// <param name="msg">текст для преобразования</param> /// <param name="obratno">метод перевода по умолчанию RU > EN</param> /// <returns></returns> public static string Translit(this string msg, bool obratno = false) { lock (_lock) { if (obratno) { return(Transliteration.Front(msg)); } else { return(Transliteration.Back(msg)); } } }
public void TestTransliterateFromEnglishToRussian() { string engLetters = "abcdefghijklmnopqrstuvwxyz"; // ISO Assert.Equal("абцдефгийклмнопрстувхыз", Transliteration.Back(engLetters)); Assert.Equal("абвгдеёжзиклмнопрстуфхцчшщъыеюя", Transliteration.Back("abvgdeyozhziklmnoprstufxcchshshh'yeyuya")); Assert.Equal(Transliteration.Back(engLetters), Transliteration.Back(engLetters.ToUpper()).ToLower()); // GOST Assert.Equal("абцдефгиклмнопрстувыз", Transliteration.Back(engLetters, Transliteration.TransliterationType.Gost)); Assert.Equal("абвгдеёжзиклмнопрстуфхцчшщЪыеыуыа", Transliteration.Back("abvgdejozhziklmnoprstufkhcchshshh'yehyuya", Transliteration.TransliterationType.Gost)); Assert.Equal( Transliteration.Back(engLetters, Transliteration.TransliterationType.Gost).ToLower(), Transliteration.Back(engLetters.ToUpper(), Transliteration.TransliterationType.Gost).ToLower()); }
public XDocument Search(string searchPattern, string searchType = "", bool fb2Only = false, int pageNumber = 0, int threshold = 50) { if (!string.IsNullOrEmpty(searchPattern)) { searchPattern = Uri.UnescapeDataString(searchPattern).Replace('+', ' ').ToLower(); } XDocument doc = new XDocument( // Add root element and namespaces new XElement("feed", new XAttribute(XNamespace.Xmlns + "dc", Namespaces.dc), new XAttribute(XNamespace.Xmlns + "os", Namespaces.os), new XAttribute(XNamespace.Xmlns + "opds", Namespaces.opds), new XElement("id", "tag:search:" + searchPattern), new XElement("title", Localizer.Text("Search results")), new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("icon", "/series.ico"), // Add links Links.opensearch, Links.search, Links.start, Links.self) ); List <string> authors = new List <string>(); List <Book> titles = new List <Book>(); if (string.IsNullOrEmpty(searchType)) { string transSearchPattern = Transliteration.Back(searchPattern, TransliterationType.GOST); authors = LibraryFactory.GetLibrary().GetAuthorsByName(searchPattern, true); if (authors.Count == 0 && !string.IsNullOrEmpty(transSearchPattern)) { authors = LibraryFactory.GetLibrary().GetAuthorsByName(transSearchPattern, true); } titles = LibraryFactory.GetLibrary().GetBooksByTitle(searchPattern); if (titles.Count == 0 && !string.IsNullOrEmpty(transSearchPattern)) { titles = LibraryFactory.GetLibrary().GetBooksByTitle(transSearchPattern); } } if (string.IsNullOrEmpty(searchType) && authors.Count > 0 && titles.Count > 0) { // Add two navigation entries: search by authors name and book title doc.Root.Add( new XElement("entry", new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("id", "tag:search:author"), new XElement("title", Localizer.Text("Search authors")), new XElement("content", Localizer.Text("Search authors by name"), new XAttribute("type", "text")), new XElement("link", new XAttribute("href", "/search?searchType=authors&searchTerm=" + Uri.EscapeDataString(searchPattern)), new XAttribute("type", "application/atom+xml;profile=opds-catalog"))), new XElement("entry", new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("id", "tag:search:title"), new XElement("title", Localizer.Text("Search books")), new XElement("content", Localizer.Text("Search books by title"), new XAttribute("type", "text")), new XElement("link", new XAttribute("href", "/search?searchType=books&searchTerm=" + Uri.EscapeDataString(searchPattern)), new XAttribute("type", "application/atom+xml;profile=opds-catalog"))) ); } else if (searchType.Equals("authors") || (authors.Count > 0 && titles.Count == 0)) { return(new AuthorsCatalog().GetCatalog(searchPattern, true)); } else if (searchType.Equals("books") || (titles.Count > 0 && authors.Count == 0)) { if (pageNumber > 0) { searchPattern += "/" + pageNumber; } return(new BooksCatalog().GetCatalogByTitle(searchPattern, fb2Only, 0, 1000)); } return(doc); }
/// <summary> /// /// </summary> /// <param name="searchPattern"></param> /// <param name="threshold"></param> /// <returns></returns> public XDocument GetCatalog(string searchPattern, bool isOpenSearch = false, int threshold = 100) { if (!string.IsNullOrEmpty(searchPattern)) { searchPattern = Uri.UnescapeDataString(searchPattern).Replace('+', ' ').ToLower(); } XDocument doc = new XDocument( // Add root element and namespaces new XElement("feed", new XAttribute(XNamespace.Xmlns + "dc", Namespaces.dc), new XAttribute(XNamespace.Xmlns + "os", Namespaces.os), new XAttribute(XNamespace.Xmlns + "opds", Namespaces.opds), new XElement("id", "tag:authors"), new XElement("title", Localizer.Text("Books by authors")), new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("icon", "/authors.ico"), // Add links Links.opensearch, Links.search, Links.start) ); // Get all authors names starting with searchPattern List <string> Authors = Library.GetAuthorsByName(searchPattern, isOpenSearch); // For search, also check transliterated names if (isOpenSearch) { // Try transliteration string translit = Transliteration.Back(searchPattern, TransliterationType.GOST); if (!string.IsNullOrEmpty(translit)) { List <string> transAuthors = Library.GetAuthorsByName(translit, isOpenSearch); if (transAuthors.Count > 0) { Authors.AddRange(transAuthors); } } } // if there are more authors then threshold, try to collapse them into groups // and render these groups first and authors after them if (Authors.Count > threshold) { Dictionary <string, int> catalogGroups = null; do { catalogGroups = (from a in Authors group a by(a.Length > searchPattern.Length ? a.Substring(0, searchPattern.Length + 1).Capitalize(true) : a.Capitalize(true)) into g where g.Count() > 1 select new { Name = g, Count = g.Count() }).ToDictionary(x => x.Name.Key, y => y.Count); if (catalogGroups.Count == 1) { searchPattern = catalogGroups.First().Key; } else { break; } } while (true); // remove entry that exactly matches search pattern to avoid recursion catalogGroups.Remove(searchPattern.Capitalize(true)); // remove entries that are groupped ( if any ) foreach (var kv in catalogGroups) { Authors.RemoveAll(a => a.StartsWith(kv.Key, StringComparison.InvariantCultureIgnoreCase)); } // Add catalog groups foreach (KeyValuePair <string, int> cg in catalogGroups) { doc.Root.Add( new XElement("entry", new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("id", "tag:authors:" + cg.Key), new XElement("title", cg.Key), new XElement("content", string.Format(Localizer.Text("Total authors on {0}: {1}"), cg.Key, cg.Value), new XAttribute("type", "text")), new XElement("link", new XAttribute("href", "/authorsindex/" + Uri.EscapeDataString(cg.Key)), new XAttribute("type", "application/atom+xml;profile=opds-catalog")) ) ); } } // Add catalog entries foreach (string author in Authors) { var booksCount = Library.GetBooksByAuthorCount(author); doc.Root.Add( new XElement("entry", new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("id", "tag:authors:" + author), new XElement("title", author), new XElement("content", string.Format(Localizer.Text("Books: {0}"), booksCount), new XAttribute("type", "text")), new XElement("link", new XAttribute("href", "/author/" + Uri.EscapeDataString(author)), new XAttribute("type", "application/atom+xml;profile=opds-catalog")) ) ); } return(doc); }
/// <summary> /// Returns books catalog for specific search /// </summary> /// <param name="searchPattern">Keyword to search</param> /// <param name="searchFor">Type of search</param> /// <param name="acceptFB2">Client can accept fb2 files</param> /// <param name="threshold">Items per page</param> /// <returns></returns> private XDocument GetCatalog(string searchPattern, SearchFor searchFor, bool acceptFB2, int threshold = 100) { if (!string.IsNullOrEmpty(searchPattern)) { searchPattern = Uri.UnescapeDataString(searchPattern).Replace('+', ' '); } XDocument doc = new XDocument( // Add root element and namespaces new XElement("feed", new XAttribute(XNamespace.Xmlns + "dc", Namespaces.dc), new XAttribute(XNamespace.Xmlns + "os", Namespaces.os), new XAttribute(XNamespace.Xmlns + "opds", Namespaces.opds), new XElement("id", "tag:books"), new XElement("title", Localizer.Text("Books by author ") + searchPattern), new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("icon", "/icons/books.ico"), // Add links Links.opensearch, Links.search, Links.start) ); int pageNumber = 0; // Extract and remove page number from the search pattern int j = searchPattern.IndexOf('/'); if (j > 0) { int.TryParse(searchPattern.Substring(j + 1), out pageNumber); searchPattern = searchPattern.Substring(0, j); } // Get author's books string catalogType = string.Empty; List <Book> books = new List <Book>(); switch (searchFor) { case SearchFor.Author: books = Library.GetBooksByAuthor(searchPattern); catalogType = "/author/" + Uri.EscapeDataString(searchPattern); break; case SearchFor.Sequence: books = Library.GetBooksBySequence(searchPattern); catalogType = "/sequence/" + Uri.EscapeDataString(searchPattern); break; case SearchFor.Genre: books = Library.GetBooksByGenre(searchPattern); catalogType = "/genre/" + Uri.EscapeDataString(searchPattern); break; case SearchFor.Title: books = Library.GetBooksByTitle(searchPattern); // For search, also return books by if (threshold > 50) { string translit = Transliteration.Back(searchPattern, TransliterationType.GOST); if (!string.IsNullOrEmpty(translit)) { List <Book> transTitles = Library.GetBooksByTitle(translit); if (transTitles.Count > 0) { books.AddRange(transTitles); } } } break; } // For sequences, sort books by sequence number if (searchFor == SearchFor.Sequence) { books = books.OrderBy(b => b.NumberInSequence).ToList(); } // else sort by title else { books = books.OrderBy(b => b.Title, new OPDSComparer(TinyOPDS.Properties.Settings.Default.SortOrder > 0)).ToList(); } int startIndex = pageNumber * threshold; int endIndex = startIndex + ((books.Count / threshold == 0) ? books.Count : Math.Min(threshold, books.Count - startIndex)); if (searchFor == SearchFor.Title) { if ((pageNumber + 1) * threshold < books.Count) { catalogType = string.Format("/search?searchType=books&searchTerm={0}&pageNumber={1}", Uri.EscapeDataString(searchPattern), pageNumber + 1); doc.Root.Add(new XElement("link", new XAttribute("href", catalogType), new XAttribute("rel", "next"), new XAttribute("type", "application/atom+xml;profile=opds-catalog"))); } } else if ((pageNumber + 1) * threshold < books.Count) { catalogType += "/" + (pageNumber + 1); doc.Root.Add(new XElement("link", new XAttribute("href", catalogType), new XAttribute("rel", "next"), new XAttribute("type", "application/atom+xml;profile=opds-catalog"))); } bool useCyrillic = TinyOPDS.Properties.Settings.Default.SortOrder > 0; List <Genre> genres = Library.Genres; // Add catalog entries for (int i = startIndex; i < endIndex; i++) { Book book = books.ElementAt(i); XElement entry = new XElement("entry", new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("id", "tag:book:" + book.ID), new XElement("title", book.Title) ); foreach (string author in book.Authors) { entry.Add( new XElement("author", new XElement("name", author), new XElement("uri", "/author/" + Uri.EscapeDataString(author) ))); } foreach (string genreStr in book.Genres) { Genre genre = genres.Where(g => g.Tag.Equals(genreStr)).FirstOrDefault(); if (genre != null) { entry.Add(new XElement("category", new XAttribute("term", (useCyrillic ? genre.Translation : genre.Name)), new XAttribute("label", (useCyrillic ? genre.Translation : genre.Name)))); } } // Build a content entry (translator(s), year, size, annotation etc.) string bookInfo = string.Empty; if (!string.IsNullOrEmpty(book.Annotation)) { bookInfo += string.Format(@"<p>{0}<br/></p>", System.Security.SecurityElement.Escape(book.Annotation.Trim())); } if (book.Translators.Count > 0) { bookInfo += string.Format("<b>{0} </b>", Localizer.Text("Translation:")); foreach (string translator in book.Translators) { bookInfo += translator + " "; } bookInfo += "<br/>"; } if (book.BookDate != DateTime.MinValue) { bookInfo += string.Format("<b>{0}</b> {1}<br/>", Localizer.Text("Year of publication:"), book.BookDate.Year); } if (!string.IsNullOrEmpty(book.Sequence)) { bookInfo += string.Format("<b>{0} {1} #{2}</b><br/>", Localizer.Text("Series:"), book.Sequence, book.NumberInSequence); } entry.Add( new XElement(Namespaces.dc + "language", book.Language), new XElement(Namespaces.dc + "format", book.BookType == BookType.FB2 ? "fb2+zip" : "epub+zip"), new XElement("content", new XAttribute("type", "text/html"), XElement.Parse("<div>" + bookInfo + "<br/></div>")), new XElement("format", book.BookType == BookType.EPUB ? "epub" : "fb2"), new XElement("size", string.Format("{0} Kb", (int)book.DocumentSize / 1024))); if (book.HasCover) { entry.Add( // Adding cover page and thumbnail links new XElement("link", new XAttribute("href", "/cover/" + book.ID + ".jpeg"), new XAttribute("rel", "http://opds-spec.org/image"), new XAttribute("type", "image/jpeg")), new XElement("link", new XAttribute("href", "/cover/" + book.ID + ".jpeg"), new XAttribute("rel", "x-stanza-cover-image"), new XAttribute("type", "image/jpeg")), new XElement("link", new XAttribute("href", "/thumbnail/" + book.ID + ".jpeg"), new XAttribute("rel", "http://opds-spec.org/thumbnail"), new XAttribute("type", "image/jpeg")), new XElement("link", new XAttribute("href", "/thumbnail/" + book.ID + ".jpeg"), new XAttribute("rel", "x-stanza-cover-image-thumbnail"), new XAttribute("type", "image/jpeg")) // Adding download links ); } string fileName = Uri.EscapeDataString(Transliteration.Front(string.Format("{0}_{1}", book.Authors.First(), book.Title)).SanitizeFileName()); string url = "/" + string.Format("{0}/{1}", book.ID, fileName); if (book.BookType == BookType.EPUB || (book.BookType == BookType.FB2 && !acceptFB2 && !string.IsNullOrEmpty(TinyOPDS.Properties.Settings.Default.ConvertorPath))) { entry.Add(new XElement("link", new XAttribute("href", url + ".epub"), new XAttribute("rel", "http://opds-spec.org/acquisition/open-access"), new XAttribute("type", "application/epub+zip"))); } if (book.BookType == BookType.FB2) { entry.Add(new XElement("link", new XAttribute("href", url + ".fb2.zip"), new XAttribute("rel", "http://opds-spec.org/acquisition/open-access"), new XAttribute("type", "application/fb2+zip"))); } // For search requests, lets add navigation links for author and series (if any) if (searchFor != SearchFor.Author) { foreach (string author in book.Authors) { entry.Add(new XElement("link", new XAttribute("href", "/author/" + Uri.EscapeDataString(author)), new XAttribute("rel", "related"), new XAttribute("type", "application/atom+xml;profile=opds-catalog"), new XAttribute("title", string.Format(Localizer.Text("All books by author {0}"), author)))); } } if (searchFor != SearchFor.Sequence && !string.IsNullOrEmpty(book.Sequence)) { entry.Add(new XElement("link", new XAttribute("href", "/sequence/" + Uri.EscapeDataString(book.Sequence)), new XAttribute("rel", "related"), new XAttribute("type", "application/atom+xml;profile=opds-catalog"), new XAttribute("title", string.Format(Localizer.Text("All books by series {0}"), book.Sequence)))); } doc.Root.Add(entry); } return(doc); }
/// <summary> /// /// </summary> /// <param name="searchPattern"></param> /// <param name="threshold"></param> /// <returns></returns> public XDocument GetCatalog(string searchPattern, bool isOpenSearch = false, int threshold = 50) { if (!string.IsNullOrEmpty(searchPattern)) { searchPattern = Uri.UnescapeDataString(searchPattern).Replace('+', ' ').ToLower(); } XDocument doc = new XDocument( // Add root element and namespaces new XElement("feed", new XAttribute(XNamespace.Xmlns + "dc", Namespaces.dc), new XAttribute(XNamespace.Xmlns + "os", Namespaces.os), new XAttribute(XNamespace.Xmlns + "opds", Namespaces.opds), new XElement("title", Localizer.Text("Books by authors")), new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("icon", "/authors.ico"), // Add links Links.opensearch, Links.search, Links.start) ); // Get all authors names starting with searchPattern List <string> Authors = LibraryFactory.GetLibrary().GetAuthorsByName(searchPattern, isOpenSearch); // For search, also check transliterated names if (isOpenSearch) { // Try transliteration string translit = Transliteration.Back(searchPattern, TransliterationType.GOST); if (!string.IsNullOrEmpty(translit)) { List <string> transAuthors = LibraryFactory.GetLibrary().GetAuthorsByName(translit, isOpenSearch); if (transAuthors.Count > 0) { Authors.AddRange(transAuthors); } } } if (Authors.Count > threshold) { Dictionary <string, int> authors = null; do { authors = (from a in Authors group a by(a.Length > searchPattern.Length ? a.Substring(0, searchPattern.Length + 1) : a) into g where g.Count() > 1 select new { Name = g, Count = g.Count() }).ToDictionary(x => x.Name.Key, y => y.Count); if (authors.Count == 1) { searchPattern = authors.First().Key; } } while (authors.Count <= 1); // Add catalog entries foreach (KeyValuePair <string, int> author in authors) { doc.Root.Add( new XElement("entry", new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("id", "tag:authors:" + author.Key), new XElement("title", author.Key), new XElement("content", string.Format(Localizer.Text("Total authors on {0}: {1}"), author.Key, author.Value), new XAttribute("type", "text")), new XElement("link", new XAttribute("href", "/authorsindex/" + Uri.EscapeDataString(author.Key)), new XAttribute("type", "application/atom+xml;profile=opds-catalog")) ) ); } } // else { // Add catalog entries foreach (string author in Authors) { var booksCount = LibraryFactory.GetLibrary().GetBooksByAuthorCount(author); doc.Root.Add( new XElement("entry", new XElement("updated", DateTime.UtcNow.ToUniversalTime()), new XElement("id", "tag:authors:" + author), new XElement("title", author), new XElement("content", string.Format(Localizer.Text("Books: {0}"), booksCount), new XAttribute("type", "text")), new XElement("link", new XAttribute("href", "/author/" + Uri.EscapeDataString(author)), new XAttribute("type", "application/atom+xml;profile=opds-catalog")) ) ); } } return(doc); }