薄っぺらりん

厚くしていきたい

parsercherがv3.1.0になったよ!

parsercher v3.1.0

前回のリリース記事「Rustでタグをパースするクレートを公開したよ! - 薄っぺらりん」からバージョンアップしてv3.1.0をリリースしました。
いくつかAPIを追加したりしたので今回はその辺を書こうと思います。変更点はgithubのReleasesやCHANGELOG.mdに記載しています。

github.com

目次

部分木の検索ができるようになりました

Dom構造体ツリーから別のDom構造体ツリーを使用して部分木を検索できます。
これにはparsercher::search_dom()を使用します。
次の例はdocからneedleと一致する部分木を取得するものです。 list1list3が一致します。
needle:

  <ul class="targetList">
    <li class="key1"></li>
    <li class="key2"></li>
  </ul>

例:

let doc = r#"
  <body>
    <ul id="list1" class="targetList">
      <li class="key1">1-1</li>
      <li class="key2"><span>1-2</span></li>
    </ul>

    <ul id="list2">
      <li class="key1">2-1</li>
      <li>2-2</li>
    </ul>

    <div>
      <div>
        <ul class="targetList">
          <ul id="list3" class="targetList">
            <li class="key1">3-1</li>
            <li class="item">3-2</li>
            <li class="key2">3-3</li>
          </ul>
        </ul>
      </div>
    </div>

    <ul id="list4">
      <li class="key1">4-1</li>
      <li class="key2">4-2</li>
    </ul>
  </body>
"#;

let doc_dom = parsercher::parse(&doc).unwrap();

let needle = r#"
  <ul class="targetList">
    <li class="key1"></li>
    <li class="key2"></li>
  </ul>
"#;
let needle_dom = parsercher::parse(&needle).unwrap();
// Remove `root`dom of needle_dom
let needle_dom = needle_dom.get_children().unwrap().get(0).unwrap();

if let Some(dom) = parsercher::search_dom(&doc_dom, &needle_dom) {
    parsercher::print_dom_tree(&dom);
}

出力:

<root>
  <ul id="list1" class="targetList">
    <li class="key1">
      TEXT: "1-1"
    <li class="key2">
      <span>
        TEXT: "1-2"
  <ul class="targetList" id="list3">
    <li class="key1">
      TEXT: "3-1"
    <li class="item">
      TEXT: "3-2"
    <li class="key2">
      TEXT: "3-3"

タグの属性を検索できるようになりました

全てのタグから任意の属性の値を取得できます。
これにはparsercher::search_attr()を使用します。
複数の属性を指定できるparsercher::search_attrs()もあります。
例えば、全てのid属性から値を取得する場合は次のようにします。

let doc = r#"
  <head>
    <meta charset="UTF-8">
    <meta id="value1">
    <title>sample html</title>
  </head>
  <body id="value2">
    <h1>sample</h1>

    <div id="value3"></div>

    <ol>
      <li>first</li>
      <li id="value4">second</li>
      <li>therd</li>
    </ol>
  </body>
"#;

let dom = parsercher::parse(&doc).unwrap();

let values = parsercher::search_attr(&dom, "id").unwrap();
assert_eq!(values.len(), 4);
assert_eq!(values[0], "value1".to_string());
assert_eq!(values[1], "value2".to_string());
assert_eq!(values[2], "value3".to_string());
assert_eq!(values[3], "value4".to_string());

id属性に加えてclass属性の値も取得する場合は次のようにします。

let doc = r#"
  <head>
    <meta charset="UTF-8">
    <meta id="id1">
    <title>sample html</title>
  </head>
  <body id="id2" class="class1">
    <h1>sample</h1>

    <div align="center" class="class2"></div>

    <ol>
      <li>first</li>
      <li class="class3">second</li>
      <li>therd</li>
    </ol>
  </body>
"#;

let dom = parsercher::parse(&doc).unwrap();

let attrs = vec!["id", "class"];
let values = parsercher::search_attrs(&dom, &attrs).unwrap();
assert_eq!(values.len(), 5);
assert_eq!(values[0], "id1".to_string());
assert_eq!(values[1], "id2".to_string());
assert_eq!(values[2], "class1".to_string());
assert_eq!(values[3], "class2".to_string());
assert_eq!(values[4], "class3".to_string());

木の十分条件を評価できるようになりました

木の十分条件を評価するDom::p_implies_q_tree()を追加しました。 部分木の検索はこれにより実現されています。
また、Dom構造体の十分条件を評価するDom::p_implies_q()を追加したほか、以前までタグの十分条件を評価するためにあったparsercher::satisfy_sufficient_condition()Tag::p_implies_q()に変更しました。

以下は木の十分条件を評価する例です。

let p = r#"
  <body>
    <div></div>
    <ul>
      <li class="item"><li>
    </ul>
  </body>
"#;
let p_dom = parsercher::parse(&p).unwrap();

let q = r#"
  <body>
    <div id="content"></div>
    <ul id="liset1">
      <li class="item">item1<li>
      <li class="item">item2<li>
      <li class="item">item3<li>
    </ul>
  </body>
"#;
let q_dom = parsercher::parse(&q).unwrap();

assert_eq!(Dom::p_implies_q_tree(&p_dom, &q_dom), true);

domモジュール下の構造体が使いやすくなりました

  • Tag, Text, Comment構造体について、コンストラクタの引数の型がStringから&strになりました。
  • Tag構造体の属性設定がより簡単に記述できるようになりました。従来のHashMapを使用する方法はTag::set_attrs()として残っています。
// <div id="section" class="alert alert-primary">
let mut tag = Tag::new("div");
tag.set_attr("id", "section");
tag.set_attr("class", "alert alert-primary");
  • PartialEqを継承しました。Dom構造体も含むため、木を等価演算子で比較できます。
let a = r#"
<head>
  <title>sample</title>
</head>
<body>
  <h1>section</h1>
  <ul>
    <li>list1</li>
    <li>list2</li>
  </ul>
</body>
"#;
let a_dom = parsercher::parse(&a);

let b = r#"
<head>
  <title>sample</title>
</head>
<body>
  <h1>section</h1>
  <ul>
    <li>list1</li>
    <li>list2</li>
  </ul>
</body>
"#;
let b_dom = parsercher::parse(&b);

assert_eq!(a_dom == b_dom, true);
assert_eq!(a_dom != b_dom, false);

まとめ

API追加の合間に既存API名の変更を行っていたので短期間にメジャーバージョンが3まで来ました。思い付きで楽しく作っていて都度リリースしているので、セマンティックバージョニングを採用しているとメジャーバージョンが上がりがちです。crates.ioの他のクレートを見ていると同じことになっているものもあるので、そんなもんかなと思います。数字が大きくなるのはスマートではないけれど別に悪いことではないし、バージョン番号は差異があることを示すための数字に過ぎない気もします。

自分は会社や仕事のことがずっと頭から離れないのですが、最近は「あとはどんな機能があったら便利かな」と考えたりすることもあって、いい気分転換になっている気がします。