使用Golang批量修改XML的lastModifiedBy字段:mxj解析丢字段的解决方法
lastModifiedBy in XML Files Without Losing Fields Got it, let's solve this problem of updating the lastModifiedBy field from Tom Hanks to Jerry Garcia across hundreds of XML files—without losing any fields in the process. Since converting XML to maps with mxj caused data loss, here are two reliable, targeted approaches that focus only on modifying that specific field:
Approach 1: Use xmlquery for Direct Node Manipulation
This library is perfect for querying and modifying XML nodes directly, which avoids the pitfalls of converting to maps and losing structure/namespaces.
- First, install the library:
go get github.com/antchfx/xmlquery
- Here's a complete batch processing example:
package main import ( "fmt" "os" "path/filepath" "github.com/antchfx/xmlquery" ) func main() { // Replace with your target directory containing XML files targetDir := "./xml-files" err := filepath.Walk(targetDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && filepath.Ext(path) == ".xml" { fmt.Printf("Processing file: %s\n", path) if err := updateLastModifiedBy(path); err != nil { return fmt.Errorf("failed to process %s: %w", path, err) } } return nil }) if err != nil { fmt.Printf("Batch processing failed: %v\n", err) } } func updateLastModifiedBy(filePath string) error { // Read the XML file f, err := os.Open(filePath) if err != nil { return err } defer f.Close() // Parse XML document doc, err := xmlquery.Parse(f) if err != nil { return err } // Query for the lastModifiedBy node (account for namespaces) // Adjust the XPath if your exact namespace/structure differs node := xmlquery.FindOne(doc, "//*[local-name()='lastModifiedBy']") if node != nil { // Update the node's text content node.InnerText = "Jerry Garcia" } // Write the modified XML back to the file outputFile, err := os.Create(filePath) if err != nil { return err } defer outputFile.Close() // Ensure proper XML declaration is preserved _, err = outputFile.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` + "\n") if err != nil { return err } _, err = outputFile.WriteString(doc.OutputXML(true)) return err }
The XPath //*[local-name()='lastModifiedBy'] works even with namespaces (like your cp: prefix) by targeting the node's local name, so you don't have to worry about hardcoding namespace prefixes.
Approach 2: Use encoding/xml with Structs (Preserve All Fields)
If you prefer using Go's standard library, you can define a struct that matches the core properties section, while preserving other fields with the xml:",any" tag to avoid data loss.
package main import ( "encoding/xml" "fmt" "os" "path/filepath" ) // Define struct to match the coreProperties element, preserving all other content type CoreProperties struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"` LastModifiedBy string `xml:"lastModifiedBy"` // Catch-all for other elements/attributes to prevent loss OtherElements []xml.Element `xml:",any"` OtherAttributes []xml.Attr `xml:",any"` } // Wrapper to hold the entire XML document type XMLDocument struct { XMLDeclaration xml.ProcInst `xml:"xml,attr"` CoreProps CoreProperties // Catch-all for other top-level elements OtherContent []xml.Element `xml:",any"` } func main() { targetDir := "./xml-files" err := filepath.Walk(targetDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && filepath.Ext(path) == ".xml" { fmt.Printf("Processing file: %s\n", path) if err := updateWithStdLib(path); err != nil { return fmt.Errorf("failed to process %s: %w", path, err) } } return nil }) if err != nil { fmt.Printf("Batch processing failed: %v\n", err) } } func updateWithStdLib(filePath string) error { data, err := os.ReadFile(filePath) if err != nil { return err } var doc XMLDocument if err := xml.Unmarshal(data, &doc); err != nil { return err } // Update the field if it exists if doc.CoreProps.LastModifiedBy == "Tom Hanks" { doc.CoreProps.LastModifiedBy = "Jerry Garcia" } // Marshal back to XML, preserving formatting modifiedData, err := xml.MarshalIndent(doc, "", " ") if err != nil { return err } // Prepend the XML declaration (MarshalIndent doesn't include it by default) finalData := append([]byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`+"\n"), modifiedData...) return os.WriteFile(filePath, finalData, 0644) }
This approach uses the standard library and ensures no fields are lost by using the xml:",any" tag to capture any content not explicitly defined in the struct.
Both methods will safely update just the lastModifiedBy field while keeping all other XML content intact—perfect for your batch processing needs.
内容的提问来源于stack exchange,提问作者Gofurther

