mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
1 parent
78bb70d5a2
commit
be8465884a
19 files changed
+137
-130
No files matched your search
@@ -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";
|
import { FontManager, FontPicker } from "./ofdgo_font.js";
|
||||||
|
|
||||||
const MM_TO_PX = 96 / 25.4;
|
const MM_TO_PX = 96 / 25.4;
|
||||||
@@ -9063,9 +9063,9 @@ function mountPageLinks(page, surface) {
|
|||||||
anchor.style.height = `${link.height / page.height * 100}%`;
|
anchor.style.height = `${link.height / page.height * 100}%`;
|
||||||
if (link.path) {
|
if (link.path) {
|
||||||
anchor.classList.add("page-region-link");
|
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}`);
|
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);
|
path.setAttribute("d", link.path);
|
||||||
region.append(path);
|
region.append(path);
|
||||||
anchor.append(region);
|
anchor.append(region);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const PX_PER_MM = 96 / 25.4;
|
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 FORMAT_KEYS = ["font", "size", "color", "bold", "italic", "underline", "strikeout", "shadow", "background"];
|
||||||
const RICH_CLIPBOARD = "application/x-ofdgo-rich-text";
|
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)) {
|
if (object.type === "PathObject" && (object.shape || object.outline || object.contours?.length)) {
|
||||||
node.classList.add("edit-contour");
|
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.classList.add("edit-outline");
|
||||||
svg.setAttribute("preserveAspectRatio", "none");
|
svg.setAttribute("preserveAspectRatio", "none");
|
||||||
let contour;
|
let contour;
|
||||||
if (object.shape || object.oriented || !object.contours?.length) {
|
if (object.shape || object.oriented || !object.contours?.length) {
|
||||||
const shape = object.shape || object.oriented?.kind;
|
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);
|
if (!shape) contour.setAttribute("d", object.outline);
|
||||||
svg.append(contour);
|
svg.append(contour);
|
||||||
} else {
|
} else {
|
||||||
for (const path of object.contours) {
|
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);
|
contour.setAttribute("d", path.path);
|
||||||
svg.append(contour);
|
svg.append(contour);
|
||||||
}
|
}
|
||||||
@@ -1354,10 +1355,10 @@ export class CanvasEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createPreview(surface, page, shape, style) {
|
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.classList.add("edit-preview");
|
||||||
preview.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
|
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("fill", style.fill ? style.fillColor : "none");
|
||||||
node.setAttribute("stroke", style.stroke ? style.strokeColor : "none");
|
node.setAttribute("stroke", style.stroke ? style.strokeColor : "none");
|
||||||
node.setAttribute("stroke-width", style.lineWidth);
|
node.setAttribute("stroke-width", style.lineWidth);
|
||||||
|
|||||||
@@ -679,7 +679,7 @@ func checkFontBinding(ctx context.Context, object TextObject, font *FontResource
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// externalFontName 去除PDF字体子集前缀,不猜测或改写其他名称
|
// externalFontName 去除PDF字体子集前缀
|
||||||
// 入参: name 字体名称
|
// 入参: name 字体名称
|
||||||
// 返回: string 外部匹配名称
|
// 返回: string 外部匹配名称
|
||||||
func externalFontName(name string) 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)
|
return fmt.Errorf("unknown file dependencies in %s", name)
|
||||||
}
|
}
|
||||||
for _, attr := range node.Attr {
|
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
|
uncertain = true
|
||||||
return fmt.Errorf("unknown file dependencies in %s", name)
|
return fmt.Errorf("unknown file dependencies in %s", name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func scanEditorFontFilesMode(ctx context.Context, reader *Reader, index int, ins
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, attr := range node.attrs {
|
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
|
scan.unsafe = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -671,7 +671,7 @@ func (m *editorPageImport) outlines() ([]byte, error) {
|
|||||||
if attr.Name.Space == "" || attr.Name.Space == "xmlns" {
|
if attr.Name.Space == "" || attr.Name.Space == "xmlns" {
|
||||||
continue
|
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
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Errorf("cannot remap outline extension attribute %q", attr.Name.Local)
|
return fmt.Errorf("cannot remap outline extension attribute %q", attr.Name.Local)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import (
|
|||||||
// invoiceTagType 标识发票票面编辑关联,业务数据保持原规范格式
|
// invoiceTagType 标识发票票面编辑关联,业务数据保持原规范格式
|
||||||
const invoiceTagType = "OFDGo.Invoice"
|
const invoiceTagType = "OFDGo.Invoice"
|
||||||
|
|
||||||
// invoiceNamespace 标识发票编辑关联的命名空间,版本仅随不兼容的语义变更升级
|
// invoiceNamespace 表示发票编辑关联命名空间
|
||||||
const invoiceNamespace = "https://ofdgo.aite.me/ns/invoice/1"
|
const invoiceNamespace = "https://ofdgo.aite.me/ns/invoice/1"
|
||||||
|
|
||||||
// InvoiceCreateOptions 指定结构化附件名称和可选票面,未指定票面时不新增页面
|
// InvoiceCreateOptions 指定结构化附件名称和可选票面,未指定票面时不新增页面
|
||||||
@@ -1129,7 +1129,7 @@ func invoiceRemapBindings(before, after Invoice, bindings []InvoiceBinding) ([]I
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoiceBindingCoverage 拒绝将未关联的字段修改误报为已同步,不猜测缺失图元
|
// invoiceBindingCoverage 检查修改字段的票面关联覆盖情况
|
||||||
// 入参: before 原发票, after 新发票, bindings 显式关联
|
// 入参: before 原发票, after 新发票, bindings 显式关联
|
||||||
// 返回: error 未绑定字段错误
|
// 返回: error 未绑定字段错误
|
||||||
func invoiceBindingCoverage(before, after Invoice, bindings []InvoiceBinding) error {
|
func invoiceBindingCoverage(before, after Invoice, bindings []InvoiceBinding) error {
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ func (e *Editor) repairSourceObjectIDs(data []byte, root *editorXML) ([]byte, bo
|
|||||||
return editorPatchXML(data, patches), true, nil
|
return editorPatchXML(data, patches), true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDuplicateObjectReferences 拒绝无法判定目标的编号引用,不猜测其所属对象
|
// checkDuplicateObjectReferences 检查编号引用的目标歧义
|
||||||
// 入参: duplicates 重复编号
|
// 入参: duplicates 重复编号
|
||||||
// 返回: error 引用歧义或读取错误
|
// 返回: error 引用歧义或读取错误
|
||||||
func (e *Editor) checkDuplicateObjectReferences(duplicates map[string]bool) error {
|
func (e *Editor) checkDuplicateObjectReferences(duplicates map[string]bool) error {
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ func (s *editorReferenceScan) accept(token xml.Token) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, attr := range token.Attr {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if attr.Name.Space != "" {
|
if attr.Name.Space != "" {
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ func normalizeResourceReferences(references map[string]bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// editorResourceNode 按资源标识查找标准资源节点,等价别名冲突时不猜测
|
// editorResourceNode 按资源标识查找标准资源节点并检查别名冲突
|
||||||
// 入参: root 资源文件根节点, group 资源集合名称, kind 资源类型, id 资源标识
|
// 入参: root 资源文件根节点, group 资源集合名称, kind 资源类型, id 资源标识
|
||||||
// 返回: *editorXML 资源节点,未找到时为nil
|
// 返回: *editorXML 资源节点,未找到时为nil
|
||||||
func editorResourceNode(root *editorXML, group, kind, id string) *editorXML {
|
func editorResourceNode(root *editorXML, group, kind, id string) *editorXML {
|
||||||
|
|||||||
+4
-4
@@ -23,7 +23,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 发票读取限额,不扫描未声明资源或嵌套附件包
|
// 发票读取限额
|
||||||
const (
|
const (
|
||||||
invoiceXMLLimit = 16 << 20
|
invoiceXMLLimit = 16 << 20
|
||||||
invoiceTotalLimit = 64 << 20
|
invoiceTotalLimit = 64 << 20
|
||||||
@@ -63,7 +63,7 @@ type Invoice struct {
|
|||||||
Facts []InvoiceFact `json:"facts,omitempty"`
|
Facts []InvoiceFact `json:"facts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvoiceParty 保存购销双方信息,不从名称或其他字段推测税号
|
// InvoiceParty 保存购销双方信息
|
||||||
type InvoiceParty struct {
|
type InvoiceParty struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
TaxID string `json:"taxId,omitempty"`
|
TaxID string `json:"taxId,omitempty"`
|
||||||
@@ -94,7 +94,7 @@ type InvoiceSource struct {
|
|||||||
NodePath string `json:"nodePath,omitempty"`
|
NodePath string `json:"nodePath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvoiceIssue 保存来源明确的解析诊断,Code为机器可读类别,不包含真伪判断
|
// InvoiceIssue 保存来源明确的解析诊断,Code为机器可读类别
|
||||||
type InvoiceIssue struct {
|
type InvoiceIssue struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
@@ -103,7 +103,7 @@ type InvoiceIssue struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InvoiceFact 保存命名空间限定的业务事实、原始数值及精度属性,Nil与空文本分别表达
|
// InvoiceFact 保存命名空间限定的业务事实、原始数值及精度属性,Nil与空文本分别表达
|
||||||
// References保留对象引用标引的原始页标识和有序图元标识,不推测明细归属
|
// References保留对象引用标引的原始页标识和有序图元标识
|
||||||
type InvoiceFact struct {
|
type InvoiceFact struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Namespace string `json:"namespace,omitempty"`
|
Namespace string `json:"namespace,omitempty"`
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func invoiceAppearanceMatches(text TextObject, value string) bool {
|
|||||||
return actual == expected
|
return actual == expected
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoiceLayoutFields 按稳定顺序展开公共字段及明细,不推算缺失值
|
// invoiceLayoutFields 按稳定顺序展开公共字段及明细
|
||||||
// 入参: invoice 发票数据
|
// 入参: invoice 发票数据
|
||||||
// 返回: []invoiceLayoutField 字段列表
|
// 返回: []invoiceLayoutField 字段列表
|
||||||
func invoiceLayoutFields(invoice Invoice) []invoiceLayoutField {
|
func invoiceLayoutFields(invoice Invoice) []invoiceLayoutField {
|
||||||
@@ -181,11 +181,11 @@ func invoiceLayoutFactVisible(invoice Invoice, fact InvoiceFact) bool {
|
|||||||
// 入参: value 发票,fact 原始字段
|
// 入参: value 发票,fact 原始字段
|
||||||
// 返回: 公共字段关联及是否明确映射
|
// 返回: 公共字段关联及是否明确映射
|
||||||
func invoiceEDRMFactBinding(value Invoice, fact InvoiceFact) (InvoiceBinding, bool) {
|
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
|
return InvoiceBinding{}, false
|
||||||
}
|
}
|
||||||
var path strings.Builder
|
var path strings.Builder
|
||||||
nodePath := strings.ReplaceAll(fact.Source.NodePath, "{"+invoiceEDRM+"}", "{}")
|
nodePath := strings.ReplaceAll(fact.Source.NodePath, "{"+InvoiceStandardEDRM2019+"}", "{}")
|
||||||
if value.Standard == ofdNamespace2016 {
|
if value.Standard == ofdNamespace2016 {
|
||||||
nodePath = strings.Replace(nodePath, "/root[1]", "/eInvoice[1]", 1)
|
nodePath = strings.Replace(nodePath, "/root[1]", "/eInvoice[1]", 1)
|
||||||
nodePath = strings.ReplaceAll(nodePath, "/", "/{}")
|
nodePath = strings.ReplaceAll(nodePath, "/", "/{}")
|
||||||
|
|||||||
+47
-47
@@ -81,7 +81,7 @@ type InvoiceContext struct {
|
|||||||
EndDate string `json:"endDate,omitempty"`
|
EndDate string `json:"endDate,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvoiceUnit 保存单一度量的XBRL单位,不推测币种或数量单位
|
// InvoiceUnit 保存单一度量的XBRL单位
|
||||||
type InvoiceUnit struct {
|
type InvoiceUnit struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
@@ -121,7 +121,7 @@ type invoiceXMLPatch struct {
|
|||||||
func InvoiceFields(value Invoice) []InvoiceFieldDescriptor {
|
func InvoiceFields(value Invoice) []InvoiceFieldDescriptor {
|
||||||
format, supported := invoiceWriteFormat(value)
|
format, supported := invoiceWriteFormat(value)
|
||||||
probe := Invoice{Items: []InvoiceItem{{}}}
|
probe := Invoice{Items: []InvoiceItem{{}}}
|
||||||
if value.Standard == invoiceEDRM || value.Standard == ofdNamespace2016 {
|
if value.Standard == InvoiceStandardEDRM2019 || value.Standard == ofdNamespace2016 {
|
||||||
for _, path := range invoiceEDRMPaths() {
|
for _, path := range invoiceEDRMPaths() {
|
||||||
item := &probe.Items[0]
|
item := &probe.Items[0]
|
||||||
if value.Standard == ofdNamespace2016 {
|
if value.Standard == ofdNamespace2016 {
|
||||||
@@ -242,7 +242,7 @@ func InvoiceFields(value Invoice) []InvoiceFieldDescriptor {
|
|||||||
// 返回: 已适配格式列表
|
// 返回: 已适配格式列表
|
||||||
func InvoiceFormats() []InvoiceFormat {
|
func InvoiceFormats() []InvoiceFormat {
|
||||||
result := []InvoiceFormat{{Kind: "einv", Standard: InvoiceStandardEInvoice, Version: "0.3", Read: true, Create: true, Edit: true, Items: true}}
|
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})
|
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} {
|
for _, standard := range []string{InvoiceStandardVAT2023, InvoiceStandardDigital2023, InvoiceStandardAir2023, InvoiceStandardRail2023} {
|
||||||
kind, version := invoiceTaxonomy(standard)
|
kind, version := invoiceTaxonomy(standard)
|
||||||
@@ -268,7 +268,7 @@ func invoiceWriteContext(ctx context.Context) context.Context {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoiceWriteFormat 查找完整格式标识,不推测缺失版本
|
// invoiceWriteFormat 按标准、票种和版本查找格式
|
||||||
// 入参: value 发票
|
// 入参: value 发票
|
||||||
// 返回: 格式与匹配状态
|
// 返回: 格式与匹配状态
|
||||||
func invoiceWriteFormat(value Invoice) (InvoiceFormat, bool) {
|
func invoiceWriteFormat(value Invoice) (InvoiceFormat, bool) {
|
||||||
@@ -496,7 +496,7 @@ func EncodeInvoiceWithOptions(ctx context.Context, value Invoice, options Invoic
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var data []byte
|
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 {
|
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")
|
return nil, fmt.Errorf("EDRM creation does not support EInvoice sections")
|
||||||
}
|
}
|
||||||
@@ -646,7 +646,7 @@ func invoiceWriteRepresented(want, got Invoice) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoiceEncodeXBRL 构造单主体实例,扩展事实不得携带无法重建的上下文或元组
|
// invoiceEncodeXBRL 构造单主体XBRL实例
|
||||||
// 入参: value 发票,options 显式分类标准、上下文和单位
|
// 入参: value 发票,options 显式分类标准、上下文和单位
|
||||||
// 返回: XBRL数据及表达错误
|
// 返回: XBRL数据及表达错误
|
||||||
func invoiceEncodeXBRL(value Invoice, options InvoiceEncodingOptions) ([]byte, error) {
|
func invoiceEncodeXBRL(value Invoice, options InvoiceEncodingOptions) ([]byte, error) {
|
||||||
@@ -663,7 +663,7 @@ func invoiceEncodeXBRL(value Invoice, options InvoiceEncodingOptions) ([]byte, e
|
|||||||
}
|
}
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString(xml.Header)
|
b.WriteString(xml.Header)
|
||||||
fmt.Fprintf(&b, `<xbrli:xbrl xmlns:xbrli="%s" xmlns:xsi="%s" xmlns:iso4217="%s" xmlns:f="%s" xmlns:link="%s" xmlns:xlink="%s">`, invoiceXBRL, invoiceXSI, InvoiceCurrencyNamespace, value.Standard, invoiceLinkbase, invoiceXLink)
|
fmt.Fprintf(&b, `<xbrli:xbrl xmlns:xbrli="%s" xmlns:xsi="%s" xmlns:iso4217="%s" xmlns:f="%s" xmlns:link="%s" xmlns:xlink="%s">`, invoiceXBRL, XMLSchemaInstanceNamespace, InvoiceCurrencyNamespace, value.Standard, invoiceLinkbase, XLinkNamespace)
|
||||||
fmt.Fprintf(&b, `<link:schemaRef xlink:type="simple" xlink:href="%s"/><xbrli:context id="%s"><xbrli:entity><xbrli:identifier scheme="%s">%s</xbrli:identifier></xbrli:entity><xbrli:period>`, invoiceWriteEscape(options.SchemaRef), options.Context.ID, invoiceWriteEscape(options.Context.Scheme), invoiceWriteEscape(options.Context.Identifier))
|
fmt.Fprintf(&b, `<link:schemaRef xlink:type="simple" xlink:href="%s"/><xbrli:context id="%s"><xbrli:entity><xbrli:identifier scheme="%s">%s</xbrli:identifier></xbrli:entity><xbrli:period>`, invoiceWriteEscape(options.SchemaRef), options.Context.ID, invoiceWriteEscape(options.Context.Scheme), invoiceWriteEscape(options.Context.Identifier))
|
||||||
if options.Context.Instant != "" {
|
if options.Context.Instant != "" {
|
||||||
if options.Context.StartDate != "" || options.Context.EndDate != "" {
|
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"}}
|
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 发票
|
// 入参: value 发票
|
||||||
// 返回: XML及不可表达的扩展错误
|
// 返回: XML及不可表达的扩展错误
|
||||||
func invoiceEncodePlain(value Invoice) ([]byte, error) {
|
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 {
|
if change.Revision != "" && change.Revision != revision {
|
||||||
return nil, fmt.Errorf("stale invoice source 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"
|
isNil := oldNil == "true" || oldNil == "1"
|
||||||
expectedNil := false
|
expectedNil := false
|
||||||
if change.ExpectedNil != nil {
|
if change.ExpectedNil != nil {
|
||||||
@@ -1082,7 +1082,7 @@ func invoiceValidateXMLWrite(ctx context.Context, before, after []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoiceWriteXMLProblems 按节点路径及问题值区分诊断,避免不同候选的同类问题相互抵消
|
// invoiceWriteXMLProblems 按节点路径及问题值归类诊断
|
||||||
// 入参: ctx 上下文,data 原文,tuples 是否检查元组必有字段
|
// 入参: ctx 上下文,data 原文,tuples 是否检查元组必有字段
|
||||||
// 返回: 定位诊断及解析错误
|
// 返回: 定位诊断及解析错误
|
||||||
func invoiceWriteXMLProblems(ctx context.Context, data []byte, tuples bool) (map[string]InvoiceIssue, error) {
|
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
|
path += "/" + n.name.Local
|
||||||
kind, _ := invoiceTaxonomy(n.name.Space)
|
kind, _ := invoiceTaxonomy(n.name.Space)
|
||||||
nilValue := invoiceXMLAttr(n, invoiceXSI, "nil")
|
nilValue := invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil")
|
||||||
empty := nilValue == "true" || nilValue == "1"
|
empty := nilValue == "true" || nilValue == "1"
|
||||||
date := kind != "" && invoiceWriteDateFact(kind, n.name.Local)
|
date := kind != "" && invoiceWriteDateFact(kind, n.name.Local)
|
||||||
plainDate := root.name == (xml.Name{Local: "EInvoice"}) && path == "/EInvoice/TaxSupervisionInfo/IssueTime"
|
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) {
|
if len(n.children) == 0 && !empty && (date || plainDate || edrmDate) {
|
||||||
value := n.text.String()
|
value := n.text.String()
|
||||||
parseValue := value
|
parseValue := value
|
||||||
@@ -1157,7 +1157,7 @@ func invoiceWriteXMLProblems(ctx context.Context, data []byte, tuples bool) (map
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, value := range report.Invoices {
|
for _, value := range report.Invoices {
|
||||||
if value.Standard != invoiceEDRM {
|
if value.Standard != InvoiceStandardEDRM2019 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
issues, err := ValidateInvoice(ctx, value)
|
issues, err := ValidateInvoice(ctx, value)
|
||||||
@@ -1171,7 +1171,7 @@ func invoiceWriteXMLProblems(ctx context.Context, data []byte, tuples bool) (map
|
|||||||
for _, issue := range report.Issues {
|
for _, issue := range report.Issues {
|
||||||
value := ""
|
value := ""
|
||||||
if n := nodes[issue.Source.NodePath]; n != nil {
|
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)
|
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]})
|
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:" + prefix}, Value: n.ns[prefix]})
|
||||||
}
|
}
|
||||||
for _, a := range n.attrs {
|
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
|
continue
|
||||||
}
|
}
|
||||||
start.Attr = append(start.Attr, a)
|
start.Attr = append(start.Attr, a)
|
||||||
}
|
}
|
||||||
if nilValue {
|
if nilValue {
|
||||||
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: invoiceXSI, Local: "nil"}, Value: "true"})
|
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: XMLSchemaInstanceNamespace, Local: "nil"}, Value: "true"})
|
||||||
} else if invoiceXMLAttr(n, invoiceXSI, "nil") == "true" || invoiceXMLAttr(n, invoiceXSI, "nil") == "1" {
|
} else if invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") == "true" || invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil") == "1" {
|
||||||
kind, _ := invoiceTaxonomy(n.name.Space)
|
kind, _ := invoiceTaxonomy(n.name.Space)
|
||||||
if invoiceNumeric(kind, n.name.Local) {
|
if invoiceNumeric(kind, n.name.Local) {
|
||||||
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "decimals"}, Value: "INF"})
|
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 {
|
if err = invoiceWriteNewIssues(filter(oldIssues), filter(newIssues)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if value.Standard == invoiceEDRM {
|
if value.Standard == InvoiceStandardEDRM2019 {
|
||||||
return invoiceUpdateEDRM(ctx, data, old, value)
|
return invoiceUpdateEDRM(ctx, data, old, value)
|
||||||
}
|
}
|
||||||
root, spans, err := invoiceWriteSpans(ctx, data)
|
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")
|
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 明确提供的发票业务字段
|
// 入参: value 明确提供的发票业务字段
|
||||||
// 返回: 独立XML及不可表达错误
|
// 返回: 独立XML及不可表达错误
|
||||||
func invoiceEncodeEDRM(value Invoice) ([]byte, error) {
|
func invoiceEncodeEDRM(value Invoice) ([]byte, error) {
|
||||||
@@ -1551,7 +1551,7 @@ func invoiceEncodeEDRM(value Invoice) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString(xml.Header)
|
b.WriteString(xml.Header)
|
||||||
fmt.Fprintf(&b, `<eInvoice xmlns="%s" Version="1.0">`, invoiceEDRM)
|
fmt.Fprintf(&b, `<eInvoice xmlns="%s" Version="1.0">`, InvoiceStandardEDRM2019)
|
||||||
write := func(name, text string) {
|
write := func(name, text string) {
|
||||||
if text != "" {
|
if text != "" {
|
||||||
fmt.Fprintf(&b, "<%s>%s</%s>", name, invoiceWriteEscape(text), name)
|
fmt.Fprintf(&b, "<%s>%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"} {
|
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
|
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 {
|
if len(groups) > 1 {
|
||||||
return nil, fmt.Errorf("ambiguous invoice group %s", name)
|
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"} {
|
for _, group := range []string{"", "Buyer", "Seller"} {
|
||||||
parent := root
|
parent := root
|
||||||
if group != "" {
|
if group != "" {
|
||||||
parents := invoiceChildren(root, invoiceEDRM, group)
|
parents := invoiceChildren(root, InvoiceStandardEDRM2019, group)
|
||||||
if len(parents) > 0 {
|
if len(parents) > 0 {
|
||||||
parent = parents[0]
|
parent = parents[0]
|
||||||
} else {
|
} else {
|
||||||
parent = nil
|
parent = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
desired := &invoiceXMLNode{name: xml.Name{Space: invoiceEDRM, Local: group}}
|
desired := &invoiceXMLNode{name: xml.Name{Space: InvoiceStandardEDRM2019, Local: group}}
|
||||||
var names []string
|
var names []string
|
||||||
for _, path := range invoiceEDRMPaths() {
|
for _, path := range invoiceEDRMPaths() {
|
||||||
parts := strings.Split(path, "/")
|
parts := strings.Split(path, "/")
|
||||||
@@ -1651,10 +1651,10 @@ func invoiceUpdateEDRM(ctx context.Context, data []byte, old, value Invoice) ([]
|
|||||||
}
|
}
|
||||||
name := parts[len(parts)-1]
|
name := parts[len(parts)-1]
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
node := &invoiceXMLNode{name: xml.Name{Space: invoiceEDRM, Local: name}}
|
node := &invoiceXMLNode{name: xml.Name{Space: InvoiceStandardEDRM2019, Local: name}}
|
||||||
text := *target
|
text := *target
|
||||||
if name == "IssueDate" && parent != nil {
|
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 != "" {
|
if len(existing) == 1 && strings.Contains(existing[0].text.String(), "年") && text != "" {
|
||||||
date, err := time.Parse("2006-01-02", text)
|
date, err := time.Parse("2006-01-02", text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1676,26 +1676,26 @@ func invoiceUpdateEDRM(ctx context.Context, data []byte, old, value Invoice) ([]
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
patches = append(patches, patch)
|
patches = append(patches, patch)
|
||||||
} else {
|
} else {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if len(invoiceChildren(parent, invoiceEDRM, name)) == 0 && group == "" {
|
if len(invoiceChildren(parent, InvoiceStandardEDRM2019, name)) == 0 && group == "" {
|
||||||
raw, err := invoiceWriteNode(invoiceChildren(desired, invoiceEDRM, name)[0])
|
raw, err := invoiceWriteNode(invoiceChildren(desired, InvoiceStandardEDRM2019, name)[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
patches = append(patches, patch)
|
patches = append(patches, patch)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
changes, err := invoiceWriteMergeChildren(data, spans, parent, desired, invoiceEDRM, []string{name})
|
changes, err := invoiceWriteMergeChildren(data, spans, parent, desired, InvoiceStandardEDRM2019, []string{name})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1704,15 +1704,15 @@ func invoiceUpdateEDRM(ctx context.Context, data []byte, old, value Invoice) ([]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(old.Items, value.Items) {
|
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 {
|
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() {
|
for _, path := range invoiceEDRMPaths() {
|
||||||
if !strings.HasPrefix(path, "/eInvoice/GoodsInfos/GoodsInfo/") {
|
if !strings.HasPrefix(path, "/eInvoice/GoodsInfos/GoodsInfo/") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p, _ := invoiceEDRMField(&value, &item, path)
|
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.text.WriteString(*p)
|
||||||
leaf.parts = []invoiceXMLPart{{text: *p}}
|
leaf.parts = []invoiceXMLPart{{text: *p}}
|
||||||
node.children = append(node.children, leaf)
|
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.children = append(generated.children, node)
|
||||||
generated.parts = append(generated.parts, invoiceXMLPart{child: node})
|
generated.parts = append(generated.parts, invoiceXMLPart{child: node})
|
||||||
}
|
}
|
||||||
parents := invoiceChildren(root, invoiceEDRM, "GoodsInfos")
|
parents := invoiceChildren(root, InvoiceStandardEDRM2019, "GoodsInfos")
|
||||||
if len(parents) == 0 {
|
if len(parents) == 0 {
|
||||||
raw, err := invoiceWriteNode(generated)
|
raw, err := invoiceWriteNode(generated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1801,7 +1801,7 @@ func invoiceWriteUnmapped(before, after []InvoiceFact) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoiceWriteOriginalOptions 读取原上下文及单位,不猜测或改写定义
|
// invoiceWriteOriginalOptions 读取原上下文及单位定义
|
||||||
// 入参: root 原XML,value 原候选
|
// 入参: root 原XML,value 原候选
|
||||||
// 返回: 原定义及不支持的复杂上下文错误
|
// 返回: 原定义及不支持的复杂上下文错误
|
||||||
func invoiceWriteOriginalOptions(root *invoiceXMLNode, value Invoice) (InvoiceEncodingOptions, error) {
|
func invoiceWriteOriginalOptions(root *invoiceXMLNode, value Invoice) (InvoiceEncodingOptions, error) {
|
||||||
@@ -1815,7 +1815,7 @@ func invoiceWriteOriginalOptions(root *invoiceXMLNode, value Invoice) (InvoiceEn
|
|||||||
}
|
}
|
||||||
for _, n := range root.children {
|
for _, n := range root.children {
|
||||||
if n.name == (xml.Name{Space: invoiceLinkbase, Local: "schemaRef"}) && options.SchemaRef == "" {
|
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 {
|
if n.name == (xml.Name{Space: invoiceXBRL, Local: "context"}) && invoiceXMLAttr(n, "", "id") == ref {
|
||||||
dims, ok := invoiceContext(n)
|
dims, ok := invoiceContext(n)
|
||||||
@@ -1939,15 +1939,15 @@ func invoiceWriteMergeChildren(data []byte, spans map[string]invoiceXMLSpan, old
|
|||||||
if len(existing[0].children) > 0 {
|
if len(existing[0].children) > 0 {
|
||||||
return nil, fmt.Errorf("non-leaf invoice field %s", name)
|
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
|
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
|
continue
|
||||||
}
|
}
|
||||||
if len(targets) > 0 {
|
if len(targets) > 0 {
|
||||||
oldNil := invoiceXMLAttr(existing[0], invoiceXSI, "nil")
|
oldNil := invoiceXMLAttr(existing[0], XMLSchemaInstanceNamespace, "nil")
|
||||||
newNil := invoiceXMLAttr(targets[0], invoiceXSI, "nil")
|
newNil := invoiceXMLAttr(targets[0], XMLSchemaInstanceNamespace, "nil")
|
||||||
wasNil := oldNil == "true" || oldNil == "1"
|
wasNil := oldNil == "true" || oldNil == "1"
|
||||||
isNil := newNil == "true" || newNil == "1"
|
isNil := newNil == "true" || newNil == "1"
|
||||||
if !s.empty && wasNil == isNil {
|
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)
|
space, name := value.Standard, invoiceWriteTuple(value.Kind)
|
||||||
names := invoiceWriteItemNames(value.Kind)
|
names := invoiceWriteItemNames(value.Kind)
|
||||||
if value.Standard == invoiceEDRM {
|
if value.Standard == InvoiceStandardEDRM2019 {
|
||||||
name = "GoodsInfo"
|
name = "GoodsInfo"
|
||||||
names = strings.Fields("Item Specification MeasurementDimension Quantity Price Amount TaxScheme TaxAmount")
|
names = strings.Fields("Item Specification MeasurementDimension Quantity Price Amount TaxScheme TaxAmount")
|
||||||
} else if value.Standard == InvoiceStandardEInvoice {
|
} else if value.Standard == InvoiceStandardEInvoice {
|
||||||
@@ -2047,7 +2047,7 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i
|
|||||||
if path == "" {
|
if path == "" {
|
||||||
for _, f := range item.Fields {
|
for _, f := range item.Fields {
|
||||||
mapped := invoiceItemField(&InvoiceItem{}, value.Kind, f.Name)
|
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)
|
return nil, fmt.Errorf("unsupported new invoice item fact %s", f.Name)
|
||||||
}
|
}
|
||||||
if mapped != nil {
|
if mapped != nil {
|
||||||
@@ -2073,12 +2073,12 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s, ok := spans[f.Source.NodePath]
|
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")
|
return nil, fmt.Errorf("unmapped item fact changes require PatchInvoice")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n := original[path]
|
n := original[path]
|
||||||
if value.Standard == invoiceEDRM {
|
if value.Standard == InvoiceStandardEDRM2019 {
|
||||||
previous := originalItems[path]
|
previous := originalItems[path]
|
||||||
if err := invoiceWriteUnmapped(previous.Fields, item.Fields); err != nil {
|
if err := invoiceWriteUnmapped(previous.Fields, item.Fields); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -2089,7 +2089,7 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i
|
|||||||
before, _ := invoiceEDRMField(&old, &previous, field)
|
before, _ := invoiceEDRMField(&old, &previous, field)
|
||||||
after, _ := invoiceEDRMField(&value, &item, field)
|
after, _ := invoiceEDRMField(&value, &item, field)
|
||||||
if before != nil && after != nil && *before == *after {
|
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 {
|
if len(children) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -2172,11 +2172,11 @@ func invoiceWriteOrderedInsert(data []byte, spans map[string]invoiceXMLSpan, par
|
|||||||
return invoiceWriteInsert(spans[parent.path], raw, data)
|
return invoiceWriteInsert(spans[parent.path], raw, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoiceWriteCreation 检查官方元组序列中的必有字段,不虚构缺失业务值
|
// invoiceWriteCreation 检查官方元组序列中的必有字段
|
||||||
// 入参: value 发票
|
// 入参: value 发票
|
||||||
// 返回: 缺少元组字段错误
|
// 返回: 缺少元组字段错误
|
||||||
func invoiceWriteCreation(value Invoice) error {
|
func invoiceWriteCreation(value Invoice) error {
|
||||||
if value.Standard == InvoiceStandardEInvoice || value.Standard == invoiceEDRM {
|
if value.Standard == InvoiceStandardEInvoice || value.Standard == InvoiceStandardEDRM2019 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for i, item := range value.Items {
|
for i, item := range value.Items {
|
||||||
|
|||||||
+36
-48
@@ -28,30 +28,6 @@ import (
|
|||||||
"time"
|
"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版增值税发票分类标准
|
// InvoiceStandardVAT2023 表示2023版增值税发票分类标准
|
||||||
const InvoiceStandardVAT2023 = "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/inv"
|
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版航空运输电子客票行程单分类标准
|
// InvoiceStandardAir2021 表示2021版航空运输电子客票行程单分类标准
|
||||||
const InvoiceStandardAir2021 = "http://xbrl.mof.gov.cn/taxonomy/2021-11-30/atr"
|
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版电子发票业务标准
|
// InvoiceStandardEDRM2019 表示2019版电子发票业务标准
|
||||||
const InvoiceStandardEDRM2019 = "http://www.edrm.org.cn/schema/e-invoice/2019"
|
const InvoiceStandardEDRM2019 = "http://www.edrm.org.cn/schema/e-invoice/2019"
|
||||||
|
|
||||||
// invoiceEDRM 表示2019版电子发票业务命名空间
|
// InvoiceStandardEInvoice 表示无命名空间的EInvoice业务标准,版本由Invoice.Version指定
|
||||||
const invoiceEDRM = InvoiceStandardEDRM2019
|
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十进制词法
|
// invoiceDecimalPattern 仅接受XML十进制词法
|
||||||
var invoiceDecimalPattern = regexp.MustCompile(`^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$`)
|
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")
|
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
|
index := 1
|
||||||
if current != nil {
|
if current != nil {
|
||||||
for k, v := range current.ns {
|
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) {
|
if source.Kind == "tag" && invoiceObjectReferences(root) {
|
||||||
return report, nil
|
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)
|
return invoicePlainXML(ctx, root, source)
|
||||||
}
|
}
|
||||||
if root.name != (xml.Name{Space: invoiceXBRL, Local: "xbrl"}) {
|
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")
|
ref := invoiceXMLAttr(n, "", "contextRef")
|
||||||
s := source
|
s := source
|
||||||
s.NodePath += n.path
|
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 {
|
if len(n.children) > 0 {
|
||||||
fact.Value = string(n.inner)
|
fact.Value = string(n.inner)
|
||||||
}
|
}
|
||||||
@@ -431,8 +419,8 @@ func parseInvoiceXML(ctx context.Context, data []byte, source InvoiceSource) (In
|
|||||||
}
|
}
|
||||||
inv := &report.Invoices[i]
|
inv := &report.Invoices[i]
|
||||||
inv.Facts = append(inv.Facts, fact)
|
inv.Facts = append(inv.Facts, fact)
|
||||||
nilValue := invoiceXMLAttr(n, invoiceXSI, "nil")
|
nilValue := invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil")
|
||||||
if invoiceXMLHasAttr(n, invoiceXSI, "nil") && nilValue != "true" && nilValue != "false" && nilValue != "1" && nilValue != "0" {
|
if invoiceXMLHasAttr(n, XMLSchemaInstanceNamespace, "nil") && nilValue != "true" && nilValue != "false" && nilValue != "1" && nilValue != "0" {
|
||||||
issue("invalid-value", fact.Name, "invalid xsi:nil", s)
|
issue("invalid-value", fact.Name, "invalid xsi:nil", s)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -948,7 +936,7 @@ func invoiceMonetary(kind, name string) bool {
|
|||||||
return invoiceNumeric(kind, name) && name != "QuantityOfGoodsOrServices" && name != "VatRate" && name != "TaxRate"
|
return invoiceNumeric(kind, name) && name != "QuantityOfGoodsOrServices" && name != "VatRate" && name != "TaxRate"
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoiceCurrencyUnit 检查单一ISO币种度量,不猜测币种
|
// invoiceCurrencyUnit 检查单一ISO币种度量
|
||||||
// 入参: unit 展开的单位表示
|
// 入参: unit 展开的单位表示
|
||||||
// 返回: 是否为三位大写币种度量
|
// 返回: 是否为三位大写币种度量
|
||||||
func invoiceCurrencyUnit(unit string) bool {
|
func invoiceCurrencyUnit(unit string) bool {
|
||||||
@@ -1015,7 +1003,7 @@ func invoiceObjectReferences(root *invoiceXMLNode) bool {
|
|||||||
count := 0
|
count := 0
|
||||||
var walk func(*invoiceXMLNode) bool
|
var walk func(*invoiceXMLNode) bool
|
||||||
walk = func(n *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" {
|
if n.name.Local == "ObjectRef" {
|
||||||
count++
|
count++
|
||||||
return len(n.children) == 0 && strings.TrimSpace(n.text.String()) != "" && invoiceXMLAttr(n, "", "PageRef") != ""
|
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}}
|
inv := Invoice{Kind: "einv", Standard: InvoiceStandardEInvoice, Version: "0.3", Sources: []InvoiceSource{source}}
|
||||||
namespace, itemPath := "", "/EInvoice/EInvoiceData/IssuItemInformation"
|
namespace, itemPath := "", "/EInvoice/EInvoiceData/IssuItemInformation"
|
||||||
if root.name.Local == "eInvoice" {
|
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})
|
report.Issues = append(report.Issues, InvoiceIssue{Code: "unsupported", Message: "unsupported eInvoice namespace or version", Source: source})
|
||||||
return report, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
inv.Kind, inv.Standard, inv.Version = "inv", invoiceEDRM, "1.0"
|
inv.Kind, inv.Standard, inv.Version = "inv", InvoiceStandardEDRM2019, "1.0"
|
||||||
namespace, itemPath = invoiceEDRM, "/eInvoice/GoodsInfos/GoodsInfo"
|
namespace, itemPath = InvoiceStandardEDRM2019, "/eInvoice/GoodsInfos/GoodsInfo"
|
||||||
} else {
|
} else {
|
||||||
headers := invoiceChildren(root, "", "Header")
|
headers := invoiceChildren(root, "", "Header")
|
||||||
if len(headers) != 1 {
|
if len(headers) != 1 {
|
||||||
@@ -1095,18 +1083,18 @@ func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSo
|
|||||||
}
|
}
|
||||||
s := source
|
s := source
|
||||||
s.NodePath += n.path
|
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)
|
inv.Facts = append(inv.Facts, f)
|
||||||
if item != nil {
|
if item != nil {
|
||||||
item.Fields = append(item.Fields, f)
|
item.Fields = append(item.Fields, f)
|
||||||
}
|
}
|
||||||
nilValue := invoiceXMLAttr(n, invoiceXSI, "nil")
|
nilValue := invoiceXMLAttr(n, XMLSchemaInstanceNamespace, "nil")
|
||||||
if invoiceXMLHasAttr(n, invoiceXSI, "nil") && nilValue != "true" && nilValue != "false" && nilValue != "1" && nilValue != "0" {
|
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})
|
report.Issues = append(report.Issues, InvoiceIssue{Code: "invalid-value", Field: f.Name, Message: "invalid xsi:nil", Source: s})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dst, numeric := invoicePlainField(&inv, item, path)
|
dst, numeric := invoicePlainField(&inv, item, path)
|
||||||
if inv.Standard == invoiceEDRM {
|
if inv.Standard == InvoiceStandardEDRM2019 {
|
||||||
dst, numeric = invoiceEDRMField(&inv, item, path)
|
dst, numeric = invoiceEDRMField(&inv, item, path)
|
||||||
}
|
}
|
||||||
if dst == nil {
|
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})
|
report.Issues = append(report.Issues, InvoiceIssue{Code: "invalid-value", Field: f.Name, Message: "nil fact contains a value", Source: s})
|
||||||
return nil
|
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
|
return nil
|
||||||
}
|
}
|
||||||
if numeric && !f.Nil && invoiceDecimal(f.Value) == 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 {
|
if err := walk(root, "", nil); err != nil {
|
||||||
return InvoiceReport{}, err
|
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 {
|
if date, err := time.Parse("2006年01月02日", inv.Date); err == nil {
|
||||||
inv.Date = date.Format("2006-01-02")
|
inv.Date = date.Format("2006-01-02")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,11 @@ import (
|
|||||||
"golang.org/x/image/draw"
|
"golang.org/x/image/draw"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// xmpNamespace 表示XMP元数据命名空间
|
||||||
|
const xmpNamespace = "http://ns.adobe.com/xap/1.0/"
|
||||||
|
|
||||||
// creatorXMP 记录独立导出文件的制作软件
|
// creatorXMP 记录独立导出文件的制作软件
|
||||||
const creatorXMP = `<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/"><xmp:CreatorTool>` + ofdCreator + `</xmp:CreatorTool></rdf:Description></rdf:RDF>`
|
const creatorXMP = `<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:xmp="` + xmpNamespace + `"><xmp:CreatorTool>` + ofdCreator + `</xmp:CreatorTool></rdf:Description></rdf:RDF>`
|
||||||
|
|
||||||
// imageMetadataWriter 在图像固定头之后插入元数据,不缓存图像主体
|
// imageMetadataWriter 在图像固定头之后插入元数据,不缓存图像主体
|
||||||
type imageMetadataWriter struct {
|
type imageMetadataWriter struct {
|
||||||
@@ -279,7 +282,7 @@ func (r *Renderer) RenderToJPEG(page *PageContent, writer io.Writer, options *jp
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 := make([]byte, len(metadata)+4)
|
||||||
segment[0], segment[1] = 0xff, 0xe1
|
segment[0], segment[1] = 0xff, 0xe1
|
||||||
binary.BigEndian.PutUint16(segment[2:], uint16(len(metadata)+2))
|
binary.BigEndian.PutUint16(segment[2:], uint16(len(metadata)+2))
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// svgNamespace 表示SVG预览文档命名空间
|
||||||
|
const svgNamespace = "http://www.w3.org/2000/svg"
|
||||||
|
|
||||||
// PreviewOptions 指定页面显示方式,光栅预览不提供SVG对象分组
|
// PreviewOptions 指定页面显示方式,光栅预览不提供SVG对象分组
|
||||||
type PreviewOptions struct {
|
type PreviewOptions struct {
|
||||||
Raster bool
|
Raster bool
|
||||||
@@ -50,7 +53,7 @@ func (r *Renderer) RenderPreview(page *PageContent, writer io.Writer, options Pr
|
|||||||
return SVGResources{}, err
|
return SVGResources{}, err
|
||||||
}
|
}
|
||||||
name := fmt.Sprintf("ofdgo-image-%x", sha256.Sum256(pixels.Bytes()))
|
name := fmt.Sprintf("ofdgo-image-%x", sha256.Sum256(pixels.Bytes()))
|
||||||
_, err = fmt.Fprintf(writer, `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%gmm" height="%gmm" viewBox="0 0 %g %g"><image width="%g" height="%g" preserveAspectRatio="none" xlink:href="%s"/></svg>`, box.W, box.H, box.W, box.H, box.W, box.H, name)
|
_, err = fmt.Fprintf(writer, `<svg xmlns="`+svgNamespace+`" xmlns:xlink="`+XLinkNamespace+`" width="%gmm" height="%gmm" viewBox="0 0 %g %g"><image width="%g" height="%g" preserveAspectRatio="none" xlink:href="%s"/></svg>`, box.W, box.H, box.W, box.H, box.W, box.H, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SVGResources{}, err
|
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")
|
return fmt.Errorf("font has no visible glyphs")
|
||||||
}
|
}
|
||||||
width, height := min(count, 8)*36, (count+7)/8*40
|
width, height := min(count, 8)*36, (count+7)/8*40
|
||||||
_, err = fmt.Fprintf(writer, `<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d">%s</svg>`, width, height, width, height, paths.Bytes())
|
_, err = fmt.Fprintf(writer, `<svg xmlns="`+svgNamespace+`" width="%d" height="%d" viewBox="0 0 %d %d">%s</svg>`, width, height, width, height, paths.Bytes())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
+1
-1
@@ -283,7 +283,7 @@ func sameResourceID(first, second string) bool {
|
|||||||
return id != "" && id == editorResourceID(second)
|
return id != "" && id == editorResourceID(second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resourceValue 优先查找原标识与标准十进制标识,回退别名有冲突时不猜测
|
// resourceValue 优先查找原标识与标准十进制标识,回退时检查别名冲突
|
||||||
// 入参: values 已加载资源, id 标识文本
|
// 入参: values 已加载资源, id 标识文本
|
||||||
// 返回: T 资源值, bool 是否存在唯一结果
|
// 返回: T 资源值, bool 是否存在唯一结果
|
||||||
func resourceValue[T comparable](values map[string]T, id string) (T, bool) {
|
func resourceValue[T comparable](values map[string]T, id string) (T, bool) {
|
||||||
|
|||||||
+1
-1
@@ -394,7 +394,7 @@ func scanResourcePage(ctx context.Context, file *editorFontXML, pages map[string
|
|||||||
return false, false, nil, nil
|
return false, false, nil, nil
|
||||||
}
|
}
|
||||||
for _, attr := range token.Attr {
|
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
|
return false, false, nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func encodeStructureNode(node StructureNode) ([]byte, error) {
|
|||||||
switch node.Namespace {
|
switch node.Namespace {
|
||||||
case "":
|
case "":
|
||||||
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: ""})
|
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}
|
start.Name = xml.Name{Local: "xml:" + node.Name}
|
||||||
}
|
}
|
||||||
for _, attr := range node.Attributes {
|
for _, attr := range node.Attributes {
|
||||||
@@ -73,20 +73,20 @@ func encodeStructureNode(node StructureNode) ([]byte, error) {
|
|||||||
return output.Bytes(), nil
|
return output.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateStructureNode 校验XML名称和文本,禁止重复属性及伪造命名空间声明
|
// validateStructureNode 校验标引节点名称、属性和文本
|
||||||
// 入参: node 节点字段
|
// 入参: node 节点字段
|
||||||
// 返回: error 字段校验错误
|
// 返回: error 字段校验错误
|
||||||
func validateStructureNode(node StructureNode) error {
|
func validateStructureNode(node StructureNode) error {
|
||||||
if !ofdXMLIDValid(node.Name) || node.Name == "xmlns" {
|
if !ofdXMLIDValid(node.Name) || node.Name == "xmlns" {
|
||||||
return fmt.Errorf("invalid custom tag node name: %s", node.Name)
|
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")
|
return fmt.Errorf("invalid custom tag text or namespace")
|
||||||
}
|
}
|
||||||
seen := make(map[xml.Name]bool)
|
seen := make(map[xml.Name]bool)
|
||||||
for _, attr := range node.Attributes {
|
for _, attr := range node.Attributes {
|
||||||
name := xml.Name{Space: attr.Namespace, Local: attr.Name}
|
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)
|
return fmt.Errorf("invalid or duplicate custom tag attribute: %s", attr.Name)
|
||||||
}
|
}
|
||||||
seen[name] = true
|
seen[name] = true
|
||||||
@@ -159,7 +159,7 @@ func updateStructureNodeXML(data []byte, source *editorXML, node StructureNode)
|
|||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
if namespace == "http://www.w3.org/XML/1998/namespace" {
|
if namespace == XMLNamespace {
|
||||||
return "xml:" + name
|
return "xml:" + name
|
||||||
}
|
}
|
||||||
for _, attr := range attrs {
|
for _, attr := range attrs {
|
||||||
|
|||||||
@@ -19,6 +19,18 @@ import (
|
|||||||
"unicode/utf8"
|
"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命名空间
|
// 旧版与2016版OFD命名空间
|
||||||
const (
|
const (
|
||||||
ofdNamespaceLegacy = "http://www.ofdspec.org"
|
ofdNamespaceLegacy = "http://www.ofdspec.org"
|
||||||
|
|||||||
Reference in new issue
Block a user