Added global xml prefs for simplicity

This commit is contained in:
Mike Farah 2021-12-21 16:52:54 +11:00
parent 6bcbd873a6
commit 2ee9c65bc2
13 changed files with 155 additions and 28 deletions

View File

@ -3,6 +3,7 @@ package cmd
import ( import (
"os" "os"
"github.com/mikefarah/yq/v4/pkg/yqlib"
"github.com/spf13/cobra" "github.com/spf13/cobra"
logging "gopkg.in/op/go-logging.v1" logging "gopkg.in/op/go-logging.v1"
) )
@ -37,6 +38,8 @@ See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.`,
} }
logging.SetBackend(backend) logging.SetBackend(backend)
yqlib.XmlPreferences.AttributePrefix = xmlAttributePrefix
yqlib.XmlPreferences.ContentName = xmlContentName
}, },
} }

View File

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

View File

@ -14,11 +14,13 @@ 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 | | | XML | from_xml | to_xml(i)/@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).
XML uses the `--xml-attribute-prefix` and `xml-content-name` flags to identify attributes and content fields.
## Encode value as json string ## Encode value as json string
Given a sample.yml file of: Given a sample.yml file of:
@ -272,6 +274,61 @@ cat thing1,thing2 true 3.40
dog thing3 false 12 dog thing3 false 12
``` ```
## Encode value as xml string
Given a sample.yml file of:
```yaml
a:
cool:
foo: bar
+id: hi
```
then
```bash
yq eval '.a | to_xml' sample.yml
```
will output
```yaml
<cool id="hi">
<foo>bar</foo>
</cool>
```
## Encode value as xml string on a single line
Given a sample.yml file of:
```yaml
a:
cool:
foo: bar
+id: hi
```
then
```bash
yq eval '.a | @xml' sample.yml
```
will output
```yaml
<cool id="hi"><foo>bar</foo></cool>
```
## Encode value as xml string with custom indentation
Given a sample.yml file of:
```yaml
a:
cool:
foo: bar
+id: hi
```
then
```bash
yq eval '.a | to_xml(1)' sample.yml
```
will output
```yaml
<cool id="hi">
<foo>bar</foo>
</cool>
```
## Decode a xml encoded string ## Decode a xml encoded string
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@ -14,8 +14,10 @@ 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 | | | XML | from_xml | to_xml(i)/@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).
XML uses the `--xml-attribute-prefix` and `xml-content-name` flags to identify attributes and content fields.

View File

@ -1,8 +1,10 @@
# XML # XML
At the moment, `yq` only supports decoding `xml` (into one of the other supported output formats). Encode and decode to and from XML. Whitespace is not conserved for round trips - but the order of the fields are.
As yaml does not have the concept of attributes, these are converted to regular fields with a prefix to prevent clobbering. Consecutive xml nodes with the same name are assumed to be arrays. As yaml does not have the concept of attributes, xml attributes are converted to regular fields with a prefix to prevent clobbering. This defaults to "+", use the `--xml-attribute-prefix` to change.
Consecutive xml nodes with the same name are assumed to be arrays.
All values in XML are assumed to be strings - but you can use `from_yaml` to parse them into their correct types: All values in XML are assumed to be strings - but you can use `from_yaml` to parse them into their correct types:
@ -10,3 +12,12 @@ All values in XML are assumed to be strings - but you can use `from_yaml` to par
``` ```
yq e -p=xml '.myNumberField |= from_yaml' my.xml yq e -p=xml '.myNumberField |= from_yaml' my.xml
``` ```
XML nodes that have attributes then plain content, e.g:
```xml
<cat name="tiger">meow</cat>
```
The content of the node will be set as a field in the map with the key "+content". Use the `--xml-content-name` flag to change this.

View File

@ -1,8 +1,10 @@
# XML # XML
At the moment, `yq` only supports decoding `xml` (into one of the other supported output formats). Encode and decode to and from XML. Whitespace is not conserved for round trips - but the order of the fields are.
As yaml does not have the concept of attributes, these are converted to regular fields with a prefix to prevent clobbering. Consecutive xml nodes with the same name are assumed to be arrays. As yaml does not have the concept of attributes, xml attributes are converted to regular fields with a prefix to prevent clobbering. This defaults to "+", use the `--xml-attribute-prefix` to change.
Consecutive xml nodes with the same name are assumed to be arrays.
All values in XML are assumed to be strings - but you can use `from_yaml` to parse them into their correct types: All values in XML are assumed to be strings - but you can use `from_yaml` to parse them into their correct types:
@ -11,6 +13,15 @@ All values in XML are assumed to be strings - but you can use `from_yaml` to par
yq e -p=xml '.myNumberField |= from_yaml' my.xml yq e -p=xml '.myNumberField |= from_yaml' my.xml
``` ```
XML nodes that have attributes then plain content, e.g:
```xml
<cat name="tiger">meow</cat>
```
The content of the node will be set as a field in the map with the key "+content". Use the `--xml-content-name` flag to change this.
## Parse xml: simple ## Parse xml: simple
Given a sample.xml file of: Given a sample.xml file of:
```xml ```xml
@ -97,8 +108,7 @@ 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:
@ -117,8 +127,7 @@ 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.
@ -138,8 +147,7 @@ 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.
@ -157,6 +165,5 @@ yq e -o=xml '.' sample.yml
``` ```
will output will output
```xml ```xml
<cat name="tiger">cool</cat> <cat name="tiger">cool</cat>```
```

View File

@ -28,12 +28,25 @@ func NewXmlEncoder(writer io.Writer, indent int, attributePrefix string, content
func (e *xmlEncoder) Encode(node *yaml.Node) error { func (e *xmlEncoder) Encode(node *yaml.Node) error {
switch node.Kind { switch node.Kind {
case yaml.MappingNode: case yaml.MappingNode:
return e.encodeTopLevelMap(node) err := e.encodeTopLevelMap(node)
if err != nil {
return err
}
var charData xml.CharData = []byte("\n")
err = e.xmlEncoder.EncodeToken(charData)
if err != nil {
return err
}
return e.xmlEncoder.Flush()
case yaml.DocumentNode: case yaml.DocumentNode:
return e.Encode(unwrapDoc(node)) 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)
return e.xmlEncoder.EncodeToken(charData) err := e.xmlEncoder.EncodeToken(charData)
if err != nil {
return err
}
return e.xmlEncoder.Flush()
} }
return fmt.Errorf("unsupported type %v", node.Tag) return fmt.Errorf("unsupported type %v", node.Tag)
} }

View File

@ -322,6 +322,9 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`toyaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat)) lexer.Add([]byte(`toyaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat))
lexer.Add([]byte(`to_yaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat)) lexer.Add([]byte(`to_yaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat))
lexer.Add([]byte(`toxml\([0-9]+\)`), encodeWithIndent(XmlOutputFormat))
lexer.Add([]byte(`to_xml\([0-9]+\)`), encodeWithIndent(XmlOutputFormat))
lexer.Add([]byte(`tojson\([0-9]+\)`), encodeWithIndent(JsonOutputFormat)) lexer.Add([]byte(`tojson\([0-9]+\)`), encodeWithIndent(JsonOutputFormat))
lexer.Add([]byte(`to_json\([0-9]+\)`), encodeWithIndent(JsonOutputFormat)) lexer.Add([]byte(`to_json\([0-9]+\)`), encodeWithIndent(JsonOutputFormat))
@ -346,6 +349,10 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`to_tsv`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: TsvOutputFormat})) lexer.Add([]byte(`to_tsv`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: TsvOutputFormat}))
lexer.Add([]byte(`@tsv`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: TsvOutputFormat})) lexer.Add([]byte(`@tsv`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: TsvOutputFormat}))
lexer.Add([]byte(`toxml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: XmlOutputFormat}))
lexer.Add([]byte(`to_xml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: XmlOutputFormat, indent: 2}))
lexer.Add([]byte(`@xml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: XmlOutputFormat, indent: 0}))
lexer.Add([]byte(`fromyaml`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: YamlInputFormat})) lexer.Add([]byte(`fromyaml`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: YamlInputFormat}))
lexer.Add([]byte(`fromjson`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: YamlInputFormat})) lexer.Add([]byte(`fromjson`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: YamlInputFormat}))
lexer.Add([]byte(`fromxml`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: XmlInputFormat})) lexer.Add([]byte(`fromxml`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: XmlInputFormat}))
@ -359,9 +366,9 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`load`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: false, decoder: NewYamlDecoder()})) lexer.Add([]byte(`load`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: false, decoder: NewYamlDecoder()}))
lexer.Add([]byte(`xmlload`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: false, decoder: NewXmlDecoder("+", "+content")})) lexer.Add([]byte(`xmlload`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: false, decoder: NewXmlDecoder(XmlPreferences.AttributePrefix, XmlPreferences.ContentName)}))
lexer.Add([]byte(`load_xml`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: false, decoder: NewXmlDecoder("+", "+content")})) lexer.Add([]byte(`load_xml`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: false, decoder: NewXmlDecoder(XmlPreferences.AttributePrefix, XmlPreferences.ContentName)}))
lexer.Add([]byte(`loadxml`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: false, decoder: NewXmlDecoder("+", "+content")})) lexer.Add([]byte(`loadxml`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: false, decoder: NewXmlDecoder(XmlPreferences.AttributePrefix, XmlPreferences.ContentName)}))
lexer.Add([]byte(`strload`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: true})) lexer.Add([]byte(`strload`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: true}))
lexer.Add([]byte(`load_str`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: true})) lexer.Add([]byte(`load_str`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: true}))

View File

@ -13,6 +13,13 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type xmlPreferences struct {
AttributePrefix string
ContentName string
}
var XmlPreferences = xmlPreferences{AttributePrefix: "+", ContentName: "+content"}
var log = logging.MustGetLogger("yq-lib") var log = logging.MustGetLogger("yq-lib")
// GetLogger returns the yq logger instance. // GetLogger returns the yq logger instance.

View File

@ -82,7 +82,7 @@ func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
case YamlInputFormat: case YamlInputFormat:
decoder = NewYamlDecoder() decoder = NewYamlDecoder()
case XmlInputFormat: case XmlInputFormat:
decoder = NewXmlDecoder("+a", "+content") decoder = NewXmlDecoder(XmlPreferences.AttributePrefix, XmlPreferences.ContentName)
} }
var results = list.New() var results = list.New()

View File

@ -168,6 +168,30 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: \"foo:\\n a: frog\"\n", "D0, P[], (doc)::a: \"foo:\\n a: frog\"\n",
}, },
}, },
{
description: "Encode value as xml string",
document: `{a: {cool: {foo: "bar", +id: hi}}}`,
expression: `.a | to_xml`,
expected: []string{
"D0, P[a], (!!str)::<cool id=\"hi\">\n <foo>bar</foo>\n</cool>\n",
},
},
{
description: "Encode value as xml string on a single line",
document: `{a: {cool: {foo: "bar", +id: hi}}}`,
expression: `.a | @xml`,
expected: []string{
"D0, P[a], (!!str)::<cool id=\"hi\"><foo>bar</foo></cool>\n",
},
},
{
description: "Encode value as xml string with custom indentation",
document: `{a: {cool: {foo: "bar", +id: hi}}}`,
expression: `.a | to_xml(1)`,
expected: []string{
"D0, P[a], (!!str)::<cool id=\"hi\">\n <foo>bar</foo>\n</cool>\n",
},
},
{ {
description: "Decode a xml encoded string", description: "Decode a xml encoded string",
document: `a: "<foo>bar</foo>"`, document: `a: "<foo>bar</foo>"`,

View File

@ -43,7 +43,7 @@ func OutputFormatFromString(format string) (PrinterOutputFormat, error) {
case "xml", "x": case "xml", "x":
return XmlOutputFormat, nil 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|xml]", format)
} }
} }
@ -108,7 +108,7 @@ func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error {
case YamlOutputFormat: case YamlOutputFormat:
encoder = NewYamlEncoder(writer, p.indent, p.colorsEnabled) encoder = NewYamlEncoder(writer, p.indent, p.colorsEnabled)
case XmlOutputFormat: case XmlOutputFormat:
encoder = NewXmlEncoder(writer, p.indent, "+", "+content") encoder = NewXmlEncoder(writer, p.indent, XmlPreferences.AttributePrefix, XmlPreferences.ContentName)
} }
return encoder.Encode(node) return encoder.Encode(node)

View File

@ -110,8 +110,6 @@ var xmlScenarios = []xmlScenario{
}, },
} }
//encode
func testXmlScenario(t *testing.T, s *xmlScenario) { func testXmlScenario(t *testing.T, s *xmlScenario) {
if s.encodeScenario { if s.encodeScenario {
test.AssertResultWithContext(t, s.expected, yamlToXml(s.input, 2), s.description) test.AssertResultWithContext(t, s.expected, yamlToXml(s.input, 2), s.description)
@ -179,7 +177,7 @@ func documentXmlEncodeScenario(w *bufio.Writer, s xmlScenario) {
writeOrPanic(w, "```bash\nyq e -o=xml '.' sample.yml\n```\n") writeOrPanic(w, "```bash\nyq e -o=xml '.' sample.yml\n```\n")
writeOrPanic(w, "will output\n") writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```xml\n%v\n```\n\n", yamlToXml(s.input, 2))) writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", yamlToXml(s.input, 2)))
} }
func TestXmlScenarios(t *testing.T) { func TestXmlScenarios(t *testing.T) {