This commit is contained in:
Mike Farah 2022-10-21 15:58:47 +11:00
parent 2e9c91f8a1
commit 585cafc1c0
7 changed files with 206 additions and 141 deletions

View File

@ -12,35 +12,16 @@ import (
) )
type xmlDecoder struct { type xmlDecoder struct {
reader io.Reader reader io.Reader
readAnything bool readAnything bool
attributePrefix string finished bool
directiveName string prefs xmlPreferences
procInstPrefix string
contentName string
strictMode bool
keepNamespace bool
useRawToken bool
finished bool
skipDirectives bool
skipProcInst bool
} }
func NewXMLDecoder(attributePrefix string, contentName string, strictMode bool, keepNamespace bool, useRawToken bool, skipDirectives bool, skipProcInst bool) Decoder { func NewXMLDecoder(prefs xmlPreferences) Decoder {
if contentName == "" {
contentName = "content"
}
return &xmlDecoder{ return &xmlDecoder{
attributePrefix: attributePrefix, finished: false,
contentName: contentName, prefs: prefs,
finished: false,
strictMode: strictMode,
keepNamespace: keepNamespace,
useRawToken: useRawToken,
directiveName: "_directive_",
procInstPrefix: "_procInst_",
skipDirectives: skipDirectives,
skipProcInst: skipProcInst,
} }
} }
@ -75,7 +56,7 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
yamlNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} yamlNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
if len(n.Data) > 0 { if len(n.Data) > 0 {
label := dec.contentName label := dec.prefs.ContentName
labelNode := createScalarNode(label, label) labelNode := createScalarNode(label, label)
labelNode.HeadComment = dec.processComment(n.HeadComment) labelNode.HeadComment = dec.processComment(n.HeadComment)
labelNode.FootComment = dec.processComment(n.FootComment) labelNode.FootComment = dec.processComment(n.FootComment)
@ -211,7 +192,7 @@ type element struct {
// of the map keys. // of the map keys.
func (dec *xmlDecoder) decodeXML(root *xmlNode) error { func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
xmlDec := xml.NewDecoder(dec.reader) xmlDec := xml.NewDecoder(dec.reader)
xmlDec.Strict = dec.strictMode xmlDec.Strict = dec.prefs.StrictMode
// That will convert the charset if the provided XML is non-UTF-8 // That will convert the charset if the provided XML is non-UTF-8
xmlDec.CharsetReader = charset.NewReaderLabel xmlDec.CharsetReader = charset.NewReaderLabel
@ -222,7 +203,7 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
} }
getToken := func() (xml.Token, error) { getToken := func() (xml.Token, error) {
if dec.useRawToken { if dec.prefs.UseRawToken {
return xmlDec.RawToken() return xmlDec.RawToken()
} }
return xmlDec.Token() return xmlDec.Token()
@ -250,12 +231,12 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
// Extract attributes as children // Extract attributes as children
for _, a := range se.Attr { for _, a := range se.Attr {
if dec.keepNamespace { if dec.prefs.KeepNamespace {
if a.Name.Space != "" { if a.Name.Space != "" {
a.Name.Local = a.Name.Space + ":" + a.Name.Local a.Name.Local = a.Name.Space + ":" + a.Name.Local
} }
} }
elem.n.AddChild(dec.attributePrefix+a.Name.Local, &xmlNode{Data: a.Value}) elem.n.AddChild(dec.prefs.AttributePrefix+a.Name.Local, &xmlNode{Data: a.Value})
} }
case xml.CharData: case xml.CharData:
// Extract XML data (if any) // Extract XML data (if any)
@ -289,12 +270,12 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
} }
case xml.ProcInst: case xml.ProcInst:
if !dec.skipProcInst { if !dec.prefs.SkipProcInst {
elem.n.AddChild(dec.procInstPrefix+se.Target, &xmlNode{Data: string(se.Inst)}) elem.n.AddChild(dec.prefs.ProcInstPrefix+se.Target, &xmlNode{Data: string(se.Inst)})
} }
case xml.Directive: case xml.Directive:
if !dec.skipDirectives { if !dec.prefs.SkipDirectives {
elem.n.AddChild(dec.directiveName, &xmlNode{Data: string(se)}) elem.n.AddChild(dec.prefs.DirectiveName, &xmlNode{Data: string(se)})
} }
} }
} }

View File

@ -30,7 +30,7 @@ yq -p=xml '.' sample.xml
``` ```
will output will output
```yaml ```yaml
_procInst_xml: version="1.0" encoding="UTF-8" +p_xml: version="1.0" encoding="UTF-8"
cat: cat:
says: meow says: meow
legs: "4" legs: "4"
@ -55,7 +55,7 @@ yq -p=xml ' (.. | select(tag == "!!str")) |= from_yaml' sample.xml
``` ```
will output will output
```yaml ```yaml
_procInst_xml: version="1.0" encoding="UTF-8" +p_xml: version="1.0" encoding="UTF-8"
cat: cat:
says: meow says: meow
legs: 4 legs: 4
@ -77,7 +77,7 @@ yq -p=xml '.' sample.xml
``` ```
will output will output
```yaml ```yaml
_procInst_xml: version="1.0" encoding="UTF-8" +p_xml: version="1.0" encoding="UTF-8"
animal: animal:
- cat - cat
- goat - goat
@ -99,9 +99,9 @@ yq -p=xml '.' sample.xml
``` ```
will output will output
```yaml ```yaml
_procInst_xml: version="1.0" encoding="UTF-8" +p_xml: version="1.0" encoding="UTF-8"
cat: cat:
+legs: "4" +@legs: "4"
legs: "7" legs: "7"
``` ```
@ -119,14 +119,14 @@ yq -p=xml '.' sample.xml
``` ```
will output will output
```yaml ```yaml
_procInst_xml: version="1.0" encoding="UTF-8" +p_xml: version="1.0" encoding="UTF-8"
cat: cat:
+content: meow +content: meow
+legs: "4" +@legs: "4"
``` ```
## Parse xml: custom dtd ## Parse xml: custom dtd
DTD entities are ignored. DTD entities are processed as directives.
Given a sample.xml file of: Given a sample.xml file of:
```xml ```xml
@ -142,18 +142,45 @@ Given a sample.xml file of:
``` ```
then then
```bash ```bash
yq -p=xml '.' sample.xml yq -p=xml -o=xml '.' sample.xml
``` ```
will output will output
```yaml ```xml
_procInst_xml: version="1.0" <?xml version="1.0"?>
_directive_: |- <!DOCTYPE root [
DOCTYPE root [ <!ENTITY writer "Blah.">
<!ENTITY writer "Blah."> <!ENTITY copyright "Blah">
<!ENTITY copyright "Blah"> ]>
] <root>
root: <item>&amp;writer;&amp;copyright;</item>
item: '&writer;&copyright;' </root>
```
## Parse xml: skip custom dtd
DTDs are directives, skip over directives to skip DTDs.
Given a sample.xml file of:
```xml
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY writer "Blah.">
<!ENTITY copyright "Blah">
]>
<root>
<item>&writer;&copyright;</item>
</root>
```
then
```bash
yq -p=xml -o=xml --xml-skip-directives '.' sample.xml
```
will output
```xml
<?xml version="1.0"?>
<root>
<item>&amp;writer;&amp;copyright;</item>
</root>
``` ```
## Parse xml: with comments ## Parse xml: with comments
@ -225,7 +252,7 @@ will output
instead of instead of
```xml ```xml
<?xml version="1.0"?> <?xml version="1.0"?>
<map xmlns="some-namespace" xsi="some-instance" schemaLocation="some-url"></map> <map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url"></map>
``` ```
## Parse xml: keep raw attribute namespace ## Parse xml: keep raw attribute namespace
@ -244,7 +271,7 @@ yq -p=xml -o=xml --xml-keep-namespace --xml-raw-token '.' sample.xml
will output will output
```xml ```xml
<?xml version="1.0"?> <?xml version="1.0"?>
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map> <map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url"></map>
``` ```
instead of instead of
@ -293,7 +320,7 @@ Fields with the matching xml-attribute-prefix are assumed to be attributes.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
cat: cat:
+name: tiger +@name: tiger
meows: true meows: true
``` ```
@ -314,7 +341,7 @@ Fields with the matching xml-content-name is assumed to be content.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
cat: cat:
+name: tiger +@name: tiger
+content: cool +content: cool
``` ```
@ -359,11 +386,11 @@ Use the special xml names to add/modify proc instructions and directives.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
_procInst_xml: version="1.0" +p_xml: version="1.0"
_directive_: 'DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" ' +directive: 'DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" '
apple: apple:
_procInst_coolioo: version="1.0" +p_coolioo: version="1.0"
_directive_: 'CATYPE meow purr puss ' +directive: 'CATYPE meow purr puss '
b: things b: things
``` ```

View File

@ -9,24 +9,19 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
var XMLPreferences = xmlPreferences{AttributePrefix: "+", ContentName: "+content", StrictMode: false, UseRawToken: false}
type xmlEncoder struct { type xmlEncoder struct {
attributePrefix string indentString string
contentName string writer io.Writer
indentString string prefs xmlPreferences
directiveName string
procInstPrefix string
writer io.Writer
} }
func NewXMLEncoder(indent int, attributePrefix string, contentName string) Encoder { func NewXMLEncoder(indent int, prefs xmlPreferences) Encoder {
var indentString = "" var indentString = ""
for index := 0; index < indent; index++ { for index := 0; index < indent; index++ {
indentString = indentString + " " indentString = indentString + " "
} }
return &xmlEncoder{attributePrefix, contentName, indentString, "_directive_", "_procInst_", nil} return &xmlEncoder{indentString, nil, prefs}
} }
func (e *xmlEncoder) CanHandleAliases() bool { func (e *xmlEncoder) CanHandleAliases() bool {
@ -97,8 +92,8 @@ func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *yaml.Node) er
return err return err
} }
if strings.HasPrefix(key.Value, e.procInstPrefix) { if strings.HasPrefix(key.Value, e.prefs.ProcInstPrefix) {
name := strings.Replace(key.Value, e.procInstPrefix, "", 1) name := strings.Replace(key.Value, e.prefs.ProcInstPrefix, "", 1)
procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)} procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)}
if err := encoder.EncodeToken(procInst); err != nil { if err := encoder.EncodeToken(procInst); err != nil {
return err return err
@ -106,7 +101,7 @@ func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *yaml.Node) er
if _, err := e.writer.Write([]byte("\n")); err != nil { if _, err := e.writer.Write([]byte("\n")); err != nil {
log.Warning("Unable to write newline, skipping: %w", err) log.Warning("Unable to write newline, skipping: %w", err)
} }
} else if key.Value == e.directiveName { } else if key.Value == e.prefs.DirectiveName {
var directive xml.Directive = []byte(value.Value) var directive xml.Directive = []byte(value.Value)
if err := encoder.EncodeToken(directive); err != nil { if err := encoder.EncodeToken(directive); err != nil {
return err return err
@ -205,6 +200,13 @@ func (e *xmlEncoder) encodeArray(encoder *xml.Encoder, node *yaml.Node, start xm
return e.encodeComment(encoder, footComment(node)) return e.encodeComment(encoder, footComment(node))
} }
func (e *xmlEncoder) isAttribute(name string) bool {
return strings.HasPrefix(name, e.prefs.AttributePrefix) &&
name != e.prefs.ContentName &&
name != e.prefs.DirectiveName &&
!strings.HasPrefix(name, e.prefs.ProcInstPrefix)
}
func (e *xmlEncoder) encodeMap(encoder *xml.Encoder, node *yaml.Node, start xml.StartElement) error { func (e *xmlEncoder) encodeMap(encoder *xml.Encoder, node *yaml.Node, start xml.StartElement) error {
log.Debug("its a map") log.Debug("its a map")
@ -213,9 +215,9 @@ func (e *xmlEncoder) encodeMap(encoder *xml.Encoder, node *yaml.Node, start xml.
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) && key.Value != e.contentName { if e.isAttribute(key.Value) {
if value.Kind == yaml.ScalarNode { if value.Kind == yaml.ScalarNode {
attributeName := strings.Replace(key.Value, e.attributePrefix, "", 1) attributeName := strings.Replace(key.Value, e.prefs.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})
} else { } else {
return fmt.Errorf("cannot use %v as attribute, only scalars are supported", value.Tag) return fmt.Errorf("cannot use %v as attribute, only scalars are supported", value.Tag)
@ -237,24 +239,18 @@ func (e *xmlEncoder) encodeMap(encoder *xml.Encoder, node *yaml.Node, start xml.
if err != nil { if err != nil {
return err return err
} }
if strings.HasPrefix(key.Value, e.procInstPrefix) { if strings.HasPrefix(key.Value, e.prefs.ProcInstPrefix) {
name := strings.Replace(key.Value, e.procInstPrefix, "", 1) name := strings.Replace(key.Value, e.prefs.ProcInstPrefix, "", 1)
procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)} procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)}
if err := encoder.EncodeToken(procInst); err != nil { if err := encoder.EncodeToken(procInst); err != nil {
return err return err
} }
} else if key.Value == e.directiveName { } else if key.Value == e.prefs.DirectiveName {
var directive xml.Directive = []byte(value.Value) var directive xml.Directive = []byte(value.Value)
if err := encoder.EncodeToken(directive); err != nil { if err := encoder.EncodeToken(directive); err != nil {
return err return err
} }
} else if !strings.HasPrefix(key.Value, e.attributePrefix) && key.Value != e.contentName { } else if key.Value == e.prefs.ContentName {
start := xml.StartElement{Name: xml.Name{Local: key.Value}}
err := e.doEncode(encoder, value, start)
if err != nil {
return err
}
} else if key.Value == e.contentName {
// directly encode the contents // directly encode the contents
err = e.encodeComment(encoder, headAndLineComment(value)) err = e.encodeComment(encoder, headAndLineComment(value))
if err != nil { if err != nil {
@ -269,6 +265,12 @@ func (e *xmlEncoder) encodeMap(encoder *xml.Encoder, node *yaml.Node, start xml.
if err != nil { if err != nil {
return err return err
} }
} else if !e.isAttribute(key.Value) {
start := xml.StartElement{Name: xml.Name{Local: key.Value}}
err := e.doEncode(encoder, value, start)
if err != nil {
return err
}
} }
err = e.encodeComment(encoder, footComment(key)) err = e.encodeComment(encoder, footComment(key))
if err != nil { if err != nil {

View File

@ -76,7 +76,7 @@ var participleYqRules = []*participleYqRule{
{"Base64d", `@base64d`, decodeOp(Base64InputFormat), 0}, {"Base64d", `@base64d`, decodeOp(Base64InputFormat), 0},
{"Base64", `@base64`, encodeWithIndent(Base64OutputFormat, 0), 0}, {"Base64", `@base64`, encodeWithIndent(Base64OutputFormat, 0), 0},
{"LoadXML", `load_?xml|xml_?load`, loadOp(NewXMLDecoder(XMLPreferences.AttributePrefix, XMLPreferences.ContentName, XMLPreferences.StrictMode, XMLPreferences.KeepNamespace, XMLPreferences.UseRawToken), false), 0}, {"LoadXML", `load_?xml|xml_?load`, loadOp(NewXMLDecoder(XMLPreferences), false), 0},
{"LoadBase64", `load_?base64`, loadOp(NewBase64Decoder(), false), 0}, {"LoadBase64", `load_?base64`, loadOp(NewBase64Decoder(), false), 0},

View File

@ -27,8 +27,28 @@ type xmlPreferences struct {
StrictMode bool StrictMode bool
KeepNamespace bool KeepNamespace bool
UseRawToken bool UseRawToken bool
ProcInstPrefix string
DirectiveName string
SkipProcInst bool
SkipDirectives bool
} }
func NewDefaultXmlPreferences() xmlPreferences {
return xmlPreferences{
AttributePrefix: "+@",
ContentName: "+content",
StrictMode: false,
KeepNamespace: true,
UseRawToken: false,
ProcInstPrefix: "+p_",
DirectiveName: "+directive",
SkipProcInst: false,
SkipDirectives: false,
}
}
var XMLPreferences = NewDefaultXmlPreferences()
var log = logging.MustGetLogger("yq-lib") var log = logging.MustGetLogger("yq-lib")
var PrettyPrintExp = `(... | (select(tag != "!!str"), select(tag == "!!str") | select(test("(?i)^(y|yes|n|no|on|off)$") | not)) ) style=""` var PrettyPrintExp = `(... | (select(tag != "!!str"), select(tag == "!!str") | select(test("(?i)^(y|yes|n|no|on|off)$") | not)) ) style=""`

View File

@ -23,7 +23,7 @@ func configureEncoder(format PrinterOutputFormat, indent int) Encoder {
case YamlOutputFormat: case YamlOutputFormat:
return NewYamlEncoder(indent, false, true, true) return NewYamlEncoder(indent, false, true, true)
case XMLOutputFormat: case XMLOutputFormat:
return NewXMLEncoder(indent, XMLPreferences.AttributePrefix, XMLPreferences.ContentName) return NewXMLEncoder(indent, XMLPreferences)
case Base64OutputFormat: case Base64OutputFormat:
return NewBase64Encoder() return NewBase64Encoder()
} }
@ -104,12 +104,7 @@ func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
case YamlInputFormat: case YamlInputFormat:
decoder = NewYamlDecoder() decoder = NewYamlDecoder()
case XMLInputFormat: case XMLInputFormat:
decoder = NewXMLDecoder( decoder = NewXMLDecoder(XMLPreferences)
XMLPreferences.AttributePrefix,
XMLPreferences.ContentName,
XMLPreferences.StrictMode,
XMLPreferences.KeepNamespace,
XMLPreferences.UseRawToken)
case Base64InputFormat: case Base64InputFormat:
decoder = NewBase64Decoder() decoder = NewBase64Decoder()
case PropertiesInputFormat: case PropertiesInputFormat:

View File

@ -58,7 +58,7 @@ cat:
d: d:
# in d before # in d before
z: z:
+sweet: cool +@sweet: cool
# in d after # in d after
# in y after # in y after
# in_cat_after # in_cat_after
@ -98,11 +98,11 @@ cat:
d: d:
- # in d before - # in d before
z: z:
+sweet: cool +@sweet: cool
# in d after # in d after
- # in d2 before - # in d2 before
z: z:
+sweet: cool2 +@sweet: cool2
# in d2 after # in d2 after
# in y after # in y after
# in_cat_after # in_cat_after
@ -159,18 +159,18 @@ const inputXMLWithNamespacedAttr = `
</map> </map>
` `
const expectedYAMLWithNamespacedAttr = `_procInst_xml: version="1.0" const expectedYAMLWithNamespacedAttr = `+p_xml: version="1.0"
map: map:
+xmlns: some-namespace +@xmlns: some-namespace
+xmlns:xsi: some-instance +@xmlns:xsi: some-instance
+some-instance:schemaLocation: some-url +@some-instance:schemaLocation: some-url
` `
const expectedYAMLWithRawNamespacedAttr = `_procInst_xml: version="1.0" const expectedYAMLWithRawNamespacedAttr = `+p_xml: version="1.0"
map: map:
+xmlns: some-namespace +@xmlns: some-namespace
+xmlns:xsi: some-instance +@xmlns:xsi: some-instance
+xsi:schemaLocation: some-url +@xsi:schemaLocation: some-url
` `
const xmlWithCustomDtd = ` const xmlWithCustomDtd = `
@ -183,18 +183,19 @@ const xmlWithCustomDtd = `
<item>&writer;&copyright;</item> <item>&writer;&copyright;</item>
</root>` </root>`
const expectedDtd = `_procInst_xml: version="1.0" const expectedDtd = `<?xml version="1.0"?>
_directive_: |- <!DOCTYPE root [
DOCTYPE root [ <!ENTITY writer "Blah.">
<!ENTITY writer "Blah."> <!ENTITY copyright "Blah">
<!ENTITY copyright "Blah"> ]>
] <root>
root: <item>&amp;writer;&amp;copyright;</item>
item: '&writer;&copyright;' </root>
` `
const expectedSkippedDtd = `root: const expectedSkippedDtd = `<root>
item: '&writer;&copyright;' <item>&writer;&copyright;</item>
</root>
` `
const xmlWithProcInstAndDirectives = `<?xml version="1.0"?> const xmlWithProcInstAndDirectives = `<?xml version="1.0"?>
@ -206,11 +207,11 @@ const xmlWithProcInstAndDirectives = `<?xml version="1.0"?>
</apple> </apple>
` `
const yamlWithProcInstAndDirectives = `_procInst_xml: version="1.0" const yamlWithProcInstAndDirectives = `+p_xml: version="1.0"
_directive_: 'DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" ' +directive: 'DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" '
apple: apple:
_procInst_coolioo: version="1.0" +p_coolioo: version="1.0"
_directive_: 'CATYPE meow purr puss ' +directive: 'CATYPE meow purr puss '
b: things b: things
` `
@ -258,13 +259,14 @@ var xmlScenarios = []formatScenario{
subdescription: "DTD entities are processed as directives.", subdescription: "DTD entities are processed as directives.",
input: xmlWithCustomDtd, input: xmlWithCustomDtd,
expected: expectedDtd, expected: expectedDtd,
scenarioType: "roundtrip",
}, },
{ {
description: "Parse xml: custom dtd", description: "Parse xml: skip custom dtd",
subdescription: "DTD entities are processed as directives.", subdescription: "DTDs are directives, skip over directives to skip DTDs.",
input: xmlWithCustomDtd, input: xmlWithCustomDtd,
expected: expectedSkippedDtd, expected: expectedSkippedDtd,
scenarioType: "c", scenarioType: "roundtrip-skip-directives",
}, },
{ {
description: "Parse xml: with comments", description: "Parse xml: with comments",
@ -360,20 +362,21 @@ var xmlScenarios = []formatScenario{
{ {
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>\n", expected: "<cat name=\"tiger\">\n <meows>true</meows>\n</cat>\n",
scenarioType: "encode", scenarioType: "encode",
}, },
{ {
description: "double prefix",
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>\n", expected: "<cat +@name=\"tiger\">\n <meows>true</meows>\n</cat>\n",
scenarioType: "encode", scenarioType: "encode",
}, },
{ {
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>\n", expected: "<cat name=\"tiger\">cool</cat>\n",
scenarioType: "encode", scenarioType: "encode",
}, },
@ -410,17 +413,23 @@ var xmlScenarios = []formatScenario{
func testXMLScenario(t *testing.T, s formatScenario) { func testXMLScenario(t *testing.T, s formatScenario) {
switch s.scenarioType { switch s.scenarioType {
case "", "decode": case "", "decode":
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder("+", "+content", false, false, false, false, false), NewYamlEncoder(4, false, true, true)), s.description) test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder(XMLPreferences), NewYamlEncoder(4, false, true, true)), s.description)
case "encode": case "encode":
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewYamlDecoder(), NewXMLEncoder(2, "+", "+content")), s.description) test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewYamlDecoder(), NewXMLEncoder(2, XMLPreferences)), s.description)
case "roundtrip": case "roundtrip":
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder("+", "+content", false, false, false, false, false), NewXMLEncoder(2, "+", "+content")), s.description) test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder(XMLPreferences), NewXMLEncoder(2, XMLPreferences)), s.description)
case "decode-keep-ns": case "decode-keep-ns":
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder("+", "+content", false, true, false, false, false), NewYamlEncoder(2, false, true, true)), s.description) prefs := NewDefaultXmlPreferences()
prefs.KeepNamespace = true
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder(prefs), NewYamlEncoder(2, false, true, true)), s.description)
case "decode-raw-token": case "decode-raw-token":
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder("+", "+content", false, true, true, false, false), NewYamlEncoder(2, false, true, true)), s.description) prefs := NewDefaultXmlPreferences()
case "encode-": prefs.UseRawToken = true
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder("+", "+content", false, true, true, true, true), NewYamlEncoder(2, false, true, true)), s.description) test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder(prefs), NewYamlEncoder(2, false, true, true)), s.description)
case "roundtrip-skip-directives":
prefs := NewDefaultXmlPreferences()
prefs.SkipDirectives = true
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(2, prefs)), s.description)
default: default:
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType)) panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
} }
@ -443,6 +452,8 @@ func documentXMLScenario(t *testing.T, w *bufio.Writer, i interface{}) {
documentXMLDecodeKeepNsScenario(w, s) documentXMLDecodeKeepNsScenario(w, s)
case "decode-raw-token": case "decode-raw-token":
documentXMLDecodeKeepNsRawTokenScenario(w, s) documentXMLDecodeKeepNsRawTokenScenario(w, s)
case "roundtrip-skip-directives":
documentXMLSkipDirectrivesScenario(w, s)
default: default:
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType)) panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
@ -468,7 +479,7 @@ func documentXMLDecodeScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -p=xml '%v' sample.xml\n```\n", expression)) writeOrPanic(w, fmt.Sprintf("```bash\nyq -p=xml '%v' sample.xml\n```\n", expression))
writeOrPanic(w, "will output\n") writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder("+", "+content", false, false, false), NewYamlEncoder(2, false, true, true)))) writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder(XMLPreferences), NewYamlEncoder(2, false, true, true))))
} }
func documentXMLDecodeKeepNsScenario(w *bufio.Writer, s formatScenario) { func documentXMLDecodeKeepNsScenario(w *bufio.Writer, s formatScenario) {
@ -485,11 +496,14 @@ func documentXMLDecodeKeepNsScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, "then\n") writeOrPanic(w, "then\n")
writeOrPanic(w, "```bash\nyq -p=xml -o=xml --xml-keep-namespace '.' sample.xml\n```\n") writeOrPanic(w, "```bash\nyq -p=xml -o=xml --xml-keep-namespace '.' sample.xml\n```\n")
writeOrPanic(w, "will output\n") writeOrPanic(w, "will output\n")
prefs := NewDefaultXmlPreferences()
prefs.KeepNamespace = true
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(2, prefs))))
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder("+", "+content", false, true, false, false, false), NewXMLEncoder(2, "+", "+content")))) prefsWithout := NewDefaultXmlPreferences()
prefs.KeepNamespace = false
writeOrPanic(w, "instead of\n") writeOrPanic(w, "instead of\n")
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder("+", "+content", false, false, false, false, false), NewXMLEncoder(2, "+", "+content")))) writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder(prefsWithout), NewXMLEncoder(2, prefsWithout))))
} }
func documentXMLDecodeKeepNsRawTokenScenario(w *bufio.Writer, s formatScenario) { func documentXMLDecodeKeepNsRawTokenScenario(w *bufio.Writer, s formatScenario) {
@ -507,10 +521,16 @@ func documentXMLDecodeKeepNsRawTokenScenario(w *bufio.Writer, s formatScenario)
writeOrPanic(w, "```bash\nyq -p=xml -o=xml --xml-keep-namespace --xml-raw-token '.' sample.xml\n```\n") writeOrPanic(w, "```bash\nyq -p=xml -o=xml --xml-keep-namespace --xml-raw-token '.' sample.xml\n```\n")
writeOrPanic(w, "will output\n") writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder("+", "+content", false, true, true, false, false), NewXMLEncoder(2, "+", "+content")))) prefs := NewDefaultXmlPreferences()
prefs.KeepNamespace = true
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(2, prefs))))
prefsWithout := NewDefaultXmlPreferences()
prefsWithout.KeepNamespace = false
writeOrPanic(w, "instead of\n") writeOrPanic(w, "instead of\n")
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder("+", "+content", false, false, false, false, false), NewXMLEncoder(2, "+", "+content")))) writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder(prefsWithout), NewXMLEncoder(2, prefsWithout))))
} }
func documentXMLEncodeScenario(w *bufio.Writer, s formatScenario) { func documentXMLEncodeScenario(w *bufio.Writer, s formatScenario) {
@ -528,7 +548,7 @@ func documentXMLEncodeScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, "```bash\nyq -o=xml '.' sample.yml\n```\n") writeOrPanic(w, "```bash\nyq -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", processFormatScenario(s, NewYamlDecoder(), NewXMLEncoder(2, "+", "+content")))) writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewYamlDecoder(), NewXMLEncoder(2, XMLPreferences))))
} }
func documentXMLRoundTripScenario(w *bufio.Writer, s formatScenario) { func documentXMLRoundTripScenario(w *bufio.Writer, s formatScenario) {
@ -546,7 +566,27 @@ func documentXMLRoundTripScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, "```bash\nyq -p=xml -o=xml '.' sample.xml\n```\n") writeOrPanic(w, "```bash\nyq -p=xml -o=xml '.' sample.xml\n```\n")
writeOrPanic(w, "will output\n") writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder("+", "+content", false, false, false, false, false), NewXMLEncoder(2, "+", "+content")))) writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder(XMLPreferences), NewXMLEncoder(2, XMLPreferences))))
}
func documentXMLSkipDirectrivesScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.xml file of:\n")
writeOrPanic(w, fmt.Sprintf("```xml\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
writeOrPanic(w, "```bash\nyq -p=xml -o=xml --xml-skip-directives '.' sample.xml\n```\n")
writeOrPanic(w, "will output\n")
prefs := NewDefaultXmlPreferences()
prefs.SkipDirectives = true
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", processFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(2, prefs))))
} }
func TestXMLScenarios(t *testing.T) { func TestXMLScenarios(t *testing.T) {