diff --git a/internal/codec/pdf/metadata.go b/internal/codec/pdf/metadata.go index a8d044e..2d44495 100644 --- a/internal/codec/pdf/metadata.go +++ b/internal/codec/pdf/metadata.go @@ -22,6 +22,33 @@ import ( "github.com/xiaoqidun/pdfgo" ) +// NormalizeDate 校验PDF日期,非严格模式补齐完整时区偏移的分隔符 +// 入参: text PDF日期字符串, strict 是否严格校验 +// 返回: string 标准日期字符串, error 日期错误 +func NormalizeDate(text string, strict bool) (string, error) { + if text == "" { + return text, nil + } + _, err := pdfgo.ParseDate(text) + if err == nil || strict { + return text, err + } + offset := len(text) - 5 + if offset < 6 || text[offset] != '+' && text[offset] != '-' { + return text, err + } + for _, digit := range text[offset+1:] { + if digit < '0' || digit > '9' { + return text, err + } + } + normalized := text[:offset+3] + "'" + text[offset+3:] + "'" + if _, parseErr := pdfgo.ParseDate(normalized); parseErr != nil { + return text, err + } + return normalized, nil +} + // MetadataDate 保留当地日历日期,PDF省略时区按GMT,XMP省略日或时区时不推算 // 入参: text 来源日期, xmp 是否为XMP日期 // 返回: time.Time 解析值, bool 是否精确到日, bool 是否可比较绝对时间, error 日期错误 diff --git a/ofdgo_pdf.go b/ofdgo_pdf.go index 2d0cb1a..0d90095 100644 --- a/ofdgo_pdf.go +++ b/ofdgo_pdf.go @@ -33,7 +33,7 @@ var ErrPDFPassword = pdfgo.ErrPassword // PDFImportOptions 指定PDF转换的密码、运行时后端、进度通知与检查策略 // Password默认使用PDF安全处理器编码,PasswordUTF8启用UTF-8输入及标准密码准备,空值尝试空密码 -// Strict禁止恢复缺失资源或丢失内容语义,默认在报告中记录警告 +// Strict禁止日期兼容、恢复缺失资源或丢失内容语义,默认在报告中记录警告 // RendererOptions复用渲染器字体来源和后端配置,Backends优先于其中的后端设置 // RasterDPI指定局部合成与三维绘制精度,0沿用渲染器DPI,默认300dpi,Strict禁用局部合成 // OnProgress按open、pages、convert及write.*阶段报告进度,total为0表示总量未知 @@ -307,7 +307,7 @@ func ImportPDF(ctx context.Context, input *Input, options PDFImportOptions) (*Ed } if len(editor.pages) != 0 { importer.page = -1 - if err := importer.documentMetadata(ctx); err != nil { + if err := importer.documentMetadata(ctx, options.Strict); err != nil { return nil, PDFImportReport{}, fmt.Errorf("import PDF metadata: %w", err) } if err := importer.documentAttachments(ctx); err != nil { diff --git a/ofdgo_pdf_structure.go b/ofdgo_pdf_structure.go index 7946e17..4142f11 100644 --- a/ofdgo_pdf_structure.go +++ b/ofdgo_pdf_structure.go @@ -908,9 +908,9 @@ func (b *pdfStructureBindings) reference(pageID string, sequence *pdfgo.ContentS } // documentMetadata 保留PDF元数据原文并映射文档描述,来源软件与扩展字段保存在独立标引中 -// 入参: ctx 取消上下文 +// 入参: ctx 取消上下文, strict 是否严格校验 // 返回: error 读取、解析、日期或提交错误 -func (p *pdfImporter) documentMetadata(ctx context.Context) error { +func (p *pdfImporter) documentMetadata(ctx context.Context, strict bool) error { if err := ctx.Err(); err != nil { return err } @@ -918,6 +918,19 @@ func (p *pdfImporter) documentMetadata(ctx context.Context) error { if err != nil { return err } + for _, field := range []struct { + name string + text *string + }{{"CreationDate", &info.CreationDate}, {"ModDate", &info.ModDate}} { + text, err := codecpdf.NormalizeDate(*field.text, strict) + if err != nil { + return fmt.Errorf("PDF metadata %s: %w", field.name, err) + } + if text != *field.text { + p.report.Warnings = append(p.report.Warnings, pdfgo.Diagnostic{Message: "PDF metadata " + field.name + ": time zone separators normalized"}) + *field.text = text + } + } values, original, err := p.informationMetadata(ctx, info) if err != nil { return err @@ -1006,7 +1019,7 @@ func (p *pdfImporter) documentMetadata(ctx context.Context) error { return ctx.Err() } -// informationMetadata 收集已声明字段并保存原始文本,XML无法表示的字符使用Base64保留 +// informationMetadata 收集已声明字段并保存文本,日期使用校验结果,XML无法表示的字符使用Base64保留 // 入参: ctx 取消上下文, info 已解码的信息字典 // 返回: map[string]pdfMetadataValue 可映射字段, StructureNode 原始字段, error 读取或取消错误 func (p *pdfImporter) informationMetadata(ctx context.Context, info pdfgo.DocumentInfo) (map[string]pdfMetadataValue, StructureNode, error) {