为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
富国沪深 2021-12-07 10:35:16
如何为已解析的 HTML 文档的特定节点提取位置偏移量?例如,对于文档,<div>Hello, <b>World!</b></div>我希望能够知道World!is 的偏移量15:21。解析时可能会更改文档。我有一个用特殊标记渲染整个文档的解决方案,但这对性能来说真的很糟糕。有任何想法吗?package mainimport (    "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))}
查看完整描述

2 回答

?
波斯汪

TA贡献1811条经验 获得超4个赞

不是答案,但评论太长了。以下可能在某种程度上起作用:

  • 使用 aTokenizer并一步一步地遍历每个元素。

  • 将您的输入包装到自定义读取器中,该读取器在 Tokenizer 读取时记录行和列偏移量。

  • 在调用 Next() 之前和之后查询您的自定义阅读器的位置,以记录您需要的大致位置信息。

这有点痛苦,而且不太准确,但可能是您能做的最好的。


查看完整回答
反对 回复 2021-12-07
?
智慧大石

TA贡献1946条经验 获得超3个赞

我想出了一个解决方案,我们扩展(如果有另一种方法,请修复我)原始 HTML 包以及custom.go带有新导出功能的附加文件。此函数能够访问 的未导出data属性Tokenizer,该属性准确保存当前 的开始和结束位置Node。我们必须在每次读取缓冲区后调整位置。见globalBufDif。


我真的不喜欢我只需要 fork 包才能访问几个属性,但似乎这是一种 Go 方式。


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

}


查看完整回答
反对 回复 2021-12-07
  • 2 回答
  • 0 关注
  • 319 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信