This commit is contained in:
Mike Farah 2021-12-21 16:19:27 +11:00
parent ae8df5ea87
commit 6bcbd873a6
6 changed files with 51 additions and 11 deletions

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<cat>3f</cat> <cat>3f</cat>
<dog>meow:as</dog> <dog>meow:as</dog>
<dog3>true</dog3> <dog3>true</dog3>

View File

@ -14,6 +14,7 @@ These operators are useful to process yaml documents that have stringified embed
| Properties | | to_props/@props | | Properties | | to_props/@props |
| CSV | | to_csv/@csv | | CSV | | to_csv/@csv |
| TSV | | to_tsv/@tsv | | TSV | | to_tsv/@tsv |
| XML | from_xml | |
CSV and TSV format both accept either a single array or scalars (representing a single row), or an array of array of scalars (representing multiple rows). CSV and TSV format both accept either a single array or scalars (representing a single row), or an array of array of scalars (representing multiple rows).

View File

@ -141,3 +141,22 @@ will output
</cat> </cat>
``` ```
## Encode xml: attributes with content
Fields with the matching xml-content-name is assumed to be content.
Given a sample.yml file of:
```yaml
cat:
+name: tiger
+content: cool
```
then
```bash
yq e -o=xml '.' sample.yml
```
will output
```xml
<cat name="tiger">cool</cat>
```

View File

@ -12,9 +12,10 @@ import (
type xmlEncoder struct { type xmlEncoder struct {
xmlEncoder *xml.Encoder xmlEncoder *xml.Encoder
attributePrefix string attributePrefix string
contentName string
} }
func NewXmlEncoder(writer io.Writer, indent int, attributePrefix string) Encoder { func NewXmlEncoder(writer io.Writer, indent int, attributePrefix string, contentName string) Encoder {
encoder := xml.NewEncoder(writer) encoder := xml.NewEncoder(writer)
var indentString = "" var indentString = ""
@ -22,7 +23,7 @@ func NewXmlEncoder(writer io.Writer, indent int, attributePrefix string) Encoder
indentString = indentString + " " indentString = indentString + " "
} }
encoder.Indent("", indentString) encoder.Indent("", indentString)
return &xmlEncoder{encoder, attributePrefix} return &xmlEncoder{encoder, attributePrefix, contentName}
} }
func (e *xmlEncoder) Encode(node *yaml.Node) error { func (e *xmlEncoder) Encode(node *yaml.Node) error {
switch node.Kind { switch node.Kind {
@ -92,7 +93,7 @@ func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
key := node.Content[i] key := node.Content[i]
value := node.Content[i+1] value := node.Content[i+1]
if strings.HasPrefix(key.Value, e.attributePrefix) { if strings.HasPrefix(key.Value, e.attributePrefix) && key.Value != e.contentName {
if value.Kind == yaml.ScalarNode { if value.Kind == yaml.ScalarNode {
attributeName := strings.Replace(key.Value, e.attributePrefix, "", 1) attributeName := strings.Replace(key.Value, e.attributePrefix, "", 1)
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: attributeName}, Value: value.Value}) start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: attributeName}, Value: value.Value})
@ -112,12 +113,19 @@ func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
key := node.Content[i] key := node.Content[i]
value := node.Content[i+1] value := node.Content[i+1]
if !strings.HasPrefix(key.Value, e.attributePrefix) { if !strings.HasPrefix(key.Value, e.attributePrefix) && key.Value != e.contentName {
start := xml.StartElement{Name: xml.Name{Local: key.Value}} start := xml.StartElement{Name: xml.Name{Local: key.Value}}
err := e.doEncode(value, start) err := e.doEncode(value, start)
if err != nil { if err != nil {
return err return err
} }
} else if key.Value == e.contentName {
// directly encode the contents
var charData xml.CharData = []byte(value.Value)
err = e.xmlEncoder.EncodeToken(charData)
if err != nil {
return err
}
} }
} }

View File

@ -25,6 +25,7 @@ const (
PropsOutputFormat PropsOutputFormat
CsvOutputFormat CsvOutputFormat
TsvOutputFormat TsvOutputFormat
XmlOutputFormat
) )
func OutputFormatFromString(format string) (PrinterOutputFormat, error) { func OutputFormatFromString(format string) (PrinterOutputFormat, error) {
@ -39,6 +40,8 @@ func OutputFormatFromString(format string) (PrinterOutputFormat, error) {
return CsvOutputFormat, nil return CsvOutputFormat, nil
case "tsv", "t": case "tsv", "t":
return TsvOutputFormat, nil return TsvOutputFormat, nil
case "xml", "x":
return XmlOutputFormat, nil
default: default:
return 0, fmt.Errorf("unknown format '%v' please use [yaml|json|props|csv|tsv]", format) return 0, fmt.Errorf("unknown format '%v' please use [yaml|json|props|csv|tsv]", format)
} }
@ -104,6 +107,8 @@ func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error {
encoder = NewCsvEncoder(writer, '\t') encoder = NewCsvEncoder(writer, '\t')
case YamlOutputFormat: case YamlOutputFormat:
encoder = NewYamlEncoder(writer, p.indent, p.colorsEnabled) encoder = NewYamlEncoder(writer, p.indent, p.colorsEnabled)
case XmlOutputFormat:
encoder = NewXmlEncoder(writer, p.indent, "+", "+content")
} }
return encoder.Encode(node) return encoder.Encode(node)

View File

@ -28,7 +28,7 @@ func yamlToXml(sampleYaml string, indent int) string {
var output bytes.Buffer var output bytes.Buffer
writer := bufio.NewWriter(&output) writer := bufio.NewWriter(&output)
var encoder = NewXmlEncoder(writer, indent, "+") var encoder = NewXmlEncoder(writer, indent, "+", "+content")
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0, NewYamlDecoder()) inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0, NewYamlDecoder())
if err != nil { if err != nil {
panic(err) panic(err)
@ -101,6 +101,13 @@ var xmlScenarios = []xmlScenario{
expected: "<cat +name=\"tiger\">\n <meows>true</meows>\n</cat>", expected: "<cat +name=\"tiger\">\n <meows>true</meows>\n</cat>",
encodeScenario: true, encodeScenario: true,
}, },
{
description: "Encode xml: attributes with content",
subdescription: "Fields with the matching xml-content-name is assumed to be content.",
input: "cat:\n +name: tiger\n +content: cool\n",
expected: "<cat name=\"tiger\">cool</cat>",
encodeScenario: true,
},
} }
//encode //encode
@ -121,7 +128,7 @@ func documentXmlScenario(t *testing.T, w *bufio.Writer, i interface{}) {
return return
} }
if s.encodeScenario { if s.encodeScenario {
documentXmlEncodeScenario(t, w, s) documentXmlEncodeScenario(w, s)
} else { } else {
documentXmlDecodeScenario(t, w, s) documentXmlDecodeScenario(t, w, s)
} }
@ -157,7 +164,7 @@ func documentXmlDecodeScenario(t *testing.T, w *bufio.Writer, s xmlScenario) {
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String())) writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
} }
func documentXmlEncodeScenario(t *testing.T, w *bufio.Writer, s xmlScenario) { func documentXmlEncodeScenario(w *bufio.Writer, s xmlScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description)) writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" { if s.subdescription != "" {