シュッとHTMLをJSON化できるライブラリを作った

# まとめ

#### 追記 [コマンドラインツール作りました](https://qiita.com/rike422/items/6c3762c440c048fc23ce)

下記のようにhtmlとCSSSelectorを値にしたオブジェクトを渡すと、
値の部分に抽出結果が出力されるライブラリを作りました。


https://github.com/rike422/kirinuki-core
[Demo](https://rike422.github.io/kirinuki-core/)

```js:readmeから抜き出したもの
const kirinuki = require('kirinuki');
const html = `
<html>
  <head>
    <title>Hero News!</title>
  </head>
  <body>
    <div class="main">
        <h3 class="topic">Amalgam</h3>
        <ul class="news-list">
            <li>
              <span class="content">Batman come back in Gossam City!</span>
              <img class="thumbnail" src="https://exmaple.com/batman.png"></img>
            </li>
            <li>
              <span class="content">Dr. Strange got into a traffic accident.</span>
              <img  class="thumbnail" src="https://exmaple.com/strange.png"></img>
            </li>
        </ul>
    </div>
  </body>
</html>
`;
const schema = {
  topic: {
    content: ".content",
    contents: ".content"
  }
}

kirinuki(schema, html)
// > { topic: { 
// キーが複数形でない場合は、先頭のデータが入る。
// content: 'Batman come back in Gossam City!'
// 複数系の場合は配列で帰ってくる。 
// contents: [
//  'Batman come back in Gossam City!',
//  'Dr. Strange got into a traffic accident.',
// ]
// } }
```

## 経緯

[jam-api](https://github.com/gavindinubilo/jam-api)というアプリケーションがあり、
こちらのパーサを[apigateway + aws-lambda上で利用](https://github.com/rike422/jambda)したりしていたのですが、
下記のような抽出に対応していなかったため、自作しました。


```html:抽出したいhtml
<ul>
  <li class="item">
    <span class="topic">topic1</span>
    <img src="https://example.com/imgs/img1.png"/>
  </li>
  <li  class="item">
    <span class="topic">topic2</span>
    <img src="https://example.com/imgs/img2.png"/ >
  </li>
  <li  class="item">
    <span class="topic">topic3</span>
    <img src="https://example.com/imgs/img3.png"/ >
  </li>
</ul>
```

このようなリスト構造になっているhtmlに対して、下記のようなオブジェクトの配列で返してほしいと思っていました。
しかし、現在のjam-apiの仕様では配列として受け取ることしか出来ません。

```js:結果として期待しているjson
{
    "items": [
     ｛
        "topic": "topic1",
        "thumbnail": "https://example.com/imgs/img1.png"
     },
     ｛
        "topic": "topic2",
        "thumbnail": "https://example.com/imgs/img2.png"
     },
     ｛
        "topic": "topic3",
        "thumbnail": "https://example.com/imgs/img3.png"
     }
   ]
}

```

```js:jam-apiが返すjson
{
    "items": [
     ｛
        "topic": ["topic1","topic2","topic3",]
        "thumbnail": [
           "https://example.com/imgs/img1.png",
           "https://example.com/imgs/img2.png",
           "thumbnail": "https://example.com/imgs/img3.png"
         ]
     }
   ]
}
```

## 使い方

基本的には`require`|`import`したfunctionに対して、
欲しい形式のオブジェクトとHTML文字列を渡すことで戻り値でパース結果が帰ってきます。
[Demo](https://rike422.github.io/kirinuki-core/)ページでなんとなくの使用感がわかるかと思います。

jam-apiと同様、CSS Selectorで取得した要素からは自動的に値が抽出されます。

- imgタグの場合はsrc属性
- a,linkタグの場合はhref属性
- その他のタグは子要素内ののtext




### 命名規則によるデータの変換

ざっくりとしたselectorで取得すると要素が複数取得されるケースで抽出が面倒だったので、
名前による変換を実装しました。
キーの名前が単数形(ex:item)の場合、自動で先頭の値を抽出して値にします。
複数形(ex:items)の場合は配列で返します

```html
<ul>
  <li class="item">item1</li>
  <li class="item">item2</li>
  <li class="item">item3</li>
</ul>

```
```js:kirinukiに渡す形式
const schema = {
  item: ".item",
  items: ".item"
}
```

```js:結果
{
  item: "item1",
  items: ["item1", "item2", "item3"]
}
```

### 配列への展開

リスト上になっているデータをオブジェクトの配列で欲しい場合、`_unfold:true`を定義に付与すると、展開が行われます。

```html
<ul>
  <li class="item">
    <span class="topic">topic1</span>
    <img src="https://example.com/imgs/img1.png"/>
  </li>
  <li  class="item">
    <span class="topic">topic2</span>
    <img src="https://example.com/imgs/img2.png"/ >
  </li>
  <li  class="item">
    <span class="topic">topic3</span>
    <img src="https://example.com/imgs/img3.png"/ >
  </li>
</ul>
```

```js:kirinukiに渡す形式
const = {
  unfoldItems: {
    _unfold: true,
    topic: ".item .topic",
    thumbnail: ".item img"
  }
}
```
```js:結果
{
  "unfoldItems": [
    ｛
      "topic": "topic1",
      "thumbnail": "https://example.com/imgs/img1.png"
    },
    ｛
      "topic": "topic2",
      "thumbnail": "https://example.com/imgs/img2.png"
    },
    ｛
      "topic": "topic3",
      "thumbnail": "https://example.com/imgs/img3.png"
    }
  ]
}

```

### 探索パスの指定

クエリでの探索範囲を絞りたい場合は、`_root`をキーにしたCSSSelectorを定義すると、
_rootで予め絞り込んだ要素内での探索が行われます。

```html:root.html

<div>
  <ul class="main-items">
    <li class="item">
      <span class="topic">topic1</span>
      <img src="https://example.com/imgs/img1.png"/>
    </li>
    <li  class="item">
      <span class="topic">topic2</span>
      <img src="https://example.com/imgs/img2.png"/ >
    </li>
    <li  class="item">
      <span class="topic">topic3</span>
      <img src="https://example.com/imgs/img3.png"/ >
    </li>
  </ul>
  <ul  class="sub-items">
    <li class="item">
      <span class="topic">sub-topic1</span>
      <img src="https://example.com/imgs/sub-img1.png"/>
    </li>
    <li  class="item">
      <span class="topic">sub-topic2</span>
      <img src="https://example.com/imgs/sub-img2.png"/ >
    </li>
    <li  class="item">
      <span class="topic">sub-topic3</span>
      <img src="https://example.com/imgs/sub-img3.png"/ >
    </li>
  </ul>
</div>

```

```js:kirinukiに渡す形式
{
    "mainItems": {
        "_root": ".main-items",
        "_unfold": true,
        "content": ".topic",
        "image": "img"
    },
    "subItems": {
        "_root": ".sub-items",
        "_unfold": true,
        "content": ".topic",
        "image": "img"
    }
}
```

```js:結果

{
  "mainItems": [
    ｛
      "topic": "topic1",
      "thumbnail": "https://example.com/imgs/img1.png"
    },
    ｛
      "topic": "topic2",
      "thumbnail": "https://example.com/imgs/img2.png"
    },
    ｛
      "topic": "topic3",
      "thumbnail": "https://example.com/imgs/img3.png"
    }
  ],
  "subItems": [
    ｛
      "topic": "sub-topic1",
      "thumbnail": "https://example.com/imgs/sub-img1.png"
    },
    ｛
      "topic": "sub-topic2",
      "thumbnail": "https://example.com/imgs/sub-img2.png"
    },
    ｛
      "topic": "sub-topic3",
      "thumbnail": "https://example.com/imgs/sub-img3.png"
    }
  ]
}

```

### 属性の取得



### 課題・感想

- 書いてる途中に気づきましたが、`<a>`タグのhrefとテキストを取得することが出来ません。
    - \["img","text"]のように、取得したい属性:値を配列で渡してもらうか、keyに_textなどつけると該当する属性を取りに行くなどの機能の実装すると思います。
    - [実装しました](https://rike422.github.io/kirinuki-core/)
- デモページの構築に[katatema.js](https://github.com/r7kamura/katatema)を利用しました。めちゃくちゃ便利でした。
    - 一点ハマったところとして、cheerioをwebpackでビルドする際、json-loaderが必要で、カスタムのwebpack.configを定義する方法がない？ため、browserfiyで一度ビルドしたものを更にwebpackで読み込む必要がありました。
- jam-apiっぽいAPIアプリ作ると思います。
- 英語