diff --git a/go.mod b/go.mod index d431a09..412538b 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,9 @@ go 1.21 require ( github.com/rs/zerolog v1.28.0 github.com/stretchr/testify v1.8.4 - github.com/tamerh/xpath v1.0.0 ) require ( - github.com/antchfx/xpath v1.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect diff --git a/go.sum b/go.sum index ca6bd53..d0e3e55 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= -github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -16,8 +14,6 @@ github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tamerh/xpath v1.0.0 h1:NccMES/Ej8slPCFDff73Kf6V1xu9hdbuKf2RyDsxf5Q= -github.com/tamerh/xpath v1.0.0/go.mod h1:t0wnh72FQlOVEO20f2Dl3EoVxso9GnLREh1WTpvNmJQ= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= diff --git a/pkg/xixo/calback_test.go b/pkg/xixo/calback_test.go index f40f94b..88bc58d 100644 --- a/pkg/xixo/calback_test.go +++ b/pkg/xixo/calback_test.go @@ -69,11 +69,10 @@ func TestJsonCallback(t *testing.T) { editedRoot, err := xixo.XMLElementToJSONCallback(jsonCallback)(root) assert.Nil(t, err) - element1, err := editedRoot.SelectElement("element1") - - assert.Nil(t, err) - - assert.Equal(t, "newChildContent", element1.InnerText) + assert.Equal(t, + "\n\tnewChildContent\n\tContenu2 \n", + editedRoot.String(), + ) } func badJSONCallback(source string) (string, error) { diff --git a/pkg/xixo/callback.go b/pkg/xixo/callback.go index fb08a8f..ce2fa74 100644 --- a/pkg/xixo/callback.go +++ b/pkg/xixo/callback.go @@ -11,11 +11,6 @@ type CallbackMap func(map[string]string) (map[string]string, error) type CallbackJSON func(string) (string, error) -type Attribute struct { - Name string - Value string -} - // XMLElementToMapCallback transforms an XML element into a map, applies a callback function, // adds parent attributes, and updates child elements. func XMLElementToMapCallback(callback CallbackMap) Callback { @@ -34,13 +29,11 @@ func XMLElementToMapCallback(callback CallbackMap) Callback { // Extract parent attributes and add them to the XML element. parentAttributes := extractParentAttributes(dict) for _, attr := range parentAttributes { - xmlElement.AddAttribute(attr.Name, attr.Value) + xmlElement.AddAttribute(attr) } - children, err := xmlElement.SelectElements("child::*") - if err != nil { - return nil, err - } + children := xmlElement.childs + // Select child elements and update their text content and attributes. childAttributes := extractChildAttributes(dict) @@ -51,7 +44,7 @@ func XMLElementToMapCallback(callback CallbackMap) Callback { if attributes, ok := childAttributes[child.Name]; ok { for _, attr := range attributes { - child.AddAttribute(attr.Name, attr.Value) + child.AddAttribute(attr) } } } @@ -64,13 +57,13 @@ func XMLElementToMapCallback(callback CallbackMap) Callback { func extractExistedAttributes(xmlElement *XMLElement, dict map[string]string) { for name, child := range xmlElement.Childs { - for attr, value := range child[0].Attrs { - dict[name+"@"+attr] = value + for attrName, attr := range child[0].Attrs { + dict[name+"@"+attrName] = attr.Value } } - for attr, value := range xmlElement.Attrs { - dict["@"+attr] = value + for attrName, attr := range xmlElement.Attrs { + dict["@"+attrName] = attr.Value } } diff --git a/pkg/xixo/element.go b/pkg/xixo/element.go index 973f639..0b4c27a 100644 --- a/pkg/xixo/element.go +++ b/pkg/xixo/element.go @@ -5,9 +5,38 @@ import ( "strings" ) +type Quote string + +const ( + SimpleQuote = "'" + DoubleQuotes = "\"" +) + +func ParseQuoteType(car byte) Quote { + if car == '\'' { + return SimpleQuote + } + + return DoubleQuotes +} + +type Attribute struct { + Name string + Value string + Quote Quote +} + +func (attr Attribute) String() string { + if attr.Quote == SimpleQuote { + return fmt.Sprintf("%s='%s'", attr.Name, attr.Value) + } + + return fmt.Sprintf("%s=\"%s\"", attr.Name, attr.Value) +} + type XMLElement struct { Name string - Attrs map[string]string + Attrs map[string]Attribute AttrKeys []string InnerText string Childs map[string][]XMLElement @@ -16,7 +45,6 @@ type XMLElement struct { // filled when xpath enabled childs []*XMLElement parent *XMLElement - attrs []*xmlAttr localName string prefix string @@ -24,21 +52,6 @@ type XMLElement struct { autoClosable bool } -type xmlAttr struct { - name string - value string -} - -// SelectElements finds child elements with the specified xpath expression. -func (n *XMLElement) SelectElements(exp string) ([]*XMLElement, error) { - return find(n, exp) -} - -// SelectElement finds child elements with the specified xpath expression. -func (n *XMLElement) SelectElement(exp string) (*XMLElement, error) { - return findOne(n, exp) -} - func (n *XMLElement) FirstChild() *XMLElement { if n.childs == nil { return nil @@ -100,7 +113,7 @@ func (n *XMLElement) String() string { attributes := n.Name + " " for _, key := range n.AttrKeys { - attributes += fmt.Sprintf("%s=\"%s\" ", key, n.Attrs[key]) + attributes += n.Attrs[key].String() + " " } attributes = strings.Trim(attributes, " ") @@ -119,30 +132,31 @@ func (n *XMLElement) String() string { n.Name) } -func (n *XMLElement) AddAttribute(name string, value string) { +func (n *XMLElement) AddAttribute(attr Attribute) { if n.Attrs == nil { - n.Attrs = make(map[string]string) + n.Attrs = make(map[string]Attribute) } // if name don't exsite in Attrs yet - if _, ok := n.Attrs[name]; !ok { + if _, ok := n.Attrs[attr.Name]; !ok { // Add un key in slice to keep the order of attributes - n.AttrKeys = append(n.AttrKeys, name) + n.AttrKeys = append(n.AttrKeys, attr.Name) + } else { + attr.Quote = n.Attrs[attr.Name].Quote } // change the value of attribute - n.Attrs[name] = value + n.Attrs[attr.Name] = attr } func NewXMLElement() *XMLElement { return &XMLElement{ Name: "", - Attrs: map[string]string{}, + Attrs: map[string]Attribute{}, AttrKeys: make([]string, 0), InnerText: "", Childs: map[string][]XMLElement{}, Err: nil, childs: []*XMLElement{}, parent: nil, - attrs: []*xmlAttr{}, localName: "", prefix: "", } diff --git a/pkg/xixo/element_test.go b/pkg/xixo/element_test.go index b6d11f4..2ea3fb3 100644 --- a/pkg/xixo/element_test.go +++ b/pkg/xixo/element_test.go @@ -144,9 +144,11 @@ func TestAddAttributsShouldSaved(t *testing.T) { root = xixo.NewXMLElement() root.Name = name - root.AddAttribute("foo", "bar") + attr := xixo.Attribute{"foo", "bar", xixo.SimpleQuote} - expected := map[string]string{"foo": "bar"} + root.AddAttribute(attr) + + expected := map[string]xixo.Attribute{"foo": attr} assert.Equal(t, root.Attrs, expected) } @@ -157,7 +159,7 @@ func TestAddAttributsShouldInOutputWithString(t *testing.T) { root := xixo.NewXMLElement() root.Name = parentTag root.InnerText = "Hello" - root.AddAttribute("foo", "bar") + root.AddAttribute(xixo.Attribute{"foo", "bar", xixo.DoubleQuotes}) expected := "Hello" assert.Equal(t, expected, root.String()) @@ -169,11 +171,11 @@ func TestEditAttributsShouldInOutputWithString(t *testing.T) { root := xixo.NewXMLElement() root.Name = parentTag root.InnerText = "Hello" - root.AddAttribute("foo", "bar") + root.AddAttribute(xixo.Attribute{"foo", "bar", xixo.DoubleQuotes}) expected := "Hello" assert.Equal(t, expected, root.String()) - root.AddAttribute("foo", "bas") + root.AddAttribute(xixo.Attribute{"foo", "bas", xixo.DoubleQuotes}) expected = "Hello" assert.Equal(t, expected, root.String()) diff --git a/pkg/xixo/parser.go b/pkg/xixo/parser.go index 0343077..63bf065 100644 --- a/pkg/xixo/parser.go +++ b/pkg/xixo/parser.go @@ -495,7 +495,8 @@ search_close_tag: if err != nil { return nil, false, x.defaultError() } - result.AddAttribute(attr, attrVal) + + result.AddAttribute(Attribute{attr, attrVal, ParseQuoteType(cur)}) x.scratch.reset() diff --git a/pkg/xixo/parser_test.go b/pkg/xixo/parser_test.go index 5475d96..a932527 100644 --- a/pkg/xixo/parser_test.go +++ b/pkg/xixo/parser_test.go @@ -233,6 +233,19 @@ func TestStreamWithoutModifications(t *testing.T) { // {input: "icb1jcb2kcb3l", element: "a"}, // {input: "icb1jcb2k", element: "a"}, + {input: "\n", element: "a"}, + {input: "\n", element: "b"}, + + {input: "\n\n", element: "a"}, + {input: "\n\n", element: "b"}, + {input: "\n\n", element: "c"}, + + {input: "\n", element: "a"}, + {input: "\n", element: "a"}, + + {input: "\n", element: "b"}, + {input: "\n", element: "b"}, + {input: "icb1jcb2k", element: "a"}, {input: "", element: "a"}, diff --git a/pkg/xixo/query.go b/pkg/xixo/query.go deleted file mode 100644 index 154bebc..0000000 --- a/pkg/xixo/query.go +++ /dev/null @@ -1,209 +0,0 @@ -package xixo - -import ( - "fmt" - - "github.com/tamerh/xpath" -) - -// CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node. -func (x *XMLParser) CreateXPathNavigator(top *XMLElement) *XMLNodeNavigator { - return &XMLNodeNavigator{curr: top, root: top, attr: -1} -} - -// Compile the given xpath expression. -func (x *XMLParser) CompileXpath(expr string) (*xpath.Expr, error) { - exp, err := xpath.Compile(expr) - if err != nil { - return nil, err - } - - return exp, nil -} - -// CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node. -func createXPathNavigator(top *XMLElement) *XMLNodeNavigator { - return &XMLNodeNavigator{curr: top, root: top, attr: -1} -} - -type XMLNodeNavigator struct { - root, curr *XMLElement - attr int -} - -// Find searches the Node that matches by the specified XPath expr. -func find(top *XMLElement, expr string) ([]*XMLElement, error) { - exp, err := xpath.Compile(expr) - if err != nil { - return []*XMLElement{}, err - } - - t := exp.Select(createXPathNavigator(top)) - - var elems []*XMLElement - - for t.MoveNext() { - current, ok := t.Current().(*XMLNodeNavigator) - if !ok { - return nil, fmt.Errorf("current is not a XMLNodeNavigator %v", current) - } - - elems = append(elems, current.curr) - } - - return elems, nil -} - -// FindOne searches the Node that matches by the specified XPath expr, -// and returns first element of matched. -func findOne(top *XMLElement, expr string) (*XMLElement, error) { - exp, err := xpath.Compile(expr) - if err != nil { - return nil, err - } - - t := exp.Select(createXPathNavigator(top)) - - var elem *XMLElement - - if t.MoveNext() { - navigator, ok := t.Current().(*XMLNodeNavigator) - if !ok { - return nil, fmt.Errorf("Current is not a XMLNodeNavigator %v", navigator) - } - - elem = navigator.curr - } - - return elem, nil -} - -func (x *XMLNodeNavigator) Current() *XMLElement { - return x.curr -} - -func (x *XMLNodeNavigator) NodeType() xpath.NodeType { - if x.curr == x.root { - return xpath.RootNode - } - - if x.attr != -1 { - return xpath.AttributeNode - } - - return xpath.ElementNode -} - -func (x *XMLNodeNavigator) LocalName() string { - if x.attr != -1 { - return x.curr.attrs[x.attr].name - } - - return x.curr.localName -} - -func (x *XMLNodeNavigator) Prefix() string { - return x.curr.prefix -} - -func (x *XMLNodeNavigator) Value() string { - if x.attr != -1 { - return x.curr.attrs[x.attr].value - } - - return x.curr.InnerText -} - -func (x *XMLNodeNavigator) Copy() xpath.NodeNavigator { - n := *x - - return &n -} - -func (x *XMLNodeNavigator) MoveToRoot() { - x.curr = x.root -} - -func (x *XMLNodeNavigator) MoveToParent() bool { - if x.attr != -1 { - x.attr = -1 - - return true - } else if node := x.curr.parent; node != nil { - x.curr = node - - return true - } - - return false -} - -func (x *XMLNodeNavigator) MoveToNextAttribute() bool { - if x.attr >= len(x.curr.attrs)-1 { - return false - } - x.attr++ - - return true -} - -func (x *XMLNodeNavigator) MoveToChild() bool { - if node := x.curr.FirstChild(); node != nil { - x.curr = node - - return true - } - - return false -} - -func (x *XMLNodeNavigator) MoveToFirst() bool { - if x.curr.parent != nil { - node := x.curr.parent.FirstChild() - if node != nil { - x.curr = node - - return true - } - } - - return false -} - -func (x *XMLNodeNavigator) MoveToPrevious() bool { - node := x.curr.PrevSibling() - if node != nil { - x.curr = node - - return true - } - - return false -} - -func (x *XMLNodeNavigator) MoveToNext() bool { - node := x.curr.NextSibling() - if node != nil { - x.curr = node - - return true - } - - return false -} - -func (x *XMLNodeNavigator) String() string { - return x.Value() -} - -func (x *XMLNodeNavigator) MoveTo(other xpath.NodeNavigator) bool { - node, ok := other.(*XMLNodeNavigator) - if !ok || node.root != x.root { - return false - } - - x.curr = node.curr - x.attr = node.attr - - return true -}