diff --git a/assets/webui/ofdgo.css b/assets/webui/ofdgo.css index ce515ad..20f5ec7 100644 --- a/assets/webui/ofdgo.css +++ b/assets/webui/ofdgo.css @@ -1215,6 +1215,7 @@ button:not(:disabled):hover { .info-custom-row input, .gradient-stop-row input, +.structure-attribute > input, .form-row input[type="text"], .form-row input[type="password"], .form-row input[type="number"], @@ -3049,26 +3050,55 @@ audio.page-media { text-align-last: justify; } +.structure-dialog .panel-row { + margin-bottom: 0; +} + +.structure-dialog .panel-row > span { + width: 4em; + text-align-last: justify; +} + +.structure-dialog .form-actions { + margin-top: 12px; +} + .structure-edit-list { display: grid; - gap: 8px; + gap: 12px; margin-top: 8px; } +.structure-edit-list:empty { + display: none; +} + .structure-attribute { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 28px; - gap: 6px; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) var(--control-height); + gap: 8px; } .structure-attribute .structure-attribute-value { - grid-column: 1 / 3; + grid-column: 1 / -1; } .structure-attribute > button { - grid-column: 3; - grid-row: 1 / 3; - align-self: center; + grid-column: -2; + grid-row: 1; + width: var(--control-height); + height: var(--control-height); + padding: 0; +} + +@media (max-width: 480px) { + .structure-attribute { + grid-template-columns: minmax(0, 1fr) var(--control-height); + } + + .structure-attribute > input[name="namespace"] { + grid-column: 1 / -1; + } } .structure-reference + .structure-reference { diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index d431fd0..885e0bb 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -8342,6 +8342,7 @@ function addStructureAttribute(attribute = {}) { row.className = "structure-attribute"; for (const [name, label] of [["name", "属性名称"], ["namespace", "命名空间"], ["value", "属性内容"]]) { const input = document.createElement("input"); + input.type = "text"; input.name = name; input.value = attribute[name] || ""; input.placeholder = label; @@ -8365,6 +8366,7 @@ function addStructureReference(reference = {}) { const row = document.createElement("div"); row.className = "structure-reference"; const page = document.createElement("select"), object = document.createElement("select"), ids = document.createElement("input"); + ids.type = "text"; page.name = "page"; ids.name = "ids"; ids.value = (reference.objectIds || []).join(","); diff --git a/ofdgo_editor_open.go b/ofdgo_editor_open.go index 73a5168..5cc3a20 100644 --- a/ofdgo_editor_open.go +++ b/ofdgo_editor_open.go @@ -320,29 +320,9 @@ func (e *Editor) sourceMaxID(progress editorProgress) (int, error) { // 返回: int 最大标识, error 错误信息 func editorXMLMaxID(input io.Reader) (int, error) { buffer := bufio.NewReader(input) - header, err := buffer.Peek(3) - if err != nil && err != io.EOF { + if found, err := editorXMLPrefix(buffer); err != nil || !found { return 0, err } - if bytes.Equal(header, []byte{0xef, 0xbb, 0xbf}) { - buffer.Discard(3) - } - for { - header, err := buffer.Peek(1) - if err == io.EOF { - return 0, nil - } - if err != nil { - return 0, err - } - if !strings.ContainsRune(" \t\r\n", rune(header[0])) { - if header[0] != '<' { - return 0, nil - } - break - } - buffer.Discard(1) - } decoder := xml.NewDecoder(buffer) maximum, started := 0, false for { diff --git a/ofdgo_editor_resources.go b/ofdgo_editor_resources.go index edaf835..c36ebe5 100644 --- a/ofdgo_editor_resources.go +++ b/ofdgo_editor_resources.go @@ -16,7 +16,6 @@ package ofdgo import ( "bufio" - "bytes" "context" "encoding/xml" "fmt" @@ -317,15 +316,11 @@ func (e *Editor) compactResourceReferences(parts map[string][]byte, reader *Read // 返回: bool 是否资源索引, bool 是否可完整判断引用 func (r *editorResourceRefs) scan(input io.Reader, name string) (bool, bool) { buffer := bufio.NewReader(input) - header, err := buffer.Peek(512) - if err != nil && err != io.EOF { + found, err := editorXMLPrefix(buffer) + if err != nil { return false, false } - header = bytes.TrimSpace(bytes.TrimPrefix(header, []byte{0xef, 0xbb, 0xbf})) - if len(header) == 0 { - return false, err == io.EOF && !strings.EqualFold(path.Ext(name), ".xml") - } - if header[0] != '<' { + if !found { return false, !strings.EqualFold(path.Ext(name), ".xml") } decoder := xml.NewDecoder(buffer) diff --git a/ofdgo_editor_xml.go b/ofdgo_editor_xml.go index 21756bb..b918018 100644 --- a/ofdgo_editor_xml.go +++ b/ofdgo_editor_xml.go @@ -15,6 +15,7 @@ package ofdgo import ( + "bufio" "bytes" "context" "encoding/xml" @@ -39,6 +40,39 @@ type editorXMLPatch struct { data []byte } +// editorXMLPrefix 跳过UTF-8字节序标记和XML空白,探测后保留首个内容字节 +// 入参: input 带缓冲的输入流,读取错误及取消信号由底层传递 +// 返回: bool 是否以XML标记起始, error 读取错误 +func editorXMLPrefix(input *bufio.Reader) (bool, error) { + header, err := input.Peek(3) + if err != nil && err != io.EOF { + return false, err + } + if bytes.Equal(header, []byte{0xef, 0xbb, 0xbf}) { + input.Discard(3) + } + for { + if _, err := input.Peek(1); err != nil { + if err == io.EOF { + return false, nil + } + return false, err + } + chunk, _ := input.Peek(input.Buffered()) + count := 0 + for count < len(chunk) { + switch chunk[count] { + case ' ', '\t', '\r', '\n': + count++ + default: + input.Discard(count) + return chunk[count] == '<', nil + } + } + input.Discard(count) + } +} + // parseEditorXML 解析节点位置,保留命名空间、注释和原始格式 // 入参: data XML原文 // 返回: *editorXML 根节点, error 错误信息