core/internal/tools/tools.go

455 lines
12 KiB
Go
Raw Normal View History

2024-03-13 16:57:12 +00:00
package tools
2024-02-19 17:48:04 +00:00
import (
"encoding/json"
2024-03-26 21:19:35 +00:00
"fmt"
2025-02-24 17:06:12 +00:00
"gitea.pena/SQuiz/common/model"
2024-03-26 17:48:49 +00:00
"github.com/xuri/excelize/v2"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
2024-02-19 17:48:04 +00:00
"io"
2024-03-26 17:48:49 +00:00
"io/ioutil"
"net/http"
"net/url"
"path/filepath"
"regexp"
2024-02-19 17:48:04 +00:00
"sort"
2024-03-26 17:48:49 +00:00
"strconv"
"strings"
2024-03-26 21:19:35 +00:00
"sync"
2024-03-26 17:48:49 +00:00
)
const (
bucketImages = "squizimages"
bucketFonts = "squizfonts"
bucketScripts = "squizscript"
bucketStyle = "squizstyle"
bucketAnswers = "squizanswer"
2024-02-19 17:48:04 +00:00
)
func WriteDataToExcel(buffer io.Writer, questions []model.Question, answers []model.Answer, s3Prefix string) error {
2024-03-26 17:48:49 +00:00
file := excelize.NewFile()
sheet := "Sheet1"
_, err := file.NewSheet(sheet)
2024-02-19 17:48:04 +00:00
if err != nil {
return err
}
sort.Slice(questions, func(i, j int) bool {
return questions[i].Page < questions[j].Page
})
2024-07-23 09:01:50 +00:00
headers, mapQueRes := prepareHeaders(questions)
2024-11-06 08:58:12 +00:00
headers = append([]string{"Дата и время"}, headers...)
2024-07-23 09:01:50 +00:00
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) {
2024-02-19 17:48:04 +00:00
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, "Результат")
2024-07-23 09:01:50 +00:00
return headers, mapQueRes
}
2024-02-19 17:48:04 +00:00
2024-07-23 09:01:50 +00:00
func categorizeAnswers(answers []model.Answer) (map[string][]model.Answer, map[string]model.Answer) {
2024-02-19 17:48:04 +00:00
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)
}
}
2024-07-23 09:01:50 +00:00
return standart, results
}
2024-03-26 17:48:49 +00:00
2024-07-23 09:01:50 +00:00
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)
2024-03-26 17:48:49 +00:00
}
2024-07-23 09:01:50 +00:00
}()
2024-11-06 08:58:12 +00:00
if err := file.SetCellValue(sheet, "A"+strconv.Itoa(row), results[session].CreatedAt.Format("2006-01-02 15:04:05")); err != nil {
2024-07-23 09:01:50 +00:00
fmt.Println(err.Error())
}
2024-11-06 08:58:12 +00:00
if err := file.SetCellValue(sheet, "B"+strconv.Itoa(row), results[session].Content); err != nil {
fmt.Println(err.Error())
}
count := 3
2024-07-23 09:01:50 +00:00
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())
2024-02-19 17:48:04 +00:00
}
}
2024-07-23 09:01:50 +00:00
count++
2024-02-19 17:48:04 +00:00
}
2024-07-23 09:01:50 +00:00
}
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)
2024-09-18 12:47:57 +00:00
if tipe != "Text" && (question.Type == model.TypeImages || question.Type == model.TypeVarImages) {
handleImage(file, sheet, cell, answer.Content, count, row, noAccept, todoMap, question.Title)
2024-07-23 09:01:50 +00:00
} 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 {
2024-09-18 15:09:54 +00:00
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 {
2024-07-23 09:01:50 +00:00
fmt.Println(err.Error())
}
2024-03-26 17:48:49 +00:00
}
2024-03-26 21:19:35 +00:00
}
2024-07-23 09:01:50 +00:00
}
2024-03-26 21:19:35 +00:00
2024-09-18 12:47:57 +00:00
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", "Вопрос")
2024-07-23 09:01:50 +00:00
if err != nil {
fmt.Println(err.Error())
}
2024-09-18 12:47:57 +00:00
}
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
}
2024-09-18 15:09:54 +00:00
descriptionsStr := strings.Join(descriptions, "\n")
2024-09-19 07:49:05 +00:00
linkText := fmt.Sprintf("%s\n Перейти в приложение %s!A%d", descriptionsStr, mediaSheet, mediaRow)
2024-09-18 12:47:57 +00:00
if err := file.SetCellValue(sheet, cell, linkText); err != nil {
fmt.Println(err.Error())
}
2024-09-19 07:48:25 +00:00
//if err := file.SetCellHyperLink(sheet, cell, fmt.Sprintf("%s!A%d", mediaSheet, mediaRow), "Location", excelize.HyperlinkOpts{
// Display: &linkText,
//}); err != nil {
// fmt.Println(err.Error())
//}
2024-09-18 12:47:57 +00:00
}
} 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
}
2024-07-23 09:01:50 +00:00
} 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
2024-02-19 17:48:04 +00:00
}
2025-05-20 13:02:14 +00:00
if urle == "" {
noAccept[content] = struct{}{}
return
}
2024-07-23 09:01:50 +00:00
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())
}
2025-05-20 13:02:14 +00:00
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())
}
}
2024-07-23 09:01:50 +00:00
noAccept[content] = struct{}{}
2024-02-19 17:48:04 +00:00
}
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
}
2024-03-26 17:48:49 +00:00
2024-03-26 21:19:35 +00:00
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)
2024-03-26 17:48:49 +00:00
}
2024-03-26 21:19:35 +00:00
return "Text"
2024-03-26 17:48:49 +00:00
}
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
}
2024-08-06 13:56:51 +00:00
defer func() {
if derr := resp.Body.Close(); derr != nil {
fmt.Printf("error close response body in downloadImage: %v", derr)
}
}()
2024-03-26 17:48:49 +00:00
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
}