删除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
}

浙公网安备 33010602011771号