From 391a83061024531edc5586d2081c14717481064e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20P=C3=A9ron?= Date: Mon, 13 Nov 2023 17:43:38 +0000 Subject: [PATCH] feat: respect indentetion and comments --- pkg/xixo/calback_test.go | 66 ++++++++++++++++++++++---------- pkg/xixo/driver_test.go | 22 +++++------ pkg/xixo/element.go | 34 +++++++++++++---- pkg/xixo/element_test.go | 81 ++++++++++++++-------------------------- pkg/xixo/parser.go | 57 ++++++++++++++++++---------- pkg/xixo/parser_test.go | 76 ++++++++++++++++++++++++++++++++----- 6 files changed, 216 insertions(+), 120 deletions(-) diff --git a/pkg/xixo/calback_test.go b/pkg/xixo/calback_test.go index de8e6e2..f40f94b 100644 --- a/pkg/xixo/calback_test.go +++ b/pkg/xixo/calback_test.go @@ -8,7 +8,13 @@ import ( "github.com/stretchr/testify/assert" ) -const newChildContent = "newChildContent" +const ( + newChildContent = "newChildContent" + rootXML = ` + Hello world ! + Contenu2 +` +) func mapCallback(dict map[string]string) (map[string]string, error) { dict["element1"] = newChildContent @@ -20,19 +26,23 @@ func mapCallback(dict map[string]string) (map[string]string, error) { func TestMapCallback(t *testing.T) { t.Parallel() - element1 := createTree() - //nolint - assert.Equal(t, "\n Hello world !\n Contenu2 \n", element1.String()) + element1 := createTreeFromXMLString(rootXML) + + assert.Equal(t, rootXML, element1.String()) editedElement1, err := xixo.XMLElementToMapCallback(mapCallback)(element1) assert.Nil(t, err) text := editedElement1.FirstChild().InnerText - assert.Equal(t, "newChildContent", text) + assert.Equal(t, newChildContent, text) - //nolint - assert.Equal(t, "\n newChildContent\n Contenu2 \n", editedElement1.String()) + expected := ` + newChildContent + Contenu2 +` + + assert.Equal(t, expected, editedElement1.String()) } func jsonCallback(source string) (string, error) { @@ -54,7 +64,7 @@ func jsonCallback(source string) (string, error) { func TestJsonCallback(t *testing.T) { t.Parallel() - root := createTree() + root := createTreeFromXMLString(rootXML) editedRoot, err := xixo.XMLElementToJSONCallback(jsonCallback)(root) assert.Nil(t, err) @@ -73,7 +83,7 @@ func badJSONCallback(source string) (string, error) { func TestBadJsonCallback(t *testing.T) { t.Parallel() - element1 := createTree() + element1 := createTreeFromXMLString(rootXML) _, err := xixo.XMLElementToJSONCallback(badJSONCallback)(element1) @@ -83,9 +93,14 @@ func TestBadJsonCallback(t *testing.T) { func TestMapCallbackWithAttributs(t *testing.T) { t.Parallel() - element1 := createTreeWithAttribut() - //nolint - assert.Equal(t, "\n Hello world !\n Contenu2 \n", element1.String()) + rootXML := ` + Hello world ! + Contenu2 + ` + + element1 := createTreeFromXMLString(rootXML) + + assert.Equal(t, rootXML, element1.String()) editedElement1, err := xixo.XMLElementToMapCallback(mapCallbackAttributs)(element1) assert.Nil(t, err) @@ -94,8 +109,12 @@ func TestMapCallbackWithAttributs(t *testing.T) { assert.Equal(t, "newChildContent", text) - //nolint - assert.Equal(t, "\n newChildContent\n Contenu2 \n", editedElement1.String()) + expected := ` + newChildContent + Contenu2 + ` + + assert.Equal(t, expected, editedElement1.String()) } func mapCallbackAttributs(dict map[string]string) (map[string]string, error) { @@ -108,9 +127,14 @@ func mapCallbackAttributs(dict map[string]string) (map[string]string, error) { func TestMapCallbackWithAttributsParentAndChilds(t *testing.T) { t.Parallel() - element1 := createTreeWithAttributParent() - //nolint - assert.Equal(t, "\n Hello world !\n Contenu2 \n", element1.String()) + rootXML := ` + Hello world ! + Contenu2 + ` + + element1 := createTreeFromXMLString(rootXML) + + assert.Equal(t, rootXML, element1.String()) editedElement1, err := xixo.XMLElementToMapCallback(mapCallbackAttributsWithParent)(element1) assert.Nil(t, err) @@ -119,8 +143,12 @@ func TestMapCallbackWithAttributsParentAndChilds(t *testing.T) { assert.Equal(t, "newChildContent", text) - //nolint - assert.Equal(t, "\n newChildContent\n Contenu2 \n", editedElement1.String()) + expected := ` + newChildContent + Contenu2 + ` + + assert.Equal(t, expected, editedElement1.String()) } func mapCallbackAttributsWithParent(dict map[string]string) (map[string]string, error) { diff --git a/pkg/xixo/driver_test.go b/pkg/xixo/driver_test.go index 938e087..8799aea 100644 --- a/pkg/xixo/driver_test.go +++ b/pkg/xixo/driver_test.go @@ -33,7 +33,7 @@ func TestFuncDriverEdit(t *testing.T) { assert.True(t, called) - expected := "\n innerTextb\n" + expected := "innerTextb" assert.Equal(t, expected, writer.String()) } @@ -59,7 +59,7 @@ func TestFuncDriverEditEmptyElement(t *testing.T) { assert.True(t, called) - expected := "\n \n" + expected := "" assert.Equal(t, expected, writer.String()) } @@ -69,9 +69,9 @@ func TestFuncDriverEdit2subscribers(t *testing.T) { // Create a reader with an XML string, an empty writer, a callback function, and a driver. reader := bytes.NewBufferString( ` - innerTexta1 - innerTexta2 -`, + innerTexta1 + innerTexta2 + `, ) writer := bytes.Buffer{} called1, called2 := false, false @@ -79,12 +79,14 @@ func TestFuncDriverEdit2subscribers(t *testing.T) { subscribers := map[string]xixo.CallbackMap{ "root1": func(input map[string]string) (map[string]string, error) { called1 = true + assert.Equal(t, input["element1"], "innerTexta1") input["element1"] = "innerTextb1" return input, nil }, "root2": func(input map[string]string) (map[string]string, error) { called2 = true + assert.Equal(t, "innerTexta2", input["element2"]) input["element2"] = "innerTextb2" return input, nil @@ -101,12 +103,8 @@ func TestFuncDriverEdit2subscribers(t *testing.T) { assert.True(t, called2) expected := ` - - innerTextb1 - - - innerTextb2 - -` + innerTextb1 + innerTextb2 + ` assert.Equal(t, expected, writer.String()) } diff --git a/pkg/xixo/element.go b/pkg/xixo/element.go index 9ab4b23..e843a8f 100644 --- a/pkg/xixo/element.go +++ b/pkg/xixo/element.go @@ -5,6 +5,15 @@ import ( "strings" ) +type CommentElement struct { + OuterTextBefore string + Comment string +} + +func (c CommentElement) String() string { + return fmt.Sprintf("%s", c.OuterTextBefore, c.Comment) +} + type XMLElement struct { Name string Attrs map[string]string @@ -12,12 +21,16 @@ type XMLElement struct { InnerText string Childs map[string][]XMLElement Err error + // filled when xpath enabled childs []*XMLElement parent *XMLElement attrs []*xmlAttr localName string prefix string + + outerTextBefore string + comments []CommentElement } type xmlAttr struct { @@ -91,11 +104,7 @@ func (n *XMLElement) String() string { xmlChilds := "" for node := n.FirstChild(); node != nil; node = node.NextSibling() { - xmlChilds += " " + node.String() + "\n" - } - - if len(xmlChilds) > 0 { - xmlChilds = "\n" + xmlChilds + xmlChilds += node.String() } attributes := n.Name + " " @@ -105,10 +114,17 @@ func (n *XMLElement) String() string { attributes = strings.Trim(attributes, " ") - return fmt.Sprintf("<%s>%s%s", + commentsString := "" + for _, comment := range n.comments { + commentsString += comment.String() + } + + return fmt.Sprintf("%s<%s>%s%s%s", + n.outerTextBefore, attributes, - n.InnerText, + commentsString, xmlChilds, + n.InnerText, n.Name) } @@ -125,6 +141,10 @@ func (n *XMLElement) AddAttribute(name string, value string) { n.Attrs[name] = value } +func (n *XMLElement) AddComment(comment CommentElement) { + n.comments = append(n.comments, comment) +} + func NewXMLElement() *XMLElement { return &XMLElement{ Name: "", diff --git a/pkg/xixo/element_test.go b/pkg/xixo/element_test.go index dbc253d..b6d11f4 100644 --- a/pkg/xixo/element_test.go +++ b/pkg/xixo/element_test.go @@ -9,15 +9,11 @@ import ( "github.com/stretchr/testify/assert" ) -const parentTag = "root" - -func createTree() *xixo.XMLElement { - rootXML := ` - - Hello world ! - Contenu2 - ` +const ( + parentTag = "root" +) +func createTreeFromXMLString(rootXML string) *xixo.XMLElement { var root *xixo.XMLElement parser := xixo.NewXMLParser(bytes.NewBufferString(rootXML), io.Discard).EnableXpath() @@ -35,10 +31,11 @@ func createTree() *xixo.XMLElement { return root } -func createTreeWithAttribut() *xixo.XMLElement { - rootXML := ` - - Hello world ! +func TestElementStringShouldReturnXML(t *testing.T) { + t.Parallel() + + rootXML := ` + Hello world ! Contenu2 ` @@ -52,51 +49,33 @@ func createTreeWithAttribut() *xixo.XMLElement { }) err := parser.Stream() - if err != nil { - return nil - } - - return root -} + assert.Nil(t, err) -func createTreeWithAttributParent() *xixo.XMLElement { - rootXML := ` - - Hello world ! + expected := ` + Hello world ! Contenu2 ` - var root *xixo.XMLElement - - parser := xixo.NewXMLParser(bytes.NewBufferString(rootXML), io.Discard).EnableXpath() - parser.RegisterCallback("root", func(x *xixo.XMLElement) (*xixo.XMLElement, error) { - root = x - - return x, nil - }) - - err := parser.Stream() - if err != nil { - return nil - } - - return root + assert.Equal(t, expected, root.String()) } -func TestElementStringShouldReturnXML(t *testing.T) { +func TestElementStringShouldReturnXMLWithSameOrder(t *testing.T) { t.Parallel() - rootXML := ` - - Hello world ! - Contenu2 - ` + rootXML := ` + Hello world ! + Contenu2 + Contenu3 + Contenu4 + Contenu5 +` var root *xixo.XMLElement parser := xixo.NewXMLParser(bytes.NewBufferString(rootXML), io.Discard).EnableXpath() parser.RegisterCallback("root", func(x *xixo.XMLElement) (*xixo.XMLElement, error) { root = x + assert.Equal(t, root.InnerText, "\n") return x, nil }) @@ -104,23 +83,18 @@ func TestElementStringShouldReturnXML(t *testing.T) { err := parser.Stream() assert.Nil(t, err) - expected := ` - Hello world ! - Contenu2 -` - - assert.Equal(t, expected, root.String()) + assert.Equal(t, rootXML, root.String()) } -func TestElementStringShouldReturnXMLWithSameOrder(t *testing.T) { +func TestElementStringShouldPreserverContentOrder(t *testing.T) { t.Parallel() rootXML := ` Hello world ! Contenu2 - Contenu2 - Contenu2 - Contenu2 + Contenu3 + Contenu4 + Contenu5 ` var root *xixo.XMLElement @@ -128,6 +102,7 @@ func TestElementStringShouldReturnXMLWithSameOrder(t *testing.T) { parser := xixo.NewXMLParser(bytes.NewBufferString(rootXML), io.Discard).EnableXpath() parser.RegisterCallback("root", func(x *xixo.XMLElement) (*xixo.XMLElement, error) { root = x + assert.Equal(t, root.InnerText, "\n") return x, nil }) diff --git a/pkg/xixo/parser.go b/pkg/xixo/parser.go index 7f55aa0..50c81be 100644 --- a/pkg/xixo/parser.go +++ b/pkg/xixo/parser.go @@ -22,6 +22,7 @@ type XMLParser struct { scratch *scratch scratch2 *scratch scratchWriter *scratch + scratchOuterText *scratch deffer bool TotalReadSize uint64 nextWrite *byte @@ -37,6 +38,7 @@ func NewXMLParser(reader io.Reader, writer io.Writer) *XMLParser { scratch: &scratch{data: make([]byte, 1024)}, scratch2: &scratch{data: make([]byte, 1024)}, scratchWriter: &scratch{data: make([]byte, 1024)}, + scratchOuterText: &scratch{data: make([]byte, 1024)}, } } @@ -145,7 +147,7 @@ func (x *XMLParser) parse() error { continue } - iscomment, err = x.isComment() + iscomment, _, err = x.readComment() if err != nil { return err @@ -170,6 +172,9 @@ func (x *XMLParser) parse() error { return err } + x.scratch2.reset() + x.scratchOuterText.reset() + continue } @@ -226,8 +231,11 @@ func (x *XMLParser) getElementTree(result *XMLElement) *XMLElement { element *XMLElement tagClosed bool iscomment bool + comment CommentElement ) + result.outerTextBefore = string(x.scratchOuterText.bytes()) + x.scratchOuterText.reset() x.scratch2.reset() // this hold the inner text for { @@ -255,7 +263,7 @@ func (x *XMLParser) getElementTree(result *XMLElement) *XMLElement { continue } - iscomment, err = x.isComment() + iscomment, comment, err = x.readComment() if err != nil { result.Err = err @@ -264,6 +272,8 @@ func (x *XMLParser) getElementTree(result *XMLElement) *XMLElement { } if iscomment { + result.AddComment(comment) + continue } @@ -284,9 +294,8 @@ func (x *XMLParser) getElementTree(result *XMLElement) *XMLElement { } if tag == result.Name { - if len(result.Childs) == 0 { - result.InnerText = string(x.scratch2.bytes()) - } + result.InnerText = string(x.scratch2.bytes()) + x.scratch2.reset() return result } @@ -344,6 +353,7 @@ func (x *XMLParser) getElementTree(result *XMLElement) *XMLElement { } } else { x.scratch2.add(cur) + x.scratchOuterText.add(cur) } } } @@ -489,9 +499,7 @@ search_close_tag: return nil, false, x.defaultError() } result.AddAttribute(attr, attrVal) - // if x.xpathEnabled { - // result.attrs = append(result.attrs, &xmlAttr{name: attr, value: attrVal}) - // } + x.scratch.reset() continue @@ -510,25 +518,26 @@ search_close_tag: } } -func (x *XMLParser) isComment() (bool, error) { +func (x *XMLParser) readComment() (bool, CommentElement, error) { var ( - c byte - err error + c byte + err error + result CommentElement ) c, err = x.readByte() if err != nil { - return false, err + return false, result, err } if c != '!' { err := x.unreadByte() if err != nil { - return false, err + return false, result, err } - return false, nil + return false, result, nil } var d, e byte @@ -536,19 +545,19 @@ func (x *XMLParser) isComment() (bool, error) { d, err = x.readByte() if err != nil { - return false, err + return false, result, err } e, err = x.readByte() if err != nil { - return false, err + return false, result, err } if d != '-' || e != '-' { err = x.defaultError() - return false, err + return false, result, err } // skip part @@ -558,14 +567,23 @@ func (x *XMLParser) isComment() (bool, error) { c, err = x.readByte() if err != nil { - return false, err + return false, result, err } if c == '>' && len(x.scratch.bytes()) > 1 && x.scratch.bytes()[len(x.scratch.bytes())-1] == '-' && x.scratch.bytes()[len(x.scratch.bytes())-2] == '-' { - return true, nil + result = CommentElement{ + string(x.scratchOuterText.bytes()), + string(x.scratch.bytes())[:len(x.scratch.bytes())-2], + } + + x.scratchOuterText.reset() + x.scratch2.reset() + x.scratch.reset() + + return true, result, nil } x.scratch.add(c) @@ -817,6 +835,7 @@ skipDecleration: func (x *XMLParser) closeTagName() (string, error) { x.scratch.reset() + x.scratchOuterText.reset() var ( c byte diff --git a/pkg/xixo/parser_test.go b/pkg/xixo/parser_test.go index 1042cca..92c3f89 100644 --- a/pkg/xixo/parser_test.go +++ b/pkg/xixo/parser_test.go @@ -159,8 +159,7 @@ func TestAttributsShouldSavedAfterParser(t *testing.T) { func TestModifyAttributsWithMapCallback(t *testing.T) { t.Parallel() // Fichier XML en entrée - inputXML := ` - + inputXML := ` Hello world! Contenu2 ! ` @@ -180,18 +179,15 @@ func TestModifyAttributsWithMapCallback(t *testing.T) { assert.Nil(t, err) // Résultat XML attendu avec le contenu modifié - expectedResultXML := ` - - newChildContent - Contenu2 ! -` + expectedResultXML := ` + newChildContent + Contenu2 ! + ` // Vérifiez si le résultat XML correspond à l'attendu resultXML := resultXMLBuffer.String() - if resultXML != expectedResultXML { - t.Errorf("Le résultat XML ne correspond pas à l'attendu.\nAttendu:\n%s\nObtenu:\n%s", expectedResultXML, resultXML) - } + assert.Equal(t, expectedResultXML, resultXML) } func TestAttributsWithMapCallbackIsInDictionary(t *testing.T) { @@ -233,9 +229,35 @@ func TestStreamWithoutModifications(t *testing.T) { input string element string }{ + // How to replace an array of sub-elements + // {input: "icb1jcb2kcb3l", element: "a"}, + // {input: "icb1jcb2k", element: "a"}, + + {input: "icb1jcb2k", element: "a"}, + {input: "", element: "a"}, {input: "", element: "a"}, + {input: "", element: "z"}, + {input: "innerZ", element: "b"}, + {input: "innerA", element: "a"}, + {input: " ", element: "b"}, + {input: " ", element: "a"}, + {input: "", element: "b"}, + {input: " ", element: "b"}, + {input: " ", element: "a"}, + {input: " ", element: "b"}, + // {input: " ", element: "a"}, + // {input: "i", element: "a"}, + + {input: "", element: "a"}, + {input: "", element: "a"}, + + {input: "i", element: "a"}, + {input: "ij", element: "a"}, + + {input: "ijk", element: "a"}, + {input: "ijk", element: "a"}, // {input: "", element: "b"}, {input: "", element: "b"}, } @@ -269,3 +291,37 @@ func TestStreamWithoutModifications(t *testing.T) { assert.Equal(t, expectedResultXML, resultXML) } } + +func TestModifyShouldPreserveIndentForElementInline(t *testing.T) { + t.Parallel() + // Fichier XML en entrée + inputXML := ` + +youenHello world! + Contenu2 ! + ` + + // Lisez les résultats du canal et construisez le XML résultant + var resultXMLBuffer bytes.Buffer + + // Créez un bufio.Reader à partir du XML en entrée + reader := bytes.NewBufferString(inputXML) + + // Créez une nouvelle instance du parser XML avec la fonction de rappel + parser := xixo.NewXMLParser(reader, &resultXMLBuffer).EnableXpath() + parser.RegisterMapCallback("root", func(m map[string]string) (map[string]string, error) { + return m, nil + }) + + // Créez un canal pour collecter les résultats du parser + err := parser.Stream() + assert.Nil(t, err) + + // Résultat XML attendu avec le contenu modifié + expectedResultXML := inputXML + + // Vérifiez si le résultat XML correspond à l'attendu + resultXML := resultXMLBuffer.String() + + assert.Equal(t, expectedResultXML, resultXML) +}