package d2svgimport

import (
	
	
	
	
	
	
	
	
	
	

	

	
	libsvg 
)

const (
	svgNamespace   = "http://www.w3.org/2000/svg"
	xlinkNamespace = "http://www.w3.org/1999/xlink"
	xmlnsNamespace = "xmlns"
	xmlNamespace   = "http://www.w3.org/XML/1998/namespace"
	xmlnsURI       = "http://www.w3.org/2000/xmlns/"
)

// Limits are required caller-selected hard limits for one SVG import. Every
// field must be positive. MaxElements and MaxPathCommands independently cap
// both parsed source and emitted scene totals. MaxResources caps declared IDs
// plus distinct embedded raster assets, and independently caps expanded
// local-use instances. MaxBytes also caps the retained encoded and decoded
// raster-asset totals so callers gain a finite image budget without a
// new required limit.
type Limits struct {
	MaxBytes              int
	MaxDepth              int
	MaxElements           int
	MaxAttributes         int
	MaxAttributeBytes     int
	MaxPathCommands       int
	MaxTransformFunctions int
	MaxUseDepth           int
	MaxResources          int
}

// Result is an owned, network-free SVG subtree plus its viewport metadata.
// Root remains in SVG user coordinates. ViewportTransform maps ViewBox into
// the intrinsic Width x Height viewport using Aspect. A caller embedding the
// subtree must clip final painting to [0,Width] x [0,Height]; in particular,
// AspectSlice intentionally maps content outside that viewport.
type Result struct {
	Root              *d2scene.Node
	ViewBox           d2scene.Box
	Width             float64
	Height            float64
	Aspect            d2scene.AspectRatio
	ViewportTransform d2scene.Matrix
	// Assets owns every immutable raster resource referenced by Image
	// primitives below Root. Keys are stable content IDs; callers embedding the
	// subtree must merge these assets into their document without mutation.
	Assets  map[d2scene.AssetID]d2scene.Asset
	Metrics Metrics
}

// ImportOptions selects caller-owned presentation context that is outside the
// imported SVG subtree. A nil CurrentColor preserves SVG's opaque-black
// initial color. ImportNodeWithOptions copies the pointed-to value before
// parsing and never retains the pointer.
type ImportOptions struct {
	CurrentColor *color.NRGBA
}

// Metrics reports bounded source and emitted-scene work for one successful
// import. Callers retaining multiple imported subscenes use these counters to
// enforce a shared document budget in addition to Limits' per-import ceilings.
type Metrics struct {
	SourceBytes          int
	ParsedElements       int
	ParsedAttributes     int
	ParsedAttributeBytes int
	ParsedPathCommands   int
	ParsedTransformFuncs int
	DeclaredResources    int
	ExpandedUseInstances int
	EmittedElements      int
	EmittedPathCommands  int
	EmbeddedRasterAssets int
	EmbeddedRasterBytes  int
	DecodedRasterBytes   int64
}

// ImportNode imports a strict SVG subset. It never reads files, resolves a
// URL, or performs network I/O. On every error it returns a nil result.
// Nested svg viewports are limited to finite absolute placement and dimensions
// with an explicit viewBox. Paint servers other than bounded local user-space
// linear gradients, clipping outside the bounded local user-space subset,
// masking, general painted text, nested SVG images, and external references
// are intentionally rejected rather than silently approximated. Embedded
// images are restricted to canonical base64 data URIs containing one static
// PNG, JPEG, GIF, or WebP resource. The sole painted
// text exception is the exact U+00B5 fallback emitted by D2's frozen MathJax
// renderer; it is converted to a pinned deterministic outline. Root ex lengths
// are accepted only with MathJax's exact inert vertical-align form, where one
// ex is deterministically eight CSS pixels.
// Non-painting title and corpus-bounded editor metadata are ignored after
// strict validation. Stylesheets are limited to a bounded simple
// class-selector subset; see stylesheet.go.
func ( context.Context,  string,  []byte,  Limits) (*Result, error) {
	return ImportNodeWithOptions(, , , , ImportOptions{})
}

// ImportNodeWithOptions imports the same strict subset as ImportNode with an
// explicit initial currentColor. An SVG color declaration still takes normal
// cascade precedence over this caller-supplied value.
func ( context.Context,  string,  []byte,  Limits,  ImportOptions) (*Result, error) {
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	if  := validateImportLimits();  != nil {
		return nil, 
	}
	if len() > .MaxBytes {
		return nil, fmt.Errorf("d2svgimport: %s is %d bytes, exceeding limit %d", redactImportSource(), len(), .MaxBytes)
	}
	if len() >= 3 && [0] == 0xef && [1] == 0xbb && [2] == 0xbf {
		 = [3:]
	}

	 := color.NRGBA{A: 255}
	if .CurrentColor != nil {
		 = *.CurrentColor
	}
	 := &svgImporter{
		ctx: , source: redactImportSource(), limits: ,
		ids: make(map[string]*svgElement), assets: make(map[d2scene.AssetID]d2scene.Asset), rootCurrentColor: ,
	}
	,  := .parse()
	if  != nil {
		return nil, 
	}
	if  := .compile();  != nil {
		return nil, 
	}
	,  := .viewport()
	if  != nil {
		return nil, 
	}
	,  := .instantiate(, defaultSVGStyleWithColor(), "", 0, false)
	if  != nil {
		return nil, 
	}
	if  == nil {
		 = d2scene.NewNode(nil)
		.ID = .id
	}
	if  := .Err();  != nil {
		return nil, 
	}
	if  := validateImportedBounds(, , .transform);  != nil {
		if  := .Err();  != nil {
			return nil, 
		}
		return nil, .errorf("imported scene has invalid or non-finite visual bounds")
	}
	if  := .Err();  != nil {
		return nil, 
	}
	return &Result{
		Root: , ViewBox: .viewBox, Width: .width, Height: .height,
		Aspect: .aspect, ViewportTransform: .transform, Assets: .assets,
		Metrics: Metrics{
			SourceBytes:          len(),
			ParsedElements:       .parsedElements,
			ParsedAttributes:     .parsedAttributes,
			ParsedAttributeBytes: .parsedAttrBytes,
			ParsedPathCommands:   .parsedPathCommand,
			ParsedTransformFuncs: .parsedTransforms,
			DeclaredResources:    len(.ids) + len(.assets),
			ExpandedUseInstances: .useInstances,
			EmittedElements:      .emittedElements,
			EmittedPathCommands:  .emittedCommands,
			EmbeddedRasterAssets: len(.assets),
			EmbeddedRasterBytes:  .rasterBytes,
			DecodedRasterBytes:   .rasterDecodedBytes,
		},
	}, nil
}

// validateImportedBounds is iterative so a caller-selected depth limit cannot
// turn otherwise bounded input into a Go call-stack failure. Primitive and
// clip bounds under their composed affine transforms form the complete
// finite-value preflight; this importer does not produce masks, filters, or
// animations.
func validateImportedBounds( context.Context,  *d2scene.Node,  d2scene.Matrix) error {
	type  struct {
		      *d2scene.Node
		 d2scene.Matrix
	}
	 := []{{: , : }}
	for len() != 0 {
		if  := .Err();  != nil {
			return 
		}
		 := [len()-1]
		 = [:len()-1]
		if . == nil {
			continue
		}
		 := ..Mul(..Transform)
		if !.IsFinite() {
			return fmt.Errorf("non-finite composed transform")
		}
		if  := validatePrimitiveBounds(, ..Primitive, );  != nil {
			return fmt.Errorf("invalid primitive bounds")
		}
		if ..Clip != nil {
			if !..Clip.Transform.IsFinite() || ..Clip.Path.FillRule > d2scene.EvenOdd {
				return fmt.Errorf("invalid clip")
			}
			if  := validatePrimitiveBounds(, ..Clip.Path, .Mul(..Clip.Transform));  != nil {
				if  := .Err();  != nil {
					return 
				}
				return fmt.Errorf("invalid clip bounds")
			}
		}
		if  := .Err();  != nil {
			return 
		}
		for  := len(..Children) - 1;  >= 0; -- {
			 = append(, {: ..Children[], : })
		}
	}
	return nil
}

func validatePrimitiveBounds( context.Context,  d2scene.Primitive,  d2scene.Matrix) error {
	var  d2scene.Path
	switch typed := .(type) {
	case d2scene.Path:
		 = 
	case *d2scene.Path:
		if  == nil {
			return nil
		}
		 = *
	default:
		,  := d2scene.PrimitiveBounds(, )
		if  != nil || !.IsFinite() {
			return fmt.Errorf("invalid primitive bounds")
		}
		return nil
	}

	// d2scene's analytic path bounds scan is intentionally independent of a
	// context. Validate large imported paths in bounded chunks, carrying their
	// current point across chunks and replacing close with its explicit segment.
	const  = 256
	 := make([]d2scene.PathCommand, 0, +1)
	var ,  d2scene.Point
	 := false
	 := func() error {
		if len() == 0 {
			return nil
		}
		 := 
		.Commands = 
		,  := d2scene.PrimitiveBounds(, )
		if  != nil || !.IsFinite() {
			return fmt.Errorf("invalid path bounds")
		}
		 = [:0]
		return .Err()
	}
	for ,  := range .Commands {
		if % == 0 {
			if  := .Err();  != nil {
				return 
			}
		}
		if len() == 0 &&  && .Kind != d2scene.MoveCommand {
			 = append(, d2scene.MoveTo(.X, .Y))
		}
		switch .Kind {
		case d2scene.MoveCommand:
			, ,  = .P1, .P1, true
		case d2scene.LineCommand:
			 = .P1
		case d2scene.QuadraticCommand:
			 = .P2
		case d2scene.CubicCommand:
			 = .P3
		case d2scene.ArcCommand:
			 = .P1
		case d2scene.CloseCommand:
			 = d2scene.LineTo(.X, .Y)
			 = 
		}
		 = append(, )
		if (+1)% == 0 {
			if  := ();  != nil {
				return 
			}
		}
	}
	return ()
}

func validateImportLimits( Limits) error {
	if .MaxBytes <= 0 || .MaxDepth <= 0 || .MaxElements <= 0 ||
		.MaxAttributes <= 0 || .MaxAttributeBytes <= 0 || .MaxPathCommands <= 0 ||
		.MaxTransformFunctions <= 0 || .MaxUseDepth <= 0 || .MaxResources <= 0 {
		return fmt.Errorf("d2svgimport: all import limits must be positive")
	}
	return nil
}

type svgElement struct {
	name      string
	id        string
	isRoot    bool
	parent    *svgElement
	attrs     map[string]string
	metadata  map[string]string
	attrOrder []string
	children  []*svgElement
	text      []byte
	classes   []string
	classSet  map[string]struct{}

	declarations map[string]string
	transform    d2scene.Matrix
	geometry     svgGeometry
	href         string
	useX         float64
	useY         float64
	gradient     *d2scene.LinearGradient
	gradientStop *d2scene.GradientStop
	clipPath     *compiledClipPath
	viewport     *svgViewport
	viewportX    float64
	viewportY    float64
}

type svgImporter struct {
	ctx              context.Context
	source           string
	limits           Limits
	ids              map[string]*svgElement
	assets           map[d2scene.AssetID]d2scene.Asset
	stylesheetRules  []stylesheetRule
	elementNamespace string
	namespaceSet     bool

	parsedElements     int
	parsedAttributes   int
	parsedAttrBytes    int
	parsedPathCommand  int
	parsedTransforms   int
	emittedElements    int
	emittedCommands    int
	useInstances       int
	stylesheetBytes    int
	mathJaxRoot        *svgElement
	rootCurrentColor   color.NRGBA
	rasterBytes        int
	rasterDecodedBytes int64
}

type contextReader struct {
	ctx    context.Context
	reader *bytes.Reader
}

func ( *contextReader) ( []byte) (int, error) {
	if  := .ctx.Err();  != nil {
		return 0, 
	}
	// Keep work between cancellation checkpoints bounded even when the XML
	// decoder requests a very large read for one attacker-controlled token.
	const  = 4096
	if len() >  {
		 = [:]
	}
	return .reader.Read()
}

func ( *svgImporter) ( []byte) (*svgElement, error) {
	 := xml.NewDecoder(&contextReader{ctx: .ctx, reader: bytes.NewReader()})
	.Strict = true
	.CharsetReader = iso88591XMLReader
	var  *svgElement
	var  []*svgElement
	 := false
	 := false
	 := false
	 := true
	for {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		,  := .Token()
		if  == io.EOF {
			break
		}
		if  != nil {
			if  := .ctx.Err();  != nil {
				return nil, 
			}
			return nil, .errorf("invalid XML near byte %d", .InputOffset())
		}
		switch token := .(type) {
		case xml.StartElement:
			 = false
			if  {
				return nil, .errorf("multiple root elements near byte %d", .InputOffset())
			}
			if len()+1 > .limits.MaxDepth {
				return nil, .errorf("XML depth exceeds limit %d", .limits.MaxDepth)
			}
			.parsedElements++
			if .parsedElements > .limits.MaxElements {
				return nil, .errorf("element count exceeds limit %d", .limits.MaxElements)
			}
			,  := .elementName(.Name)
			if  != nil {
				return nil, 
			}
			if  == nil &&  != "svg" {
				return nil, .errorf("root element must be <svg>, got <%s>", )
			}
			, , , ,  := .attributes(, .Attr)
			if  != nil {
				return nil, 
			}
			 := &svgElement{name: , id: , isRoot:  == nil, attrs: , metadata: , attrOrder: }
			if  != "" {
				if ,  := .ids[];  {
					return nil, .errorf("duplicate id")
				}
				if len(.ids) >= .limits.MaxResources {
					return nil, .errorf("ID resource count exceeds limit %d", .limits.MaxResources)
				}
				.ids[] = 
			}
			if len() == 0 {
				 = 
			} else {
				 := [len()-1]
				.parent = 
				.children = append(.children, )
			}
			 = append(, )
		case xml.EndElement:
			 = false
			if len() == 0 {
				return nil, .errorf("unexpected closing element near byte %d", .InputOffset())
			}
			,  := .elementName(.Name)
			if  != nil {
				return nil, 
			}
			 := [len()-1]
			if .name !=  {
				return nil, .errorf("closing </%s> does not match <%s>", , .name)
			}
			 = [:len()-1]
			if len() == 0 {
				 = true
			}
		case xml.CharData:
			if len() != 0 && [len()-1].name == "style" {
				if  := .appendStylesheetText([len()-1], );  != nil {
					return nil, 
				}
				if len() != 0 {
					 = false
				}
				continue
			}
			if len() != 0 && [len()-1].name == "title" {
				if  := checkpointXMLText(.ctx, );  != nil {
					return nil, 
				}
				if len() != 0 {
					 = false
				}
				continue
			}
			if len() != 0 && [len()-1].name == "dc:format" {
				if  := .appendMetadataText([len()-1], );  != nil {
					return nil, 
				}
				if len() != 0 {
					 = false
				}
				continue
			}
			if len() != 0 && [len()-1].name == "text" {
				if  := .appendPaintedText([len()-1], );  != nil {
					return nil, 
				}
				if len() != 0 {
					 = false
				}
				continue
			}
			,  := hasNonXMLSpace(.ctx, )
			if  != nil {
				return nil, 
			}
			if  {
				return nil, .errorf("text content is unsupported near byte %d", .InputOffset())
			}
			if len() != 0 {
				 = false
			}
		case xml.Comment:
			if len() != 0 && [len()-1].name == "style" {
				return nil, .errorf("CSS comments in <style> are unsupported")
			}
			// Comments carry no rendering semantics and remain bounded by MaxBytes.
			 = false
		case xml.Directive:
			if  != nil || len() != 0 ||  || !libsvg.IsSupportedExternalDoctype(, ) {
				return nil, .errorf("unsupported XML directive or DTD")
			}
			 = true
			 = false
		case xml.ProcInst:
			// encoding/xml exposes the optional declaration as a processing
			// instruction. Permit only that declaration, before the root.
			if .Target != "xml" ||  || ! ||  != nil || len() != 0 {
				return nil, .errorf("XML processing instructions are forbidden")
			}
			if  := .validateXMLDeclaration(.Inst);  != nil {
				return nil, 
			}
			 = true
			 = false
		default:
			return nil, .errorf("unsupported XML token near byte %d", .InputOffset())
		}
	}
	if  == nil {
		return nil, .errorf("document has no root <svg> element")
	}
	if len() != 0 {
		return nil, .errorf("unterminated <%s> element", [len()-1].name)
	}
	if ! {
		return nil, .errorf("unterminated root element")
	}
	return , nil
}

func ( *svgImporter) ( *svgElement,  []byte) error {
	const  = 4096
	for len() != 0 {
		if  := .ctx.Err();  != nil {
			return 
		}
		 := len()
		if  >  {
			 = 
		}
		if  > .limits.MaxBytes-.stylesheetBytes {
			return .errorf("stylesheet text exceeds byte limit %d", .limits.MaxBytes)
		}
		.text = append(.text, [:]...)
		.stylesheetBytes += 
		 = [:]
	}
	return .ctx.Err()
}

func ( *svgImporter) ( *svgElement,  []byte) error {
	const  = 4096
	for len() != 0 {
		if  := .ctx.Err();  != nil {
			return 
		}
		 := len()
		if  >  {
			 = 
		}
		// The XML source itself has already been bounded by MaxBytes. This
		// subtraction additionally prevents one element from retaining more
		// decoded character data than that same caller-selected ceiling.
		if  > .limits.MaxBytes-len(.text) {
			return .errorf("painted text exceeds byte limit %d", .limits.MaxBytes)
		}
		.text = append(.text, [:]...)
		 = [:]
	}
	return .ctx.Err()
}

func ( *svgImporter) ( []byte) error {
	 := 0
	 := func() error {
		for  < len() && isXMLSpace([]) {
			++
			if &4095 == 0 {
				if  := .ctx.Err();  != nil {
					return 
				}
			}
		}
		return nil
	}
	if  := ();  != nil {
		return 
	}
	type  struct {
		  string
		 string
	}
	var  []
	for  < len() {
		if  := .ctx.Err();  != nil {
			return 
		}
		 := 
		for  < len() && isASCIIAlpha([]) {
			++
			if &4095 == 0 {
				if  := .ctx.Err();  != nil {
					return 
				}
			}
		}
		if  ==  {
			return .errorf("invalid XML declaration")
		}
		 := string([:])
		if  := ();  != nil {
			return 
		}
		if  >= len() || [] != '=' {
			return .errorf("invalid XML declaration")
		}
		++
		if  := ();  != nil {
			return 
		}
		if  >= len() || [] != '\'' && [] != '"' {
			return .errorf("invalid XML declaration")
		}
		 := []
		++
		 := 
		for  < len() && [] !=  {
			++
			if &4095 == 0 {
				if  := .ctx.Err();  != nil {
					return 
				}
			}
		}
		if  >= len() {
			return .errorf("invalid XML declaration")
		}
		 := string([:])
		++
		 = append(, {: , : })
		if  < len() && !isXMLSpace([]) {
			return .errorf("invalid XML declaration")
		}
		if  := ();  != nil {
			return 
		}
	}
	if len() == 0 || len() > 3 || [0]. != "version" || [0]. != "1.0" {
		return .errorf("invalid XML declaration")
	}
	 := 1
	if  < len() && []. == "encoding" {
		if !equalASCIIEqualFold([]., "UTF-8") && !equalASCIIEqualFold([]., "ISO-8859-1") {
			return .errorf("only UTF-8 and ISO-8859-1 XML declarations are supported")
		}
		++
	}
	if  < len() && []. == "standalone" {
		if []. != "yes" && []. != "no" {
			return .errorf("invalid XML declaration")
		}
		++
	}
	if  != len() {
		return .errorf("invalid XML declaration")
	}
	return nil
}

func iso88591XMLReader( string,  io.Reader) (io.Reader, error) {
	if !equalASCIIEqualFold(, "ISO-8859-1") {
		return nil, errors.New("unsupported XML character encoding")
	}
	return charmap.ISO8859_1.NewDecoder().Reader(), nil
}

func isXMLSpace( byte) bool {
	switch  {
	case ' ', '\t', '\r', '\n':
		return true
	default:
		return false
	}
}

func hasNonXMLSpace( context.Context,  []byte) (bool, error) {
	for ,  := range  {
		if !isXMLSpace() {
			return true, nil
		}
		if &4095 == 0 {
			if  := .Err();  != nil {
				return false, 
			}
		}
	}
	return false, nil
}

func ( *svgImporter) ( xml.Name) (string, error) {
	switch .Space {
	case "", svgNamespace:
		if !.namespaceSet {
			.elementNamespace = .Space
			.namespaceSet = true
		} else if .Space != .elementNamespace {
			return "", .errorf("element <%s> mixes SVG and empty namespaces", displayXMLName(.Local))
		}
		switch .Local {
		case "svg", "g", "defs", "style", "path", "rect", "circle", "ellipse", "line", "polyline", "polygon", "image", "text", "use", "clipPath", "linearGradient", "stop", "title", "metadata":
			return .Local, nil
		case "script", "foreignObject":
			return "", .errorf("forbidden element <%s>", .Local)
		case "radialGradient", "pattern":
			return "", .errorf("unsupported paint server <%s>", .Local)
		default:
			return "", .errorf("unknown element <%s>", displayXMLName(.Local))
		}
	case rdfNamespace:
		if .Local == "RDF" {
			return "rdf:RDF", nil
		}
	case creativeCommonsNS:
		if .Local == "Work" {
			return "cc:Work", nil
		}
	case dublinCoreNamespace:
		if .Local == "format" || .Local == "type" {
			return "dc:" + .Local, nil
		}
	case sodipodiNamespace:
		if .Local == "namedview" {
			return "sodipodi:namedview", nil
		}
	}
	return "", .errorf("element <%s> uses unsupported namespace or metadata name", displayXMLName(.Local))
}

func ( *svgImporter) ( string,  []xml.Attr) (map[string]string, []string, map[string]string, string, error) {
	 := make(map[string]string, len())
	 := make([]string, 0, len())
	 := make(map[string]string)
	 := make(map[xml.Name]struct{}, len())
	var  string
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return nil, nil, nil, "", 
		}
		.parsedAttributes++
		if .parsedAttributes > .limits.MaxAttributes {
			return nil, nil, nil, "", .errorf("attribute count exceeds limit %d", .limits.MaxAttributes)
		}
		 := len(.Name.Space) + len(.Name.Local) + len(.Value)
		if  > .limits.MaxAttributeBytes-.parsedAttrBytes {
			return nil, nil, nil, "", .errorf("attribute bytes exceed limit %d", .limits.MaxAttributeBytes)
		}
		.parsedAttrBytes += 
		if ,  := [.Name];  {
			return nil, nil, nil, "", .errorf("element <%s> has duplicate XML attribute %q", , displayXMLName(.Name.Local))
		}
		[.Name] = struct{}{}

		if .Name.Space == xmlnsNamespace || (.Name.Space == "" && .Name.Local == "xmlns") {
			if  := .validateNamespaceDeclaration(, );  != nil {
				return nil, nil, nil, "", 
			}
			continue
		}
		,  := .ignoredNamespacedAttribute(, )
		if  != nil {
			return nil, nil, nil, "", 
		}
		if  {
			if .Name.Space == "" {
				[.Name.Local] = .Value
			}
			continue
		}
		 := .Name.Local
		switch .Name.Space {
		case "":
		case xlinkNamespace:
			if  != "href" {
				return nil, nil, nil, "", .errorf("element <%s> has unsupported xlink attribute %q", , displayXMLName())
			}
		default:
			return nil, nil, nil, "", .errorf("element <%s> attribute %q uses unsupported namespace", , displayXMLName())
		}
		if hasASCIIEqualFoldPrefix(, "on") {
			return nil, nil, nil, "", .errorf("element <%s> has forbidden event attribute %q", , displayXMLName())
		}
		,  = .validateIgnoredUnnamespacedAttribute(, , .Value)
		if  != nil {
			return nil, nil, nil, "", 
		}
		if  {
			[] = .Value
			continue
		}
		if ,  := [];  {
			return nil, nil, nil, "", .errorf("element <%s> has duplicate attribute %q", , displayXMLName())
		}
		[] = .Value
		 = append(, )
		if  == "id" {
			 = .Value
			,  := validSVGID(.ctx, )
			if  != nil {
				return nil, nil, nil, "", 
			}
			if ! {
				return nil, nil, nil, "", .errorf("element <%s> has invalid id", )
			}
		}
	}
	return , , , , nil
}

func displayXMLName( string) string {
	if len() > 64 {
		return "<oversized-name>"
	}
	return 
}

func ( *svgImporter) ( string,  xml.Attr) error {
	 := ""
	if .Name.Space == xmlnsNamespace {
		 = .Name.Local
	}
	if  != "" && .Value == "" {
		return .errorf("element <%s> has an empty prefixed namespace binding", )
	}
	if equalASCIIEqualFold(, "xmlns") || .Value == xmlnsURI {
		return .errorf("element <%s> has a forbidden xmlns namespace binding", )
	}
	if  == "xml" {
		if .Value != xmlNamespace {
			return .errorf("element <%s> has an invalid xml namespace binding", )
		}
		return nil
	}
	if hasASCIIEqualFoldPrefix(, "xml") || .Value == xmlNamespace {
		return .errorf("element <%s> has a reserved xml namespace binding", )
	}
	return nil
}

func validSVGID( context.Context,  string) (bool, error) {
	if  == "" {
		return false, nil
	}
	for ,  := range  {
		if &4095 == 0 {
			if  := .Err();  != nil {
				return false, 
			}
		}
		if  == '#' || unicode.IsSpace() || unicode.IsControl() {
			return false, nil
		}
	}
	return true, .Err()
}

func ( *svgImporter) ( string,  ...any) error {
	return fmt.Errorf("d2svgimport: %s: %s", .source, fmt.Sprintf(, ...))
}

func redactImportSource( string) string {
	if  == "" {
		return "SVG asset"
	}
	if len() > 4096 {
		return "<oversized source locator>"
	}
	 := strings.TrimSpace()
	if  == "" {
		return "SVG asset"
	}
	if strings.HasPrefix(strings.ToLower(), "data:") {
		return "data URI"
	}
	,  := url.Parse()
	if  == nil && (.Scheme != "" || .Host != "") {
		// Opaque URIs can place arbitrary credentials or bearer material in
		// Opaque, where URL user/query redaction does not reach it.
		if .Opaque != "" {
			return .Scheme + ":<redacted>"
		}
		.User = nil
		.RawQuery = ""
		.ForceQuery = false
		.Fragment = ""
		.RawFragment = ""
		return .String()
	}
	if strings.HasPrefix(, "//") {
		return "<malformed network-path reference>"
	}
	if  != nil && hasURIScheme() {
		return "<malformed URI>"
	}
	if  := strings.IndexAny(, "?#");  >= 0 {
		 = [:]
	}
	return 
}

func equalASCIIEqualFold(,  string) bool {
	return len() == len() && hasASCIIEqualFoldPrefix(, )
}

func hasASCIIEqualFoldPrefix(,  string) bool {
	if len() < len() {
		return false
	}
	for  := range  {
		 := []
		 := []
		if  >= 'A' &&  <= 'Z' {
			 += 'a' - 'A'
		}
		if  >= 'A' &&  <= 'Z' {
			 += 'a' - 'A'
		}
		if  !=  {
			return false
		}
	}
	return true
}

func hasURIScheme( string) bool {
	if  == "" || !isASCIIAlpha([0]) {
		return false
	}
	for  := 1;  < len(); ++ {
		 := []
		if  == ':' {
			return true
		}
		if !isASCIIAlpha() && ( < '0' ||  > '9') &&  != '+' &&  != '-' &&  != '.' {
			return false
		}
	}
	return false
}

func isASCIIAlpha( byte) bool {
	return  >= 'A' &&  <= 'Z' ||  >= 'a' &&  <= 'z'
}