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="([^"]+)"[^>]*>)|(?:]*>.*?]*src="([^"]+)"[^>]*>.*?)|(?:]*>.*?]*>.*?]*src="([^"]+)"[^>]*>.*?.*?)|(?:]*\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 } //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 // } // } // // // мапа для хранения обычных ответов респондентов // 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) // } // } // // // записываем данные в файл // row := 2 // for session, _ := range results { // response := standart[session] // if err := file.SetCellValue(sheet, "A"+strconv.Itoa(row), results[session].Content); err != nil { // return err // } // count := 2 // for _, q := range questions { // if !q.Deleted && q.Type != model.TypeResult { // sort.Slice(response, func(i, j int) bool { // return response[i].QuestionId < response[j].QuestionId // }) // index := binarySearch(response, q.Id) // if index != -1 { // cell := ToAlphaString(count) + strconv.Itoa(row) // typeMap := FileSearch(response[index].Content) // noAccept := make(map[string]struct{}) // todoMap := make(map[string]string) // for _, tipe := range typeMap { // if tipe != "Text" && q.Type == model.TypeImages || q.Type == model.TypeVarImages { // urle := ExtractImageURL(response[index].Content) // urlData := strings.Split(urle, " ") // for _, k := range urlData { // u, err := url.Parse(k) // if err == nil && u.Scheme != "" && u.Host != "" { // picture, err := downloadImage(k) // if err != nil { // return err // } // file.SetColWidth(sheet, ToAlphaString(count), ToAlphaString(count), 50) // file.SetRowHeight(sheet, row, 150) // if err := file.AddPictureFromBytes(sheet, cell, picture); err != nil { // return err // } // noAccept[response[index].Content] = struct{}{} // } // } // } 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 { // return err // } // if err := file.SetCellHyperLink(sheet, cell, urle, "External", excelize.HyperlinkOpts{ // Display: &display, // Tooltip: &tooltip, // }); err != nil { // return err // } // 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 { // return err // } // } // } // // } else { // cell := ToAlphaString(count) + strconv.Itoa(row) // if err := file.SetCellValue(sheet, cell, "-"); err != nil { // return err // } // } // count++ // } // } // cell := ToAlphaString(len(headers)) + strconv.Itoa(row) // if err := file.SetCellValue(sheet, cell, mapQueRes[results[session].QuestionId]); err != nil { // return err // } // row++ // } // // // cохраняем данные в буфер // if err := file.Write(buffer); err != nil { // return err // } // // return nil //}