455 lines
12 KiB
Go
455 lines
12 KiB
Go
package tools
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"gitea.pena/SQuiz/common/model"
|
|
"github.com/xuri/excelize/v2"
|
|
_ "image/gif"
|
|
_ "image/jpeg"
|
|
_ "image/png"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
bucketImages = "squizimages"
|
|
bucketFonts = "squizfonts"
|
|
bucketScripts = "squizscript"
|
|
bucketStyle = "squizstyle"
|
|
bucketAnswers = "squizanswer"
|
|
)
|
|
|
|
func WriteDataToExcel(buffer io.Writer, questions []model.Question, answers []model.Answer, s3Prefix string) error {
|
|
file := excelize.NewFile()
|
|
sheet := "Sheet1"
|
|
|
|
_, err := file.NewSheet(sheet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sort.Slice(questions, func(i, j int) bool {
|
|
return questions[i].Page < questions[j].Page
|
|
})
|
|
|
|
headers, mapQueRes := prepareHeaders(questions)
|
|
headers = append([]string{"Дата и время"}, headers...)
|
|
|
|
for col, header := range headers {
|
|
cell := ToAlphaString(col+1) + "1"
|
|
if err := file.SetCellValue(sheet, cell, header); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
sort.Slice(answers, func(i, j int) bool {
|
|
return answers[i].QuestionId < answers[j].QuestionId
|
|
})
|
|
standart, results := categorizeAnswers(answers)
|
|
|
|
var wg sync.WaitGroup
|
|
row := 2
|
|
for session := range results {
|
|
wg.Add(1)
|
|
go func(session string, response []model.Answer, row int) {
|
|
defer wg.Done()
|
|
processSession(file, sheet, session, s3Prefix, response, results, questions, mapQueRes, headers, row)
|
|
}(session, standart[session], row)
|
|
row++
|
|
}
|
|
wg.Wait()
|
|
|
|
return file.Write(buffer)
|
|
}
|
|
|
|
func prepareHeaders(questions []model.Question) ([]string, map[uint64]string) {
|
|
headers := []string{"Данные респондента"}
|
|
mapQueRes := make(map[uint64]string)
|
|
|
|
for _, q := range questions {
|
|
if !q.Deleted {
|
|
if q.Type == model.TypeResult {
|
|
mapQueRes[q.Id] = q.Title + "\n" + q.Description
|
|
} else {
|
|
headers = append(headers, q.Title)
|
|
}
|
|
}
|
|
}
|
|
headers = append(headers, "Результат")
|
|
return headers, mapQueRes
|
|
}
|
|
|
|
func categorizeAnswers(answers []model.Answer) (map[string][]model.Answer, map[string]model.Answer) {
|
|
standart := make(map[string][]model.Answer)
|
|
results := make(map[string]model.Answer)
|
|
|
|
for _, answer := range answers {
|
|
if answer.Result {
|
|
results[answer.Session] = answer
|
|
} else {
|
|
standart[answer.Session] = append(standart[answer.Session], answer)
|
|
}
|
|
}
|
|
return standart, results
|
|
}
|
|
|
|
func processSession(file *excelize.File, sheet, session, s3Prefix string, response []model.Answer, results map[string]model.Answer, questions []model.Question, mapQueRes map[uint64]string, headers []string, row int) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
fmt.Println("Recovered from panic:", r)
|
|
}
|
|
}()
|
|
|
|
if err := file.SetCellValue(sheet, "A"+strconv.Itoa(row), results[session].CreatedAt.Format("2006-01-02 15:04:05")); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
|
|
if err := file.SetCellValue(sheet, "B"+strconv.Itoa(row), results[session].Content); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
|
|
count := 3
|
|
for _, q := range questions {
|
|
if !q.Deleted && q.Type != model.TypeResult {
|
|
cell := ToAlphaString(count) + strconv.Itoa(row)
|
|
index := binarySearch(response, q.Id)
|
|
if index != -1 {
|
|
handleAnswer(file, sheet, cell, s3Prefix, response[index], q, count, row)
|
|
} else {
|
|
if err := file.SetCellValue(sheet, cell, "-"); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
cell := ToAlphaString(len(headers)) + strconv.Itoa(row)
|
|
if err := file.SetCellValue(sheet, cell, mapQueRes[results[session].QuestionId]); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
}
|
|
|
|
func handleAnswer(file *excelize.File, sheet, cell, s3Prefix string, answer model.Answer, question model.Question, count, row int) {
|
|
tipe := FileSearch(answer.Content)
|
|
noAccept := make(map[string]struct{})
|
|
todoMap := make(map[string]string)
|
|
|
|
if tipe != "Text" && (question.Type == model.TypeImages || question.Type == model.TypeVarImages) {
|
|
handleImage(file, sheet, cell, answer.Content, count, row, noAccept, todoMap, question.Title)
|
|
} else if question.Type == model.TypeFile {
|
|
handleFile(file, sheet, cell, answer.Content, s3Prefix, noAccept)
|
|
} else {
|
|
todoMap[answer.Content] = cell
|
|
}
|
|
|
|
for cnt, cel := range todoMap {
|
|
if _, ok := noAccept[cnt]; !ok {
|
|
cntArr := strings.Split(cnt, "`,`")
|
|
resultCnt := cnt
|
|
if len(cntArr) > 1 {
|
|
resultCnt = strings.Join(cntArr, "\n")
|
|
}
|
|
|
|
if len(resultCnt) > 1 && resultCnt[0] == '`' && resultCnt[len(resultCnt)-1] == '`' {
|
|
resultCnt = resultCnt[1 : len(resultCnt)-1]
|
|
}
|
|
|
|
if len(resultCnt) > 1 && resultCnt[0] == '`' {
|
|
resultCnt = resultCnt[1:]
|
|
}
|
|
|
|
if len(resultCnt) > 1 && resultCnt[len(resultCnt)-1] == '`' {
|
|
resultCnt = resultCnt[:len(resultCnt)-1]
|
|
}
|
|
|
|
if err := file.SetCellValue(sheet, cel, resultCnt); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleImage(file *excelize.File, sheet, cell, content string, count, row int, noAccept map[string]struct{}, todoMap map[string]string, questionTitle string) {
|
|
multiImgArr := strings.Split(content, "`,`")
|
|
if len(multiImgArr) > 1 {
|
|
var descriptions []string
|
|
mediaSheet := "Media"
|
|
flag, err := file.GetSheetIndex(mediaSheet)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
if flag == -1 {
|
|
_, _ = file.NewSheet(mediaSheet)
|
|
err = file.SetCellValue(mediaSheet, "A1", "Вопрос")
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
}
|
|
|
|
mediaRow := row
|
|
for i, imgContent := range multiImgArr {
|
|
if i == 0 && len(imgContent) > 1 && imgContent[0] == '`' {
|
|
imgContent = imgContent[1:]
|
|
}
|
|
|
|
if i == len(multiImgArr)-1 && len(imgContent) > 1 && imgContent[len(imgContent)-1] == '`' {
|
|
imgContent = imgContent[:len(imgContent)-1]
|
|
}
|
|
|
|
var res model.ImageContent
|
|
err := json.Unmarshal([]byte(imgContent), &res)
|
|
if err != nil {
|
|
res.Image = imgContent
|
|
}
|
|
|
|
// чек на пустой дескрипшен, есмли пустой то отмечаем как вариант ответа номер по i
|
|
if res.Description != "" {
|
|
descriptions = append(descriptions, res.Description)
|
|
} else {
|
|
descriptions = append(descriptions, fmt.Sprintf("Вариант ответа №%d", i+1))
|
|
}
|
|
|
|
urle := ExtractImageURL(res.Image)
|
|
urlData := strings.Split(urle, " ")
|
|
if len(urlData) == 1 {
|
|
u, err := url.Parse(urle)
|
|
if err == nil && u.Scheme != "" && u.Host != "" {
|
|
picture, err := downloadImage(urle)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
continue
|
|
}
|
|
err = file.SetCellValue(mediaSheet, "A"+strconv.Itoa(mediaRow), questionTitle)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
|
|
col := ToAlphaString(i + 2)
|
|
err = file.SetColWidth(mediaSheet, col, col, 50)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
err = file.SetRowHeight(mediaSheet, mediaRow, 150)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
if err := file.AddPictureFromBytes(mediaSheet, col+strconv.Itoa(mediaRow), picture); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
noAccept[content] = struct{}{}
|
|
} else {
|
|
todoMap[content] = cell
|
|
}
|
|
} else {
|
|
todoMap[imgContent] = cell
|
|
}
|
|
|
|
descriptionsStr := strings.Join(descriptions, "\n")
|
|
linkText := fmt.Sprintf("%s\n Перейти в приложение %s!A%d", descriptionsStr, mediaSheet, mediaRow)
|
|
|
|
if err := file.SetCellValue(sheet, cell, linkText); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
//if err := file.SetCellHyperLink(sheet, cell, fmt.Sprintf("%s!A%d", mediaSheet, mediaRow), "Location", excelize.HyperlinkOpts{
|
|
// Display: &linkText,
|
|
//}); err != nil {
|
|
// fmt.Println(err.Error())
|
|
//}
|
|
}
|
|
} else {
|
|
if len(content) > 1 && content[0] == '`' && content[len(content)-1] == '`' {
|
|
content = content[1 : len(content)-1]
|
|
}
|
|
var res model.ImageContent
|
|
err := json.Unmarshal([]byte(content), &res)
|
|
if err != nil {
|
|
res.Image = content
|
|
}
|
|
urle := ExtractImageURL(res.Image)
|
|
urlData := strings.Split(urle, " ")
|
|
if len(urlData) == 1 {
|
|
u, err := url.Parse(urle)
|
|
if err == nil && u.Scheme != "" && u.Host != "" {
|
|
picture, err := downloadImage(urle)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
err = file.SetColWidth(sheet, ToAlphaString(count), ToAlphaString(count), 50)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
err = file.SetRowHeight(sheet, row, 150)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
if err := file.AddPictureFromBytes(sheet, cell, picture); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
noAccept[content] = struct{}{}
|
|
} else {
|
|
todoMap[content] = cell
|
|
}
|
|
} else {
|
|
todoMap[content] = cell
|
|
}
|
|
}
|
|
}
|
|
func handleFile(file *excelize.File, sheet, cell, content, s3Prefix string, noAccept map[string]struct{}) {
|
|
urle := content
|
|
if urle != "" && !strings.HasPrefix(urle, "https") {
|
|
urle = s3Prefix + urle
|
|
}
|
|
|
|
if urle == "" {
|
|
noAccept[content] = struct{}{}
|
|
return
|
|
}
|
|
|
|
fmt.Println("ORRRRR", urle, s3Prefix)
|
|
display, tooltip := urle, urle
|
|
|
|
if err := file.SetCellValue(sheet, cell, urle); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
if err := file.SetCellHyperLink(sheet, cell, urle, "External", excelize.HyperlinkOpts{
|
|
Display: &display,
|
|
Tooltip: &tooltip,
|
|
}); err != nil {
|
|
fmt.Println(err.Error())
|
|
}
|
|
|
|
styleID, err := file.NewStyle(&excelize.Style{
|
|
Font: &excelize.Font{
|
|
Color: "0000FF",
|
|
Underline: "single",
|
|
},
|
|
})
|
|
if err != nil {
|
|
fmt.Println("NewStyle error:", err.Error())
|
|
} else {
|
|
if err := file.SetCellStyle(sheet, cell, cell, styleID); err != nil {
|
|
fmt.Println("SetCellStyle error:", err.Error())
|
|
}
|
|
}
|
|
|
|
noAccept[content] = struct{}{}
|
|
}
|
|
|
|
func binarySearch(answers []model.Answer, questionID uint64) int {
|
|
left := 0
|
|
right := len(answers) - 1
|
|
for left <= right {
|
|
mid := left + (right-left)/2
|
|
if answers[mid].QuestionId == questionID {
|
|
return mid
|
|
} else if answers[mid].QuestionId < questionID {
|
|
left = mid + 1
|
|
} else {
|
|
right = mid - 1
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func FileSearch(content string) string {
|
|
if strings.Contains(content, bucketImages) {
|
|
return FileType(content)
|
|
} else if strings.Contains(content, bucketFonts) {
|
|
return FileType(content)
|
|
} else if strings.Contains(content, bucketScripts) {
|
|
return FileType(content)
|
|
} else if strings.Contains(content, bucketStyle) {
|
|
return FileType(content)
|
|
} else if strings.Contains(content, bucketAnswers) {
|
|
return FileType(content)
|
|
}
|
|
|
|
return "Text"
|
|
}
|
|
|
|
func FileType(filename string) string {
|
|
parts := strings.Split(filename, ".")
|
|
extension := parts[len(parts)-1]
|
|
|
|
switch extension {
|
|
case "png", "jpg", "jpeg", "gif", "bmp", "svg", "webp", "tiff", "ico":
|
|
return "Image"
|
|
default:
|
|
return "File"
|
|
}
|
|
}
|
|
|
|
func downloadImage(url string) (*excelize.Picture, error) {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if derr := resp.Body.Close(); derr != nil {
|
|
fmt.Printf("error close response body in downloadImage: %v", derr)
|
|
}
|
|
}()
|
|
|
|
imgData, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ext := filepath.Ext(url)
|
|
if ext == "" {
|
|
contentType := resp.Header.Get("Content-Type")
|
|
switch {
|
|
case strings.HasPrefix(contentType, "image/jpeg"):
|
|
ext = ".jpg"
|
|
case strings.HasPrefix(contentType, "image/png"):
|
|
ext = ".png"
|
|
default:
|
|
ext = ".png"
|
|
}
|
|
}
|
|
|
|
pic := &excelize.Picture{
|
|
Extension: ext,
|
|
File: imgData,
|
|
Format: &excelize.GraphicOptions{
|
|
AutoFit: true,
|
|
Positioning: "oneCell",
|
|
},
|
|
}
|
|
return pic, nil
|
|
}
|
|
|
|
func ToAlphaString(col int) string {
|
|
var result string
|
|
for col > 0 {
|
|
col--
|
|
result = string(rune('A'+col%26)) + result
|
|
col /= 26
|
|
}
|
|
return result
|
|
}
|
|
|
|
func ExtractImageURL(htmlContent string) string {
|
|
re := regexp.MustCompile(`(?:<img[^>]*src="([^"]+)"[^>]*>)|(?:<td[^>]*>.*?<img[^>]*src="([^"]+)"[^>]*>.*?</td>)|(?:<tr[^>]*>.*?<td[^>]*>.*?<img[^>]*src="([^"]+)"[^>]*>.*?</td>.*?</tr>)|(?:<a[^>]*\s+download[^>]*>([^<]+)<\/a>)`)
|
|
matches := re.FindAllStringSubmatch(htmlContent, -1)
|
|
|
|
for _, match := range matches {
|
|
for i := 1; i < len(match); i++ {
|
|
if match[i] != "" {
|
|
return strings.TrimSpace(match[i])
|
|
}
|
|
}
|
|
}
|
|
return htmlContent
|
|
}
|