package tools
import (
"fmt"
"github.com/xuri/excelize/v2"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"io/ioutil"
"net/http"
"net/url"
"path/filepath"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
"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) 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 := []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, "Результат")
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 := 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)
}
}
processSession := func(session string, response []model.Answer, 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].Content); err != nil {
fmt.Println(err.Error())
}
count := 2
for _, q := range questions {
if !q.Deleted && q.Type != model.TypeResult {
index := binarySearch(response, q.Id)
if index != -1 {
cell := ToAlphaString(count) + strconv.Itoa(row)
tipe := FileSearch(response[index].Content)
noAccept := make(map[string]struct{})
todoMap := make(map[string]string)
if tipe != "Text" && q.Type == model.TypeImages || q.Type == model.TypeVarImages {
urle := ExtractImageURL(response[index].Content)
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())
}
file.SetColWidth(sheet, ToAlphaString(count), ToAlphaString(count), 50)
file.SetRowHeight(sheet, row, 150)
if err := file.AddPictureFromBytes(sheet, cell, picture); err != nil {
fmt.Println(err.Error())
}
noAccept[response[index].Content] = struct{}{}
} else {
todoMap[response[index].Content] = cell
}
} else {
todoMap[response[index].Content] = cell
}
} else if tipe != "Text" && q.Type == model.TypeFile {
urle := ExtractImageURL(response[index].Content)
display, tooltip := urle, urle
if err := file.SetCellValue(sheet, cell, response[index].Content); 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())
}
noAccept[response[index].Content] = struct{}{}
} else {
todoMap[response[index].Content] = cell
}
for cnt, cel := range todoMap {
if _, ok := noAccept[cnt]; !ok {
if err := file.SetCellValue(sheet, cel, cnt); err != nil {
fmt.Println(err.Error())
}
}
}
} else {
cell := ToAlphaString(count) + strconv.Itoa(row)
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())
}
}
row := 2
var wg sync.WaitGroup
for session, _ := range results {
wg.Add(1)
go func(session string, response []model.Answer, row int) {
defer wg.Done()
processSession(session, standart[session], row)
}(session, standart[session], row)
row++
}
wg.Wait()
if err := file.Write(buffer); err != nil {
return err
}
return nil
}
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 resp.Body.Close()
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(`(?:]*src="([^"]+)"[^>]*>)|(?: