Summary
- Rustでは構造体からJSONへの変換はserdeで一撃
GiHub -> https://github.com/akitenkrad/rsrpp
crates.io -> https://crates.io/crates/rsrpp
ToDo
-
pdftotextで論文から単語単位のテキストと位置情報を取得する (
Word
,Line
,Block
,Page
) -
テキストの属性 (本文, タイトル, 脚注, etc.) を判定する
-
テキストが含まれるエリアを抽出する
- 2段組みを扱えるようにする
- セクションのタイトルを識別する
-
テキストが含まれるエリアを抽出する
-
図表に含まれるテキストを除外する
-
表を除外する
- PDFを画像に変換
- 画像処理で表の位置を特定
- SVG画像,数式を除外する
-
表を除外する
- 抽出したテキストをJSONで出力できるようにする
今日のファイル
rsrpp
├── Cargo.toml
├── rsrpp
│ ├── Cargo.toml
│ └── src
│ ├── lib.rs
│ └── parser
│ ├── mod.rs <-- today
│ ├── structs.rs <-- today
│ └── tests.rs
└── rsrpp-cli
├── Cargo.toml
└── src
└── main.rs
前回までのあらすじ
前回まででPDFのパースがほぼ完了しました.
今回はパースした結果をJSONに変換する部分を実装します.
構造体 → JSON変換
Rustではserdeというクレートを使うことで,構造体を簡単にシリアライズ・デシリアライズできます.そして,serde_jsonによって簡単にJSONテキストを得ることができるので,正直今回はあまり解説することがありません.
全てはクレートの優秀さが原因です.
PDFの構造を扱うための構造体としてはPage
を実装していましたが,あちらをそのままJSONに落とすのは少々手間なので,JSON用の構造体を別途実装します.
下記のSection
はその名の通り,論文の各セクションのタイトルとテキストを保持します.
index
はセクションの順番を格納しています.
JSON変換するにあたっては,特に追加の実装は必要なく,#[derive(Serialize, Deserialize)]
を指定しておくだけでOKです.
use serde::{Deserialize, Serialize}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Section {
pub index: i8,
pub title: String,
pub contents: Vec<String>,
}
PDFをパースした結果はVec<Page>
として得られるので,それを受け取ってVec<Section>
を返すコンストラクタを実装しておきます.
実装内容についてはプログラムを読んで貰えば分かるとおり,セクションごとのHashMap
を用意して該当するテキストを集約しているだけです.
ただし,HashMap
では追加した順番が保存されないため,そのままではセクションの順序が壊れてしまいます.
そこで,セクションごとのインデックスを別途保存することで最終的にセクションの順番をソートできるようにしています.
impl Section {
pub fn from_pages(pages: &Vec<Page>) -> Vec<Section> {
let mut section_indices: HashMap<String, i8> = HashMap::new();
let mut section_map: HashMap<String, Vec<String>> = HashMap::new();
for page in pages {
for block in &page.blocks {
let keys = section_map.keys().cloned().collect::<Vec<String>>();
let text_block = block.get_text();
if keys.contains(&block.section) {
let content = section_map.get_mut(&block.section).unwrap();
content.push(text_block);
} else {
section_map.insert(block.section.clone(), vec![text_block]);
section_indices.insert(block.section.clone(), section_indices.len() as i8);
}
}
}
let mut sections = Vec::new();
for (title, contents) in section_map {
sections.push(Section {
index: section_indices.get(&title).unwrap().clone(),
title: title,
contents: contents,
});
}
sections.sort_by(|a, b| a.index.cmp(&b.index));
return sections;
}
}
Page
をSection
に変換してしまえば,あとはserde_json
を使って以下のように簡単にJSONのテキストを得ることができます.
let sections = Section::from_pages(&pages); // Vec<Section>
let json = serde_json::to_string(§ions).unwrap(); // String
次回
次回はこれまで紹介してこなかったUtil系の処理やテストたちを紹介しようと思います.