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 `