a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
120 lines
3.1 KiB
Go
120 lines
3.1 KiB
Go
package languages
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"io"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/parser"
|
|
)
|
|
|
|
// XAMLExtractor extracts structure from XAML / AXAML markup — the
|
|
// declarative UI layer of WPF, UWP, MAUI, WinUI and Avalonia .NET
|
|
// apps. XAML is well-formed XML, so it is parsed with encoding/xml
|
|
// rather than tree-sitter.
|
|
//
|
|
// What it surfaces:
|
|
// - the file node, stamped with x:Class (the code-behind type the
|
|
// view is bound to);
|
|
// - one node per x:Name'd control, so a control referenced from
|
|
// code-behind resolves to a real graph node;
|
|
// - {Binding …} expressions, recorded on the owning control so the
|
|
// data-binding surface is queryable.
|
|
type XAMLExtractor struct{}
|
|
|
|
// NewXAMLExtractor constructs a XAMLExtractor.
|
|
func NewXAMLExtractor() *XAMLExtractor { return &XAMLExtractor{} }
|
|
|
|
func (e *XAMLExtractor) Language() string { return "xaml" }
|
|
func (e *XAMLExtractor) Extensions() []string { return []string{".xaml", ".axaml"} }
|
|
|
|
// Extract parses XAML markup. A malformed document yields whatever was
|
|
// decoded before the error — never a hard failure.
|
|
func (e *XAMLExtractor) Extract(filePath string, src []byte) (*parser.ExtractionResult, error) {
|
|
result := &parser.ExtractionResult{}
|
|
fileNode := &graph.Node{
|
|
ID: filePath,
|
|
Kind: graph.KindFile,
|
|
Name: path.Base(filePath),
|
|
FilePath: filePath,
|
|
Language: "xaml",
|
|
}
|
|
result.Nodes = append(result.Nodes, fileNode)
|
|
|
|
dec := xml.NewDecoder(bytes.NewReader(src))
|
|
dec.Strict = false
|
|
rootSeen := false
|
|
seenName := map[string]bool{}
|
|
|
|
for {
|
|
tok, err := dec.Token()
|
|
if err == io.EOF || err != nil {
|
|
break // EOF or malformed — keep what was decoded
|
|
}
|
|
se, ok := tok.(xml.StartElement)
|
|
if !ok {
|
|
continue
|
|
}
|
|
line := 1 + bytes.Count(src[:clampOffset(dec.InputOffset(), len(src))], []byte{'\n'})
|
|
|
|
if !rootSeen {
|
|
rootSeen = true
|
|
if cls := xamlAttr(se, "Class"); cls != "" {
|
|
fileNode.Meta = map[string]any{"xaml_class": cls}
|
|
}
|
|
}
|
|
|
|
name := xamlAttr(se, "Name")
|
|
if name == "" || seenName[name] {
|
|
continue
|
|
}
|
|
seenName[name] = true
|
|
|
|
ctrl := &graph.Node{
|
|
ID: filePath + "::" + name,
|
|
Kind: graph.KindVariable,
|
|
Name: name,
|
|
FilePath: filePath,
|
|
StartLine: line,
|
|
EndLine: line,
|
|
Language: "xaml",
|
|
Meta: map[string]any{"xaml_element": se.Name.Local},
|
|
}
|
|
var bindings []string
|
|
for _, a := range se.Attr {
|
|
if v := strings.TrimSpace(a.Value); strings.HasPrefix(v, "{Binding") {
|
|
bindings = append(bindings, a.Name.Local+"="+v)
|
|
}
|
|
}
|
|
if len(bindings) > 0 {
|
|
ctrl.Meta["xaml_bindings"] = bindings
|
|
}
|
|
result.Nodes = append(result.Nodes, ctrl)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// xamlAttr returns the value of the attribute with the given local
|
|
// name (namespace-agnostic, so both `x:Name` and `Name` match "Name").
|
|
func xamlAttr(se xml.StartElement, local string) string {
|
|
for _, a := range se.Attr {
|
|
if a.Name.Local == local {
|
|
return strings.TrimSpace(a.Value)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func clampOffset(off int64, n int) int64 {
|
|
if off > int64(n) {
|
|
return int64(n)
|
|
}
|
|
if off < 0 {
|
|
return 0
|
|
}
|
|
return off
|
|
}
|