kdb/kdb.go

254 lines
7.0 KiB
Go

package main
import (
"errors"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"git.openprivacy.ca/sarah/kdb/citing"
"github.com/nickng/bibtex"
"html/template"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strings"
)
type Page struct {
Title string
Body template.HTML
BodyOrig string
Concept template.HTML
References map[string]*bibtex.BibEntry
}
var templates = template.Must(template.ParseFiles("templates/view.html"))
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
err := templates.ExecuteTemplate(w, tmpl+".html", p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func (p *Page) save() error {
filename := path.Join("data", p.Title+".kdb")
return ioutil.WriteFile(filename, []byte(p.Body), 0600)
}
var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9\\-]+)(/?)$")
func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil {
http.NotFound(w, r)
return "", errors.New("Invalid Page Title")
}
return m[2], nil // The title is the second subexpression.
}
func hasPage(word string) bool {
if _, err := os.Stat(path.Join("data/", normalize(word)+".kdb")); err != nil {
return false
}
return true
}
func loadPage(title string) (*Page, error) {
filename := title + ".kdb"
bodyb, err := ioutil.ReadFile(path.Join("data", filename))
if err != nil {
return nil, err
}
body := template.HTMLEscapeString(string(bodyb))
concept := template.HTML(construct_graph(title))
linked := make(map[string]bool)
references := make(map[string]*bibtex.BibEntry)
for _, word := range strings.Fields(body) {
if !linked[normalize(word)] {
if hasPage(word) {
body = strings.ReplaceAll(body, word, fmt.Sprintf("<a href='/view/%v/'>%v</a>", normalize(word), normalize(word)))
}
if strings.HasPrefix(word, "cite;") {
log.Infof("Extracting Citation")
cite, link, img, rec := citing.ExtractPDF(word)
if rec != nil {
if references[rec.CiteName] == nil {
references[rec.CiteName] = rec
}
}
body = strings.ReplaceAll(body, word, fmt.Sprintf("<a title=\"%v\" href=\"javascript:showsource('%v')\">[X]</a>%v", cite, link, img))
}
if strings.HasPrefix(word, "http") {
body = strings.ReplaceAll(body, word, fmt.Sprintf("<a href='%v'>%v</a>", word, word))
}
if strings.HasPrefix(word, "data:image/png;base64,") {
body = strings.ReplaceAll(body, word, fmt.Sprintf("<img width=75%% height=75%% src='%v'/>", word))
}
linked[normalize(word)] = true
}
}
return &Page{Title: title, Body: template.HTML(body), BodyOrig: string(bodyb), Concept: concept, References: references}, nil
}
func normalize(word string) string {
if strings.HasPrefix(word, "typeof:") {
word = word[7:]
} else if strings.HasPrefix(word, "subtype:") {
word = word[8:]
} else if strings.HasPrefix(word, "instance:") {
word = word[9:]
}
word = strings.TrimSpace(word)
return word
}
func viewHandler(w http.ResponseWriter, r *http.Request) {
title, err := getTitle(w, r)
if err != nil {
return
}
p, err := loadPage(title)
if err != nil {
p = &Page{}
p.Title = title
}
renderTemplate(w, "view", p)
}
func saveHandler(w http.ResponseWriter, r *http.Request) {
title, err := getTitle(w, r)
if err != nil {
return
}
body := r.FormValue("body")
p := &Page{Title: title, Body: template.HTML(body)}
err = p.save()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
func get_links(name string) (links []string) {
filename := name + ".kdb"
bodyb, err := ioutil.ReadFile(path.Join("data", filename))
if err != nil {
return []string{}
}
body := template.HTMLEscapeString(string(bodyb))
for _, word := range strings.Fields(body) {
if hasPage(word) {
links = append(links, word)
}
}
return
}
func construct_graph(word string) string {
seen := make(map[string]bool)
graph := "digraph {\n ratio=\"compress\";size=\"8,4!\";splines=true;\"" + word + "\";\n"
graph = construct_graph_sub(graph, word, seen, 1)
graph += "}"
ioutil.WriteFile("tmp.dot", []byte(graph), 0640)
subProcess := exec.Command("dot", "-Tsvg", "-otmp.svg", "-Nfontname=Chilanka", "tmp.dot")
subProcess.Run()
subProcess.Wait()
data, _ := ioutil.ReadFile("tmp.svg")
return "<div class=\"conceptgraph\">" + strings.ReplaceAll(string(data), `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">`, "")
}
func construct_graph_sub(graph string, word string, seen map[string]bool, depth int) string {
if depth < 0 {
return graph
}
links := get_links(normalize(word))
if !seen[normalize(word)] {
col := "fillcolor=\"white\""
if depth == 1 {
col = "fillcolor=\"#b09cbc\""
}
graph += fmt.Sprintf("\"%v\" [href=\"/view/%v\",%v,style=filled];\n", normalize(word), normalize(word), col)
}
seen[normalize(word)] = true
seen[word] = true
graph += "# " + word + "\n"
for _, link := range links {
if strings.HasPrefix(word, "typeof:") || strings.HasPrefix(word, "instance:") || strings.HasPrefix(word, "subtype:") {
word = normalize(word)
}
if !seen[normalize(link)] {
graph += fmt.Sprintf("\"%v\" [href=\"/view/%v\"];\n", normalize(link), normalize(link))
}
if normalize(word) != normalize(link) && (!seen[normalize(word)+"++"+normalize(link)] || !seen[normalize(link)+"++"+normalize(word)]) {
if strings.HasPrefix(link, "typeof:") {
graph += fmt.Sprintf("\"%v\" -> \"%v\" [style=dotted]\n", link[7:], word)
seen[word+"++"+link[7:]] = true
seen[link[7:]+"++"+word] = true
} else if strings.HasPrefix(link, "subtype:") {
graph += fmt.Sprintf("\"%v\" -> \"%v\" [style=dotted]\n", word, link[8:])
seen[word+"++"+link[8:]] = true
seen[link[8:]+"++"+word] = true
} else if strings.HasPrefix(link, "instance:") {
graph += fmt.Sprintf("\"%v\" -> \"%v\" [style=dotted]\n", word, link[9:])
seen[word+"++"+link[9:]] = true
seen[link[9:]+"++"+word] = true
} else {
graph += fmt.Sprintf("\"%v\" -> \"%v\" []\n", word, link)
}
seen[normalize(word)+"++"+normalize(link)] = true
seen[normalize(link)+"++"+normalize(word)] = true
}
//}
if !seen[link] {
graph = construct_graph_sub(graph, link, seen, depth-1)
}
}
return graph
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
root := "./data/"
var files []string
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if path != "./data/" {
files = append(files, path[5:])
}
return nil
})
body := ""
for _, file := range files {
name := strings.ReplaceAll(file, ".kdb", "")
body += fmt.Sprintf("<a href='/view/%v'>%v</a><br/>", name, name)
}
renderTemplate(w, "view", &Page{Title: "Sigil KDB", Body: template.HTML(body), BodyOrig: ""})
}
func main() {
log.SetLevel(log.LevelInfo)
http.HandleFunc("/", indexHandler)
http.HandleFunc("/view/", viewHandler)
http.HandleFunc("/save/", saveHandler)
log.Errorln(http.ListenAndServe(":8080", nil))
}