webform

web server for questionnaire.
Log | Files | Refs

main.go (7685B)


      1 package main
      2 
      3 import (
      4 	"bytes"
      5 	"encoding/json"
      6 	"fmt"
      7 	"log"
      8 	"net/http"
      9 	"os"
     10 	"os/signal"
     11 	"strconv"
     12 )
     13 
     14 // An Entry is either a Part or a Question.
     15 type Entry interface {
     16 	isPart() bool
     17 	makeAns(map[string][]string) ([]Ans, error)
     18 	HTML() string
     19 }
     20 
     21 type Part struct {
     22 	Q   string
     23 	Ent []Entry
     24 }
     25 
     26 func (*Part) isPart() bool { return true }
     27 func (p *Part) HTML() string {
     28 	html := fmt.Sprintf("<fieldset>\n<legend>%s</legend>\n", p.Q)
     29 	for _, e := range p.Ent {
     30 		html += e.HTML()
     31 	}
     32 	return html + "</fieldset>\n"
     33 }
     34 
     35 func (p *Part) makeAns(ans map[string][]string) ([]Ans, error) {
     36 	var a []Ans
     37 	for _, e := range p.Ent {
     38 		aa, err := e.makeAns(ans)
     39 		if err != nil {
     40 			return nil, err
     41 		}
     42 		a = append(a, aa...)
     43 	}
     44 	return a, nil
     45 }
     46 
     47 type Question struct {
     48 	Q         string
     49 	Qid       string
     50 	T         string // answer type: "text", "radio", "check"
     51 	RadioList []string
     52 	CheckList []string
     53 }
     54 
     55 func (*Question) isPart() bool { return false }
     56 func (q *Question) HTML() string {
     57 	switch q.T {
     58 	case "check":
     59 		html := fmt.Sprintf("<fieldset>\n<legend>%s</legend>\n", q.Q)
     60 		for n, a := range q.CheckList {
     61 			id := q.Qid + strconv.Itoa(n)
     62 			html += fmt.Sprintf("<input type=\"checkbox\" name=\"%s\" id=\"%s\" value=\"%d\">\n"+
     63 				"<label for=\"%s\">%s</label>\n", q.Qid, id, n, id, a)
     64 			if a == "その他" {
     65 				html += fmt.Sprintf("<input type=\"text\" name=\"%s_other\" id=\"%s_other\">\n", q.Qid, q.Qid)
     66 			}
     67 		}
     68 		html += "</fieldset>\n"
     69 		return html
     70 	case "radio":
     71 		html := fmt.Sprintf("<fieldset>\n<legend>%s</legend>\n", q.Q)
     72 		for n, a := range q.RadioList {
     73 			id := q.Qid + strconv.Itoa(n)
     74 			html += fmt.Sprintf("<input type=\"radio\" name=\"%s\" id=\"%s\" value=\"%d\">\n"+
     75 				"<label for=\"%s\">%s</label>\n", q.Qid, id, n, id, a)
     76 			if a == "その他" {
     77 				html += fmt.Sprintf("<input type=\"text\" name=\"%s_other\" id=\"%s_other\">\n", q.Qid, q.Qid)
     78 			}
     79 		}
     80 		html += "</fieldset>\n"
     81 		return html
     82 	case "text":
     83 		return fmt.Sprintf("<fieldset>\n<legend>%s</legend>\n"+
     84 			"<textarea name=\"%s\" id=\"%s\"></textarea></fieldset>",
     85 			q.Q, q.Qid, q.Qid)
     86 	default:
     87 		panic(fmt.Sprintf("unknown question type: %s\n", q.T))
     88 	}
     89 }
     90 
     91 func (q *Question) makeAns(ans map[string][]string) ([]Ans, error) {
     92 	a, ok := ans[q.Qid]
     93 	if !ok {
     94 		a = []string{""}
     95 	}
     96 	switch q.T {
     97 	case "check":
     98 		l := make([]string, 0)
     99 		for _, i := range a {
    100 			if i == "" { // empty answer for "その他"
    101 				continue
    102 			}
    103 			n, err := strconv.Atoi(i)
    104 			if err != nil {
    105 				return nil, fmt.Errorf("invalid answer %q for %s.", i, q.Qid)
    106 			}
    107 			if n < 0 || len(q.CheckList) <= n {
    108 				return nil, fmt.Errorf("invalid answer %q for %s.", n, q.Qid)
    109 			}
    110 			if q.CheckList[n] == "その他" {
    111 				var other string
    112 				if l, ok := ans[q.Qid+"_other"]; ok {
    113 					if len(l) > 0 {
    114 						other = l[0]
    115 					}
    116 				}
    117 				l = append(l, "その他:"+other)
    118 			} else {
    119 				l = append(l, q.CheckList[n])
    120 			}
    121 		}
    122 		return []Ans{CheckAns{Qid: q.Qid, Type: q.T, Ans: l}}, nil
    123 	case "radio":
    124 		if len(a) != 1 {
    125 			return nil, fmt.Errorf("invalid length %d for %s.\n", len(a), q.Qid)
    126 		}
    127 		if a[0] == "" { // empty answer
    128 			return []Ans{RadioAns{Qid: q.Qid, Type: q.T, Ans: ""}}, nil
    129 		}
    130 		i, err := strconv.Atoi(a[0])
    131 		if err != nil {
    132 			return nil, fmt.Errorf("invalid answer %s for %s.", a[0], q.Qid)
    133 		}
    134 		if i < 0 || len(q.RadioList) <= i {
    135 			return nil, fmt.Errorf("invalid answer %s for %s.", a[0], q.Qid)
    136 		}
    137 		if q.RadioList[i] == "その他" {
    138 			var other string
    139 			if l, ok := ans[q.Qid+"_other"]; ok {
    140 				if len(l) > 0 {
    141 					other = l[0]
    142 				}
    143 			}
    144 			return []Ans{RadioAns{Qid: q.Qid, Type: q.T, Ans: "その他:" + other}}, nil
    145 		}
    146 		return []Ans{RadioAns{Qid: q.Qid, Type: q.T, Ans: q.RadioList[i]}}, nil
    147 	case "text":
    148 		if len(a) != 1 {
    149 			return nil, fmt.Errorf("invalid length %d for %s.\n", len(a), q.Qid)
    150 		}
    151 		return []Ans{TextAns{Qid: q.Qid, Type: q.T, Ans: a[0]}}, nil
    152 	default:
    153 		panic(fmt.Errorf("unknown question type: %v", q.T))
    154 	}
    155 }
    156 
    157 type Ans interface {
    158 	qid() string
    159 }
    160 
    161 type TextAns struct {
    162 	Qid  string
    163 	Type string
    164 	Ans  string
    165 }
    166 func (a TextAns) qid() string { return a.Qid }
    167 
    168 type RadioAns struct {
    169 	Qid  string
    170 	Type string
    171 	Ans  string
    172 }
    173 func (a RadioAns) qid() string { return a.Qid }
    174 
    175 type CheckAns struct {
    176 	Qid  string
    177 	Type string
    178 	Ans  []string
    179 }
    180 func (a CheckAns) qid() string { return a.Qid }
    181 
    182 var Q Part = Part{
    183 	Q: "きのかわ弦楽合奏団第7回定期演奏会 アンケート",
    184 	Ent: []Entry{
    185 		&Question{
    186 			Q:   "この演奏会をどのようにしてお知りになりましたか。",
    187 			Qid: "information",
    188 			T:   "check",
    189 			CheckList: []string{
    190 				"ポスター", "友人・知人", "その他",
    191 			},
    192 		},
    193 		&Part{
    194 			Q: "あなたについて教えてください",
    195 			Ent: []Entry{
    196 				&Question{
    197 					Q:   "年齢",
    198 					Qid: "age",
    199 					T:   "radio",
    200 					RadioList: []string{
    201 						"10歳未満", "10代", "20代",
    202 					},
    203 				},
    204 				&Question{
    205 					Q:   "性別",
    206 					Qid: "sex",
    207 					T:   "radio",
    208 					RadioList: []string{
    209 						"男性", "女性",
    210 					},
    211 				},
    212 				&Question{
    213 					Q:   "住所",
    214 					Qid: "address",
    215 					T:   "radio",
    216 					RadioList: []string{
    217 						"和歌山市", "海南市", "その他",
    218 					},
    219 				},
    220 			},
    221 		},
    222 		&Part{
    223 			Q: "演奏の感想",
    224 			Ent: []Entry{
    225 				&Question{
    226 					Q:   "1曲目",
    227 					Qid: "feelings1",
    228 					T:   "text",
    229 				},
    230 				&Question{
    231 					Q:   "2曲目",
    232 					Qid: "feelings2",
    233 					T:   "text",
    234 				},
    235 			},
    236 		},
    237 	},
    238 }
    239 
    240 var bufc = make(chan []byte)
    241 
    242 func main() {
    243 	f, err := os.Create("a.json")
    244 	if err != nil {
    245 		log.Fatalf("create file: %v\n", err)
    246 	}
    247 	intc := make(chan os.Signal, 3)
    248 	done := make(chan struct{})
    249 	listenErrChan := make(chan struct{})
    250 	signal.Notify(intc, os.Interrupt)
    251 	go writer(intc, listenErrChan, done, bufc, f)
    252 	go func() {
    253 		http.HandleFunc("/", handler)
    254 		err := http.ListenAndServe("localhost:8000", nil)
    255 		log.Printf("listenAndServe: %v\n", err)
    256 		close(listenErrChan)
    257 	}()
    258 	<-done
    259 	os.Exit(0)
    260 }
    261 
    262 func handler(w http.ResponseWriter, r *http.Request) {
    263 	if r.Method == "POST" {
    264 		if err := r.ParseForm(); err != nil {
    265 			fmt.Fprintf(w, "server error...\n")
    266 			log.Printf("error: parse form: %v\n", err)
    267 			return
    268 		}
    269 		fmt.Fprint(w, exitPage)
    270 		log.Printf("ans: %q\n", r.PostForm)
    271 		a, err := Q.makeAns(r.PostForm)
    272 		if err != nil {
    273 			log.Printf("makeAns: %v\n", err)
    274 			return
    275 		}
    276 		A, err := json.MarshalIndent(a, "", "  ")
    277 		if err != nil {
    278 			log.Printf("marchalIndent: %v\n", err)
    279 			return
    280 		}
    281 		bufc <- A
    282 	} else {
    283 		fmt.Fprint(w, header+Q.HTML()+footer)
    284 	}
    285 }
    286 
    287 // Writer reads json data from c and writes them to f.
    288 // This function closes f after c is closed.
    289 func writer(intc <-chan os.Signal, listenErrChan <-chan struct{},
    290 	done chan<- struct{}, c <-chan []byte, f *os.File) {
    291 	defer close(done)
    292 	const bufSize = 4096
    293 	var buf bytes.Buffer
    294 L:
    295 	for {
    296 		select {
    297 		case q := <-c:
    298 			if err := json.Compact(&buf, q); err != nil {
    299 				log.Printf("error: compact: %v\n", err)
    300 				continue
    301 			}
    302 			if buf.Len() >= bufSize {
    303 				if _, err := f.Write(buf.Next(bufSize)); err != nil {
    304 					log.Printf("error: write to file: %v\n", err)
    305 					continue
    306 				}
    307 			}
    308 		case <-intc:
    309 			break L
    310 		case <-listenErrChan:
    311 			break L
    312 		}
    313 	}
    314 	if _, err := f.Write(buf.Bytes()); err != nil {
    315 		log.Printf("error: write to file: %v\n", err)
    316 	}
    317 	if err := f.Close(); err != nil {
    318 		log.Printf("close file: %v\n", err)
    319 	}
    320 }
    321 
    322 const header = `<html>
    323 <head>
    324 </head>
    325 <body>
    326 <form method="POST" onSubmit="return window.confirm('送信してもよろしいですか?')">
    327 `
    328 
    329 const footer = `<input type="submit" value="送信">
    330 </form>
    331 </body>
    332 </html>
    333 `
    334 
    335 const exitPage = `
    336 <html>
    337 送信完了しました。ご回答ありがとうございます。
    338 </html>
    339 `