From ae8df5ea87a9c4120d4777dc52bba0b3ddabd9ea Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 21 Dec 2021 16:08:37 +1100 Subject: [PATCH] wip --- pkg/yqlib/doc/usage/xml.md | 21 +++++++++++ pkg/yqlib/encoder_xml.go | 71 ++++++++++++++++++++++++++++---------- pkg/yqlib/xml_test.go | 15 +++++++- 3 files changed, 87 insertions(+), 20 deletions(-) diff --git a/pkg/yqlib/doc/usage/xml.md b/pkg/yqlib/doc/usage/xml.md index 02cf3566..1215047c 100644 --- a/pkg/yqlib/doc/usage/xml.md +++ b/pkg/yqlib/doc/usage/xml.md @@ -120,3 +120,24 @@ will output ``` +## Encode xml: attributes +Fields with the matching xml-attribute-prefix are assumed to be attributes. + +Given a sample.yml file of: +```yaml +cat: + +name: tiger + meows: true + +``` +then +```bash +yq e -o=xml '.' sample.yml +``` +will output +```xml + + true + +``` + diff --git a/pkg/yqlib/encoder_xml.go b/pkg/yqlib/encoder_xml.go index fb19caab..b5fe832d 100644 --- a/pkg/yqlib/encoder_xml.go +++ b/pkg/yqlib/encoder_xml.go @@ -4,15 +4,17 @@ import ( "encoding/xml" "fmt" "io" + "strings" yaml "gopkg.in/yaml.v3" ) type xmlEncoder struct { - xmlEncoder *xml.Encoder + xmlEncoder *xml.Encoder + attributePrefix string } -func NewXmlEncoder(writer io.Writer, indent int) Encoder { +func NewXmlEncoder(writer io.Writer, indent int, attributePrefix string) Encoder { encoder := xml.NewEncoder(writer) var indentString = "" @@ -20,12 +22,12 @@ func NewXmlEncoder(writer io.Writer, indent int) Encoder { indentString = indentString + " " } encoder.Indent("", indentString) - return &xmlEncoder{encoder} + return &xmlEncoder{encoder, attributePrefix} } func (e *xmlEncoder) Encode(node *yaml.Node) error { switch node.Kind { case yaml.MappingNode: - return e.encodeMap(node) + return e.encodeTopLevelMap(node) case yaml.DocumentNode: return e.Encode(unwrapDoc(node)) case yaml.ScalarNode: @@ -35,18 +37,24 @@ func (e *xmlEncoder) Encode(node *yaml.Node) error { return fmt.Errorf("unsupported type %v", node.Tag) } +func (e *xmlEncoder) encodeTopLevelMap(node *yaml.Node) error { + for i := 0; i < len(node.Content); i += 2 { + key := node.Content[i] + value := node.Content[i+1] + + start := xml.StartElement{Name: xml.Name{Local: key.Value}} + err := e.doEncode(value, start) + if err != nil { + return err + } + } + return nil +} + func (e *xmlEncoder) doEncode(node *yaml.Node, start xml.StartElement) error { switch node.Kind { case yaml.MappingNode: - err := e.xmlEncoder.EncodeToken(start) - if err != nil { - return err - } - err = e.encodeMap(node) - if err != nil { - return err - } - return e.xmlEncoder.EncodeToken(start.End()) + return e.encodeMap(node, start) case yaml.SequenceNode: return e.encodeArray(node, start) case yaml.ScalarNode: @@ -77,16 +85,41 @@ func (e *xmlEncoder) encodeArray(node *yaml.Node, start xml.StartElement) error return nil } -func (e *xmlEncoder) encodeMap(node *yaml.Node) error { +func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error { + + //first find all the attributes and put them on the start token for i := 0; i < len(node.Content); i += 2 { key := node.Content[i] value := node.Content[i+1] - start := xml.StartElement{Name: xml.Name{Local: key.Value}} - err := e.doEncode(value, start) - if err != nil { - return err + if strings.HasPrefix(key.Value, e.attributePrefix) { + if value.Kind == yaml.ScalarNode { + attributeName := strings.Replace(key.Value, e.attributePrefix, "", 1) + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: attributeName}, Value: value.Value}) + } else { + return fmt.Errorf("cannot use %v as attribute, only scalars are supported", value.Tag) + } } } - return nil + + err := e.xmlEncoder.EncodeToken(start) + if err != nil { + return err + } + + //now we encode non attribute tokens + for i := 0; i < len(node.Content); i += 2 { + key := node.Content[i] + value := node.Content[i+1] + + if !strings.HasPrefix(key.Value, e.attributePrefix) { + start := xml.StartElement{Name: xml.Name{Local: key.Value}} + err := e.doEncode(value, start) + if err != nil { + return err + } + } + } + + return e.xmlEncoder.EncodeToken(start.End()) } diff --git a/pkg/yqlib/xml_test.go b/pkg/yqlib/xml_test.go index babe96e3..04302e68 100644 --- a/pkg/yqlib/xml_test.go +++ b/pkg/yqlib/xml_test.go @@ -28,7 +28,7 @@ func yamlToXml(sampleYaml string, indent int) string { var output bytes.Buffer writer := bufio.NewWriter(&output) - var encoder = NewXmlEncoder(writer, indent) + var encoder = NewXmlEncoder(writer, indent, "+") inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0, NewYamlDecoder()) if err != nil { panic(err) @@ -88,6 +88,19 @@ var xmlScenarios = []xmlScenario{ expected: "\n purrs\n meows\n", encodeScenario: true, }, + { + description: "Encode xml: attributes", + subdescription: "Fields with the matching xml-attribute-prefix are assumed to be attributes.", + input: "cat:\n +name: tiger\n meows: true\n", + expected: "\n true\n", + encodeScenario: true, + }, + { + skipDoc: true, + input: "cat:\n ++name: tiger\n meows: true\n", + expected: "\n true\n", + encodeScenario: true, + }, } //encode