提取Golang中*html.Node的位置偏移量
如何提取已解析HTML文档的特定节点的位置偏移量?例如,对于文档提取Golang中*html.Node的位置偏移量,html,go,Html,Go,如何提取已解析HTML文档的特定节点的位置偏移量?例如,对于文档Hello,World
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
}
我喜欢这个想法,但我仍在寻找解决方案,以获得高精度。标记器在缓冲区大小较低时不能正常工作,而在缓冲区大小较高时精度太低。