webform

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 42e5b74e32eb7bafdcc47e058a7665e41a9ac7c6
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Sat, 20 Jul 2024 10:16:27 +0900

first

Diffstat:
Ago.mod | 3+++
Amain.go | 269+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 272 insertions(+), 0 deletions(-)

diff --git a/go.mod b/go.mod @@ -0,0 +1,3 @@ +module webform + +go 1.22.5 diff --git a/main.go b/main.go @@ -0,0 +1,269 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strconv" +) + +// An Entry is either a Part or a Question. +type Entry interface { + isPart() bool + fillAns(map[string][]string) error + HTML() string + copy() Entry +} + +type Part struct { + Q string + Ent []Entry +} + +func (*Part) isPart() bool { return true } +func (p *Part) HTML() string { + html := fmt.Sprintf("<fieldset>\n<legend>%s</legend>\n", p.Q) + for _, e := range p.Ent { + html += e.HTML() + } + return html + "</fieldset>\n" +} +func (p *Part) fillAns(ans map[string][]string) error { + for _, e := range p.Ent { + if err := e.fillAns(ans); err != nil { + return err + } + } + return nil +} +func (p *Part) copy() Entry { + pp := new(Part) + pp.Q = p.Q + if p.Ent != nil { + pp.Ent = make([]Entry, len(p.Ent)) + for i, e := range p.Ent { + pp.Ent[i] = e.copy() + } + } + return pp +} + +type Question struct { + Q string + Qid string + T string // answer type: "text", "radio", "check" + TextAns string + RadioList []string + RadioAns string + CheckList []string + CheckAns []string + OtherAns string +} + +func (*Question) isPart() bool { return false } +func (q *Question) HTML() string { + switch q.T { + case "check": + html := fmt.Sprintf("<fieldset>\n<legend>%s</legend>\n", q.Q) + for n, a := range q.CheckList { + id := q.Qid + strconv.Itoa(n) + html += fmt.Sprintf("<input type=\"checkbox\" name=\"%s\", id=\"%s\" value=\"%d\">\n"+ + "<label for=\"%s\">%s</label>\n", q.Qid, id, n, id, a) + } + html += "</fieldset>\n" + return html + case "radio": + html := fmt.Sprintf("<fieldset>\n<legend>%s</legend>\n", q.Q) + for n, a := range q.RadioList { + id := q.Qid + strconv.Itoa(n) + html += fmt.Sprintf("<input type=\"radio\" name=\"%s\", id=\"%s\" value=\"%d\">\n"+ + "<label for=\"%s\">%s</label>\n", q.Qid, id, n, id, a) + } + html += "</fieldset>\n" + return html + case "text": + return fmt.Sprintf("<fieldset>\n<legend>%s</legend>\n"+ + "<textarea name=\"%s\", id=\"%s\"></textarea></fieldset>", + q.Q, q.Qid, q.Qid) + default: + panic(fmt.Sprintf("unknown question type: %s\n", q.T)) + } +} + +func (q *Question) fillAns(ans map[string][]string) error { + a, ok := ans[q.Qid] + if !ok { + return fmt.Errorf("answer for %s not found.", q.Qid) + } + switch q.T { + case "check": + for _, i := range a { + n, err := strconv.Atoi(i) + if err != nil { + return fmt.Errorf("invalid answer %q for %s.", + i, q.Qid) + } + if n < 0 || len(q.CheckList) <= n { + return fmt.Errorf("invalid answer %q for %s.", + n, q.Qid) + } + q.CheckAns = append(q.CheckAns, q.CheckList[n]) + } + return nil + case "radio": + if len(a) != 1 { + return fmt.Errorf("length of radio answer for %s is %d.\n", + q.Qid, len(a)) + } + n, err := strconv.Atoi(a[0]) + if err != nil { + return fmt.Errorf("invalid answer %q for %s.", + a[0], q.Qid) + } + if n < 0 || len(q.RadioList) <= n { + return fmt.Errorf("invalid answer %q for %s.", + n, q.Qid) + } + q.RadioAns = q.RadioList[n] + return nil + case "text": + if len(a) != 1 { + return fmt.Errorf("length of text answer for %s is %d.\n", + q.Qid, len(a)) + } + q.TextAns = a[0] + return nil + default: + panic(fmt.Errorf("unknown question type: %v", q.T)) + } +} + +func (q *Question) copy() Entry { + qq := new(Question) + qq.Q = q.Q + qq.Qid = q.Qid + qq.T = q.T + qq.TextAns = q.TextAns + if q.RadioList != nil { + qq.RadioList = make([]string, len(q.RadioList)) + copy(qq.RadioList, q.RadioList) + } + qq.RadioAns = q.RadioAns + if q.CheckList != nil { + qq.CheckList = make([]string, len(q.CheckList)) + copy(qq.CheckList, q.CheckList) + } + if q.CheckAns != nil { + qq.CheckAns = make([]string, len(q.CheckAns)) + copy(qq.CheckAns, q.CheckAns) + } + qq.OtherAns = q.OtherAns + return qq +} + +var Q Part = Part{ + Q: "きのかわ弦楽合奏団第7回定期演奏会 アンケート", + Ent: []Entry{ + &Question{ + Q: "この演奏会をどのようにしてお知りになりましたか。", + Qid: "information", + T: "check", + CheckList: []string{ + "ポスター", "友人・知人", "その他", + }, + }, + &Part{ + Q: "あなたについて教えてください", + Ent: []Entry{ + &Question{ + Q: "年齢", + Qid: "age", + T: "radio", + RadioList: []string{ + "10歳未満", "10代", "20代", + }, + }, + &Question{ + Q: "性別", + Qid: "sex", + T: "radio", + RadioList: []string{ + "男性", "女性", + }, + }, + }, + }, + &Part{ + Q: "演奏の感想", + Ent: []Entry{ + &Question{ + Q: "1曲目", + Qid: "feelings1", + T: "text", + }, + &Question{ + Q: "2曲目", + Qid: "feelings2", + T: "text", + }, + }, + }, + }, +} + +func main() { + q, err := json.MarshalIndent(Q, "", " ") + if err != nil { + log.Printf("error: marshal: %v\n", err) + return + } + log.Printf("Q: %v\n", string(q)) + + http.HandleFunc("/", handler) + log.Fatal(http.ListenAndServe("localhost:8000", nil)) +} + +func handler(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + if err := r.ParseForm(); err != nil { + fmt.Fprintf(w, "server error...\n") + log.Printf("error: parse form: %v\n", err) + return + } + fmt.Fprint(w, exitPage) + answer, err := json.MarshalIndent(r.PostForm, "", " ") + if err != nil { + log.Printf("error: parse form to json: %v\n", err) + return + } + log.Printf("answer json: %v\n", string(answer)) + + QQ := Q.copy() + if err := QQ.fillAns(r.PostForm); err != nil { + log.Printf("fillAns: %v\n", err) + return + } + } else { + fmt.Fprint(w, header + Q.HTML() + footer) + } +} + +const header = `<html> +<head> +</head> +<body> +<form method="POST" onSubmit="return window.confirm('送信してもよろしいですか?')"> +` + +const footer = `<input type="submit" value="送信"> +</form> +</body> +</html> +` + +const exitPage = ` +<html> +送信完了しました。ご回答ありがとうございます。 +</html> +`