private static int TestSimple() { int nerror = 0; var searchstr = "apple"; var result = SearchParser.Parse(searchstr); if (result == null) { NoteError($"Parse({searchstr}) should not return a null"); nerror++; } else if (result is SearchAtom atom) { if (atom.SearchArea != "") { NoteError($"Parse({searchstr}) SearchArea was {atom.SearchArea} but it should have been blank"); nerror++; } if (atom.SearchFor != "apple") { NoteError($"Parse({searchstr}) SearchFor was {atom.SearchFor} but it should have been apple"); nerror++; } } else { NoteError($"Parse({searchstr}) should have return an atom"); nerror++; } return(nerror); }
// abc ==> public static ISearch Parse(string input) { var parser = new SearchParser(); var(result, nextIndex) = parser.Parse(input, 0); return(result); }
private static int TestConvert(string input, string expected) { int nerror = 0; var result = SearchParser.Parse(input); var actual = result.ToString(); if (actual != expected) { nerror++; NoteError($"ERROR: Parse({input}) expected {expected} but got {actual}"); } return(nerror); }
/// <summary> /// /// </summary> /// <param name="bookdb"></param> /// <param name="searchScope">One of Catalog, Pick, Reading, Downloaded, Finished, CopiedToEBookReader</param> /// <param name="search">search query like title:voyage sea</param> /// <param name="language">en or *</param> /// <param name="sortBy">One of author, title, date_download_asc, date_download_desc</param> /// <param name="andMore"></param> /// <returns></returns> static public List <BookData> SearchInternal(BookDataContext bookdb, string searchScope, string search, string language, string sortBy, out bool andMore) { // Query list is for the part of the query that can be done in the database. // This includes picking what data is returned, simple selection based on whether // a book is downloaded or not, and sorting. IQueryable <BookData> queryList = bookdb.Books; // Enumerable list is after getting a superset of books from the database. It will // reduce the number of books based on the actual search query. IEnumerable <BookData> enumerableList = null; // Final list of books to return. var resultList = new List <BookData>(); //languages: cases are "*", "en" or anything else. // * get all language; "en" get books in english OR with no language, anything else requires that language. LinqIncludes includes = LinqIncludes.None; ISearch searchOperations = string.IsNullOrWhiteSpace(search) ? null : SearchParser.Parse(search); HashSet <string> idlist = searchOperations != null?CommonQueries.BookSearchs(searchOperations) : null; const string DefaultLanguage = "en"; if (language == "*") { ; // nothing special to restrict the languages } else if (language == DefaultLanguage) // most books that aren't marked are english. { includes |= LinqIncludes.LanguageExactOrNull; } else { includes |= LinqIncludes.LanguageExact; } switch (searchScope) { default: App.Error($"Unknown search scope {searchScope}"); includes |= LinqIncludes.UserData | LinqIncludes.People; includes &= ~LinqIncludes.LanguagesFlags; break; case "Catalog": includes |= LinqIncludes.UserData | LinqIncludes.People; includes &= ~LinqIncludes.LanguagesFlags; break; case "PickToDownload": // pick book to download. Have to be extra good with the query so it's fast. includes |= LinqIncludes.DownloadData; if (!string.IsNullOrEmpty(search)) { includes |= LinqIncludes.People; } break; case "Reading": case "Downloaded": case "Finished": case "CopiedToEBookReader": includes |= LinqIncludes.NavigationData | LinqIncludes.UserData | LinqIncludes.People; // includes download break; } if (idlist != null) { queryList = queryList.Where(b => idlist.Contains(b.BookId)); } if (includes.HasFlag(LinqIncludes.People)) { queryList = queryList.Include(b => b.People); } if (includes.HasFlag(LinqIncludes.Notes)) { queryList = queryList.Include(b => b.Notes).Include(b => b.Notes.Notes); } if (includes.HasFlag(LinqIncludes.Review)) { queryList = queryList.Include(b => b.Review); } if (includes.HasFlag(LinqIncludes.DownloadData)) { queryList = queryList.Include(b => b.DownloadData); } if (includes.HasFlag(LinqIncludes.NavigationData)) { queryList = queryList.Include(b => b.NavigationData); } if (includes.HasFlag(LinqIncludes.LanguageExact)) { queryList = queryList.Where(b => b.Language == language); } if (includes.HasFlag(LinqIncludes.LanguageExactOrNull)) { queryList = queryList.Where(b => string.IsNullOrEmpty(b.Language) || b.Language == language); } // // The include list (queryable) is set up correctly. // Now do the match list. // IQueryable <BookData> matchList; switch (searchScope) { default: case "Catalog": matchList = queryList; break; case "PickToDownload": matchList = queryList .Where(b => b.DownloadData == null || b.DownloadData.CurrFileStatus != DownloadData.FileStatus.Downloaded) ; break; case "Downloaded": matchList = queryList .Where(b => b.DownloadData != null && b.DownloadData.CurrFileStatus == DownloadData.FileStatus.Downloaded) .Where(b => b.NavigationData == null || (b.NavigationData.NSwipeLeft < 1 && b.NavigationData.CurrStatus == BookNavigationData.UserStatus.NoStatus )) ; break; case "Reading": matchList = queryList .Where(b => b.DownloadData != null && b.DownloadData.CurrFileStatus == DownloadData.FileStatus.Downloaded) .Where(b => b.NavigationData != null) .Where(b => b.NavigationData.NSwipeLeft < 1) .Where(b => b.NavigationData.CurrStatus == BookNavigationData.UserStatus.Reading) ; break; case "Finished": matchList = queryList .Where(b => b.NavigationData != null) .Where(b => b.NavigationData.NSwipeLeft < 1) .Where(b => b.NavigationData.CurrStatus == BookNavigationData.UserStatus.Abandoned || b.NavigationData.CurrStatus == BookNavigationData.UserStatus.Done) ; // Matchlist used to also insist that the book be downloaded. But in reality, I might // finish a book on computer "A" and then want to see that it's finished on computer "B" //.Where(b => b.DownloadData != null) break; case "CopiedToEBookReader": matchList = queryList .Where(b => b.NavigationData != null) .Where(b => b.NavigationData.NSwipeLeft < 1) .Where(b => b.NavigationData.CurrStatus == BookNavigationData.UserStatus.CopiedToEBookReader) ; break; } // Add in sorting switch (sortBy) { default: case "title": matchList = matchList.OrderBy(b => b.Title); break; case "author": matchList = matchList.OrderBy(b => b.DenormPrimaryAuthor); break; case "date_download_asc": matchList = matchList.OrderBy(b => b.DenormDownloadDate); break; case "date_download_desc": matchList = matchList.OrderByDescending(b => b.DenormDownloadDate); break; } // Step three: filter based on search. Blank searches are special. if (searchOperations == null) { enumerableList = matchList .Take(MaxMatch + 1) .AsEnumerable(); } else { var newlist = new List <BookData>(); enumerableList = newlist; foreach (var book in matchList) { if (searchOperations.Matches(book)) { newlist.Add(book); if (newlist.Count > MaxMatch) // gotta end early :-( { break; } } } } // This seems like a lot of code just to distinguish between // "the search returned 150 books" and "the search returned // 150 books and there are more if needed." andMore = false; var nmatch = 0; nmatch = 0; foreach (var book in enumerableList) { nmatch++; if (nmatch < MaxMatch) { resultList.Add(book); } else if (nmatch > MaxMatch) { andMore = true; break; } } return(resultList); }