diff --git a/pkg/yqlib/doc/usage/xml.md b/pkg/yqlib/doc/usage/xml.md index f3396539..9b49b961 100644 --- a/pkg/yqlib/doc/usage/xml.md +++ b/pkg/yqlib/doc/usage/xml.md @@ -22,6 +22,81 @@ XML nodes that have attributes then plain content, e.g: 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 +Given a sample.xml file of: +```xml + +meow +``` +then +```bash +yq e -p=xml '.' sample.xml +``` +will output +```yaml +cat: meow +``` + +## Parse xml: array +Consecutive nodes with identical xml names are assumed to be arrays. + +Given a sample.xml file of: +```xml + +1 +2 +``` +then +```bash +yq e -p=xml '.' sample.xml +``` +will output +```yaml +animal: + - "1" + - "2" +``` + +## Parse xml: attributes +Attributes are converted to fields, with the attribute prefix. + +Given a sample.xml file of: +```xml + + + 7 + +``` +then +```bash +yq e -p=xml '.' sample.xml +``` +will output +```yaml +cat: + +legs: "4" + legs: "7" +``` + +## Parse xml: attributes with content +Content is added as a field, using the content name + +Given a sample.xml file of: +```xml + +meow +``` +then +```bash +yq e -p=xml '.' sample.xml +``` +will output +```yaml +cat: + +content: meow + +legs: "4" +``` + ## Parse xml: with comments A best attempt is made to preserve comments. @@ -69,3 +144,151 @@ cat: # after cat ``` +## Encode xml: simple +Given a sample.yml file of: +```yaml +cat: purrs +``` +then +```bash +yq e -o=xml '.' sample.yml +``` +will output +```xml +purrs +``` + +## Encode xml: array +Given a sample.yml file of: +```yaml +pets: + cat: + - purrs + - meows +``` +then +```bash +yq e -o=xml '.' sample.yml +``` +will output +```xml + + purrs + meows + +``` + +## 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 + +``` + +## 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 +cool +``` + +## 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 + + val1 + val2 + +``` + +## Round trip: with comments +A best effort is made, but comment positions and white space are not preserved perfectly. + +Given a sample.xml file of: +```xml + + + + + 3 + + + + z + + + + + + + +``` +then +```bash +yq e -p=xml '.' sample.xml +``` +will output +```yaml +# before cat +cat: + # in cat before + x: "3" # multi + # line comment + # for x + # before y + + y: + # in y before + # in d before + d: z # in d after + # in y after + +# after cat +``` + diff --git a/pkg/yqlib/encoder_xml.go b/pkg/yqlib/encoder_xml.go index 254d8f1c..c97c3abb 100644 --- a/pkg/yqlib/encoder_xml.go +++ b/pkg/yqlib/encoder_xml.go @@ -154,6 +154,10 @@ func (e *xmlEncoder) doEncode(encoder *xml.Encoder, node *yaml.Node, start xml.S func (e *xmlEncoder) encodeComment(encoder *xml.Encoder, commentStr string) error { if commentStr != "" { log.Debugf("encoding comment %v", commentStr) + if !strings.HasSuffix(commentStr, " ") { + commentStr = commentStr + " " + } + var comment xml.Comment = []byte(commentStr) err := encoder.EncodeToken(comment) if err != nil { diff --git a/pkg/yqlib/xml_test.go b/pkg/yqlib/xml_test.go index e1ed8de1..12fb81bc 100644 --- a/pkg/yqlib/xml_test.go +++ b/pkg/yqlib/xml_test.go @@ -133,10 +133,11 @@ cat: var expectedRoundtripXmlWithComments = ` 3 - - 4 + + z ` @@ -151,36 +152,36 @@ cat: # inline_cat # below_cat ` -var expectedXmlWithComments = ` - val1 - val2 - +var expectedXmlWithComments = ` + val1 + val2 + ` var xmlScenarios = []xmlScenario{ - // { - // description: "Parse xml: simple", - // input: "\nmeow", - // expected: "D0, P[], (doc)::cat: meow\n", - // }, - // { - // description: "Parse xml: array", - // subdescription: "Consecutive nodes with identical xml names are assumed to be arrays.", - // input: "\n1\n2", - // expected: "D0, P[], (doc)::animal:\n - \"1\"\n - \"2\"\n", - // }, - // { - // description: "Parse xml: attributes", - // subdescription: "Attributes are converted to fields, with the attribute prefix.", - // input: "\n\n 7\n", - // expected: "D0, P[], (doc)::cat:\n +legs: \"4\"\n legs: \"7\"\n", - // }, - // { - // description: "Parse xml: attributes with content", - // subdescription: "Content is added as a field, using the content name", - // input: "\nmeow", - // expected: "D0, P[], (doc)::cat:\n +content: meow\n +legs: \"4\"\n", - // }, + { + description: "Parse xml: simple", + input: "\nmeow", + expected: "D0, P[], (doc)::cat: meow\n", + }, + { + description: "Parse xml: array", + subdescription: "Consecutive nodes with identical xml names are assumed to be arrays.", + input: "\n1\n2", + expected: "D0, P[], (doc)::animal:\n - \"1\"\n - \"2\"\n", + }, + { + description: "Parse xml: attributes", + subdescription: "Attributes are converted to fields, with the attribute prefix.", + input: "\n\n 7\n", + expected: "D0, P[], (doc)::cat:\n +legs: \"4\"\n legs: \"7\"\n", + }, + { + description: "Parse xml: attributes with content", + subdescription: "Content is added as a field, using the content name", + input: "\nmeow", + expected: "D0, P[], (doc)::cat:\n +content: meow\n +legs: \"4\"\n", + }, { description: "Parse xml: with comments", subdescription: "A best attempt is made to preserve comments.", @@ -188,52 +189,52 @@ var xmlScenarios = []xmlScenario{ expected: expectedDecodeYamlWithComments, scenarioType: "decode", }, - // { - // description: "Encode xml: simple", - // input: "cat: purrs", - // expected: "purrs\n", - // scenarioType: "encode", - // }, - // { - // description: "Encode xml: array", - // input: "pets:\n cat:\n - purrs\n - meows", - // expected: "\n purrs\n meows\n\n", - // scenarioType: "encode", - // }, - // { - // 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\n", - // scenarioType: "encode", - // }, - // { - // skipDoc: true, - // input: "cat:\n ++name: tiger\n meows: true\n", - // expected: "\n true\n\n", - // scenarioType: "encode", - // }, - // { - // 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: "cool\n", - // scenarioType: "encode", - // }, - // { - // description: "Encode xml: comments", - // subdescription: "A best attempt is made to copy comments to xml.", - // input: yamlWithComments, - // expected: expectedXmlWithComments, - // scenarioType: "encode", - // }, - // { - // description: "Round trip: with comments", - // subdescription: "A best effort is made, but comment positions and white space are not preserved perfectly.", - // input: inputXmlWithComments, - // expected: expectedRoundtripXmlWithComments, - // scenarioType: "roundtrip", - // }, + { + description: "Encode xml: simple", + input: "cat: purrs", + expected: "purrs\n", + scenarioType: "encode", + }, + { + description: "Encode xml: array", + input: "pets:\n cat:\n - purrs\n - meows", + expected: "\n purrs\n meows\n\n", + scenarioType: "encode", + }, + { + 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\n", + scenarioType: "encode", + }, + { + skipDoc: true, + input: "cat:\n ++name: tiger\n meows: true\n", + expected: "\n true\n\n", + scenarioType: "encode", + }, + { + 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: "cool\n", + scenarioType: "encode", + }, + { + description: "Encode xml: comments", + subdescription: "A best attempt is made to copy comments to xml.", + input: yamlWithComments, + expected: expectedXmlWithComments, + scenarioType: "encode", + }, + { + description: "Round trip: with comments", + subdescription: "A best effort is made, but comment positions and white space are not preserved perfectly.", + input: inputXmlWithComments, + expected: expectedRoundtripXmlWithComments, + scenarioType: "roundtrip", + }, } func testXmlScenario(t *testing.T, s xmlScenario) {