【问题标题】:How to replace specific html tags using string tokenizer如何使用字符串标记器替换特定的 html 标签
【发布时间】:2020-11-27 15:21:16
【问题描述】:

我有一个带有 html 标记的字符串 (differMarkup),并希望通过一个标记器运行该字符串,该标记器将识别特定标签 (如 ins、dels、movs) 并将它们替换为 span 标签并将数据属性添加到也是。

所以输入看起来像这样:

`<h1>No Changes Here</h1>
    <p>This has no changes</p>
    <p id="1"><del>Delete </del>the first word</p>
    <p id="2"><ins>insertion </ins>Insert a word at the start</p>`

预期的输出是这样的:

`<h1>No Changes Here</h1>
    <p>This has no changes</p>
    <p id="1"><span class="del" data-cid=1>Delete</span>the first word</p>
    <p id="2"><span class="ins" data-cid=2>insertion</span>Insert a word at the start</p>
`

这是我目前拥有的。出于某种原因,我无法将 html 标签附加到 finalMarkup var 设置为 span 时。

const (
    htmlTagStart = 60 // Unicode `<`
    htmlTagEnd   = 62 // Unicode `>`
    differMarkup = `<h1>No Changes Here</h1>
    <p>This has no changes</p>
    <p id="1"><del>Delete </del>the first word</p>
    <p id="2"><ins>insertion </ins>Insert a word at the start</p>`  // Differ Markup Output
)

func readDifferOutput(differMarkup string) string {

    finalMarkup := ""
    tokenizer := html.NewTokenizer(strings.NewReader(differMarkup))
    token := tokenizer.Token()
loopDomTest:
    for {
        tt := tokenizer.Next()
        switch {

        case tt == html.ErrorToken:
            break loopDomTest // End of the document,  done

        case tt == html.StartTagToken, tt == html.SelfClosingTagToken:
            token = tokenizer.Token()
            tag := token.Data

            if tag == "del" {
                tokenType := tokenizer.Next()

                if tokenType == html.TextToken {
                    tag = "span"
                    finalMarkup += tag
                }

                //And add data attributes
            }

        case tt == html.TextToken:
            if token.Data == "span" {
                continue
            }
            TxtContent := strings.TrimSpace(html.UnescapeString(string(tokenizer.Text())))
            finalMarkup += TxtContent
            if len(TxtContent) > 0 {
                fmt.Printf("%s\n", TxtContent)
            }
        }
    }

    fmt.Println("tokenizer text: ", finalMarkup)

    return finalMarkup

}
```golang

【问题讨论】:

    标签: html go token stringtokenizer


    【解决方案1】:

    基本上,您想替换 H​​TML 文本中的一些节点。对于此类任务,使用 DOM(文档对象模型)比自己处理令牌要容易得多。

    您使用的包golang.org/x/net/html 还支持使用html.Node 类型对HTML 文档进行建模。要获取 HTML 文档的 DOM,请使用 html.Parse() 函数。

    所以你应该做的是遍历 DOM,并替换(修改)你想要的节点。完成修改后,您可以通过渲染 DOM 来取回 HTML 文本,为此使用 html.Render()

    这是可以做到的:

    const src = `<h1>No Changes Here</h1>
    <p>This has no changes</p>
    <p id="1"><del>Delete </del>the first word</p>
    <p id="2"><ins>insertion </ins>Insert a word at the start</p>`
    
    func main() {
        root, err := html.Parse(strings.NewReader(src))
        if err != nil {
            panic(err)
        }
    
        replace(root)
    
        if err = html.Render(os.Stdout, root); err != nil {
            panic(err)
        }
    }
    
    func replace(n *html.Node) {
        if n.Type == html.ElementNode {
            if n.Data == "del" || n.Data == "ins" {
                n.Attr = []html.Attribute{{Key: "class", Val: n.Data}}
                n.Data = "span"
            }
        }
    
        for child := n.FirstChild; child != nil; child = child.NextSibling {
            replace(child)
        }
    }
    

    这将输出:

    <html><head></head><body><h1>No Changes Here</h1>
    <p>This has no changes</p>
    <p id="1"><span class="del">Delete </span>the first word</p>
    <p id="2"><span class="ins">insertion </span>Insert a word at the start</p></body></html>
    

    这几乎就是你想要的,“额外”的事情是html 包添加了包装器&lt;html&gt;&lt;body&gt; 元素,以及一个空的&lt;head&gt;

    如果你想摆脱这些,你可以只渲染 &lt;body&gt; 元素的内容而不是整个 DOM:

    // To navigate to the <body> node:
    body := root.FirstChild. // This is <html>
                    FirstChild. // this is <head>
                    NextSibling // this is <body>
    // Render everyting in <body>
    for child := body.FirstChild; child != nil; child = child.NextSibling {
        if err = html.Render(os.Stdout, child); err != nil {
            panic(err)
        }
    }
    

    这将输出:

    <h1>No Changes Here</h1>
    <p>This has no changes</p>
    <p id="1"><span class="del">Delete </span>the first word</p>
    <p id="2"><span class="ins">insertion </span>Insert a word at the start</p>
    

    我们完成了。试试Go Playground 上的示例。

    如果你希望结果为string(而不是打印到标准输出),你可以使用bytes.Buffer作为渲染的输出,最后调用它的Buffer.String()方法:

    // Render everyting in <body>
    buf := &bytes.Buffer{}
    for child := body.FirstChild; child != nil; child = child.NextSibling {
        if err = html.Render(buf, child); err != nil {
            panic(err)
        }
    }
    
    fmt.Println(buf.String())
    

    这输出相同。在Go Playground 上试试吧。

    【讨论】:

    • 非常感谢您有见地的回答!这对我有用!答案已被采纳!
    • 有没有办法将body trim函数的html.Render的输出解析成变量?我尝试了几种方法,无法退货。当前的解决方案仅将这些值打印到控制台中。
    • @DanielBenedek 是的,这很容易。只需使用bytes.Buffer 而不是os.Stdout,最后调用它的String() 方法。在答案中添加了一个示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-16
    • 1970-01-01
    • 1970-01-01
    • 2020-06-07
    • 2021-03-20
    • 1970-01-01
    相关资源
    最近更新 更多