commit 42e5b74e32eb7bafdcc47e058a7665e41a9ac7c6
Author: Matsuda Kenji <info@mtkn.jp>
Date: Sat, 20 Jul 2024 10:16:27 +0900
first
Diffstat:
A | go.mod | | | 3 | +++ |
A | main.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>
+`