package main import ( "encoding/json" "fmt" "log" "net/http" "os" "strings" "foosoft.net/projects/jmdict" "git.elnu.com/ElnuDev/jichanorg/httputils" "github.com/gorilla/mux" ) var dict jmdict.Jmdict func LoadDict() error { const jmdictFile = "JMdict.xml" reader, err := os.Open(jmdictFile) if err != nil { return err } dict, _, err = jmdict.LoadJmdict(reader) if err != nil { return err } return nil } type Entry struct { Kanji string Reading string Definitions []Definition } type Definition struct { Definition string PartOfSpeech []string } func ParseEntry(entry jmdict.JmdictEntry) Entry { kanji := "" if len(entry.Kanji) > 0 { kanji = entry.Kanji[0].Expression } reading := "" if len(entry.Readings) > 0 { reading = entry.Readings[0].Reading } var definitions []Definition definitions = make([]Definition, len(entry.Sense)) for i, sense := range entry.Sense { definition := sense.Glossary[0].Content if len(sense.Glossary) > 1 { for _, glossary := range sense.Glossary[1:] { definition += "; " + glossary.Content } } definitions[i] = Definition{ Definition: definition, PartOfSpeech: sense.PartsOfSpeech, } } return Entry{ Kanji: kanji, Reading: reading, Definitions: definitions, } } func Search(query string) (exactResults []Entry, otherResults []Entry, truncated bool) { query = strings.TrimSpace(query) exactResults = make([]Entry, 0) otherResults = make([]Entry, 0) count := 0 for _, jmdictEntry := range dict.Entries { exactMatch := false for _, kanji := range jmdictEntry.Kanji { if kanji.Expression == query { exactMatch = true goto match } if strings.Contains(kanji.Expression, query) { goto match } } // TODO: Skip if query contains kanji for _, reading := range jmdictEntry.Readings { if strings.Contains(reading.Reading, query) { goto match } } continue match: entry := ParseEntry(jmdictEntry) if exactMatch { exactResults = append(exactResults, entry) } else { otherResults = append(otherResults, entry) } count++ if count >= 500 { truncated = true break } } return } type searchTemplateData struct { ExactResults []Entry OtherResults []Entry Truncated bool Count int } func initSearchTemplateData(exactResults []Entry, otherResults []Entry, truncated bool) searchTemplateData { return searchTemplateData{ ExactResults: exactResults, OtherResults: otherResults, Truncated: truncated, Count: len(exactResults) + len(otherResults), } } func main() { err := LoadDict() if err != nil { fmt.Println(err) return } fmt.Println("JMdict loaded!") r := mux.NewRouter() r.HandleFunc("/", httputils.GenerateHandler( "index.html", func(w http.ResponseWriter, r *http.Request) bool { return true }, func(w http.ResponseWriter, r *http.Request) any { return nil }, []string{http.MethodGet}, )) redirectToHome := func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusPermanentRedirect) } r.HandleFunc("/search", redirectToHome) r.HandleFunc("/search/", redirectToHome) r.HandleFunc("/search/{query}", httputils.GenerateHandler( "index.html", func(w http.ResponseWriter, r *http.Request) bool { return true }, func(w http.ResponseWriter, r *http.Request) any { query := mux.Vars(r)["query"] return struct { Query string Results searchTemplateData }{ Query: query, Results: initSearchTemplateData(Search(query)), } }, []string{http.MethodGet}, )) r.HandleFunc("/api/search", httputils.GenerateHandler( "search.html", func(w http.ResponseWriter, r *http.Request) bool { if r.Header.Get("Accept") != "application/json" { return true } w.Header().Set("Content-Type", "application/json; charset=utf-8") r.ParseMultipartForm(0) query := r.FormValue("q") exactResults, otherResults, _ := Search(query) jsonBytes, _ := json.Marshal(append(exactResults, otherResults...)) fmt.Fprint(w, string(jsonBytes)) return false }, func(w http.ResponseWriter, r *http.Request) any { r.ParseMultipartForm(0) query := r.FormValue("q") return initSearchTemplateData(Search(query)) }, []string{http.MethodGet}, )) r.Handle("/", http.FileServer(http.Dir("static"))) log.Fatal(http.ListenAndServe(":3334", r)) }