diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index 75ae385..325ef31 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -1,4 +1,4 @@ -import { CanvasEditor, canEditObject, editTextValue, richTextModel, fontFaceStyle, missingGlyphMessage, objectEditReason, selectedText, lineShape, selectionBounds, pagePoint } from "./ofdgo_edit.js"; +import { SVG_NAMESPACE, CanvasEditor, canEditObject, editTextValue, richTextModel, fontFaceStyle, missingGlyphMessage, objectEditReason, selectedText, lineShape, selectionBounds, pagePoint } from "./ofdgo_edit.js"; import { FontManager, FontPicker } from "./ofdgo_font.js"; const MM_TO_PX = 96 / 25.4; @@ -9063,9 +9063,9 @@ function mountPageLinks(page, surface) { anchor.style.height = `${link.height / page.height * 100}%`; if (link.path) { anchor.classList.add("page-region-link"); - const region = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const region = document.createElementNS(SVG_NAMESPACE, "svg"); region.setAttribute("viewBox", `${link.x} ${link.y} ${link.width} ${link.height}`); - const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + const path = document.createElementNS(SVG_NAMESPACE, "path"); path.setAttribute("d", link.path); region.append(path); anchor.append(region); diff --git a/assets/webui/ofdgo_edit.js b/assets/webui/ofdgo_edit.js index 15393e9..5bc4e62 100644 --- a/assets/webui/ofdgo_edit.js +++ b/assets/webui/ofdgo_edit.js @@ -1,4 +1,5 @@ const PX_PER_MM = 96 / 25.4; +export const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; const FORMAT_KEYS = ["font", "size", "color", "bold", "italic", "underline", "strikeout", "shadow", "background"]; const RICH_CLIPBOARD = "application/x-ofdgo-rich-text"; @@ -620,18 +621,18 @@ export class CanvasEditor { } if (object.type === "PathObject" && (object.shape || object.outline || object.contours?.length)) { node.classList.add("edit-contour"); - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const svg = document.createElementNS(SVG_NAMESPACE, "svg"); svg.classList.add("edit-outline"); svg.setAttribute("preserveAspectRatio", "none"); let contour; if (object.shape || object.oriented || !object.contours?.length) { const shape = object.shape || object.oriented?.kind; - contour = document.createElementNS("http://www.w3.org/2000/svg", shape === "line" ? "line" : shape === "ellipse" ? "ellipse" : shape === "rectangle" ? "rect" : "path"); + contour = document.createElementNS(SVG_NAMESPACE, shape === "line" ? "line" : shape === "ellipse" ? "ellipse" : shape === "rectangle" ? "rect" : "path"); if (!shape) contour.setAttribute("d", object.outline); svg.append(contour); } else { for (const path of object.contours) { - const contour = document.createElementNS("http://www.w3.org/2000/svg", "path"); + const contour = document.createElementNS(SVG_NAMESPACE, "path"); contour.setAttribute("d", path.path); svg.append(contour); } @@ -1354,10 +1355,10 @@ export class CanvasEditor { } createPreview(surface, page, shape, style) { - const preview = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const preview = document.createElementNS(SVG_NAMESPACE, "svg"); preview.classList.add("edit-preview"); preview.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`); - const node = document.createElementNS("http://www.w3.org/2000/svg", lineShape(shape) && shape !== "line" ? "path" : shape === "rectangle" ? "rect" : shape); + const node = document.createElementNS(SVG_NAMESPACE, lineShape(shape) && shape !== "line" ? "path" : shape === "rectangle" ? "rect" : shape); node.setAttribute("fill", style.fill ? style.fillColor : "none"); node.setAttribute("stroke", style.stroke ? style.strokeColor : "none"); node.setAttribute("stroke-width", style.lineWidth); diff --git a/ofdgo_editor_font_binding.go b/ofdgo_editor_font_binding.go index 4d32cbc..945511d 100644 --- a/ofdgo_editor_font_binding.go +++ b/ofdgo_editor_font_binding.go @@ -679,7 +679,7 @@ func checkFontBinding(ctx context.Context, object TextObject, font *FontResource return ctx.Err() } -// externalFontName 去除PDF字体子集前缀,不猜测或改写其他名称 +// externalFontName 去除PDF字体子集前缀 // 入参: name 字体名称 // 返回: string 外部匹配名称 func externalFontName(name string) string { @@ -966,7 +966,7 @@ func pruneUnreferencedFiles(ctx context.Context, reader *Reader, candidates ...s return fmt.Errorf("unknown file dependencies in %s", name) } for _, attr := range node.Attr { - if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != "http://www.w3.org/XML/1998/namespace" { + if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != XMLNamespace { uncertain = true return fmt.Errorf("unknown file dependencies in %s", name) } diff --git a/ofdgo_editor_font_scan.go b/ofdgo_editor_font_scan.go index 9660148..b4d1177 100644 --- a/ofdgo_editor_font_scan.go +++ b/ofdgo_editor_font_scan.go @@ -143,7 +143,7 @@ func scanEditorFontFilesMode(ctx context.Context, reader *Reader, index int, ins return nil } for _, attr := range node.attrs { - if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != "http://www.w3.org/XML/1998/namespace" { + if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != XMLNamespace { scan.unsafe = true } } diff --git a/ofdgo_editor_import.go b/ofdgo_editor_import.go index 03ad18d..5e38676 100644 --- a/ofdgo_editor_import.go +++ b/ofdgo_editor_import.go @@ -671,7 +671,7 @@ func (m *editorPageImport) outlines() ([]byte, error) { if attr.Name.Space == "" || attr.Name.Space == "xmlns" { continue } - if attr.Name.Space == "http://www.w3.org/2001/XMLSchema-instance" && (attr.Name.Local == "schemaLocation" || attr.Name.Local == "noNamespaceSchemaLocation") { + if attr.Name.Space == XMLSchemaInstanceNamespace && (attr.Name.Local == "schemaLocation" || attr.Name.Local == "noNamespaceSchemaLocation") { continue } return fmt.Errorf("cannot remap outline extension attribute %q", attr.Name.Local) diff --git a/ofdgo_editor_invoice.go b/ofdgo_editor_invoice.go index 7e2adbf..aaf9564 100644 --- a/ofdgo_editor_invoice.go +++ b/ofdgo_editor_invoice.go @@ -30,7 +30,7 @@ import ( // invoiceTagType 标识发票票面编辑关联,业务数据保持原规范格式 const invoiceTagType = "OFDGo.Invoice" -// invoiceNamespace 标识发票编辑关联的命名空间,版本仅随不兼容的语义变更升级 +// invoiceNamespace 表示发票编辑关联命名空间 const invoiceNamespace = "https://ofdgo.aite.me/ns/invoice/1" // InvoiceCreateOptions 指定结构化附件名称和可选票面,未指定票面时不新增页面 @@ -1129,7 +1129,7 @@ func invoiceRemapBindings(before, after Invoice, bindings []InvoiceBinding) ([]I return result, nil } -// invoiceBindingCoverage 拒绝将未关联的字段修改误报为已同步,不猜测缺失图元 +// invoiceBindingCoverage 检查修改字段的票面关联覆盖情况 // 入参: before 原发票, after 新发票, bindings 显式关联 // 返回: error 未绑定字段错误 func invoiceBindingCoverage(before, after Invoice, bindings []InvoiceBinding) error { diff --git a/ofdgo_editor_open.go b/ofdgo_editor_open.go index 9f43e0b..6d026e5 100644 --- a/ofdgo_editor_open.go +++ b/ofdgo_editor_open.go @@ -456,7 +456,7 @@ func (e *Editor) repairSourceObjectIDs(data []byte, root *editorXML) ([]byte, bo return editorPatchXML(data, patches), true, nil } -// checkDuplicateObjectReferences 拒绝无法判定目标的编号引用,不猜测其所属对象 +// checkDuplicateObjectReferences 检查编号引用的目标歧义 // 入参: duplicates 重复编号 // 返回: error 引用歧义或读取错误 func (e *Editor) checkDuplicateObjectReferences(duplicates map[string]bool) error { diff --git a/ofdgo_editor_reference.go b/ofdgo_editor_reference.go index d899d30..fc70402 100644 --- a/ofdgo_editor_reference.go +++ b/ofdgo_editor_reference.go @@ -181,7 +181,7 @@ func (s *editorReferenceScan) accept(token xml.Token) bool { } } for _, attr := range token.Attr { - if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" || attr.Name.Local == "xmlns:ofd" || attr.Name.Space == "http://www.w3.org/XML/1998/namespace" { + if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" || attr.Name.Local == "xmlns:ofd" || attr.Name.Space == XMLNamespace { continue } if attr.Name.Space != "" { diff --git a/ofdgo_editor_resources.go b/ofdgo_editor_resources.go index a3442fd..486ebb2 100644 --- a/ofdgo_editor_resources.go +++ b/ofdgo_editor_resources.go @@ -427,7 +427,7 @@ func normalizeResourceReferences(references map[string]bool) { } } -// editorResourceNode 按资源标识查找标准资源节点,等价别名冲突时不猜测 +// editorResourceNode 按资源标识查找标准资源节点并检查别名冲突 // 入参: root 资源文件根节点, group 资源集合名称, kind 资源类型, id 资源标识 // 返回: *editorXML 资源节点,未找到时为nil func editorResourceNode(root *editorXML, group, kind, id string) *editorXML { diff --git a/ofdgo_invoice.go b/ofdgo_invoice.go index 2013b33..605239c 100644 --- a/ofdgo_invoice.go +++ b/ofdgo_invoice.go @@ -23,7 +23,7 @@ import ( "strings" ) -// 发票读取限额,不扫描未声明资源或嵌套附件包 +// 发票读取限额 const ( invoiceXMLLimit = 16 << 20 invoiceTotalLimit = 64 << 20 @@ -63,7 +63,7 @@ type Invoice struct { Facts []InvoiceFact `json:"facts,omitempty"` } -// InvoiceParty 保存购销双方信息,不从名称或其他字段推测税号 +// InvoiceParty 保存购销双方信息 type InvoiceParty struct { Name string `json:"name,omitempty"` TaxID string `json:"taxId,omitempty"` @@ -94,7 +94,7 @@ type InvoiceSource struct { NodePath string `json:"nodePath,omitempty"` } -// InvoiceIssue 保存来源明确的解析诊断,Code为机器可读类别,不包含真伪判断 +// InvoiceIssue 保存来源明确的解析诊断,Code为机器可读类别 type InvoiceIssue struct { Code string `json:"code"` Message string `json:"message"` @@ -103,7 +103,7 @@ type InvoiceIssue struct { } // InvoiceFact 保存命名空间限定的业务事实、原始数值及精度属性,Nil与空文本分别表达 -// References保留对象引用标引的原始页标识和有序图元标识,不推测明细归属 +// References保留对象引用标引的原始页标识和有序图元标识 type InvoiceFact struct { Name string `json:"name"` Namespace string `json:"namespace,omitempty"` diff --git a/ofdgo_invoice_layout.go b/ofdgo_invoice_layout.go index 09f5705..8f5f5fd 100644 --- a/ofdgo_invoice_layout.go +++ b/ofdgo_invoice_layout.go @@ -70,7 +70,7 @@ func invoiceAppearanceMatches(text TextObject, value string) bool { return actual == expected } -// invoiceLayoutFields 按稳定顺序展开公共字段及明细,不推算缺失值 +// invoiceLayoutFields 按稳定顺序展开公共字段及明细 // 入参: invoice 发票数据 // 返回: []invoiceLayoutField 字段列表 func invoiceLayoutFields(invoice Invoice) []invoiceLayoutField { @@ -181,11 +181,11 @@ func invoiceLayoutFactVisible(invoice Invoice, fact InvoiceFact) bool { // 入参: value 发票,fact 原始字段 // 返回: 公共字段关联及是否明确映射 func invoiceEDRMFactBinding(value Invoice, fact InvoiceFact) (InvoiceBinding, bool) { - if value.Standard != invoiceEDRM && value.Standard != ofdNamespace2016 || fact.Namespace != value.Standard { + if value.Standard != InvoiceStandardEDRM2019 && value.Standard != ofdNamespace2016 || fact.Namespace != value.Standard { return InvoiceBinding{}, false } var path strings.Builder - nodePath := strings.ReplaceAll(fact.Source.NodePath, "{"+invoiceEDRM+"}", "{}") + nodePath := strings.ReplaceAll(fact.Source.NodePath, "{"+InvoiceStandardEDRM2019+"}", "{}") if value.Standard == ofdNamespace2016 { nodePath = strings.Replace(nodePath, "/root[1]", "/eInvoice[1]", 1) nodePath = strings.ReplaceAll(nodePath, "/", "/{}") diff --git a/ofdgo_invoice_write.go b/ofdgo_invoice_write.go index 6cc965b..5b3adae 100644 --- a/ofdgo_invoice_write.go +++ b/ofdgo_invoice_write.go @@ -81,7 +81,7 @@ type InvoiceContext struct { EndDate string `json:"endDate,omitempty"` } -// InvoiceUnit 保存单一度量的XBRL单位,不推测币种或数量单位 +// InvoiceUnit 保存单一度量的XBRL单位 type InvoiceUnit struct { ID string `json:"id"` Namespace string `json:"namespace"` @@ -121,7 +121,7 @@ type invoiceXMLPatch struct { func InvoiceFields(value Invoice) []InvoiceFieldDescriptor { format, supported := invoiceWriteFormat(value) probe := Invoice{Items: []InvoiceItem{{}}} - if value.Standard == invoiceEDRM || value.Standard == ofdNamespace2016 { + if value.Standard == InvoiceStandardEDRM2019 || value.Standard == ofdNamespace2016 { for _, path := range invoiceEDRMPaths() { item := &probe.Items[0] if value.Standard == ofdNamespace2016 { @@ -242,7 +242,7 @@ func InvoiceFields(value Invoice) []InvoiceFieldDescriptor { // 返回: 已适配格式列表 func InvoiceFormats() []InvoiceFormat { result := []InvoiceFormat{{Kind: "einv", Standard: InvoiceStandardEInvoice, Version: "0.3", Read: true, Create: true, Edit: true, Items: true}} - result = append(result, InvoiceFormat{Kind: "inv", Standard: invoiceEDRM, Version: "1.0", Read: true, Create: true, Edit: true, Items: true}) + result = append(result, InvoiceFormat{Kind: "inv", Standard: InvoiceStandardEDRM2019, Version: "1.0", Read: true, Create: true, Edit: true, Items: true}) result = append(result, InvoiceFormat{Kind: "inv", Standard: ofdNamespace2016, Version: "1.0", Read: true, Create: true, Edit: true}) for _, standard := range []string{InvoiceStandardVAT2023, InvoiceStandardDigital2023, InvoiceStandardAir2023, InvoiceStandardRail2023} { kind, version := invoiceTaxonomy(standard) @@ -268,7 +268,7 @@ func invoiceWriteContext(ctx context.Context) context.Context { return ctx } -// invoiceWriteFormat 查找完整格式标识,不推测缺失版本 +// invoiceWriteFormat 按标准、票种和版本查找格式 // 入参: value 发票 // 返回: 格式与匹配状态 func invoiceWriteFormat(value Invoice) (InvoiceFormat, bool) { @@ -496,7 +496,7 @@ func EncodeInvoiceWithOptions(ctx context.Context, value Invoice, options Invoic return nil, err } var data []byte - if value.Standard == invoiceEDRM { + if value.Standard == InvoiceStandardEDRM2019 { if len(options.HeaderXML)+len(options.SellerAuthenticationXML)+len(options.TaxSupervisionXML)+len(options.TaxBureauSignatureXML) != 0 { return nil, fmt.Errorf("EDRM creation does not support EInvoice sections") } @@ -646,7 +646,7 @@ func invoiceWriteRepresented(want, got Invoice) error { return nil } -// invoiceEncodeXBRL 构造单主体实例,扩展事实不得携带无法重建的上下文或元组 +// invoiceEncodeXBRL 构造单主体XBRL实例 // 入参: value 发票,options 显式分类标准、上下文和单位 // 返回: XBRL数据及表达错误 func invoiceEncodeXBRL(value Invoice, options InvoiceEncodingOptions) ([]byte, error) { @@ -663,7 +663,7 @@ func invoiceEncodeXBRL(value Invoice, options InvoiceEncodingOptions) ([]byte, e } var b strings.Builder b.WriteString(xml.Header) - fmt.Fprintf(&b, ``, invoiceXBRL, invoiceXSI, InvoiceCurrencyNamespace, value.Standard, invoiceLinkbase, invoiceXLink) + fmt.Fprintf(&b, ``, invoiceXBRL, XMLSchemaInstanceNamespace, InvoiceCurrencyNamespace, value.Standard, invoiceLinkbase, XLinkNamespace) fmt.Fprintf(&b, `%s`, invoiceWriteEscape(options.SchemaRef), options.Context.ID, invoiceWriteEscape(options.Context.Scheme), invoiceWriteEscape(options.Context.Identifier)) if options.Context.Instant != "" { if options.Context.StartDate != "" || options.Context.EndDate != "" { @@ -829,7 +829,7 @@ func invoicePlainGroups() []invoiceWriteField { return []invoiceWriteField{{"SellerInformation", "SellerIdNum SellerName SellerAddr SellerTelNum SellerBankName SellerBankAccNum"}, {"BuyerInformation", "BuyerIdNum BuyerName"}, {"BasicInformation", "TotalAmWithoutTax TotalTaxAm TotalTax-includedAmount"}, {"AdditionalInformation", "Remark"}} } -// invoiceEncodePlain 构造官方0.3字段路径,不捏造税务签名或编号 +// invoiceEncodePlain 构造0.3版电子发票业务XML // 入参: value 发票 // 返回: XML及不可表达的扩展错误 func invoiceEncodePlain(value Invoice) ([]byte, error) { @@ -1002,7 +1002,7 @@ func PatchInvoice(ctx context.Context, data []byte, changes []InvoiceFieldChange if change.Revision != "" && change.Revision != revision { return nil, fmt.Errorf("stale invoice source revision") } - oldNil := invoiceXMLAttr(s.node, invoiceXSI, "nil") + oldNil := invoiceXMLAttr(s.node, XMLSchemaInstanceNamespace, "nil") isNil := oldNil == "true" || oldNil == "1" expectedNil := false if change.ExpectedNil != nil { @@ -1082,7 +1082,7 @@ func invoiceValidateXMLWrite(ctx context.Context, before, after []byte) error { return nil } -// invoiceWriteXMLProblems 按节点路径及问题值区分诊断,避免不同候选的同类问题相互抵消 +// invoiceWriteXMLProblems 按节点路径及问题值归类诊断 // 入参: ctx 上下文,data 原文,tuples 是否检查元组必有字段 // 返回: 定位诊断及解析错误 func invoiceWriteXMLProblems(ctx context.Context, data []byte, tuples bool) (map[string]InvoiceIssue, error) { @@ -1112,11 +1112,11 @@ func invoiceWriteXMLProblems(ctx context.Context, data []byte, tuples bool) (map } path += "/" + n.name.Local kind, _ := invoiceTaxonomy(n.name.Space) - nilValue := invoiceXMLAttr(n, invoiceXSI, "nil") + nilValue := invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") empty := nilValue == "true" || nilValue == "1" date := kind != "" && invoiceWriteDateFact(kind, n.name.Local) plainDate := root.name == (xml.Name{Local: "EInvoice"}) && path == "/EInvoice/TaxSupervisionInfo/IssueTime" - edrmDate := n.parent == root && n.name == (xml.Name{Space: invoiceEDRM, Local: "IssueDate"}) + edrmDate := n.parent == root && n.name == (xml.Name{Space: InvoiceStandardEDRM2019, Local: "IssueDate"}) if len(n.children) == 0 && !empty && (date || plainDate || edrmDate) { value := n.text.String() parseValue := value @@ -1157,7 +1157,7 @@ func invoiceWriteXMLProblems(ctx context.Context, data []byte, tuples bool) (map return nil, err } for _, value := range report.Invoices { - if value.Standard != invoiceEDRM { + if value.Standard != InvoiceStandardEDRM2019 { continue } issues, err := ValidateInvoice(ctx, value) @@ -1171,7 +1171,7 @@ func invoiceWriteXMLProblems(ctx context.Context, data []byte, tuples bool) (map for _, issue := range report.Issues { value := "" if n := nodes[issue.Source.NodePath]; n != nil { - value = n.text.String() + "\x00" + invoiceXMLAttr(n, invoiceXSI, "nil") + value = n.text.String() + "\x00" + invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") } add(issue, value) } @@ -1196,14 +1196,14 @@ func invoiceWriteLeaf(n *invoiceXMLNode, value string, nilValue bool) ([]byte, e start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:" + prefix}, Value: n.ns[prefix]}) } for _, a := range n.attrs { - if a.Name.Space == "xmlns" || a.Name.Local == "xmlns" && a.Name.Space == "" || a.Name.Space == invoiceXSI && a.Name.Local == "nil" || nilValue && a.Name.Space == "" && (a.Name.Local == "decimals" || a.Name.Local == "precision") { + if a.Name.Space == "xmlns" || a.Name.Local == "xmlns" && a.Name.Space == "" || a.Name.Space == XMLSchemaInstanceNamespace && a.Name.Local == "nil" || nilValue && a.Name.Space == "" && (a.Name.Local == "decimals" || a.Name.Local == "precision") { continue } start.Attr = append(start.Attr, a) } if nilValue { - start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: invoiceXSI, Local: "nil"}, Value: "true"}) - } else if invoiceXMLAttr(n, invoiceXSI, "nil") == "true" || invoiceXMLAttr(n, invoiceXSI, "nil") == "1" { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: XMLSchemaInstanceNamespace, Local: "nil"}, Value: "true"}) + } else if invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") == "true" || invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") == "1" { kind, _ := invoiceTaxonomy(n.name.Space) if invoiceNumeric(kind, n.name.Local) { start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "decimals"}, Value: "INF"}) @@ -1288,7 +1288,7 @@ func UpdateInvoiceXML(ctx context.Context, data []byte, invoiceIndex int, value if err = invoiceWriteNewIssues(filter(oldIssues), filter(newIssues)); err != nil { return nil, err } - if value.Standard == invoiceEDRM { + if value.Standard == InvoiceStandardEDRM2019 { return invoiceUpdateEDRM(ctx, data, old, value) } root, spans, err := invoiceWriteSpans(ctx, data) @@ -1539,7 +1539,7 @@ func invoiceEDRMPaths() []string { return strings.Fields("/eInvoice/InvoiceCode /eInvoice/InvoiceNo /eInvoice/IssueDate /eInvoice/Buyer/BuyerName /eInvoice/Buyer/BuyerTaxID /eInvoice/Seller/SellerName /eInvoice/Seller/SellerTaxID /eInvoice/TaxInclusiveTotalAmount /eInvoice/Note /eInvoice/TaxTotalAmount /eInvoice/TaxExclusiveTotalAmount /eInvoice/GoodsInfos/GoodsInfo/Item /eInvoice/GoodsInfos/GoodsInfo/Specification /eInvoice/GoodsInfos/GoodsInfo/MeasurementDimension /eInvoice/GoodsInfos/GoodsInfo/Quantity /eInvoice/GoodsInfos/GoodsInfo/Price /eInvoice/GoodsInfos/GoodsInfo/Amount /eInvoice/GoodsInfos/GoodsInfo/TaxScheme /eInvoice/GoodsInfos/GoodsInfo/TaxAmount") } -// invoiceEncodeEDRM 按2019版路径创建业务XML,不伪造认证字段或丢弃扩展事实 +// invoiceEncodeEDRM 创建2019版电子发票业务XML // 入参: value 明确提供的发票业务字段 // 返回: 独立XML及不可表达错误 func invoiceEncodeEDRM(value Invoice) ([]byte, error) { @@ -1551,7 +1551,7 @@ func invoiceEncodeEDRM(value Invoice) ([]byte, error) { } var b strings.Builder b.WriteString(xml.Header) - fmt.Fprintf(&b, ``, invoiceEDRM) + fmt.Fprintf(&b, ``, InvoiceStandardEDRM2019) write := func(name, text string) { if text != "" { fmt.Fprintf(&b, "<%s>%s", name, invoiceWriteEscape(text), name) @@ -1609,7 +1609,7 @@ func invoiceUpdateEDRM(ctx context.Context, data []byte, old, value Invoice) ([] } for _, name := range []string{"Buyer", "Seller", "GoodsInfos"} { changed := name == "GoodsInfos" && !reflect.DeepEqual(old.Items, value.Items) || name == "Buyer" && old.Buyer != value.Buyer || name == "Seller" && old.Seller != value.Seller - groups := invoiceChildren(root, invoiceEDRM, name) + groups := invoiceChildren(root, InvoiceStandardEDRM2019, name) if len(groups) > 1 { return nil, fmt.Errorf("ambiguous invoice group %s", name) } @@ -1630,14 +1630,14 @@ func invoiceUpdateEDRM(ctx context.Context, data []byte, old, value Invoice) ([] for _, group := range []string{"", "Buyer", "Seller"} { parent := root if group != "" { - parents := invoiceChildren(root, invoiceEDRM, group) + parents := invoiceChildren(root, InvoiceStandardEDRM2019, group) if len(parents) > 0 { parent = parents[0] } else { parent = nil } } - desired := &invoiceXMLNode{name: xml.Name{Space: invoiceEDRM, Local: group}} + desired := &invoiceXMLNode{name: xml.Name{Space: InvoiceStandardEDRM2019, Local: group}} var names []string for _, path := range invoiceEDRMPaths() { parts := strings.Split(path, "/") @@ -1651,10 +1651,10 @@ func invoiceUpdateEDRM(ctx context.Context, data []byte, old, value Invoice) ([] } name := parts[len(parts)-1] names = append(names, name) - node := &invoiceXMLNode{name: xml.Name{Space: invoiceEDRM, Local: name}} + node := &invoiceXMLNode{name: xml.Name{Space: InvoiceStandardEDRM2019, Local: name}} text := *target if name == "IssueDate" && parent != nil { - existing := invoiceChildren(parent, invoiceEDRM, name) + existing := invoiceChildren(parent, InvoiceStandardEDRM2019, name) if len(existing) == 1 && strings.Contains(existing[0].text.String(), "年") && text != "" { date, err := time.Parse("2006-01-02", text) if err != nil { @@ -1676,26 +1676,26 @@ func invoiceUpdateEDRM(ctx context.Context, data []byte, old, value Invoice) ([] if err != nil { return nil, err } - patch, err := invoiceWriteOrderedInsert(data, spans, root, invoiceEDRM, group, order, raw) + patch, err := invoiceWriteOrderedInsert(data, spans, root, InvoiceStandardEDRM2019, group, order, raw) if err != nil { return nil, err } patches = append(patches, patch) } else { for _, name := range names { - if len(invoiceChildren(parent, invoiceEDRM, name)) == 0 && group == "" { - raw, err := invoiceWriteNode(invoiceChildren(desired, invoiceEDRM, name)[0]) + if len(invoiceChildren(parent, InvoiceStandardEDRM2019, name)) == 0 && group == "" { + raw, err := invoiceWriteNode(invoiceChildren(desired, InvoiceStandardEDRM2019, name)[0]) if err != nil { return nil, err } - patch, err := invoiceWriteOrderedInsert(data, spans, parent, invoiceEDRM, name, order, raw) + patch, err := invoiceWriteOrderedInsert(data, spans, parent, InvoiceStandardEDRM2019, name, order, raw) if err != nil { return nil, err } patches = append(patches, patch) continue } - changes, err := invoiceWriteMergeChildren(data, spans, parent, desired, invoiceEDRM, []string{name}) + changes, err := invoiceWriteMergeChildren(data, spans, parent, desired, InvoiceStandardEDRM2019, []string{name}) if err != nil { return nil, err } @@ -1704,15 +1704,15 @@ func invoiceUpdateEDRM(ctx context.Context, data []byte, old, value Invoice) ([] } } if !reflect.DeepEqual(old.Items, value.Items) { - generated := &invoiceXMLNode{name: xml.Name{Space: invoiceEDRM, Local: "GoodsInfos"}} + generated := &invoiceXMLNode{name: xml.Name{Space: InvoiceStandardEDRM2019, Local: "GoodsInfos"}} for _, item := range value.Items { - node := &invoiceXMLNode{name: xml.Name{Space: invoiceEDRM, Local: "GoodsInfo"}} + node := &invoiceXMLNode{name: xml.Name{Space: InvoiceStandardEDRM2019, Local: "GoodsInfo"}} for _, path := range invoiceEDRMPaths() { if !strings.HasPrefix(path, "/eInvoice/GoodsInfos/GoodsInfo/") { continue } p, _ := invoiceEDRMField(&value, &item, path) - leaf := &invoiceXMLNode{name: xml.Name{Space: invoiceEDRM, Local: path[strings.LastIndexByte(path, '/')+1:]}} + leaf := &invoiceXMLNode{name: xml.Name{Space: InvoiceStandardEDRM2019, Local: path[strings.LastIndexByte(path, '/')+1:]}} leaf.text.WriteString(*p) leaf.parts = []invoiceXMLPart{{text: *p}} node.children = append(node.children, leaf) @@ -1721,7 +1721,7 @@ func invoiceUpdateEDRM(ctx context.Context, data []byte, old, value Invoice) ([] generated.children = append(generated.children, node) generated.parts = append(generated.parts, invoiceXMLPart{child: node}) } - parents := invoiceChildren(root, invoiceEDRM, "GoodsInfos") + parents := invoiceChildren(root, InvoiceStandardEDRM2019, "GoodsInfos") if len(parents) == 0 { raw, err := invoiceWriteNode(generated) if err != nil { @@ -1801,7 +1801,7 @@ func invoiceWriteUnmapped(before, after []InvoiceFact) error { return nil } -// invoiceWriteOriginalOptions 读取原上下文及单位,不猜测或改写定义 +// invoiceWriteOriginalOptions 读取原上下文及单位定义 // 入参: root 原XML,value 原候选 // 返回: 原定义及不支持的复杂上下文错误 func invoiceWriteOriginalOptions(root *invoiceXMLNode, value Invoice) (InvoiceEncodingOptions, error) { @@ -1815,7 +1815,7 @@ func invoiceWriteOriginalOptions(root *invoiceXMLNode, value Invoice) (InvoiceEn } for _, n := range root.children { if n.name == (xml.Name{Space: invoiceLinkbase, Local: "schemaRef"}) && options.SchemaRef == "" { - options.SchemaRef = invoiceXMLAttr(n, invoiceXLink, "href") + options.SchemaRef = invoiceXMLAttr(n, XLinkNamespace, "href") } if n.name == (xml.Name{Space: invoiceXBRL, Local: "context"}) && invoiceXMLAttr(n, "", "id") == ref { dims, ok := invoiceContext(n) @@ -1939,15 +1939,15 @@ func invoiceWriteMergeChildren(data []byte, spans map[string]invoiceXMLSpan, old if len(existing[0].children) > 0 { return nil, fmt.Errorf("non-leaf invoice field %s", name) } - if len(targets) == 0 && (existing[0].text.String() == "" || invoiceXMLAttr(existing[0], invoiceXSI, "nil") == "true" || invoiceXMLAttr(existing[0], invoiceXSI, "nil") == "1") { + if len(targets) == 0 && (existing[0].text.String() == "" || invoiceXMLAttr(existing[0], XMLSchemaInstanceNamespace, "nil") == "true" || invoiceXMLAttr(existing[0], XMLSchemaInstanceNamespace, "nil") == "1") { continue } - if len(targets) > 0 && existing[0].text.String() == targets[0].text.String() && invoiceXMLAttr(existing[0], invoiceXSI, "nil") == invoiceXMLAttr(targets[0], invoiceXSI, "nil") { + if len(targets) > 0 && existing[0].text.String() == targets[0].text.String() && invoiceXMLAttr(existing[0], XMLSchemaInstanceNamespace, "nil") == invoiceXMLAttr(targets[0], XMLSchemaInstanceNamespace, "nil") { continue } if len(targets) > 0 { - oldNil := invoiceXMLAttr(existing[0], invoiceXSI, "nil") - newNil := invoiceXMLAttr(targets[0], invoiceXSI, "nil") + oldNil := invoiceXMLAttr(existing[0], XMLSchemaInstanceNamespace, "nil") + newNil := invoiceXMLAttr(targets[0], XMLSchemaInstanceNamespace, "nil") wasNil := oldNil == "true" || oldNil == "1" isNil := newNil == "true" || newNil == "1" if !s.empty && wasNil == isNil { @@ -1995,7 +1995,7 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i } space, name := value.Standard, invoiceWriteTuple(value.Kind) names := invoiceWriteItemNames(value.Kind) - if value.Standard == invoiceEDRM { + if value.Standard == InvoiceStandardEDRM2019 { name = "GoodsInfo" names = strings.Fields("Item Specification MeasurementDimension Quantity Price Amount TaxScheme TaxAmount") } else if value.Standard == InvoiceStandardEInvoice { @@ -2047,7 +2047,7 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i if path == "" { for _, f := range item.Fields { mapped := invoiceItemField(&InvoiceItem{}, value.Kind, f.Name) - if value.Standard == InvoiceStandardEInvoice || value.Standard == invoiceEDRM || !invoiceWriteContains(names, f.Name) { + if value.Standard == InvoiceStandardEInvoice || value.Standard == InvoiceStandardEDRM2019 || !invoiceWriteContains(names, f.Name) { return nil, fmt.Errorf("unsupported new invoice item fact %s", f.Name) } if mapped != nil { @@ -2073,12 +2073,12 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i continue } s, ok := spans[f.Source.NodePath] - if !ok || s.node.parent == nil || s.node.parent.path != path || f.Value != s.node.text.String() || f.Nil != (invoiceXMLAttr(s.node, invoiceXSI, "nil") == "true" || invoiceXMLAttr(s.node, invoiceXSI, "nil") == "1") { + if !ok || s.node.parent == nil || s.node.parent.path != path || f.Value != s.node.text.String() || f.Nil != (invoiceXMLAttr(s.node, XMLSchemaInstanceNamespace, "nil") == "true" || invoiceXMLAttr(s.node, XMLSchemaInstanceNamespace, "nil") == "1") { return nil, fmt.Errorf("unmapped item fact changes require PatchInvoice") } } n := original[path] - if value.Standard == invoiceEDRM { + if value.Standard == InvoiceStandardEDRM2019 { previous := originalItems[path] if err := invoiceWriteUnmapped(previous.Fields, item.Fields); err != nil { return nil, err @@ -2089,7 +2089,7 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i before, _ := invoiceEDRMField(&old, &previous, field) after, _ := invoiceEDRMField(&value, &item, field) if before != nil && after != nil && *before == *after { - children := invoiceChildren(n, invoiceEDRM, child.name.Local) + children := invoiceChildren(n, InvoiceStandardEDRM2019, child.name.Local) if len(children) == 0 { continue } @@ -2172,11 +2172,11 @@ func invoiceWriteOrderedInsert(data []byte, spans map[string]invoiceXMLSpan, par return invoiceWriteInsert(spans[parent.path], raw, data) } -// invoiceWriteCreation 检查官方元组序列中的必有字段,不虚构缺失业务值 +// invoiceWriteCreation 检查官方元组序列中的必有字段 // 入参: value 发票 // 返回: 缺少元组字段错误 func invoiceWriteCreation(value Invoice) error { - if value.Standard == InvoiceStandardEInvoice || value.Standard == invoiceEDRM { + if value.Standard == InvoiceStandardEInvoice || value.Standard == InvoiceStandardEDRM2019 { return nil } for i, item := range value.Items { diff --git a/ofdgo_invoice_xml.go b/ofdgo_invoice_xml.go index 974ebd3..2c9c49b 100644 --- a/ofdgo_invoice_xml.go +++ b/ofdgo_invoice_xml.go @@ -28,30 +28,6 @@ import ( "time" ) -// invoiceXMLNamespace 表示XML预定义命名空间 -const invoiceXMLNamespace = "http://www.w3.org/XML/1998/namespace" - -// invoiceXSI 表示XMLSchema实例命名空间 -const invoiceXSI = "http://www.w3.org/2001/XMLSchema-instance" - -// invoiceXLink 表示XML链接命名空间 -const invoiceXLink = "http://www.w3.org/1999/xlink" - -// invoiceXMLSignature 表示XML数字签名命名空间 -const invoiceXMLSignature = "http://www.w3.org/2000/09/xmldsig#" - -// invoiceXBRL 表示XBRL实例命名空间 -const invoiceXBRL = "http://www.xbrl.org/2003/instance" - -// invoiceLinkbase 表示XBRL链接库命名空间 -const invoiceLinkbase = "http://www.xbrl.org/2003/linkbase" - -// invoiceDimension 表示XBRL实例维度命名空间 -const invoiceDimension = "http://xbrl.org/2006/xbrldi" - -// InvoiceCurrencyNamespace 表示InvoiceUnit中ISO货币度量使用的命名空间 -const InvoiceCurrencyNamespace = "http://www.xbrl.org/2003/iso4217" - // InvoiceStandardVAT2023 表示2023版增值税发票分类标准 const InvoiceStandardVAT2023 = "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/inv" @@ -70,17 +46,29 @@ const InvoiceStandardVAT2021 = "http://xbrl.mof.gov.cn/taxonomy/2021-09-30/inv" // InvoiceStandardAir2021 表示2021版航空运输电子客票行程单分类标准 const InvoiceStandardAir2021 = "http://xbrl.mof.gov.cn/taxonomy/2021-11-30/atr" -// InvoiceStandardEInvoice 表示无命名空间的EInvoice业务标准,版本由Invoice.Version指定 -const InvoiceStandardEInvoice = "EInvoice" - -// InvoiceStandardObjectReference 表示基于OFD对象引用的发票标引,业务值来自当前文档的图元 -const InvoiceStandardObjectReference = ofdNamespace2016 - // InvoiceStandardEDRM2019 表示2019版电子发票业务标准 const InvoiceStandardEDRM2019 = "http://www.edrm.org.cn/schema/e-invoice/2019" -// invoiceEDRM 表示2019版电子发票业务命名空间 -const invoiceEDRM = InvoiceStandardEDRM2019 +// InvoiceStandardEInvoice 表示无命名空间的EInvoice业务标准,版本由Invoice.Version指定 +const InvoiceStandardEInvoice = "EInvoice" + +// InvoiceStandardObjectReference 表示基于OFD对象引用的发票标引 +const InvoiceStandardObjectReference = ofdNamespace2016 + +// InvoiceCurrencyNamespace 表示InvoiceUnit中ISO货币度量使用的命名空间 +const InvoiceCurrencyNamespace = "http://www.xbrl.org/2003/iso4217" + +// invoiceXBRL 表示XBRL实例命名空间 +const invoiceXBRL = "http://www.xbrl.org/2003/instance" + +// invoiceLinkbase 表示XBRL链接库命名空间 +const invoiceLinkbase = "http://www.xbrl.org/2003/linkbase" + +// invoiceDimension 表示XBRL实例维度命名空间 +const invoiceDimension = "http://xbrl.org/2006/xbrldi" + +// invoiceXMLSignature 表示XML数字签名命名空间 +const invoiceXMLSignature = "http://www.w3.org/2000/09/xmldsig#" // invoiceDecimalPattern 仅接受XML十进制词法 var invoiceDecimalPattern = regexp.MustCompile(`^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$`) @@ -195,7 +183,7 @@ func invoiceXMLTree(ctx context.Context, data []byte) (*invoiceXMLNode, error) { return nil, fmt.Errorf("invoice XML exceeds namespace URI limit") } } - n := &invoiceXMLNode{name: v.Name, attrs: v.Attr, parent: current, ns: map[string]string{"xml": invoiceXMLNamespace}, counts: map[xml.Name]int{}, start: d.InputOffset()} + n := &invoiceXMLNode{name: v.Name, attrs: v.Attr, parent: current, ns: map[string]string{"xml": XMLNamespace}, counts: map[xml.Name]int{}, start: d.InputOffset()} index := 1 if current != nil { for k, v := range current.ns { @@ -302,7 +290,7 @@ func parseInvoiceXML(ctx context.Context, data []byte, source InvoiceSource) (In if source.Kind == "tag" && invoiceObjectReferences(root) { return report, nil } - if root.name == (xml.Name{Local: "EInvoice"}) || root.name.Local == "eInvoice" && (root.name.Space == "" || root.name.Space == invoiceEDRM) { + if root.name == (xml.Name{Local: "EInvoice"}) || root.name.Local == "eInvoice" && (root.name.Space == "" || root.name.Space == InvoiceStandardEDRM2019) { return invoicePlainXML(ctx, root, source) } if root.name != (xml.Name{Space: invoiceXBRL, Local: "xbrl"}) { @@ -413,7 +401,7 @@ func parseInvoiceXML(ctx context.Context, data []byte, source InvoiceSource) (In ref := invoiceXMLAttr(n, "", "contextRef") s := source s.NodePath += n.path - fact := InvoiceFact{Name: n.name.Local, Namespace: n.name.Space, Value: n.text.String(), Context: ref, Unit: invoiceXMLAttr(n, "", "unitRef"), Decimals: invoiceXMLAttr(n, "", "decimals"), Precision: invoiceXMLAttr(n, "", "precision"), Source: s, Nil: invoiceXMLAttr(n, invoiceXSI, "nil") == "true" || invoiceXMLAttr(n, invoiceXSI, "nil") == "1", Dimensions: dimensions[ref]} + fact := InvoiceFact{Name: n.name.Local, Namespace: n.name.Space, Value: n.text.String(), Context: ref, Unit: invoiceXMLAttr(n, "", "unitRef"), Decimals: invoiceXMLAttr(n, "", "decimals"), Precision: invoiceXMLAttr(n, "", "precision"), Source: s, Nil: invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") == "true" || invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") == "1", Dimensions: dimensions[ref]} if len(n.children) > 0 { fact.Value = string(n.inner) } @@ -431,8 +419,8 @@ func parseInvoiceXML(ctx context.Context, data []byte, source InvoiceSource) (In } inv := &report.Invoices[i] inv.Facts = append(inv.Facts, fact) - nilValue := invoiceXMLAttr(n, invoiceXSI, "nil") - if invoiceXMLHasAttr(n, invoiceXSI, "nil") && nilValue != "true" && nilValue != "false" && nilValue != "1" && nilValue != "0" { + nilValue := invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") + if invoiceXMLHasAttr(n, XMLSchemaInstanceNamespace, "nil") && nilValue != "true" && nilValue != "false" && nilValue != "1" && nilValue != "0" { issue("invalid-value", fact.Name, "invalid xsi:nil", s) return nil } @@ -948,7 +936,7 @@ func invoiceMonetary(kind, name string) bool { return invoiceNumeric(kind, name) && name != "QuantityOfGoodsOrServices" && name != "VatRate" && name != "TaxRate" } -// invoiceCurrencyUnit 检查单一ISO币种度量,不猜测币种 +// invoiceCurrencyUnit 检查单一ISO币种度量 // 入参: unit 展开的单位表示 // 返回: 是否为三位大写币种度量 func invoiceCurrencyUnit(unit string) bool { @@ -1015,7 +1003,7 @@ func invoiceObjectReferences(root *invoiceXMLNode) bool { count := 0 var walk func(*invoiceXMLNode) bool walk = func(n *invoiceXMLNode) bool { - if n.name.Space == ofdNamespace2016 || n.name.Space == ofdNamespaceLegacy || n.name.Space == "ofd" || n.name.Space == invoiceEDRM { + if n.name.Space == ofdNamespace2016 || n.name.Space == ofdNamespaceLegacy || n.name.Space == "ofd" || n.name.Space == InvoiceStandardEDRM2019 { if n.name.Local == "ObjectRef" { count++ return len(n.children) == 0 && strings.TrimSpace(n.text.String()) != "" && invoiceXMLAttr(n, "", "PageRef") != "" @@ -1052,12 +1040,12 @@ func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSo inv := Invoice{Kind: "einv", Standard: InvoiceStandardEInvoice, Version: "0.3", Sources: []InvoiceSource{source}} namespace, itemPath := "", "/EInvoice/EInvoiceData/IssuItemInformation" if root.name.Local == "eInvoice" { - if invoiceXMLAttr(root, "", "Version") != "1.0" || len(invoiceChildren(root, invoiceEDRM, "InvoiceNo")) == 0 { + if invoiceXMLAttr(root, "", "Version") != "1.0" || len(invoiceChildren(root, InvoiceStandardEDRM2019, "InvoiceNo")) == 0 { report.Issues = append(report.Issues, InvoiceIssue{Code: "unsupported", Message: "unsupported eInvoice namespace or version", Source: source}) return report, nil } - inv.Kind, inv.Standard, inv.Version = "inv", invoiceEDRM, "1.0" - namespace, itemPath = invoiceEDRM, "/eInvoice/GoodsInfos/GoodsInfo" + inv.Kind, inv.Standard, inv.Version = "inv", InvoiceStandardEDRM2019, "1.0" + namespace, itemPath = InvoiceStandardEDRM2019, "/eInvoice/GoodsInfos/GoodsInfo" } else { headers := invoiceChildren(root, "", "Header") if len(headers) != 1 { @@ -1095,18 +1083,18 @@ func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSo } s := source s.NodePath += n.path - f := InvoiceFact{Name: n.name.Local, Namespace: n.name.Space, Value: n.text.String(), Source: s, Nil: invoiceXMLAttr(n, invoiceXSI, "nil") == "true" || invoiceXMLAttr(n, invoiceXSI, "nil") == "1"} + f := InvoiceFact{Name: n.name.Local, Namespace: n.name.Space, Value: n.text.String(), Source: s, Nil: invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") == "true" || invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") == "1"} inv.Facts = append(inv.Facts, f) if item != nil { item.Fields = append(item.Fields, f) } - nilValue := invoiceXMLAttr(n, invoiceXSI, "nil") - if invoiceXMLHasAttr(n, invoiceXSI, "nil") && nilValue != "true" && nilValue != "false" && nilValue != "1" && nilValue != "0" { + nilValue := invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") + if invoiceXMLHasAttr(n, XMLSchemaInstanceNamespace, "nil") && nilValue != "true" && nilValue != "false" && nilValue != "1" && nilValue != "0" { report.Issues = append(report.Issues, InvoiceIssue{Code: "invalid-value", Field: f.Name, Message: "invalid xsi:nil", Source: s}) return nil } dst, numeric := invoicePlainField(&inv, item, path) - if inv.Standard == invoiceEDRM { + if inv.Standard == InvoiceStandardEDRM2019 { dst, numeric = invoiceEDRMField(&inv, item, path) } if dst == nil { @@ -1116,7 +1104,7 @@ func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSo report.Issues = append(report.Issues, InvoiceIssue{Code: "invalid-value", Field: f.Name, Message: "nil fact contains a value", Source: s}) return nil } - if numeric && inv.Standard == invoiceEDRM && strings.TrimSpace(f.Value) == "" && !f.Nil { + if numeric && inv.Standard == InvoiceStandardEDRM2019 && strings.TrimSpace(f.Value) == "" && !f.Nil { return nil } if numeric && !f.Nil && invoiceDecimal(f.Value) == nil { @@ -1144,7 +1132,7 @@ func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSo if err := walk(root, "", nil); err != nil { return InvoiceReport{}, err } - if inv.Standard == invoiceEDRM && inv.Date != "" { + if inv.Standard == InvoiceStandardEDRM2019 && inv.Date != "" { if date, err := time.Parse("2006年01月02日", inv.Date); err == nil { inv.Date = date.Format("2006-01-02") } diff --git a/ofdgo_render_output.go b/ofdgo_render_output.go index 0aff578..f566494 100644 --- a/ofdgo_render_output.go +++ b/ofdgo_render_output.go @@ -31,8 +31,11 @@ import ( "golang.org/x/image/draw" ) +// xmpNamespace 表示XMP元数据命名空间 +const xmpNamespace = "http://ns.adobe.com/xap/1.0/" + // creatorXMP 记录独立导出文件的制作软件 -const creatorXMP = `` + ofdCreator + `` +const creatorXMP = `` + ofdCreator + `` // imageMetadataWriter 在图像固定头之后插入元数据,不缓存图像主体 type imageMetadataWriter struct { @@ -279,7 +282,7 @@ func (r *Renderer) RenderToJPEG(page *PageContent, writer io.Writer, options *jp if err != nil { return err } - metadata := []byte("http://ns.adobe.com/xap/1.0/\x00" + creatorXMP) + metadata := []byte(xmpNamespace + "\x00" + creatorXMP) segment := make([]byte, len(metadata)+4) segment[0], segment[1] = 0xff, 0xe1 binary.BigEndian.PutUint16(segment[2:], uint16(len(metadata)+2)) diff --git a/ofdgo_render_preview.go b/ofdgo_render_preview.go index 798c991..77355a0 100644 --- a/ofdgo_render_preview.go +++ b/ofdgo_render_preview.go @@ -22,6 +22,9 @@ import ( "io" ) +// svgNamespace 表示SVG预览文档命名空间 +const svgNamespace = "http://www.w3.org/2000/svg" + // PreviewOptions 指定页面显示方式,光栅预览不提供SVG对象分组 type PreviewOptions struct { Raster bool @@ -50,7 +53,7 @@ func (r *Renderer) RenderPreview(page *PageContent, writer io.Writer, options Pr return SVGResources{}, err } name := fmt.Sprintf("ofdgo-image-%x", sha256.Sum256(pixels.Bytes())) - _, err = fmt.Fprintf(writer, ``, box.W, box.H, box.W, box.H, box.W, box.H, name) + _, err = fmt.Fprintf(writer, ``, box.W, box.H, box.W, box.H, box.W, box.H, name) if err != nil { return SVGResources{}, err } @@ -120,6 +123,6 @@ func (r *Renderer) RenderFontPreview(ctx context.Context, id string, writer io.W return fmt.Errorf("font has no visible glyphs") } width, height := min(count, 8)*36, (count+7)/8*40 - _, err = fmt.Fprintf(writer, `%s`, width, height, width, height, paths.Bytes()) + _, err = fmt.Fprintf(writer, `%s`, width, height, width, height, paths.Bytes()) return err } diff --git a/ofdgo_res.go b/ofdgo_res.go index 2692da1..3cc254b 100644 --- a/ofdgo_res.go +++ b/ofdgo_res.go @@ -283,7 +283,7 @@ func sameResourceID(first, second string) bool { return id != "" && id == editorResourceID(second) } -// resourceValue 优先查找原标识与标准十进制标识,回退别名有冲突时不猜测 +// resourceValue 优先查找原标识与标准十进制标识,回退时检查别名冲突 // 入参: values 已加载资源, id 标识文本 // 返回: T 资源值, bool 是否存在唯一结果 func resourceValue[T comparable](values map[string]T, id string) (T, bool) { diff --git a/ofdgo_resource.go b/ofdgo_resource.go index ca86899..972bbaf 100644 --- a/ofdgo_resource.go +++ b/ofdgo_resource.go @@ -394,7 +394,7 @@ func scanResourcePage(ctx context.Context, file *editorFontXML, pages map[string return false, false, nil, nil } for _, attr := range token.Attr { - if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != "http://www.w3.org/XML/1998/namespace" { + if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != XMLNamespace { return false, false, nil, nil } } diff --git a/ofdgo_structure_xml.go b/ofdgo_structure_xml.go index a5900c9..314398c 100644 --- a/ofdgo_structure_xml.go +++ b/ofdgo_structure_xml.go @@ -45,7 +45,7 @@ func encodeStructureNode(node StructureNode) ([]byte, error) { switch node.Namespace { case "": start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: ""}) - case "http://www.w3.org/XML/1998/namespace": + case XMLNamespace: start.Name = xml.Name{Local: "xml:" + node.Name} } for _, attr := range node.Attributes { @@ -73,20 +73,20 @@ func encodeStructureNode(node StructureNode) ([]byte, error) { return output.Bytes(), nil } -// validateStructureNode 校验XML名称和文本,禁止重复属性及伪造命名空间声明 +// validateStructureNode 校验标引节点名称、属性和文本 // 入参: node 节点字段 // 返回: error 字段校验错误 func validateStructureNode(node StructureNode) error { if !ofdXMLIDValid(node.Name) || node.Name == "xmlns" { return fmt.Errorf("invalid custom tag node name: %s", node.Name) } - if !structureXMLText(node.Text) || !structureXMLText(node.Namespace) || node.Namespace == "http://www.w3.org/2000/xmlns/" { + if !structureXMLText(node.Text) || !structureXMLText(node.Namespace) || node.Namespace == xmlDeclarationNamespace { return fmt.Errorf("invalid custom tag text or namespace") } seen := make(map[xml.Name]bool) for _, attr := range node.Attributes { name := xml.Name{Space: attr.Namespace, Local: attr.Name} - if !ofdXMLIDValid(attr.Name) || attr.Name == "xmlns" || attr.Namespace == "http://www.w3.org/2000/xmlns/" || !structureXMLText(attr.Namespace) || !structureXMLText(attr.Value) || seen[name] { + if !ofdXMLIDValid(attr.Name) || attr.Name == "xmlns" || attr.Namespace == xmlDeclarationNamespace || !structureXMLText(attr.Namespace) || !structureXMLText(attr.Value) || seen[name] { return fmt.Errorf("invalid or duplicate custom tag attribute: %s", attr.Name) } seen[name] = true @@ -159,7 +159,7 @@ func updateStructureNodeXML(data []byte, source *editorXML, node StructureNode) if namespace == "" { return name } - if namespace == "http://www.w3.org/XML/1998/namespace" { + if namespace == XMLNamespace { return "xml:" + name } for _, attr := range attrs { diff --git a/ofdgo_xml.go b/ofdgo_xml.go index 4a10f4c..1588ce5 100644 --- a/ofdgo_xml.go +++ b/ofdgo_xml.go @@ -19,6 +19,18 @@ import ( "unicode/utf8" ) +// XMLNamespace 表示XML预定义命名空间 +const XMLNamespace = "http://www.w3.org/XML/1998/namespace" + +// XMLSchemaInstanceNamespace 表示XMLSchema实例命名空间 +const XMLSchemaInstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance" + +// XLinkNamespace 表示XML链接命名空间 +const XLinkNamespace = "http://www.w3.org/1999/xlink" + +// xmlDeclarationNamespace 表示XML命名空间声明标识 +const xmlDeclarationNamespace = "http://www.w3.org/2000/xmlns/" + // 旧版与2016版OFD命名空间 const ( ofdNamespaceLegacy = "http://www.ofdspec.org"