Want to contribute? Fork me on Codeberg.org!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

191 lines
4.2 KiB

2 years ago
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
2 years ago
"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
2 years ago
}
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
}
2 years ago
}
definitions[i] = Definition{
Definition: definition,
PartOfSpeech: sense.PartsOfSpeech,
}
2 years ago
}
return Entry{
Kanji: kanji,
Reading: reading,
Definitions: definitions,
}
}
func Search(query string) queryResult {
query = strings.TrimSpace(query)
exactResults := make([]Entry, 0)
otherResults := make([]Entry, 0)
truncated := false
count := 0
2 years ago
for _, jmdictEntry := range dict.Entries {
exactMatch := false
2 years ago
for _, kanji := range jmdictEntry.Kanji {
if kanji.Expression == query {
exactMatch = true
goto match
}
if strings.Contains(kanji.Expression, query) {
2 years ago
goto match
}
}
// TODO: Skip if query contains kanji
2 years ago
for _, reading := range jmdictEntry.Readings {
if strings.Contains(reading.Reading, query) {
2 years ago
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
}
2 years ago
}
return queryResult{
Query: query,
ExactResults: exactResults,
OtherResults: otherResults,
Truncated: truncated,
Count: len(exactResults) + len(otherResults),
}
2 years ago
}
type queryResult struct {
// Fields must be capitalized
// to be accessible in templates
Query string
ExactResults []Entry
OtherResults []Entry
Truncated bool
Count int
}
2 years ago
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},
))
rawSearchHandler := func(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(0)
q := r.FormValue("q")
var redirect string
if q == "" {
redirect = "/"
} else {
redirect = "/search/" + q
}
http.Redirect(w, r, redirect, http.StatusMovedPermanently)
}
r.HandleFunc("/search", rawSearchHandler)
r.HandleFunc("/search/", rawSearchHandler)
r.HandleFunc("/search/{query}", httputils.GenerateHandler(
// template file
func(w http.ResponseWriter, r *http.Request) string {
if r.Header.Get("HX-Request") == "" {
return "index.html"
}
return "search.html"
},
// handler whether or not to use template
2 years ago
func(w http.ResponseWriter, r *http.Request) bool {
// If Accept: applicaiton/json we'll use the template
2 years ago
if r.Header.Get("Accept") != "application/json" {
return true
}
// Otherwise, let's send JSON
query := mux.Vars(r)["query"]
result := Search(query)
jsonBytes, _ := json.Marshal(append(result.ExactResults, result.OtherResults...))
2 years ago
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprint(w, string(jsonBytes))
2 years ago
return false
},
// template data
2 years ago
func(w http.ResponseWriter, r *http.Request) any {
// Only runs if handler returns true
query := mux.Vars(r)["query"]
return Search(query)
2 years ago
},
[]string{http.MethodGet},
2 years ago
))
r.Handle("/", http.FileServer(http.Dir("static")))
log.Fatal(http.ListenAndServe(":3334", r))
}