提取Golang中*html.Node的位置偏移量

提取Golang中*html.Node的位置偏移量,html,go,Html,Go,如何提取已解析HTML文档的特定节点的位置偏移量?例如,对于文档Hello,World

如何提取已解析HTML文档的特定节点的位置偏移量?例如,对于文档
Hello,World世界的偏移量
15:21
。解析时可能会更改文档

我有一个解决方案,可以用特殊标记呈现整个文档,但这对性能非常不利。有什么想法吗

package main

import (
    "bytes"
    "golang.org/x/net/html"
    "golang.org/x/net/html/atom"
    "log"
    "strings"
)

func nodeIndexOffset(context *html.Node, node *html.Node) (int, int) {
    if node.Type != html.TextNode {
        node = node.FirstChild
    }
    originalData := node.Data

    var buf bytes.Buffer
    node.Data = "|start|" + originalData
    _ = html.Render(&buf, context.FirstChild)
    start := strings.Index(buf.String(), "|start|")

    buf = bytes.Buffer{}
    node.Data = originalData + "|end|"
    _ = html.Render(&buf, context.FirstChild)
    end := strings.Index(buf.String(), "|end|")

    node.Data = originalData
    return start, end
}

func main() {
    s := "<div>Hello, <b>World!</b></div>"
    var context html.Node
    context = html.Node{
        Type:     html.ElementNode,
        Data:     "body",
        DataAtom: atom.Body,
    }
    nodes, err := html.ParseFragment(strings.NewReader(s), &context)
    if err != nil {
        log.Fatal(err)
    }
    for _, node := range nodes {
        context.AppendChild(node)
    }
    world := nodes[0].FirstChild.NextSibling.FirstChild
    log.Println("target", world)
    log.Println(nodeIndexOffset(&context, world))
}
主程序包
进口(
“字节”
“golang.org/x/net/html”
“golang.org/x/net/html/atom”
“日志”
“字符串”
)
func nodeIndexOffset(context*html.Node,Node*html.Node)(int,int){
如果node.Type!=html.TextNode{
node=node.FirstChild
}
originalData:=节点。数据
var buf字节。缓冲区
node.Data=“|开始|”+原始数据
_=html.Render(&buf,context.FirstChild)
start:=strings.Index(buf.String(),“| start |”)
buf=字节。缓冲区{}
node.Data=originalData+“| end |”
_=html.Render(&buf,context.FirstChild)
end:=strings.Index(buf.String(),“| end |”)
node.Data=originalData
返回开始,结束
}
func main(){
s:=“你好,世界!”
var context html.Node
context=html.Node{
类型:html.ElementNode,
数据:“机构”,
DataAtom:atom.Body,
}
节点,err:=html.ParseFragment(strings.NewReader和context)
如果错误!=零{
log.Fatal(错误)
}
对于u,节点:=范围节点{
AppendChild(节点)
}
世界:=节点[0]。FirstChild.NextSibling.FirstChild
log.Println(“目标”,世界)
Println(nodeIndexOffset(&context,world))
}

不是答案,但太长,无法发表评论。以下几点在某种程度上是可行的:

  • 使用
    标记器
    ,逐个遍历每个元素
  • 将您的输入包装到一个自定义读取器中,该读取器记录行和行 标记器从中读取时的列偏移量
  • 在调用Next()之前和之后,查询自定义读取器中的职位 记录所需的大致位置信息

这有点痛苦,也不太准确,但可能是您能做到的最好的解决方案。

我提出了一个解决方案,我们扩展了原始HTML包(如果有其他方法,请修复我),其中包含附加的
自定义。使用新的导出功能转到
文件。此函数能够访问
标记器
的未报告
数据
属性,该属性准确地保存当前
节点
的开始和结束位置。我们必须在每次读取缓冲区后调整位置。请参见
globalBufDif

我真的不喜欢这样,我只需要使用fork包来访问几个属性,但这似乎是一种可行的方法

func parseWithIndexes(p *parser) (map[*Node][2]int, error) {
    // Iterate until EOF. Any other error will cause an early return.
    var err error
    var globalBufDif int
    var prevEndBuf int
    var tokenIndex [2]int
    tokenMap := make(map[*Node][2]int)
    for err != io.EOF {
        // CDATA sections are allowed only in foreign content.
        n := p.oe.top()
        p.tokenizer.AllowCDATA(n != nil && n.Namespace != "")

        t := p.top().FirstChild
        for {
            if t != nil && t.NextSibling != nil {
                t = t.NextSibling
            } else {
                break
            }
        }
        tokenMap[t] = tokenIndex
        if prevEndBuf > p.tokenizer.data.end {
            globalBufDif += prevEndBuf
        }
        prevEndBuf = p.tokenizer.data.end
        // Read and parse the next token.
        p.tokenizer.Next()
        tokenIndex = [2]int{p.tokenizer.data.start + globalBufDif, p.tokenizer.data.end + globalBufDif}

        p.tok = p.tokenizer.Token()
        if p.tok.Type == ErrorToken {
            err = p.tokenizer.Err()
            if err != nil && err != io.EOF {
                return tokenMap, err
            }
        }
        p.parseCurrentToken()
    }
    return tokenMap, nil
}

// ParseFragmentWithIndexes parses a fragment of HTML and returns the nodes
// that were found. If the fragment is the InnerHTML for an existing element,
// pass that element in context.
func ParseFragmentWithIndexes(r io.Reader, context *Node) ([]*Node, map[*Node][2]int, error) {
    contextTag := ""
    if context != nil {
        if context.Type != ElementNode {
            return nil, nil, errors.New("html: ParseFragment of non-element Node")
        }
        // The next check isn't just context.DataAtom.String() == context.Data because
        // it is valid to pass an element whose tag isn't a known atom. For example,
        // DataAtom == 0 and Data = "tagfromthefuture" is perfectly consistent.
        if context.DataAtom != a.Lookup([]byte(context.Data)) {
            return nil, nil, fmt.Errorf("html: inconsistent Node: DataAtom=%q, Data=%q", context.DataAtom, context.Data)
        }
        contextTag = context.DataAtom.String()
    }
    p := &parser{
        tokenizer: NewTokenizerFragment(r, contextTag),
        doc: &Node{
            Type: DocumentNode,
        },
        scripting: true,
        fragment:  true,
        context:   context,
    }

    root := &Node{
        Type:     ElementNode,
        DataAtom: a.Html,
        Data:     a.Html.String(),
    }
    p.doc.AppendChild(root)
    p.oe = nodeStack{root}
    p.resetInsertionMode()

    for n := context; n != nil; n = n.Parent {
        if n.Type == ElementNode && n.DataAtom == a.Form {
            p.form = n
            break
        }
    }

    tokenMap, err := parseWithIndexes(p)
    if err != nil {
        return nil, nil, err
    }

    parent := p.doc
    if context != nil {
        parent = root
    }

    var result []*Node
    for c := parent.FirstChild; c != nil; {
        next := c.NextSibling
        parent.RemoveChild(c)
        result = append(result, c)
        c = next
    }
    return result, tokenMap, nil
}

我喜欢这个想法,但我仍在寻找解决方案,以获得高精度。标记器在缓冲区大小较低时不能正常工作,而在缓冲区大小较高时精度太低。