73517


Rustのhtml5everを使用して、HTMLをパースし、一部を削除した結果を出力したいです。  
調べたところ、`remove_from_parent` という関数を使用出来るかと考えています。

<https://docs.rs/markup5ever/0.10.0/markup5ever/interface/tree_builder/trait.TreeSink.html#tymethod.remove_from_parent>

例として、`remove_from_parent` 関数を使い、span要素を全て削除するサンプルを作ろうとしています。  
現在のコードの一部は以下です。  
`// remove all span element here` のコメントの箇所で削除しようとしています。  
どのように削除すれば良いでしょうか？

lin.rs

[code]

    extern crate html5ever;
    extern crate markup5ever;
    extern crate markup5ever_rcdom;
    
    use markup5ever_rcdom::{NodeData, RcDom, Handle, SerializableHandle};
    use html5ever::parse_document;
    use html5ever::serialize::{SerializeOpts, serialize};
    use html5ever::tendril::TendrilSink;
    
    use wasm_bindgen::prelude::*;
    use web_sys::console::log_1;
    
    #[wasm_bindgen]
    extern {
        pub fn alert(s: &str);
    }
    
    #[wasm_bindgen]
    pub fn convert() {
        let data = "<html><body><span>should be removed</span></body></html>";
        let dom: RcDom = parse_document(RcDom::default(), Default::default()).one(data);
    
        search_iter(&dom.document, &dom);
    
        let mut bytes = vec![];
        let document: SerializableHandle = dom.document.clone().into();
        serialize(&mut bytes, &document, SerializeOpts::default()).unwrap();
        let converted_html = String::from_utf8(bytes).unwrap();
    
        alert(&format!("{:?}", converted_html));
    }
    
    fn search_iter(node: &Handle, dom: &RcDom) {
        match node.data {
            NodeData::Element { ref name, .. } => {
                if name.local.to_string().eq("span") {
                    log_1(&JsValue::from("This is span tag"));
                    // remove all span element here
                }
            },
            _ => {}
        };
        for child in node.children.borrow().iter() {
          search_iter(child, dom);
        }
    }
    
[/code]

Cargo.toml

[code]

    [dependencies]
    wasm-bindgen = "0.2"
    html5ever = "0.25.1"
    markup5ever = "0.10.0"
    markup5ever_rcdom = "0.1.0"
    web-sys = {"version" = "0.3.44", features=['console']}
    
[/code]

index.html

[code]

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
      </head>
      <body>
        <script type="module">
            import * as mod  from "./hello_wasm.js";
            (async () => {
                await mod.default();
                mod.convert();
            })();
        </script>
      </body>
    </html>
    
[/code]


CC BY-SA 4.0
2021-01-21T11:34:16.407
73472
2021-01-23T04:37:54.363
2021-01-21T22:27:02.820
17347
17347
question
rust
Rustのhtml5everのremove_from_parent関数の使用方法を教えてください


> 調べたところ、remove_from_parent という関数を使用出来るかと考えています

私はhtml5everを使ったことがないので少し手こずりましたが、ご質問の答えはわかりました。以下のようにして調べました。

  1. 質問で挙げられていたmarkup5everクレートのドキュメントを読んだ 
    * `remove_from_parent`は`TreeSink`トレイトのメソッドであることがわかった
    * しかし、markup5everにはその`TreeSink`トレイトを実装している型がない
  2. markup5everとhtml5everのGitHubリポジトリーで、examplesフォルダー内にあるサンプルを見てみた 
    * 手がかりになりそうな情報はなかった
  3. GitHubでコード検索した。（検索ワード：`remove_from_parent`、言語：Rust） 
    * 検索結果：[https://github.com/search?l=Rust&q=remove_from_parent&type=Code](https://github.com/search?l=Rust&q=remove_from_parent&type=Code)
    * 参考になりそうなコードが見つかった：[brave/brave-core - scorer.rs#L192-L198](https://github.com/brave/brave-core/blob/612896e4b30cca7352dfe2aa1247e2995f97b580/components/speedreader/rust/lib/src/readability/src/scorer.rs#L192-L198)

検索で見つかったコードから、`TreeSink`トレイトはご質問のコードで使われている`RcDom`が実装していることがわかりました。（`RcDom`はmarkup5ever_rcdomクレートにある）

つまり、以下のように書けそうです。

[code]

                if name.local.to_string().eq("span") {
                    log_1(&JsValue::from("This is span tag"));
                    // remove all span element here
                    dom.remove_from_parent(&node);   // ← この行を追加した
                }
    
[/code]

しかしこれはコンパイルエラーになりました。

[code]

    error[E0596]: cannot borrow `*dom` as mutable, as it is behind a `&` reference
      --> src/bin/main1.rs:33:13
       |
    29 | fn search_iter(node: &Handle, dom: &RcDom, nodes_to_remove: &mut Vec<Handle>) {
       |                                    ------ help: consider changing this to be a mutable reference: `&mut RcDom`
    ...
    33 |             dom.remove_from_parent(&node);
       |             ^^^ `dom` is a `&` reference, so the data it refers to cannot be borrowed as mutable
    
[/code]

`remove_from_parent`は`self`として`&mut
RcDom`を取ろうとするのですが、`dom`は`&RcDom`なのでできないそうです。

コンパイルエラーで勧められたとおりに何箇所か修正すると、最後はこのエラーで行き詰まりました。

[code]

    error[E0502]: cannot borrow `dom` as mutable because it is also borrowed as immutable
      --> src/bin/main1.rs:14:32
       |
    14 |     search_iter(&dom.document, &mut dom, &mut nodes_to_remove);
       |     ----------- -------------  ^^^^^^^^ mutable borrow occurs here
       |     |           |
       |     |           immutable borrow occurs here
       |     immutable borrow later used by call
    
[/code]

`dom`にはすでに不変借用があるので、可変借用を同時に作れないわけです。

参考にしたコードをもう一度見てみると、処理を2つのステップに分けていることがわかりました。

  * **ステップ1** ：domをトラバース（ツリー全体を移動）して、削除の対象となるノードを洗い出す。対象ノードは`Vec<RcDom>`に格納する
  * **ステップ2** ：洗い出したノードを削除する

これにならってコードを修正したところ、コンパイルエラーが解消し、以下のような出力が得られました。

**実行結果**

[code]

    Found a span tag
    "<html><head></head><body></body></html>"
    
[/code]

修正後のコードは以下のようになります。（wasmにコンパイルしなくて済むように、`log_1()`を`println!()`で置き換えています）

[code]

    use html5ever::{
        local_name, parse_document, serialize, serialize::SerializeOpts, tendril::TendrilSink,
        tree_builder::TreeSink,
    };
    use markup5ever_rcdom::{Handle, NodeData, RcDom, SerializableHandle};
    use std::rc::Rc;
    
    pub fn convert() {
        let data = "<html><body><span>should be removed</span></body></html>";
        let mut dom: RcDom = parse_document(RcDom::default(), Default::default()).one(data);
    
        // spanノードを探してnodes_to_removeに入れる
        let mut nodes_to_remove = vec![];
        search_iter(&dom.document, &dom, &mut nodes_to_remove);
    
        // 見つかったspanノードを削除する
        for node in nodes_to_remove {
            dom.remove_from_parent(&node);
        }
    
        let mut bytes = vec![];
        let document: SerializableHandle = dom.document.into();
        serialize(&mut bytes, &document, SerializeOpts::default()).unwrap();
        let converted_html = String::from_utf8(bytes).unwrap();
    
        println!("{:?}", converted_html);
    }
    
    fn search_iter(node: &Handle, dom: &RcDom, nodes_to_remove: &mut Vec<Handle>) {
        if let NodeData::Element { ref name, .. } = node.data {
            // Local値はlocal_name!マクロで作れる
            if name.local == local_name!("span") {
                println!("Found a span tag");
                // ノードをnodes_to_removeに入れる
                nodes_to_remove.push(Rc::clone(&node));
            }
        }
    
        for child in node.children.borrow().iter() {
            search_iter(child, dom, nodes_to_remove);
        }
    }
    
[/code]


CC BY-SA 4.0
2021-01-23T04:30:49.233
73517
2021-01-23T04:37:54.363
2021-01-23T04:37:54.363
14101
14101
73472
answer