lib/xml.go (117 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package lib
import (
"bytes"
"io"
"strings"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/elastic/mito/lib/xml"
)
// XML returns a cel.EnvOption to configure extended functions for XML
// decoding. The parameter specifies the CEL type adapter to use and a
// map of names to XSD document descriptions.
// A nil adapter is valid and will give an option using the default type
// adapter, types.DefaultTypeAdapter. A nil XSD mapping is valid and
// will give an option that performs best effort decoding leaving all
// values as strings and elevating elements to lists when more than one
// item is found for the path.
//
// # Decode XML
//
// decode_xml returns the object described by the XML encoding of the receiver
// or parameter, using an optional named XSD:
//
// <bytes>.decode_xml() -> <dyn>
// <string>.decode_xml() -> <dyn>
// decode_xml(<bytes>) -> <dyn>
// decode_xml(<string>) -> <dyn>
// <bytes>.decode_xml(<string>) -> <dyn>
// <string>.decode_xml(<string>) -> <dyn>
// decode_xml(<bytes>, <string>) -> <dyn>
// decode_xml(<string>, <string>) -> <dyn>
//
// Examples:
//
// "<?xml vers... ...>".decode_xml() // return { ... }
// b"<?xml vers... ...>".decode_xml() // return { ... }
// "<?xml vers... ...>".decode_xml("xsd") // return { ... }
// b"<?xml vers... ...>".decode_xml("xsd") // return { ... }
func XML(adapter types.Adapter, xsd map[string]string) (cel.EnvOption, error) {
if adapter == nil {
adapter = types.DefaultTypeAdapter
}
details := make(map[string]map[string]xml.Detail, len(xsd))
var err error
for name, doc := range xsd {
details[name], err = xml.Details([]byte(doc))
if err != nil {
return nil, err
}
}
return cel.Lib(xmlLib{adapter: adapter, xsdDetails: details}), nil
}
type xmlLib struct {
adapter types.Adapter
xsdDetails map[string]map[string]xml.Detail
}
func (l xmlLib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
cel.Function("decode_xml",
// Without type information.
cel.MemberOverload(
"string_decode_xml",
[]*cel.Type{cel.StringType},
cel.DynType,
cel.UnaryBinding(catch(l.decodeXML)),
),
cel.Overload(
"decode_xml_string",
[]*cel.Type{cel.StringType},
cel.DynType,
cel.UnaryBinding(catch(l.decodeXML)),
),
cel.MemberOverload(
"bytes_decode_xml",
[]*cel.Type{cel.BytesType},
cel.DynType,
cel.UnaryBinding(catch(l.decodeXML)),
),
cel.Overload(
"decode_xml_bytes",
[]*cel.Type{cel.BytesType},
cel.DynType,
cel.UnaryBinding(catch(l.decodeXML)),
),
// With type information.
cel.MemberOverload(
"string_decode_xml_string",
[]*cel.Type{cel.StringType, cel.StringType},
cel.DynType,
cel.BinaryBinding(catch(l.decodeXMLWithXSD)),
),
cel.Overload(
"decode_xml_string_string",
[]*cel.Type{cel.StringType, cel.StringType},
cel.DynType,
cel.BinaryBinding(catch(l.decodeXMLWithXSD)),
),
cel.MemberOverload(
"bytes_decode_xml_string",
[]*cel.Type{cel.BytesType, cel.StringType},
cel.DynType,
cel.BinaryBinding(catch(l.decodeXMLWithXSD)),
),
cel.Overload(
"decode_xml_bytes_string",
[]*cel.Type{cel.BytesType, cel.StringType},
cel.DynType,
cel.BinaryBinding(catch(l.decodeXMLWithXSD)),
),
),
}
}
func (xmlLib) ProgramOptions() []cel.ProgramOption { return nil }
func (l xmlLib) decodeXML(arg ref.Val) ref.Val {
return l.decodeXMLWithXSD(arg, types.String(""))
}
func (l xmlLib) decodeXMLWithXSD(arg0, arg1 ref.Val) ref.Val {
xsd, ok := arg1.(types.String)
if !ok {
return types.ValOrErr(xsd, "no such overload for decode_xml: %s", arg1.Type())
}
details, ok := l.xsdDetails[string(xsd)]
if !ok && xsd != "" {
return types.NewErr("no xsd %s", xsd)
}
var r io.Reader
switch msg := arg0.(type) {
case types.Bytes:
r = bytes.NewReader(msg)
case types.String:
r = strings.NewReader(string(msg))
default:
return types.NoSuchOverloadErr()
}
cdata, v, err := xml.Unmarshal(r, details)
if err != nil {
return types.NewErr("failed to unmarshal XML document: %v", err)
}
m := make(map[string]any)
if cdata != "" {
m["#text"] = cdata
}
if v != nil {
m["doc"] = v
}
return l.adapter.NativeToValue(m)
}