You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

336 lines
7.5 KiB

package convert
import (
"archive/zip"
"encoding/xml"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"godo/libs"
)
// 常见图片扩展名列表
var imageExtensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tif", ".tiff"}
// 直接遍历ZIP并查找可能的图片文件
func findImagesInZip(zr *zip.Reader) ([]string, error) {
var images []string
cacheDir := libs.GetCacheDir()
for _, f := range zr.File {
if isImageFile(f.Name) {
images = append(images, f.Name)
if err := extractImageToCache(zr, f.Name, cacheDir); err != nil {
log.Printf("Error extracting image %s to cache: %v", f.Name, err)
}
}
}
return images, nil
}
// 判断文件是否为图片文件
func isImageFile(fileName string) bool {
ext := strings.ToLower(filepath.Ext(fileName))
for _, imgExt := range imageExtensions {
if ext == imgExt {
return true
}
}
return false
}
// 从zip中提取图片到缓存目录
func extractImageToCache(zr *zip.Reader, imageName, cacheDir string) error {
fileInZip, err := getFileByName(zr.File, imageName)
if err != nil {
return err
}
rc, err := fileInZip.Open()
if err != nil {
return fmt.Errorf("failed to open file %s in zip: %w", imageName, err)
}
defer rc.Close()
justFileName := filepath.Base(imageName)
outFilePath := filepath.Join(cacheDir, justFileName)
outFile, err := os.Create(outFilePath)
if err != nil {
return fmt.Errorf("failed to create file %s: %w", outFilePath, err)
}
defer outFile.Close()
_, err = io.Copy(outFile, rc)
if err != nil {
return fmt.Errorf("failed to copy image content to %s: %w", outFilePath, err)
}
// 提取图片周围的文本内容
textContent, err := getSurroundingTextForOffice(zr, imageName)
if err != nil {
log.Printf("Error getting surrounding text for image %s: %v", imageName, err)
} else {
textFilePath := filepath.Join(cacheDir, strings.TrimSuffix(justFileName, filepath.Ext(justFileName))+".txt")
if err := saveTextToFile(textContent, textFilePath); err != nil {
log.Printf("Error saving text to file %s: %v", textFilePath, err)
}
}
return nil
}
// 从zip.File数组中根据文件名查找并返回对应的文件
func getFileByName(files []*zip.File, name string) (*zip.File, error) {
for _, file := range files {
if file.Name == name {
return file, nil
}
}
return nil, fmt.Errorf("file %s not found in zip archive", name)
}
// 获取 .pptx, .xlsx 或 .docx 文件中图片周围的文本内容
func getSurroundingTextForOffice(zr *zip.Reader, imageName string) (string, error) {
imageDir := filepath.Dir(imageName)
xmlFiles, err := findRelevantXMLFiles(zr, imageDir)
if err != nil {
return "", err
}
for _, xmlFile := range xmlFiles {
fileInZip, err := getFileByName(zr.File, xmlFile)
if err != nil {
continue
}
rc, err := fileInZip.Open()
if err != nil {
continue
}
defer rc.Close()
doc, err := parseXMLDocument(rc, imageDir)
if err != nil {
continue
}
surroundingText := getSurroundingText(doc, filepath.Base(imageName))
if surroundingText != "" {
return truncateText(surroundingText), nil
}
}
return "", fmt.Errorf("no surrounding text found for image %s", imageName)
}
// 查找相关的XML文件
func findRelevantXMLFiles(zr *zip.Reader, imageDir string) ([]string, error) {
switch {
case strings.Contains(imageDir, "ppt/media"):
return findFilesByPattern(zr, "ppt/slides/slide*.xml"), nil
case strings.Contains(imageDir, "xl/media"):
return findFilesByPattern(zr, "xl/worksheets/sheet*.xml"), nil
case strings.Contains(imageDir, "word/media"):
return []string{"word/document.xml"}, nil
default:
return nil, fmt.Errorf("unknown image directory %s", imageDir)
}
}
// 解析XML文档
func parseXMLDocument(rc io.ReadCloser, imageDir string) (interface{}, error) {
var doc interface{}
switch {
case strings.Contains(imageDir, "ppt/media"):
doc = &PPTXDocument{}
case strings.Contains(imageDir, "xl/media"):
doc = &XLSXDocument{}
case strings.Contains(imageDir, "word/media"):
doc = &DOCXDocument{}
default:
return nil, fmt.Errorf("unknown image directory %s", imageDir)
}
if err := xml.NewDecoder(rc).Decode(doc); err != nil {
return nil, err
}
return doc, nil
}
// 获取图片周围的文本内容
func getSurroundingText(doc interface{}, imagePath string) string {
switch d := doc.(type) {
case *PPTXDocument:
for _, slide := range d.Slides {
for _, shape := range slide.Shapes {
if shape.Type == "pic" && shape.ImagePath == imagePath {
return getTextFromSlide(slide)
}
}
}
case *XLSXDocument:
for _, sheet := range d.Sheets {
for _, drawing := range sheet.Drawings {
for _, image := range drawing.Images {
if image.ImagePath == imagePath {
return getTextFromSheet(sheet)
}
}
}
}
case *DOCXDocument:
for _, paragraph := range d.Body.Paragraphs {
for _, run := range paragraph.Runs {
for _, pic := range run.Pictures {
if pic.ImagePath == imagePath {
return getTextFromParagraph(paragraph)
}
}
}
}
}
return ""
}
// 查找符合模式的文件
func findFilesByPattern(zr *zip.Reader, pattern string) []string {
var files []string
for _, f := range zr.File {
if matched, _ := filepath.Match(pattern, f.Name); matched {
files = append(files, f.Name)
}
}
return files
}
// 将文本内容保存到文件
func saveTextToFile(text, filePath string) error {
return os.WriteFile(filePath, []byte(text), 0644)
}
// 截断文本,确保不超过80个字符
func truncateText(text string) string {
if len(text) > 80 {
return text[:80]
}
return text
}
// PPTXDocument 结构体定义
type PPTXDocument struct {
Slides []Slide `xml:"p:sld"`
}
type Slide struct {
Shapes []Shape `xml:"p:cSld>p:spTree>p:sp"`
}
type Shape struct {
Type string `xml:"p:pic"`
ImagePath string `xml:"p:pic>p:blipFill>a:blip/@r:embed"`
Elements []Element `xml:"p:txBody>a:p>a:r"`
}
type Element struct {
Type string `xml:"a:t"`
Value string `xml:",chardata"`
}
// XLSXDocument 结构体定义
type XLSXDocument struct {
Sheets []Sheet `xml:"worksheet"`
}
type Sheet struct {
Rows []Row `xml:"sheetData>row"`
Drawings []Drawing `xml:"drawing"`
}
type Row struct {
Cells []Cell `xml:"c"`
}
type Cell struct {
Value string `xml:"v"`
}
type Drawing struct {
Images []Image `xml:"xdr:pic"`
}
type Image struct {
ImagePath string `xml:"xdr:pic>xdr:blipFill>a:blip/@r:embed"`
}
// DOCXDocument 结构体定义
type DOCXDocument struct {
Body struct {
Paragraphs []Paragraph `xml:"w:p"`
} `xml:"w:body"`
}
type Paragraph struct {
Runs []Run `xml:"w:r"`
}
type Run struct {
Pictures []Picture `xml:"w:drawing"`
Text []Text `xml:"w:t"`
}
type Text struct {
Value string `xml:",chardata"`
}
type Picture struct {
ImagePath string `xml:"wp:docPr/@name"`
}
// 从幻灯片中提取文本
func getTextFromSlide(slide Slide) string {
var text string
for _, shape := range slide.Shapes {
if shape.Type != "pic" {
text += getTextFromShape(shape)
}
}
return text
}
// 从形状中提取文本
func getTextFromShape(shape Shape) string {
var text string
for _, element := range shape.Elements {
text += element.Value
}
return text
}
// 从工作表中提取文本
func getTextFromSheet(sheet Sheet) string {
var text string
for _, row := range sheet.Rows {
for _, cell := range row.Cells {
text += cell.Value
}
}
return text
}
// 从段落中提取文本
func getTextFromParagraph(paragraph Paragraph) string {
var text string
for _, run := range paragraph.Runs {
for _, t := range run.Text {
text += t.Value
}
}
return text
}