Skip to content

Latest commit

 

History

History
153 lines (134 loc) · 4.64 KB

File metadata and controls

153 lines (134 loc) · 4.64 KB

xinclude

The xinclude package processes XInclude directives in helium documents.

Import path: github.com/lestrrat-go/helium/xinclude

package examples_test

import (
  "context"
  "fmt"
  "os"
  "path/filepath"

  "github.com/lestrrat-go/helium"
  "github.com/lestrrat-go/helium/xinclude"
)

func Example_xinclude_process() {
  // XInclude allows XML documents to include content from other XML files.
  // The main document references an external fragment via <xi:include>.
  const mainSrc = `<?xml version="1.0"?>
<doc xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include href="fragment.xml"/>
</doc>`

  // This is the content of the included fragment.
  const fragmentSrc = `<?xml version="1.0"?>
<included>hello from fragment</included>`

  // Create a temporary directory and write both files to it.
  // The parser needs real files on disk because XInclude resolves
  // hrefs relative to the base URI of the including document.
  dir, err := os.MkdirTemp(".", ".tmp-xinclude-*")
  if err != nil {
    fmt.Printf("failed to create temp dir: %s\n", err)
    return
  }
  defer os.RemoveAll(dir)

  // Convert to absolute path so the XInclude processor can correctly
  // resolve relative hrefs without path-doubling issues.
  absDir, err := filepath.Abs(dir)
  if err != nil {
    fmt.Printf("failed to get abs path: %s\n", err)
    return
  }

  mainPath := filepath.Join(absDir, "main.xml")
  fragPath := filepath.Join(absDir, "fragment.xml")
  if err := os.WriteFile(mainPath, []byte(mainSrc), 0644); err != nil {
    fmt.Printf("failed to write: %s\n", err)
    return
  }
  if err := os.WriteFile(fragPath, []byte(fragmentSrc), 0644); err != nil {
    fmt.Printf("failed to write: %s\n", err)
    return
  }

  // parseMain is a helper that parses the main document from disk.
  // SetBaseURI tells the parser the file's location so relative hrefs
  // in xi:include can be resolved.
  parseMain := func() (*helium.Document, error) {
    data, err := os.ReadFile(mainPath)
    if err != nil {
      return nil, err
    }
    p := helium.NewParser().BaseURI(mainPath)
    return p.Parse(context.Background(), data)
  }

  // --- Default behavior: marker nodes ---
  //
  // By default (matching libxml2's behavior), xinclude.Process replaces
  // each xi:include element with a pair of XIncludeStart/XIncludeEnd
  // marker nodes that bracket the included content. These markers
  // serialize as empty <xi:include> elements, allowing applications to
  // track which parts of the tree were included.
  doc, err := parseMain()
  if err != nil {
    fmt.Printf("failed to parse: %s\n", err)
    return
  }

  // Configure an XInclude processor.
  proc := xinclude.NewProcessor().
    BaseURI(mainPath). // resolve relative hrefs against this path
    NoBaseFixup()      // skip xml:base fixup on included nodes

  n, err := proc.Process(context.Background(), doc)
  if err != nil {
    fmt.Printf("xinclude error: %s\n", err)
    return
  }
  fmt.Printf("substitutions: %d\n", n)

  s, err := helium.WriteString(doc)
  if err != nil {
    fmt.Printf("failed to serialize: %s\n", err)
    return
  }
  fmt.Printf("with markers:\n%s", s)

  // --- WithNoXIncludeMarkers: clean output ---
  //
  // WithNoXIncludeMarkers (equivalent to libxml2's XML_PARSE_NOXINCNODE)
  // removes the xi:include elements entirely after substitution,
  // leaving only the included content in the tree.
  doc, err = parseMain()
  if err != nil {
    fmt.Printf("failed to parse: %s\n", err)
    return
  }

  // Same processor settings, plus NoXIncludeMarkers to suppress the
  // bracketing xi:include nodes.
  proc = xinclude.NewProcessor().
    BaseURI(mainPath).
    NoBaseFixup().
    NoXIncludeMarkers() // strip marker nodes from the result tree

  n, err = proc.Process(context.Background(), doc)
  if err != nil {
    fmt.Printf("xinclude error: %s\n", err)
    return
  }
  fmt.Printf("substitutions: %d\n", n)

  s, err = helium.WriteString(doc)
  if err != nil {
    fmt.Printf("failed to serialize: %s\n", err)
    return
  }
  fmt.Printf("without markers:\n%s", s)
  // Output:
  // substitutions: 1
  // with markers:
  // <?xml version="1.0"?>
  // <doc xmlns:xi="http://www.w3.org/2001/XInclude">
  //   <xi:include></xi:include><included>hello from fragment</included><xi:include></xi:include>
  // </doc>
  // substitutions: 1
  // without markers:
  // <?xml version="1.0"?>
  // <doc xmlns:xi="http://www.w3.org/2001/XInclude">
  //   <included>hello from fragment</included>
  // </doc>
}

source: examples/xinclude_process_example_test.go