删除docx中AI生成的特殊标记

在如今AI满天飞的时代,我们自然而然会把AI生成的内容复制到Word中完成作业、报告或文章中,但时不时会出现一些比较讨厌的现象:某些内容既不能编辑、也不能删除。下面代码完成这些讨厌的地方清理工作:

package main

import (
	"archive/zip"
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Printf("使用方法: %s <docx文件路径>\n", os.Args[0])
		os.Exit(1)
	}

	docxPath := os.Args[1]

	// 检查文件是否存在
	if _, err := os.Stat(docxPath); os.IsNotExist(err) {
		log.Fatalf("文件不存在: %s", docxPath)
	}

	// 创建备份文件
	backupPath := docxPath + ".backup.docx"
	err := copyFile(docxPath, backupPath)
	if err != nil {
		log.Fatalf("创建备份文件失败: %v", err)
	}
	fmt.Printf("已创建备份文件: %s\n", backupPath)

	// 处理docx文件,带错误恢复
	err = processDocxFileWithSimplifiedLogic(docxPath)
	if err != nil {
		log.Printf("处理失败: %v", err)
		log.Println("尝试从备份恢复原文件...")

		// 从备份恢复
		if err := restoreFromBackup(docxPath, backupPath); err != nil {
			log.Printf("恢复备份失败: %v", err)
			log.Printf("原文件可能已损坏,请检查备份文件: %s", backupPath)
			os.Exit(1)
		}
		log.Printf("已从备份恢复文件: %s", docxPath)
		os.Exit(1)
	}

	fmt.Println("处理完成!")
}

// 验证输入文件
func validateInputFile(docxPath string) error {
	// 检查文件大小
	info, err := os.Stat(docxPath)
	if err != nil {
		return fmt.Errorf("无法读取文件状态: %v", err)
	}
	if info.Size() == 0 {
		return fmt.Errorf("文件为空")
	}
	if info.Size() > 100*1024*1024 { // 限制100MB
		return fmt.Errorf("文件过大(%d MB),超过100MB限制", info.Size()/1024/1024)
	}
	return nil
}

// 简化的docx文件处理逻辑
func processDocxFileWithSimplifiedLogic(docxPath string) error {
	// 验证输入文件
	if err := validateInputFile(docxPath); err != nil {
		return err
	}

	// 打开docx文件(zip格式)
	reader, err := zip.OpenReader(docxPath)
	if err != nil {
		return fmt.Errorf("无法打开docx文件,文件可能已损坏: %v", err)
	}
	defer reader.Close()

	// 检查zip文件中是否有有效文件
	if len(reader.File) == 0 {
		return fmt.Errorf("docx文件为空或损坏")
	}

	// 创建临时目录
	tempDir, err := os.MkdirTemp("", "docx-process-")
	if err != nil {
		return fmt.Errorf("创建临时目录失败: %v", err)
	}
	defer os.RemoveAll(tempDir)

	var processed bool

	// 处理XML文件
	for _, file := range reader.File {
		if strings.HasPrefix(file.Name, "word/") && strings.HasSuffix(file.Name, ".xml") {
			content, modified, err := processXMLFileContent(file)
			if err != nil {
				return fmt.Errorf("处理文件 %s 失败: %v", file.Name, err)
			}

			if modified {
				// 保存修改后的内容到临时文件
				tempPath := filepath.Join(tempDir, filepath.Base(file.Name))
				err = os.WriteFile(tempPath, content, 0644)
				if err != nil {
					return fmt.Errorf("保存临时文件失败: %v", err)
				}
				processed = true
			}
		}
	}

	if !processed {
		fmt.Println("没有找到需要处理的SDT节点")
		return nil
	}

	// 创建新的docx文件
	err = createModifiedDocx(docxPath, reader, tempDir)
	if err != nil {
		return fmt.Errorf("创建修改后的文件失败: %v", err)
	}

	return nil
}

// 处理XML文件内容
func processXMLFileContent(file *zip.File) ([]byte, bool, error) {
	fileReader, err := file.Open()
	if err != nil {
		return nil, false, fmt.Errorf("打开压缩文件失败: %v", err)
	}
	defer fileReader.Close()

	// 使用缓冲区读取,避免大文件内存问题
	var buf bytes.Buffer
	_, err = io.Copy(&buf, fileReader)
	if err != nil {
		return nil, false, fmt.Errorf("读取文件内容失败: %v", err)
	}
	content := buf.Bytes()

	// 检查是否包含sdt节点
	if !bytes.Contains(content, []byte("<w:sdt")) {
		return content, false, nil
	}

	// 移除包含lock的SDT节点
	modifiedContent, modified := removeLockedSDTs(content)
	return modifiedContent, modified, nil
}

// 移除包含lock的SDT节点
func removeLockedSDTs(content []byte) ([]byte, bool) {
	xmlStr := string(content)
	var result strings.Builder
	modified := false
	start := 0

	for {
		// 查找sdt开始标签
		sdtStart := strings.Index(xmlStr[start:], "<w:sdt")
		if sdtStart == -1 {
			result.WriteString(xmlStr[start:])
			break
		}
		sdtStart += start

		// 查找sdt结束标签
		sdtEnd := strings.Index(xmlStr[sdtStart:], "</w:sdt>")
		if sdtEnd == -1 {
			result.WriteString(xmlStr[start:])
			break
		}
		sdtEnd += sdtStart + len("</w:sdt>")

		// 提取SDT节点内容
		sdtContent := xmlStr[sdtStart:sdtEnd]

		// 检查是否包含lock
		if containsLock(sdtContent) {
			fmt.Printf("找到并删除包含lock的SDT节点\n")
			// 写入前面未处理的内容,跳过这个SDT节点
			result.WriteString(xmlStr[start:sdtStart])
			modified = true
		} else {
			// 保留这个SDT节点
			result.WriteString(xmlStr[start:sdtEnd])
		}

		start = sdtEnd
	}

	return []byte(result.String()), modified
}

// 检查是否包含lock
func containsLock(content string) bool {
	return strings.Contains(content, "<w:lock") ||
		strings.Contains(content, "<lock") ||
		strings.Contains(content, "lock=\"sdtLocked\"") ||
		strings.Contains(content, "w:lock=\"sdtLocked\"")
}

// 创建修改后的docx文件
func createModifiedDocx(outputPath string, originalReader *zip.ReadCloser, tempDir string) error {
	// 创建临时输出文件,防止写入过程中失败
	tempOutputPath := outputPath + ".tmp"
	outputFile, err := os.Create(tempOutputPath)
	if err != nil {
		return fmt.Errorf("创建临时输出文件失败: %v", err)
	}

	// 确保在函数退出时清理临时文件
	defer func() {
		outputFile.Close()
		if err != nil {
			os.Remove(tempOutputPath) // 如果出错,清理临时文件
		}
	}()

	zipWriter := zip.NewWriter(outputFile)

	// 复制所有文件,处理过的用新版本
	for _, file := range originalReader.File {
		fileName := filepath.Base(file.Name)
		tempFile := filepath.Join(tempDir, fileName)

		// 如果临时目录中有对应的处理过的文件,使用它
		if _, err := os.Stat(tempFile); err == nil {
			content, err := os.ReadFile(tempFile)
			if err != nil {
				zipWriter.Close()
				return fmt.Errorf("读取临时文件失败: %v", err)
			}

			fileWriter, err := zipWriter.Create(file.Name)
			if err != nil {
				zipWriter.Close()
				return fmt.Errorf("创建zip条目失败: %v", err)
			}

			_, err = fileWriter.Write(content)
			if err != nil {
				zipWriter.Close()
				return fmt.Errorf("写入zip内容失败: %v", err)
			}
		} else {
			// 直接复制原文件
			fileWriter, err := zipWriter.Create(file.Name)
			if err != nil {
				zipWriter.Close()
				return fmt.Errorf("创建zip条目失败: %v", err)
			}

			fileReader, err := file.Open()
			if err != nil {
				zipWriter.Close()
				return fmt.Errorf("打开源文件失败: %v", err)
			}

			_, err = io.Copy(fileWriter, fileReader)
			fileReader.Close()
			if err != nil {
				zipWriter.Close()
				return fmt.Errorf("复制文件内容失败: %v", err)
			}
		}
	}

	// 正确关闭zip写入器
	err = zipWriter.Close()
	if err != nil {
		return fmt.Errorf("关闭zip写入器失败: %v", err)
	}

	// 正确关闭输出文件
	err = outputFile.Close()
	if err != nil {
		return fmt.Errorf("关闭输出文件失败: %v", err)
	}

	// 验证生成的zip文件
	if !isValidZipFile(tempOutputPath) {
		return fmt.Errorf("生成的zip文件无效")
	}

	// 替换原文件
	err = os.Rename(tempOutputPath, outputPath)
	if err != nil {
		return fmt.Errorf("替换原文件失败: %v", err)
	}

	return nil
}

// 验证zip文件是否有效
func isValidZipFile(path string) bool {
	reader, err := zip.OpenReader(path)
	if err != nil {
		return false
	}
	reader.Close()
	return true
}

// 从备份恢复文件
func restoreFromBackup(originalPath, backupPath string) error {
	if _, err := os.Stat(backupPath); os.IsNotExist(err) {
		return fmt.Errorf("备份文件不存在,无法恢复")
	}

	// 删除损坏的原文件
	if err := os.Remove(originalPath); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("删除损坏文件失败: %v", err)
	}

	// 复制备份文件
	return copyFile(backupPath, originalPath)
}

// 复制文件
func copyFile(src, dst string) error {
	source, err := os.Open(src)
	if err != nil {
		return err
	}
	defer source.Close()

	destination, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destination.Close()

	_, err = io.Copy(destination, source)
	return err
}
posted @ 2026-04-16 23:17  卓能文  阅读(48)  评论(0)    收藏  举报