diff --git a/ImageProcesser.go b/ImageProcesser.go index 1c677e7..614b9eb 100644 --- a/ImageProcesser.go +++ b/ImageProcesser.go @@ -41,18 +41,20 @@ func processImage(img *Image) (imgXMLStr string, err error) { imgExt := strings.TrimLeft(strings.ToLower(path.Ext(imgPath)), ".") contentTypesName := "[Content_Types].xml" contentTypesNode := t.fileToXMLStruct(contentTypesName) - for _, node := range contentTypesNode.Nodes { + contentTypesNode.iterate(func(node *xmlNode) bool { if strings.ToLower(node.Attr("Extension")) == imgExt { isContainType = true + return true } - } + return false + }) if !isContainType { - contentTypesNode.Nodes = append(contentTypesNode.Nodes, &xmlNode{ + contentTypesNode.addSub(&xmlNode{ XMLName: xml.Name{ Space: "", Local: "Default", }, - Attrs: []*xml.Attr{ + Attrs: []xml.Attr{ {Name: xml.Name{Space: "", Local: "Extension"}, Value: imgExt}, {Name: xml.Name{Space: "", Local: "ContentType"}, Value: "image/" + imgExt}, }, @@ -70,13 +72,13 @@ func processImage(img *Image) (imgXMLStr string, err error) { } else { relNode = t.fileToXMLStruct(relName) } - rid := fmt.Sprintf("rId%d", len(relNode.Nodes)+1) - relNode.Nodes = append(relNode.Nodes, &xmlNode{ + rid := fmt.Sprintf("rId%d", relNode.childLenght+1) + relNode.addSub(&xmlNode{ XMLName: xml.Name{ Space: "", Local: "Relationship", }, - Attrs: []*xml.Attr{ + Attrs: []xml.Attr{ {Name: xml.Name{Space: "", Local: "Id"}, Value: rid}, {Name: xml.Name{Space: "", Local: "Type"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"}, {Name: xml.Name{Space: "", Local: "Target"}, Value: "media/" + imgPath}, diff --git a/Param.go b/Param.go index 7e08e9a..15c101f 100644 --- a/Param.go +++ b/Param.go @@ -49,6 +49,7 @@ type Param struct { Trigger *ParamTrigger RowPlaceholder string + Index int //slice data index,expandPlaceholders function needs } // NewParam .. @@ -238,7 +239,7 @@ func (p *Param) RunTrigger(xnode *xmlNode) { } n := xnode.closestUp(ntypes) - if n == nil || n.isDeleted { + if n == nil { // aurora.Red("EMPTY parent of %v", xnode.Tag()) return } @@ -250,24 +251,23 @@ func (p *Param) RunTrigger(xnode *xmlNode) { isListRemove = isListRemove || (isListItem && p.Trigger.Scope == TriggerScopeSection) // :section if isListRemove && isListItem { // find all list items as this - for _, wpNode := range n.parent.Nodes { + n.parent.childFirst.iterate(func(wpNode *xmlNode) bool { isitem, listid := wpNode.IsListItem() if !isitem || listid != listID { // aurora.Red("--- %s [%s]", wpNode, wpNode.AllContents()) - continue + return false } if p.Trigger.Command == TriggerCommandRemove { - wpNode.Nodes = nil wpNode.delete() } - } + return false + }) return } // Simple cases if p.Trigger.Command == TriggerCommandRemove { // fmt.Printf("Trigger: [%s] [%s]\t Command=[%s]\n", aurora.Blue(p.AbsoluteKey), aurora.Magenta(p.Trigger.String()), aurora.BgMagenta(p.Trigger.Command)) - n.Nodes = nil n.delete() return } diff --git a/ParamList.go b/ParamList.go index 3f21e9f..b781f2d 100644 --- a/ParamList.go +++ b/ParamList.go @@ -5,6 +5,7 @@ import ( "log" "reflect" "regexp" + "strings" ) // ParamList .. @@ -244,3 +245,63 @@ func (params ParamList) Walk(fn func(*Param)) { p.Walk(fn, p.Level+1) } } + +func (params ParamList) WalkWithEnd(fn func(*Param) bool) { + for _, p := range params { + if fn(p) { + continue + } + p.Params.WalkWithEnd(fn) + } +} + +func (p ParamList) FindAllByKey(key string) []*Param { + keySlice := strings.Split(key, ".") + var ret []*Param + p.findAllByKey(nil, nil, 0, 1, keySlice, &ret) + return ret +} + +func (p ParamList) findAllByKey(privParamList, paramList []int, offset, depth int, key []string, params *[]*Param) ([]int, int) { + if depth > len(key) { + return nil, 0 + } + currLev := strings.Join(key[:depth], ".") + for i, param := range p { + curr := make([]int, len(paramList)) + copy(curr, paramList) + if param.parent != nil && param.parent.Type == SliceParam { + curr = append(curr, i+1) + } + if param.Type == StructParam { + privParamList, offset = param.Params.findAllByKey(privParamList, curr, offset, depth+1, key, params) + } else { + if param.CompactKey == currLev || param.AbsoluteKey == currLev { + if param.Type == SliceParam { + privParamList, offset = param.Params.findAllByKey(privParamList, curr, offset, depth, key, params) + continue + } + if depth == len(key) { + index := 1 + if len(curr) > 0 { + index = curr[len(curr)-1] + if len(curr) == len(privParamList) { + for i := range curr[:len(curr)-1] { + if privParamList[i] != curr[i] { + offset += privParamList[len(privParamList)-1] + break + } + } + } + } + param.Index = index + offset + privParamList = curr + *params = append(*params, param) + } else { + privParamList, offset = param.Params.findAllByKey(privParamList, curr, offset, depth+1, key, params) + } + } + } + } + return privParamList, offset +} diff --git a/Template.helpers.go b/Template.helpers.go index 713d294..cd7cc71 100644 --- a/Template.helpers.go +++ b/Template.helpers.go @@ -17,7 +17,7 @@ func (t *Template) bytesToXMLStruct(buf []byte) *xmlNode { buf = bytes.ReplaceAll(buf, []byte(" 1 { imgNode := t.bytesToXMLStruct([]byte(param.Value)) imgNode.parent = xnode.parent - xnode.parent.Nodes = append(xnode.parent.Nodes, imgNode) + xnode.add(imgNode) } } // Empty the content before deleting to prevent reprocessing when params walk @@ -121,32 +124,23 @@ func (t *Template) matchBrokenRightPlaceholder(content string) bool { return t.matchBrokenPlaceholder(content, false) } -func (t Template) GetContentPrefixList(content []byte) []string { +func (t Template) GetAttrParam(attr string) []string { var ret []string var record strings.Builder start := false - length := len(content) - for i, v := range content { - if i == 0 { - continue - } - - if v == '{' && content[i-1] == '{' { + length := len(attr) + for i := 1; i < length-1; i++ { + if attr[i] == '{' && attr[i-1] == '{' { start = true continue } + if start && (attr[i] == ' ' || (attr[i] == '}' && length-1 > i && attr[i+1] == '}')) { + ret = append(ret, record.String()) + record.Reset() + start = false + } if start { - if v == ' ' || (v == '}' && length-1 > i && content[i+1] == '}') { - ret = append(ret, record.String()) - record.Reset() - start = false - } - if v == '.' { - ret = append(ret, record.String()) - record.Reset() - continue - } - record.WriteByte(v) + record.WriteByte(attr[i]) } } return ret diff --git a/Template.stage.funcs.go b/Template.stage.funcs.go index e4201fa..92b0b54 100644 --- a/Template.stage.funcs.go +++ b/Template.stage.funcs.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/xml" "strings" + "sync" ) // Collect and trigger placeholders with trigger but unset in `t.params` @@ -44,157 +45,107 @@ func (t *Template) triggerMissingParams(xnode *xmlNode) { // Expand complex placeholders func (t *Template) expandPlaceholders(xnode *xmlNode) { - type xmlNodeContent struct { - node *xmlNode - contents []byte + if t.params == nil { + return } - prefixNodeMap := map[string][]xmlNodeContent{} xnode.WalkWithEnd(func(nrow *xmlNode) bool { + var once sync.Once if nrow.isNew { return false } if !nrow.isRowElement() { return false } + var max int contents := nrow.AllContents() - prefixList := t.GetContentPrefixList(contents) - for _, prefix := range prefixList { - prefixNodeMap[prefix] = append(prefixNodeMap[prefix], xmlNodeContent{ - contents: contents, - node: nrow, - }) - } - return true - }) - t.params.Walk(func(p *Param) { - if p.Type != SliceParam { - return - } + rowParams := rowParams(contents) + rowPlaceholders := make(map[string]*placeholder) + for _, rowParam := range rowParams { + var placeholderType placeholderType + if len(rowParam.Separator) > 0 { + placeholderType = inlinePlaceholder + } else { + placeholderType = rowPlaceholder + } - prefixes := []string{ - p.AbsoluteKey, - p.ToCompact(p.AbsoluteKey), - } - if prefixes[0] == prefixes[1] { - prefixes = prefixes[:1] - } - var max int - for _, prefix := range prefixes { - nodeList, ok := prefixNodeMap[prefix] - if !ok { + var trigger string + if rowParam.Trigger != nil { + trigger = " " + rowParam.Trigger.String() + } + + paramData := t.params.FindAllByKey(rowParam.AbsoluteKey) + if len(paramData) == 0 { continue } - for i := range nodeList { - node := nodeList[i] - nrow := node.node - rowParams := rowParams(node.contents) - rowPlaceholders := make(map[string]*placeholder) - // Collect placeholder that for expansion - for _, rowParam := range rowParams { - var placeholderType placeholderType - if len(rowParam.Separator) > 0 { - placeholderType = inlinePlaceholder - } else { - placeholderType = rowPlaceholder - } + placeholders := make([]string, paramData[len(paramData)-1].Index) - var trigger string - if rowParam.Trigger != nil { - trigger = " " + rowParam.Trigger.String() + for _, param := range paramData { + placeholders[param.Index-1] = "{{" + param.AbsoluteKey + trigger + "}}" + } + rowPlaceholders[rowParam.RowPlaceholder] = &placeholder{ + Type: placeholderType, + Placeholders: placeholders, + Separator: strings.TrimLeft(rowParam.Separator, " "), + } + if max < len(placeholders) { + max = len(placeholders) + } + } + nnews := make([]*xmlNode, max) + for oldPlaceholder, newPlaceholder := range rowPlaceholders { + switch newPlaceholder.Type { + case inlinePlaceholder: + nrow.Walk(func(n *xmlNode) { + if !inSlice(n.XMLName.Local, []string{"w-t"}) || len(n.Content) == 0 { + return } - - var isMatch bool - var index = -1 - currentLevel := p.Level - placeholders := make([]string, 0, len(p.Params)) - p.WalkFunc(func(p *Param) { - if p.Level == currentLevel+1 { - index++ - } - if rowParam.AbsoluteKey == p.CompactKey { - isMatch = true - placeholders = append(placeholders, "{{"+p.AbsoluteKey+trigger+"}}") + n.Content = bytes.ReplaceAll(n.Content, []byte(oldPlaceholder), []byte(strings.Join(newPlaceholder.Placeholders, newPlaceholder.Separator))) + }) + case rowPlaceholder: + defer once.Do(func() { + nrow.delete() + }) + for i := max - 1; i >= 0; i-- { + if nnews[i] == nil { + nnews[i] = nrow.cloneAndAppend() + } + nnews[i].Walk(func(n *xmlNode) { + if !inSlice(n.XMLName.Local, []string{"w-t"}) || len(n.Content) == 0 { + return } - }) - - if isMatch { - rowPlaceholders[rowParam.RowPlaceholder] = &placeholder{ - Type: placeholderType, - Placeholders: placeholders, - Separator: strings.TrimLeft(rowParam.Separator, " "), + replaceData := oldPlaceholder + if i < len(newPlaceholder.Placeholders) && newPlaceholder.Placeholders[i] != "" { + replaceData = newPlaceholder.Placeholders[i] } + n.Content = bytes.ReplaceAll(n.Content, []byte(oldPlaceholder), []byte(replaceData)) - if max < len(placeholders) { - max = len(placeholders) - } - } - } - // Expand placeholder exactly - nnews := make([]*xmlNode, max, max) - for oldPlaceholder, newPlaceholder := range rowPlaceholders { - switch newPlaceholder.Type { - case inlinePlaceholder: - nrow.Walk(func(n *xmlNode) { - if !inSlice(n.XMLName.Local, []string{"w-t"}) || len(n.Content) == 0 { - return - } - n.Content = bytes.ReplaceAll(n.Content, []byte(oldPlaceholder), []byte(strings.Join(newPlaceholder.Placeholders, newPlaceholder.Separator))) - }) - case rowPlaceholder: - defer func() { - nrow.delete() - }() - for i, placeholder := range newPlaceholder.Placeholders { - if nnews[i] == nil { - nnews[i] = nrow.cloneAndAppend() - } - nnews[i].Walk(func(n *xmlNode) { - if !inSlice(n.XMLName.Local, []string{"w-t"}) || len(n.Content) == 0 { - return - } - n.Content = bytes.ReplaceAll(n.Content, []byte(oldPlaceholder), []byte(placeholder)) - }) - } - } + }) } } } - }) - - // Cloned nodes are marked as new by default. - // After expanding mark as old so next operations doesn't ignore them - xnode.Walk(func(n *xmlNode) { - n.isNew = false + return true }) } // Replace single params by type func (t *Template) replaceSingleParams(xnode *xmlNode, triggerParamOnly bool) { - replaceAttr := []*xml.Attr{} - xnodeList := []*xmlNode{} - xnode.Walk(func(n *xmlNode) { - if n == nil || n.isDeleted { - return - } - for _, attr := range n.Attrs { - if strings.Contains(attr.Value, "{{") { - replaceAttr = append(replaceAttr, attr) - } - } - xnodeList = append(xnodeList, n) - }) paramAbsoluteKeyMap := map[string]*Param{} t.params.Walk(func(p *Param) { - for _, v := range replaceAttr { - v.Value = string(p.replaceIn([]byte(v.Value))) - } if p.Type != StringParam && p.Type != ImageParam { return } paramAbsoluteKeyMap[p.AbsoluteKey] = p }) - for i := range xnodeList { - n := xnodeList[i] + xnode.Walk(func(n *xmlNode) { + for i := range n.Attrs { + for _, key := range t.GetAttrParam(n.Attrs[i].Value) { + p, ok := paramAbsoluteKeyMap[key] + if !ok { + continue + } + n.Attrs[i].Value = string(p.replaceIn([]byte(n.Attrs[i].Value))) + } + } for _, key := range n.GetContentPrefixList() { p, ok := paramAbsoluteKeyMap[key] if !ok { @@ -202,13 +153,12 @@ func (t *Template) replaceSingleParams(xnode *xmlNode, triggerParamOnly bool) { } t.replaceAndRunTrigger(p, n, triggerParamOnly) } - } + }) } func (t *Template) replaceAndRunTrigger(p *Param, n *xmlNode, triggerParamOnly bool) { // Trigger: does placeholder have trigger if p.Trigger = p.extractTriggerFrom(n.Content); p.Trigger != nil { - // if defer func() { p.RunTrigger(n) }() @@ -240,9 +190,8 @@ func (t *Template) enhanceMarkup(xnode *xmlNode) { if !isListItem { return } - // n.XMLName.Local = "w-item" - n.Attrs = append(n.Attrs, &xml.Attr{ + n.Attrs = append(n.Attrs, xml.Attr{ Name: xml.Name{Local: "list-id"}, Value: listID, }) @@ -319,7 +268,6 @@ func (t *Template) fixBrokenPlaceholders(xnode *xmlNode) { // fmt.Printf("OK [%s] + [%s]\n", aurora.Green(brokenNode.AllContents()), aurora.Green(n.AllContents())) brokenNode.Content = append(brokenNode.Content, n.AllContents()...) // aurora.Magenta("[%s] %v -- %v -- %v -- %v", brokenNode.Content, brokenNode.Tag(), brokenNode.parent.Tag(), brokenNode.parent.parent.Tag(), brokenNode.parent.parent.parent.Tag()) - n.Nodes = nil n.delete() return } diff --git a/xml.node.go b/xml.node.go index 023d81a..645c7f0 100644 --- a/xml.node.go +++ b/xml.node.go @@ -24,14 +24,58 @@ var NodeRowTypes = []string{"w-tr", "w-p"} var NodeSectionTypes = []string{"w-tbl", "w-p"} type xmlNode struct { - XMLName xml.Name - Attrs []*xml.Attr `xml:",any,attr"` - Content []byte `xml:",chardata"` - Nodes []*xmlNode `xml:",any"` + XMLName xml.Name + Attrs []xml.Attr `xml:",any,attr"` + Content []byte `xml:",chardata"` + childFirst *xmlNode + childLast *xmlNode + next *xmlNode + priv *xmlNode + parent *xmlNode + childLenght int + isNew bool // added recently +} + +func (xnode *xmlNode) addSub(n *xmlNode) { + xnode.childLenght++ + if xnode.childFirst == nil { + xnode.childFirst = n + xnode.childLast = n + return + } + xnode.childLast.next = n + n.priv = xnode.childLast + xnode.childLast = n - parent *xmlNode - isNew bool // added recently - isDeleted bool +} + +func (xnode *xmlNode) add(n *xmlNode) { + if xnode.parent == nil { + nn := xnode + for nn.next != nil { + nn = nn.next + } + nn.next = n + n.priv = nn + return + } + xnode.parent.addSub(n) +} + +func (xnode *xmlNode) iterate(fn func(node *xmlNode) bool) { + if xnode == nil { + return + } + n := xnode + if fn(n) { + return + } + for n.next != nil { + n = n.next + if fn(n) { + break + } + } } func (xnode xmlNode) GetContentPrefixList() (ret []string) { @@ -71,77 +115,133 @@ func (xnode xmlNode) ContentHasPrefix(str string) bool { // UnmarshalXML .. func (xnode *xmlNode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { // n.Attrs = start.Attr - type x xmlNode - return d.DecodeElement((*x)(xnode), &start) + xnode.Attrs = start.Attr + xnode.XMLName = start.Name + n := xnode + for { + token, err := d.Token() + if err != nil { + break + } + switch t := token.(type) { + case xml.StartElement: + sub := &xmlNode{ + XMLName: t.Name, + Attrs: t.Attr, + parent: n, + } + n.addSub(sub) + n = sub + case xml.EndElement: + n = n.parent + case xml.CharData: + n.Content = t.Copy() + } + } + return nil } -// Walk down all nodes and do custom stuff with given function -func (xnode *xmlNode) Walk(fn func(*xmlNode)) { - // Using index to iterate nodes instead of for-range to process dynamic nodes - for i := 0; i < len(xnode.Nodes); i++ { - n := xnode.Nodes[i] - - if n == nil { - continue - } +// MarshalXML .. +func (xnode *xmlNode) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + // n.Attrs = start.Attr + defer e.Close() + return xnode.XMLEncode(e) - fn(n) // do your custom stuff +} - if n.Nodes != nil { - // continue only if have deeper nodes - n.Walk(fn) +func (xnode *xmlNode) XMLEncode(e *xml.Encoder) error { + err := e.EncodeToken(xml.StartElement{ + Name: xnode.XMLName, + Attr: xnode.Attrs, + }) + if err != nil { + return err + } + if len(xnode.Content) != 0 { + err = e.EncodeToken(xml.CharData(xnode.Content)) + if err != nil { + return err } } + if xnode.childFirst != nil { + xnode.childFirst.iterate(func(node *xmlNode) bool { + err = node.XMLEncode(e) + return err != nil + }) + } + + if err != nil { + return err + } + return e.EncodeToken(xml.EndElement{ + Name: xnode.XMLName, + }) } -// fn return true ,end walk -func (xnode *xmlNode) WalkWithEnd(fn func(*xmlNode) bool) { - // Using index to iterate nodes instead of for-range to process dynamic nodes - for i := 0; i < len(xnode.Nodes); i++ { - n := xnode.Nodes[i] +// Walk down all nodes and do custom stuff with given function +func (xnode *xmlNode) Walk(fn func(*xmlNode)) { + if xnode.childFirst == nil { + return + } + xnode.childFirst.walk(fn) +} - if n == nil { - continue +func (xnode *xmlNode) walk(fn func(*xmlNode)) { + xnode.iterate(func(node *xmlNode) bool { + fn(node) + if node.childFirst != nil { + node.childFirst.walk(fn) } + return false + }) +} - end := fn(n) // do your custom stuff - - if end { - continue - } +// fn return true ,end walk +func (xnode *xmlNode) WalkWithEnd(fn func(*xmlNode) bool) { + if xnode.childFirst == nil { + return + } + xnode.childFirst.walkWithEnd(fn) +} - if n.Nodes != nil { - // continue only if have deeper nodes - n.WalkWithEnd(fn) +func (xnode *xmlNode) walkWithEnd(fn func(*xmlNode) bool) { + xnode.iterate(func(node *xmlNode) bool { + if (!fn(node)) && node.childFirst != nil { + node.childFirst.walkWithEnd(fn) } - } + return false + }) } // Walk down all nodes and do custom stuff with given function func (xnode *xmlNode) WalkTree(depth int, fn func(int, *xmlNode)) { - for _, n := range xnode.Nodes { - if n == nil { - continue - } - - fn(depth+1, n) // do your custom stuff + if xnode.childFirst == nil { + return + } + xnode.childFirst.walkTree(depth, fn) +} - if n.Nodes != nil { - n.WalkTree(depth+1, fn) +func (xnode *xmlNode) walkTree(depth int, fn func(int, *xmlNode)) { + xnode.iterate(func(node *xmlNode) bool { + fn(depth, xnode) + if node.childFirst != nil { + fn(depth+1, node.childFirst) } - } + return false + }) } // Contents - return contents of this and all childs contents merge func (xnode *xmlNode) AllContents() []byte { - if xnode == nil || xnode.isDeleted { + if xnode == nil { return nil } - buf := xnode.Content + xnode.Walk(func(n *xmlNode) { buf = append(buf, n.Content...) }) + return buf } @@ -228,38 +328,35 @@ func (xnode *xmlNode) AnyChildContains(buf []byte) bool { //} // index of element inside parent.Nodes slice -func (xnode *xmlNode) index() int { - if xnode != nil && xnode.parent != nil { - for i, n := range xnode.parent.Nodes { - if xnode == n { - return i - } - } - } - return -1 -} +// func (xnode *xmlNode) index() int { +// if xnode != nil && xnode.parent != nil { +// for i, n := range xnode.parent.Nodes { +// if xnode == n { +// return i +// } +// } +// } +// return -1 +// } // Clone and Add after this // return new xmlNode func (xnode *xmlNode) cloneAndAppend() *xmlNode { - parent := xnode.parent - + if xnode == nil { + return xnode + } // new copy node - nnew := xnode.clone(parent) //set parent - nnew.isDeleted = false + nnew := xnode.clone(xnode.parent) //set parent nnew.isNew = true - // Find node index in parent hierarchy and chose next index as copy place - i := xnode.index() - if i == -1 { - // Return existing instance to avoid nil errors - // But this node not added to xml structure list, so dissapears in output - return nnew + tmp := xnode.next + xnode.next = nnew + nnew.priv = xnode + nnew.next = tmp + if tmp != nil { + tmp.priv = nnew } - // Insert into specific index - parent.Nodes = append(parent.Nodes[:i], append([]*xmlNode{nnew}, parent.Nodes[i:]...)...) - return nnew } @@ -270,15 +367,18 @@ func (xnode *xmlNode) clone(parent *xmlNode) *xmlNode { return nil } - xnodeCopy := &xmlNode{} - *xnodeCopy = *xnode - xnodeCopy.Nodes = nil - xnodeCopy.isDeleted = false - xnodeCopy.isNew = true - xnodeCopy.parent = parent - - for _, n := range xnode.Nodes { - xnodeCopy.Nodes = append(xnodeCopy.Nodes, n.clone(xnodeCopy)) + xnodeCopy := &xmlNode{ + XMLName: xnode.XMLName, + Attrs: xnode.Attrs, + Content: xnode.Content, + isNew: true, + parent: parent, + } + if xnode.childFirst != nil { + xnode.childFirst.iterate(func(node *xmlNode) bool { + xnodeCopy.addSub(node.clone(xnodeCopy)) + return false + }) } return xnodeCopy @@ -286,39 +386,48 @@ func (xnode *xmlNode) clone(parent *xmlNode) *xmlNode { // Delete node func (xnode *xmlNode) delete() { - // xnode.printTree("Delete") - - // remove from list - index := xnode.index() - if index != -1 { - xnode.parent.Nodes[index] = nil + xnode.childLenght = 0 + xnode.childFirst = nil + xnode.childLast = nil + if xnode.parent != nil { + xnode.parent.childLenght-- + if xnode.parent.childFirst == xnode { + xnode.parent.childFirst = xnode.next + } + if xnode.parent.childLast == xnode { + xnode.parent.childLast = xnode.priv + } + } + if xnode.priv != nil { + xnode.priv.next = xnode.next + } + if xnode.next != nil { + xnode.next.priv = xnode.priv } - xnode.Nodes = nil - xnode.isDeleted = true } // Find closest parent way up by node type func (xnode *xmlNode) closestUp(nodeTypes []string) *xmlNode { + if xnode.parent == nil { + return nil + } + var n *xmlNode for _, ntype := range nodeTypes { - if xnode.parent == nil { - continue - } - if xnode.parent.isDeleted { - continue - } // aurora.Magenta("[%s] == [%s]", xnode.parent.Tag(), ntype) if xnode.parent.Tag() == ntype { // aurora.Green("found parent: [%s] == [%s]", xnode.parent.Tag(), ntype) return xnode.parent } - - for _, n := range xnode.parent.Nodes { - if n.Tag() == ntype { - // aurora.Green("found parent: [%s] == [%s]", n.Tag(), ntype) - return n + xnode.parent.childFirst.iterate(func(node *xmlNode) bool { + if node.Tag() == ntype { + n = node + return true } - + return false + }) + if n != nil { + return n } if pn := xnode.parent.closestUp([]string{ntype}); pn != nil { @@ -332,7 +441,6 @@ func (xnode *xmlNode) closestUp(nodeTypes []string) *xmlNode { // ReplaceInContents - replace plain text contents with something func (xnode *xmlNode) ReplaceInContents(old, new []byte) []byte { xnode.Walk(func(n *xmlNode) { - n.Content = bytes.ReplaceAll(n.Content, old, new) }) return xnode.AllContents() @@ -350,11 +458,7 @@ func (xnode *xmlNode) Tag() string { // String get node as string for debugging purposes // prints useful information func (xnode *xmlNode) String() string { - s := fmt.Sprintf("#%d: ", xnode.index()) - if xnode.isDeleted { - s += aurora.Red(" !!DELETED!! ").String() - - } + var s string s += fmt.Sprintf("-- %p -- ", xnode) s += fmt.Sprintf("%s: ", xnode.Tag()) @@ -363,8 +467,7 @@ func (xnode *xmlNode) String() string { } s += fmt.Sprintf("[Content:%s]", xnode.Content) - s += fmt.Sprintf(" %3d", len(xnode.Nodes)) - // s += fmt.Sprintf("[%s]", xnode.AllContents()) + s += fmt.Sprintf("[%s]", xnode.AllContents()) s += fmt.Sprintf("\tParent: %s", xnode.parent.Tag()) // s += fmt.Sprintf("\t-- %s", xnode.StylesString()) return s @@ -390,9 +493,6 @@ func (xnode *xmlNode) printTree(label string) { if xnode.isNew { s = aurora.Cyan(s).String() } - if xnode.isDeleted { - s = aurora.Red(s).String() - } // pointers s += fmt.Sprintf("|%p|", n) @@ -438,25 +538,27 @@ func (xnode *xmlNode) nodeBySelector(selector string) *xmlNode { selector = strings.TrimSpace(selector) selector = strings.ReplaceAll(selector, " ", "") tags := strings.Split(selector, ">") - + var n *xmlNode for i, tag := range tags { - for _, n := range xnode.Nodes { - if n.Tag() == tag { + xnode.childFirst.iterate(func(node *xmlNode) bool { + if node.Tag() == tag { if len(tags[i:]) == 1 { - // aurora.HiGreen("FOUND: %s", tag) - return n + n = node + return true } - selector = strings.Join(tags[i:], ">") // aurora.Green("NEXT: %s", selector) - - return n.nodeBySelector(selector) + n = node.nodeBySelector(selector) + if n != nil { + return true + } } - } + return false + }) } // aurora.Red("Selector not found: [%s]", selector) - return nil + return n } // get attribute value