This commit is contained in:
Mike Farah 2021-12-21 17:59:44 +11:00
parent c48b1612df
commit 4d9583d1ee
6 changed files with 148 additions and 40 deletions

View File

@ -1,15 +1,8 @@
strings: # above_cat
- a: banana cat: # inline_cat
- a: cat # above_array
- a: apple array: # inline_array
- 3 # inline_3
numbers: # above_4
- a: 12 - 4 # inline_4
- a: 13 # below_cat
- a: 120
obj:
- a: {cat: "adog"}
- a: {cat: "doga"}
- a: apple

View File

@ -108,7 +108,8 @@ yq e -o=xml '.' sample.yml
``` ```
will output will output
```xml ```xml
<cat>purrs</cat>``` <cat>purrs</cat>
```
## Encode xml: array ## Encode xml: array
Given a sample.yml file of: Given a sample.yml file of:
@ -127,7 +128,8 @@ will output
<pets> <pets>
<cat>purrs</cat> <cat>purrs</cat>
<cat>meows</cat> <cat>meows</cat>
</pets>``` </pets>
```
## Encode xml: attributes ## Encode xml: attributes
Fields with the matching xml-attribute-prefix are assumed to be attributes. Fields with the matching xml-attribute-prefix are assumed to be attributes.
@ -147,7 +149,8 @@ will output
```xml ```xml
<cat name="tiger"> <cat name="tiger">
<meows>true</meows> <meows>true</meows>
</cat>``` </cat>
```
## Encode xml: attributes with content ## Encode xml: attributes with content
Fields with the matching xml-content-name is assumed to be content. Fields with the matching xml-content-name is assumed to be content.
@ -165,5 +168,33 @@ yq e -o=xml '.' sample.yml
``` ```
will output will output
```xml ```xml
<cat name="tiger">cool</cat>``` <cat name="tiger">cool</cat>
```
## Encode xml: comments
A best attempt is made to copy comments to xml.
Given a sample.yml file of:
```yaml
# above_cat
cat: # inline_cat
# above_array
array: # inline_array
- val1 # inline_val1
# above_val2
- val2 # inline_val2
# below_cat
```
then
```bash
yq e -o=xml '.' sample.yml
```
will output
```xml
<!-- above_cat inline_cat--><cat><!-- above_array inline_array-->
<array><!-- inline_val1-->val1</array>
<array><!-- above_val2 inline_val2-->val2</array>
</cat><!-- below_cat-->
```

View File

@ -3,7 +3,6 @@ package yqlib
import ( import (
"fmt" "fmt"
"io" "io"
"strings"
"github.com/magiconair/properties" "github.com/magiconair/properties"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
@ -30,9 +29,7 @@ func (pe *propertiesEncoder) Encode(node *yaml.Node) error {
} }
func (pe *propertiesEncoder) doEncode(p *properties.Properties, node *yaml.Node, path string) error { func (pe *propertiesEncoder) doEncode(p *properties.Properties, node *yaml.Node, path string) error {
p.SetComment(path, p.SetComment(path, headAndLineComment(node))
strings.Replace(node.HeadComment, "#", "", 1)+
strings.Replace(node.LineComment, "#", "", 1))
switch node.Kind { switch node.Kind {
case yaml.ScalarNode: case yaml.ScalarNode:
_, _, err := p.Set(path, node.Value) _, _, err := p.Set(path, node.Value)

View File

@ -32,14 +32,20 @@ func (e *xmlEncoder) Encode(node *yaml.Node) error {
if err != nil { if err != nil {
return err return err
} }
var charData xml.CharData = []byte("\n") case yaml.DocumentNode:
err = e.xmlEncoder.EncodeToken(charData) err := e.encodeComment(headAndLineComment(node))
if err != nil {
return err
}
err = e.Encode(unwrapDoc(node))
if err != nil {
return err
}
err = e.encodeComment(footComment(node))
if err != nil { if err != nil {
return err return err
} }
return e.xmlEncoder.Flush()
case yaml.DocumentNode:
return e.Encode(unwrapDoc(node))
case yaml.ScalarNode: case yaml.ScalarNode:
var charData xml.CharData = []byte(node.Value) var charData xml.CharData = []byte(node.Value)
err := e.xmlEncoder.EncodeToken(charData) err := e.xmlEncoder.EncodeToken(charData)
@ -47,9 +53,13 @@ func (e *xmlEncoder) Encode(node *yaml.Node) error {
return err return err
} }
return e.xmlEncoder.Flush() return e.xmlEncoder.Flush()
} default:
return fmt.Errorf("unsupported type %v", node.Tag) return fmt.Errorf("unsupported type %v", node.Tag)
} }
var charData xml.CharData = []byte("\n")
return e.xmlEncoder.EncodeToken(charData)
}
func (e *xmlEncoder) encodeTopLevelMap(node *yaml.Node) error { func (e *xmlEncoder) encodeTopLevelMap(node *yaml.Node) error {
for i := 0; i < len(node.Content); i += 2 { for i := 0; i < len(node.Content); i += 2 {
@ -57,7 +67,16 @@ func (e *xmlEncoder) encodeTopLevelMap(node *yaml.Node) error {
value := node.Content[i+1] value := node.Content[i+1]
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.encodeComment(headAndLineComment(key))
if err != nil {
return err
}
err = e.doEncode(value, start)
if err != nil {
return err
}
err = e.encodeComment(footComment(key))
if err != nil { if err != nil {
return err return err
} }
@ -65,6 +84,22 @@ func (e *xmlEncoder) encodeTopLevelMap(node *yaml.Node) error {
return nil return nil
} }
func (e *xmlEncoder) encodeStart(node *yaml.Node, start xml.StartElement) error {
err := e.xmlEncoder.EncodeToken(start)
if err != nil {
return err
}
return e.encodeComment(headAndLineComment(node))
}
func (e *xmlEncoder) encodeEnd(node *yaml.Node, start xml.StartElement) error {
err := e.xmlEncoder.EncodeToken(start.End())
if err != nil {
return err
}
return e.encodeComment(footComment(node))
}
func (e *xmlEncoder) doEncode(node *yaml.Node, start xml.StartElement) error { func (e *xmlEncoder) doEncode(node *yaml.Node, start xml.StartElement) error {
switch node.Kind { switch node.Kind {
case yaml.MappingNode: case yaml.MappingNode:
@ -72,22 +107,33 @@ func (e *xmlEncoder) doEncode(node *yaml.Node, start xml.StartElement) error {
case yaml.SequenceNode: case yaml.SequenceNode:
return e.encodeArray(node, start) return e.encodeArray(node, start)
case yaml.ScalarNode: case yaml.ScalarNode:
err := e.xmlEncoder.EncodeToken(start) err := e.encodeStart(node, start)
if err != nil { if err != nil {
return err return err
} }
var charData xml.CharData = []byte(node.Value) var charData xml.CharData = []byte(node.Value)
err = e.xmlEncoder.EncodeToken(charData) err = e.xmlEncoder.EncodeToken(charData)
if err != nil { if err != nil {
return err return err
} }
return e.xmlEncoder.EncodeToken(start.End())
return e.encodeEnd(node, start)
} }
return fmt.Errorf("unsupported type %v", node.Tag) return fmt.Errorf("unsupported type %v", node.Tag)
} }
func (e *xmlEncoder) encodeComment(commentStr string) error {
if commentStr != "" {
var comment xml.Comment = []byte(commentStr)
err := e.xmlEncoder.EncodeToken(comment)
if err != nil {
return err
}
}
return nil
}
func (e *xmlEncoder) encodeArray(node *yaml.Node, start xml.StartElement) error { func (e *xmlEncoder) encodeArray(node *yaml.Node, start xml.StartElement) error {
for i := 0; i < len(node.Content); i++ { for i := 0; i < len(node.Content); i++ {
value := node.Content[i] value := node.Content[i]
@ -116,7 +162,7 @@ func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
} }
} }
err := e.xmlEncoder.EncodeToken(start) err := e.encodeStart(node, start)
if err != nil { if err != nil {
return err return err
} }
@ -126,6 +172,11 @@ 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]
err := e.encodeComment(headAndLineComment(key))
if err != nil {
return err
}
if !strings.HasPrefix(key.Value, e.attributePrefix) && key.Value != e.contentName { 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)
@ -140,7 +191,11 @@ func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
return err return err
} }
} }
err = e.encodeComment(footComment(key))
if err != nil {
return err
}
} }
return e.xmlEncoder.EncodeToken(start.End()) return e.encodeEnd(node, start)
} }

View File

@ -236,6 +236,15 @@ func createScalarNode(value interface{}, stringValue string) *yaml.Node {
return node return node
} }
func headAndLineComment(node *yaml.Node) string {
return strings.Replace(node.HeadComment, "#", "", 1) +
strings.Replace(node.LineComment, "#", "", 1)
}
func footComment(node *yaml.Node) string {
return strings.Replace(node.FootComment, "#", "", 1)
}
func createValueOperation(value interface{}, stringValue string) *Operation { func createValueOperation(value interface{}, stringValue string) *Operation {
var node *yaml.Node = createScalarNode(value, stringValue) var node *yaml.Node = createScalarNode(value, stringValue)

View File

@ -52,6 +52,22 @@ type xmlScenario struct {
encodeScenario bool encodeScenario bool
} }
var yamlWithComments = `# above_cat
cat: # inline_cat
# above_array
array: # inline_array
- val1 # inline_val1
# above_val2
- val2 # inline_val2
# below_cat
`
var expectedXmlWithComments = `<!-- above_cat inline_cat--><cat><!-- above_array inline_array-->
<array><!-- inline_val1-->val1</array>
<array><!-- above_val2 inline_val2-->val2</array>
</cat><!-- below_cat-->
`
var xmlScenarios = []xmlScenario{ var xmlScenarios = []xmlScenario{
{ {
description: "Parse xml: simple", description: "Parse xml: simple",
@ -79,33 +95,40 @@ var xmlScenarios = []xmlScenario{
{ {
description: "Encode xml: simple", description: "Encode xml: simple",
input: "cat: purrs", input: "cat: purrs",
expected: "<cat>purrs</cat>", expected: "<cat>purrs</cat>\n",
encodeScenario: true, encodeScenario: true,
}, },
{ {
description: "Encode xml: array", description: "Encode xml: array",
input: "pets:\n cat:\n - purrs\n - meows", input: "pets:\n cat:\n - purrs\n - meows",
expected: "<pets>\n <cat>purrs</cat>\n <cat>meows</cat>\n</pets>", expected: "<pets>\n <cat>purrs</cat>\n <cat>meows</cat>\n</pets>\n",
encodeScenario: true, encodeScenario: true,
}, },
{ {
description: "Encode xml: attributes", description: "Encode xml: attributes",
subdescription: "Fields with the matching xml-attribute-prefix are assumed to be attributes.", subdescription: "Fields with the matching xml-attribute-prefix are assumed to be attributes.",
input: "cat:\n +name: tiger\n meows: true\n", input: "cat:\n +name: tiger\n meows: true\n",
expected: "<cat name=\"tiger\">\n <meows>true</meows>\n</cat>", expected: "<cat name=\"tiger\">\n <meows>true</meows>\n</cat>\n",
encodeScenario: true, encodeScenario: true,
}, },
{ {
skipDoc: true, skipDoc: true,
input: "cat:\n ++name: tiger\n meows: true\n", input: "cat:\n ++name: tiger\n meows: true\n",
expected: "<cat +name=\"tiger\">\n <meows>true</meows>\n</cat>", expected: "<cat +name=\"tiger\">\n <meows>true</meows>\n</cat>\n",
encodeScenario: true, encodeScenario: true,
}, },
{ {
description: "Encode xml: attributes with content", description: "Encode xml: attributes with content",
subdescription: "Fields with the matching xml-content-name is assumed to be content.", subdescription: "Fields with the matching xml-content-name is assumed to be content.",
input: "cat:\n +name: tiger\n +content: cool\n", input: "cat:\n +name: tiger\n +content: cool\n",
expected: "<cat name=\"tiger\">cool</cat>", expected: "<cat name=\"tiger\">cool</cat>\n",
encodeScenario: true,
},
{
description: "Encode xml: comments",
subdescription: "A best attempt is made to copy comments to xml.",
input: yamlWithComments,
expected: expectedXmlWithComments,
encodeScenario: true, encodeScenario: true,
}, },
} }