xml

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2025 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Examples

Constants

View Source
const (
	AuthNone       = ""
	AuthBasic      = "Basic"
	AuthBearer     = "Bearer"
	AuthWSSecurity = "WSSecurity"
)

Tipos de Autenticación

Variables

This section is empty.

Functions

func AsBool

func AsBool(v any) bool

AsBool parses a boolean leniently. True: true, "true", "1", "yes", "on", "ok". False: everything else.

func AsFloat

func AsFloat(v any) float64

AsFloat attempts to convert any value to a float64. Returns 0.0 if conversion fails.

func AsInt

func AsInt(v any) int

AsInt attempts to convert any value to an int. Returns 0 if conversion fails. Supports strings like "123", floats (truncated), and bools (1/0).

func AsSlice

func AsSlice(v any) []any

AsSlice guarantees the return of a []any. If the input is nil, returns empty slice. If the input is a single element, returns a slice with that element.

func AsString

func AsString(v any) string

AsString safely converts any value to a string. It handles primitives, maps (JSON representation), and nil (empty string).

func AsTime

func AsTime(v any, layouts ...string) (time.Time, error)

AsTime parses a string into time.Time using a list of potential layouts. Default layouts: RFC3339, YYYY-MM-DD, and generic SQL timestamps.

func Attributes

func Attributes(data map[string]any) map[string]any

Attributes returns only the keys that represent XML attributes (starting with "@").

func Children

func Children(data map[string]any) map[string]any

Children returns only the keys that represent child nodes (excluding attributes and #text).

func Clone

func Clone(data map[string]any) map[string]any

Clone creates a deep copy of the map, ensuring no reference sharing. Crucial for immutable operations or concurrent access.

func Delete

func Delete(data map[string]any, path string) error

Delete removes a value or a complete node at the specified path. It supports deletion of map keys ("user/email") and array indices ("users/user[1]").

func FilterSlice

func FilterSlice[T any](input []T, predicate func(T) bool) []T

FilterSlice returns a new slice containing only elements that satisfy the predicate.

func FindFirst

func FindFirst[T any](input []T, predicate func(T) bool) (T, bool)

FindFirst returns the first element satisfying the predicate, or zero value if not found.

func Flatten

func Flatten(data map[string]any) map[string]any

Flatten converts a nested map into a single-level map with dot notation. Example: {"a": {"b": 1}} -> {"a.b": 1} Useful for exporting to CSV or searching.

func Get

func Get[T any](data map[string]any, path string) (T, error)

Get retrieves a value at the specified path and asserts it to type T. Usage: name, err := xml.Get[string](m, "user/name")

func Has

func Has(data map[string]any, key string) bool

Has checks if a key exists in the map (shallow check).

func Keys

func Keys(data map[string]any) []string

Keys returns a sorted list of all keys in the map.

func MapSlice

func MapSlice[T any, R any](input []T, transform func(T) R) []R

MapSlice applies a transformation function to each element in a slice.

func MapToJSON

func MapToJSON(data map[string]any) (string, error)

MapToJSON converts the XML map into a JSON string. This helper is particularly useful for debugging purposes or for preparing API responses.

func MapToStruct

func MapToStruct(data map[string]any, result any) error

MapToStruct converts the dynamic map into a user-defined struct. It uses JSON serialization as an intermediate layer, which is the cleanest and most robust approach to map dynamic keys to struct fields (respecting tags).

func MapXML

func MapXML(r io.Reader, opts ...Option) (map[string]any, error)

MapXML reads the entire XML input from the reader and returns a dynamic map[string]any. It handles hierarchical data, attributes, CDATA, comments, and preserves mixed-content order via "#seq".

Example

This example will appear in the Go documentation as a real-world use case.

xmlData := `<user><name>Arthur</name><role>Admin</role></user>`

// 1. Convert XML to Map
m, _ := MapXML(strings.NewReader(xmlData))

// 2. Query value
// CORRECTION: Since <name> has no attributes, the parser simplifies it directly to the value.
// No need to use "/#text".
name, _ := Query(m, "user/name")
role, _ := Query(m, "user/role")

fmt.Printf("User: %s, Role: %s\n", name, role)
Output:

User: Arthur, Role: Admin

func Marshal

func Marshal(data map[string]any, opts ...Option) (string, error)

Marshal returns the XML as a string (Helper wrapper).

func Merge

func Merge(base, override map[string]any)

Merge recursively merges the 'override' map into the 'base' map. If a key exists in both maps and they are sub-maps, they are merged recursively. Otherwise, the value in 'base' is overwritten by the value from 'override'. Note: This modifies 'base' in place.

func MergeDeep

func MergeDeep(base, override map[string]any)

MergeDeep is an alias for Merge (included for API clarity regarding deep merging).

func Omit

func Omit(data map[string]any, keys ...string) map[string]any

Omit returns a new map excluding the specified keys.

func Pick

func Pick(data map[string]any, keys ...string) map[string]any

Pick returns a new map containing only the specified keys.

func Query

func Query(data any, path string) (any, error)

Query is a convenience wrapper around QueryAll that returns the first matching result. It returns an error if no matching node is found. This is useful when you expect a single value or only care about the first match.

Example (Filtering)

ExampleQuery_filtering demonstrates the advanced filtering capabilities. You can search for nodes based on their attributes or child values using [key=value].

xmlData := `
	<users>
		<user id="1" type="admin"><name>Alice</name></user>
		<user id="2" type="guest"><name>Bob</name></user>
		<user id="3" type="admin"><name>Charlie</name></user>
	</users>`

m, _ := MapXML(strings.NewReader(xmlData), ForceArray("user"))

// 1. Find the name of the user with id=2
guestName, _ := Query(m, "users/user[id=2]/name")

// 2. Find the name of the user with type=admin (First match)
// Note: Use "@" for attributes in filters if needed, though simple keys work too depending on parsing.
adminName, _ := Query(m, "users/user[@type=admin]/name")

fmt.Printf("Guest: %s, Admin: %s\n", guestName, adminName)
Output:

Guest: Bob, Admin: Alice

func QueryAll

func QueryAll(data any, path string) ([]any, error)

QueryAll searches the data structure for all nodes matching the provided path. It returns a slice of matches found.

Path Syntax:

  • Deep Navigation: "library/section/book" (Traverse nested maps)
  • Array Indexing: "users/user[0]" (Access specific index)
  • Attribute/Value Filtering: "users/user[id=5]" or "book[@lang=en]"
  • Text Extraction: "book/title/#text" (Explicit text node access)

Note: If the path targets a list directly (e.g., "tags"), QueryAll returns the list itself as a single result in the slice, rather than flattening it. QueryAll searches the data structure for all nodes matching the provided path. It returns a slice of matches found.

Path Syntax:

  • Deep Navigation: "library/section/book"
  • Deep Search: "//error" (Find "error" nodes anywhere)
  • Array Indexing: "users/user[0]"
  • Filter Logic: "book[price>10]" or "user[role='admin']" or "user[id!=5]"
  • Filter Funcs: "book[contains(title, 'Go')]" or "user[starts-with(name, 'A')]"
  • Wildcards: "items/*/sku"
  • Custom Funcs: "items/func:isNumeric/id"
  • Meta-Properties: "items/#count" (Returns number of children)
  • Text Extraction: "book/title/#text"

func RegisterQueryFunction

func RegisterQueryFunction(name string, fn QueryFunction)

RegisterQueryFunction registers a custom function for use in Query paths. The name used here must match the segment in the path after "func:". Example: RegisterQueryFunction("startsWithBox", ...) -> Path "items/func:startsWithBox/sku"

func RunCLI

func RunCLI()

RunCLI transforms the program into a basic command-line utility. It detects command arguments, reads XML from STDIN, and outputs JSON.

Usage:

cat data.xml | go run main.go query "users/user[0]/name"

func Set

func Set(data map[string]any, path string, value any) error

Set updates or inserts a value at a given path. It supports map keys ("user/name") and specific indices of existing arrays ("users/user[0]/name"). Note: It creates intermediate map keys automatically, but it does NOT create arrays.

Example

ExampleModifyAndSave shows how to read XML, modify values dynamically, and write it back to a clean XML format.

xmlData := `<config><debug>false</debug><timeout>30</timeout></config>`
m, _ := MapXML(strings.NewReader(xmlData))

// 1. Update existing values
Set(m, "config/debug", "true")

// 2. Add new values (automatically creates map keys if parent exists)
Set(m, "config/server/port", "8080")

// 3. Write back to XML
NewEncoder(os.Stdout).Encode(m)
Output:

<config><debug>true</debug><server><port>8080</port></server><timeout>30</timeout></config>

func Text

func Text(data any) string

Text extracts ALL text content recursively from a node and its children. Equivalent to jQuery's .text(). Useful for search indexing.

Example

ExampleTextExtraction shows how to extract deep text content from a structure, similar to the jQuery .text() method. Useful for search indexing or summarizing.

xmlData := `
	<article>
		<h1>Breaking News</h1>
		<content>
			<p>The <b>stock market</b> reached record highs today.</p>
			<p>Details at 11.</p>
		</content>
	</article>`

m, _ := MapXML(strings.NewReader(xmlData))

// 1. Extract all text from the <content> tag, ignoring structure
contentNode, _ := Query(m, "article/content")
fullText := Text(contentNode)

fmt.Println(fullText)
Output:

The stock market reached record highs today.Details at 11.

func ToJSON

func ToJSON(r io.Reader, opts ...Option) ([]byte, error)

ToJSON scans the XML from the reader and returns the JSON representation. It performs smart simplification: 1. Unwraps the root element if it's a single key. 2. Removes internal metadata (#seq, #comments, etc.). 3. Renames attributes by removing the '@' prefix.

func Validate

func Validate(data any, rules []Rule) []string

============================================================================ VALIDATION ENGINE ============================================================================ (Mantener el código de Validate igual que tenías, está correcto)

Example
xmlData := `<config><port>8080</port></config>`
m, _ := MapXML(strings.NewReader(xmlData), EnableExperimental())

rules := []Rule{
	{Path: "config/port", Type: "int", Min: 1024, Max: 65535},
}

errs := Validate(m, rules)
if len(errs) == 0 {
	fmt.Println("Valid Configuration")
}
Output:

Valid Configuration

Types

type ClientOption

type ClientOption func(*SoapClient)

ClientOption para configuración funcional.

func WithBasicAuth

func WithBasicAuth(user, pass string) ClientOption

func WithBearerToken

func WithBearerToken(token string) ClientOption

func WithHeader

func WithHeader(key, value string) ClientOption

func WithSoapActionBase

func WithSoapActionBase(base string) ClientOption

func WithTimeout

func WithTimeout(d time.Duration) ClientOption

func WithWSSecurity

func WithWSSecurity(user, pass string) ClientOption

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder writes XML directly to an io.Writer.

func NewEncoder

func NewEncoder(w io.Writer, opts ...Option) *Encoder

NewEncoder creates a configured encoder.

Example
data := map[string]any{
	"response": map[string]any{
		"@status": "ok",
		"message": "Hello World",
	},
}

// Write clean XML to stdout
NewEncoder(os.Stdout).Encode(data)
Output:

<response status="ok"><message>Hello World</message></response>

func (*Encoder) Encode

func (e *Encoder) Encode(data map[string]any) error

Encode writes the map data as XML. It ensures there is exactly one root element (ignoring metadata keys like #seq).

type Option

type Option func(*config)

Option defines a function to modify the parser configuration.

func EnableExperimental

func EnableExperimental() Option

EnableExperimental (Soup Mode) enables aggressive settings for parsing dirty HTML. It activates: 1. Type Inference (strings are converted to int/bool/float if possible). 2. Lenient Mode (ignores strict XML syntax errors). 3. Soup Mode (Case-insensitive normalization for tags and attributes). 4. Void Element support (e.g., <br>, <img>, <input>).

Example

ExampleSoupMode demonstrates how to scrape data from messy, real-world HTML. It uses "EnableExperimental" to handle void tags (like <br>, <img>), case insensitivity, and sanitizes <script> tags automatically.

htmlData := `
	<html>
		<body bgcolor="#FFF">
			<div class="product-list">
				<div id="p1" data-stock="yes">
					<h2>Gaming Mouse</h2>
					<span class="price">$50</span>
					<img src="mouse.jpg">
				</div>
			</div>
		</body>
	</html>`

// 1. Enable Soup Mode (Lenient + Void Tags + Normalization)
m, _ := MapXML(strings.NewReader(htmlData), EnableExperimental())

// 2. Query using normalized paths (all lowercase) and attributes
name, _ := Get[string](m, "html/body/div/div/h2/#text")
price, _ := Get[string](m, "html/body/div/div/span/#text")
stock, _ := Get[string](m, "html/body/div/div/@data-stock")

fmt.Printf("Product: %s, Price: %s, Stock: %s\n", name, price, stock)
Output:

Product: Gaming Mouse, Price: $50, Stock: yes

func EnableLegacyCharsets

func EnableLegacyCharsets() Option

EnableLegacyCharsets habilita el soporte para ISO-8859-1 y Windows-1252.

Example
// IMPORTANTE: Construimos el XML simulando que viene de un archivo antiguo.
// Usamos escapes hexadecimales para forzar bytes ISO-8859-1 reales:
// \xF3 es 'ó' en ISO-8859-1 (en UTF-8 sería \xC3\xB3)
// \xF1 es 'ñ' en ISO-8859-1 (en UTF-8 sería \xC3\xB1)
xmlData := "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
	"<transacci\xF3n>\n" +
	"    <descripci\xF3n>Pago de expensas a\xF1o 2023</descripci\xF3n>\n" +
	"    <moneda>ARS</moneda>\n" +
	"</transacci\xF3n>"

// El parser detectará el encoding en el header y usará nuestro CharsetReader
// para transformar esos bytes \xF3 y \xF1 a UTF-8 válido internamente.
m, err := MapXML(strings.NewReader(xmlData), EnableLegacyCharsets())
if err != nil {
	fmt.Println("Error:", err)
	return
}

// Buscamos usando nombres de tags normales (en UTF-8/Go nativo)
// Nótese que aquí SÍ escribimos 'ó' normal, porque el mapa 'm' ya está en UTF-8.
desc, _ := Query(m, "transacción/descripción")
fmt.Println(desc)
Output:

Pago de expensas año 2023

func ForceArray

func ForceArray(keys ...string) Option

ForceArray returns an Option that forces specific tags to be parsed as arrays ([]any). This prevents the common XML-to-JSON ambiguity where single items are parsed as objects.

Example
// Case where there is only one item, but we want to treat it as a list
xmlData := `<list><item>One</item></list>`

m, _ := MapXML(strings.NewReader(xmlData), ForceArray("item"))

// CORRECTION: Same here, we look for the item directly, not its internal #text
items, _ := QueryAll(m, "list/item")
fmt.Println("Items:", len(items))
Output:

Items: 1

func RegisterNamespace

func RegisterNamespace(alias, url string) Option

RegisterNamespace returns an Option that registers a short alias for a namespace URL. Example: RegisterNamespace("html", "http://www.w3.org/html")

func WithPrettyPrint

func WithPrettyPrint() Option

WithPrettyPrint enables indentation for the Encoder (output beautification).

func WithValueHook

func WithValueHook(tagName string, fn func(string) any) Option

WithValueHook returns an Option that registers a custom transformation function for a specific tag. Use this to parse dates, custom formats, or decrypt data on the fly.

type QueryFunction

type QueryFunction func(key string) bool

QueryFunction defines the signature for custom key filter functions. It receives a map key (string) and returns true if the key should be traversed.

type Rule

type Rule struct {
	// Path to the element to validate (e.g., "server/port").
	Path string

	// Required enforces that the path must exist.
	Required bool

	// Type enforces the data type ("int", "float", "string", "array", "bool").
	Type string

	// Min enforces a minimum numeric value.
	Min float64

	// Max enforces a maximum numeric value.
	Max float64

	// Regex enforces a string pattern match.
	Regex string

	// Enum enforces that the value must be one of the provided strings.
	Enum []string
}

Rule defines a validation constraint for the Validate engine. It is used to enforce schema-like requirements on dynamic maps.

type SoapClient

type SoapClient struct {
	EndpointURL    string
	Namespace      string
	HttpClient     *http.Client
	SoapActionBase string
	Headers        map[string]string

	// Configuración de Auth
	AuthType     string
	AuthUsername string
	AuthPassword string
	AuthToken    string
}

SoapClient permite llamadas dinámicas a servicios SOAP sin structs.

func NewSoapClient

func NewSoapClient(endpoint, namespace string, opts ...ClientOption) *SoapClient

NewSoapClient crea un nuevo cliente.

func (*SoapClient) Call

func (c *SoapClient) Call(action string, payload map[string]any) (map[string]any, error)

Call ejecuta una acción SOAP.

type Stream

type Stream[T any] struct {
	// contains filtered or unexported fields
}

Stream allows iterating over huge XML files efficiently without loading the entire content into memory. It leverages Go Generics to yield typed structs directly.

func NewStream

func NewStream[T any](r io.Reader, tagName string, opts ...Option) *Stream[T]

NewStream initializes a new streaming iterator for a specific XML tag. r: The input reader (file, http body, etc). tagName: The local name of the XML element to iterate over (e.g., "Item", "Entry"). opts: Variadic options (e.g., EnableLegacyCharsets)

Example

ExampleStream demonstrates how to process huge XML files efficiently. Instead of loading the entire file into RAM, it iterates element by element using Go Generics to map data directly to a struct.

xmlData := `
	<orders>
		<Order><Id>101</Id><Total>500</Total></Order>
		<Order><Id>102</Id><Total>1200</Total></Order>
		<Order><Id>103</Id><Total>300</Total></Order>
	</orders>`

type Order struct {
	Id    int `xml:"Id"`
	Total int `xml:"Total"`
}

// 1. Create a Stream for "Order" tags
stream := NewStream[Order](strings.NewReader(xmlData), "Order")

// 2. Iterate efficiently
totalRevenue := 0
for order := range stream.Iter() {
	if order.Total > 1000 {
		fmt.Printf("High Value Order: %d\n", order.Id)
	}
	totalRevenue += order.Total
}
fmt.Printf("Total Revenue: %d\n", totalRevenue)
Output:

High Value Order: 102
Total Revenue: 2000

func (*Stream[T]) Iter

func (s *Stream[T]) Iter() <-chan T

Iter returns a read-only channel of items of type T. It is a convenience wrapper around IterWithContext using context.Background().

Usage:

stream := xml.NewStream[MyStruct](reader, "MyTag")
for item := range stream.Iter() {
    // process item
}

func (*Stream[T]) IterWithContext

func (s *Stream[T]) IterWithContext(ctx context.Context) <-chan T

IterWithContext returns a channel of items, respecting the provided Context. Use this method if you need to cancel the streaming process early or handle timeouts.

Usage:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
for item := range stream.IterWithContext(ctx) { ... }

type SyntaxError

type SyntaxError struct {
	Msg  string
	Line int
	Err  error // Original underlying error
}

SyntaxError wraps the standard xml.SyntaxError but exposes fields publicly and adds context if available.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

func (*SyntaxError) Unwrap

func (e *SyntaxError) Unwrap() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL