Files
larksuite__cli/shortcuts/slides/slides_shared.go
tianyouskrrr 7cfbea68d9 feat(slides): normalize replace-slide part aliases (#2225)
Normalize deterministic +replace-slide part aliases (replace→block_replace,
target_id→block_id, and payload field folding), and reject semantically
different actions up front.

Point whole-page actions at +update-slide now that it is GA:
- page_replace / slide_replace recovery guidance and the reference error
  table now direct callers to `slides +update-slide` (in-place whole-page
  rewrite) instead of the deprecated +replace-pages.
- drop the +replace-pages reference doc, completing the #2227 deprecation
  (the command stays; its deprecation signal is carried by --help and the
  output JSON `deprecated` field).

Generalize whole-page validation gating to the behavior rather than a
command name (SKILL.md, workflow/validation-xml.md): "整页写回后" instead of
"每次通过 +update-slide 整页写回后". Folds in PR #2255.
2026-08-10 19:28:03 +08:00

86 lines
2.7 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package slides
import (
"encoding/xml"
"errors"
"io"
"strings"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/shortcuts/common"
)
// Shared helpers for the whole-page slides commands. They lived in
// slides_replace_pages.go, which is now deprecated in favour of +update-slide
// but still registered; +add-slide and +delete-slide still use them.
// validateCompleteSlideXML checks that content is exactly one complete <slide>
// document: a single <slide> root, nothing but whitespace outside it, and every
// element closed. It reports the structural problem alone — callers re-tag the
// error with the flag it came from.
func validateCompleteSlideXML(content string) error {
dec := xml.NewDecoder(strings.NewReader(content))
depth := 0
seenRoot := false
for {
tok, err := dec.Token()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return err
}
switch t := tok.(type) {
case xml.StartElement:
if depth == 0 {
if seenRoot {
return invalidSlideXMLStructureError("multiple root elements")
}
if t.Name.Local != "slide" {
return invalidSlideXMLStructureError("root element is <%s>, want <slide>", t.Name.Local)
}
seenRoot = true
}
depth++
case xml.EndElement:
depth--
case xml.ProcInst:
// An `<?xml ...?>` prolog copied from a generic XML sample is
// well-formed, so nothing local used to object and it reached the
// backend, which answers 4001000 buildSnNode once the presentation
// already exists. Every caller of this validator posts to
// .../slide, and that is the endpoint that rejects it. Measured, so
// it does not get "made consistent" later: .../slide/replace takes
// the same prolog and applies the content, which is why
// +update-slide and +replace-slide stay permissive.
return invalidSlideXMLStructureError("<?%s ...?> declaration is not supported; remove it so the document starts with <slide>", t.Target)
case xml.CharData:
if depth == 0 && strings.TrimSpace(string(t)) != "" {
return invalidSlideXMLStructureError("non-whitespace text outside root element")
}
}
}
if !seenRoot {
return invalidSlideXMLStructureError("missing root element")
}
if depth != 0 {
return invalidSlideXMLStructureError("unclosed XML element")
}
return nil
}
func invalidSlideXMLStructureError(format string, args ...interface{}) error {
return errs.NewValidationError(errs.SubtypeInvalidArgument, format, args...)
}
// revisionFromData extracts revision_id from a response payload when present.
func revisionFromData(data map[string]interface{}) (int, bool) {
if _, ok := data["revision_id"]; !ok {
return 0, false
}
return int(common.GetFloat(data, "revision_id")), true
}