Np-Urのデータ分析教室

オーブンソースデータなどWeb上から入手できるデータを用いて、RとPython両方使って分析した結果を書いていきます

テンプレートを使って esa 記事からオリジナルのWordファイル生成するShinyアプリケーション作り

前回は、esa API とPandocを使って、Shiny上でesa記事をWordとPDFファイルに変換するツールを作りました。

www.randpy.tokyo

ただし、Wordファイルの見た目はデフォルトのままで特にいじっていませんでした。
今回は、テンプレートファイルを使うことで、オリジナルのスタイルのWordファイルを作成してみます。

例えば、会社で指定されたフォーマットを使わなければならない場合などに参考にしてください。
なお、PDFのレイアウトをいじるのはかなり難しそうだったので、今回はWordのみです。

構成概要

構成は前回とほぼ一緒です。

使用しやすいようにWeb アプリケーションとして、R Shinyを使っていますが、特に拘りはないです。
Shinyからユーザにアクセスしてもらい、内部ではPandocを叩くことでWordやPDFファイルを作成します。

そして、前回同様すべてDocker上で作成していきます。

完成イメージ

f:id:Np-Ur:20200505220128p:plain

esaの記事idを入力し、「Markdownを取得」ボタンを押すと、記事内容の取得を行います。
(ここまでは前回同様)

その後、テンプレート用のdocxファイル(タイトルや本文・各見出しのスタイルが設定されたもの)をアップロードし、それに沿ったスタイルでWordファイルを作成することができます。
そして、「Download Word」や「Download PDF」をクリックすることで、ダウンロードできます。

なお、ユーザーがテンプレートファイルを用意しなくてデフォルトの設定でWordファイルを作成できるようにはなっています。

今回もコードはGIthubに置いてあります。
github.com


テンプレートとは

docxファイルをWordで開くと、各見出しや本文のスタイルを設定することができます。
詳しくは公式サイトを参照してください。

多くの方が一度は設定したことがあると思います。

このように、どの要素にどんなスタイルを適用するか設定したファイルをPandocではテンプレートと呼んでいます。

Pandocでは、専用のテンプレートファイルを用意することで、それに沿ったdocxファイルを生成してくれます。

ベースのテンプレート取得

Pandoc環境があれば、ベースとなるテンプレートファイルを簡単に取得することができます。

前回立ち上げたコンテナにログインして、テンプレート ファイルを取得しましょう。

以下を実行すると、コンテナにログインできます。
(XXXXXX 部分は立ち上げたコンテナのIDを指定してください。)

$ docker exec -it XXXXXX /bin/bash

ログインしている状態で、以下を実行すると、reference.docx という名前でベースとなる テンプレートファイルが生成されます。

$ pandoc --print-default-data-file reference.docx > reference.docx

こちらのファイルをホスト側に持ってきてください。(もしくは、マウントしているフォルダで上記を実行してください。)

ファイルを開くと、以下のようなサンプルのスタイルが適用されてたテキストが記述されています。
f:id:Np-Ur:20200505233152p:plain

例えば、「Heading 1」には、Wordでいう見出し1のスタイルが設定されています。
この見出し1のスタイルを変更すると、esa内で「#」1つから始まる見出し部分が変更されます。

ソースコード紹介

ファイル構成ですが、前回のものに「shiny-src-template/」ディレクトリ を加えて以下のようにしました。

- shiny-src/
  - esa_info.csv
  - ui.R
  - server.R
  - www/
- shiny-src-template/
  - esa_info.csv
  - ui.R
  - server.R
  - www/
    - reference.docx
- docker-compose.yaml
- Dockerfile

shiny-src-template/www/ ディレクトリ 以下に、先ほど取得したreference.docxを置いておいてください。

Dockerfileとesa_info.csvについては前回と同様なので割愛し、その他を以下節で説明していきます。

docker-compose.yaml

docker-compose.yamlは、マウントディレクトリを一つ増やして以下のようにしました。
最終行が追加されています。

version: '3'
services: 
  esa-shiny:
    container_name: esa-shiny
    image: esa-shiny:1.0
    ports:
      - 3838:3838
    # environment:
    #   - ESA_API_KEY=XXXXXXXXXXXXXXXX
    #   - ESA_TEAM_NAME=XXXXXX
    volumes: 
      - $PWD/shiny-src:/srv/shiny-server/esa
      - $PWD/shiny-src-template:/srv/shiny-server/esa-word-template

ui.Rとserver.R

今回も、前回同様 ui.Rとserver.Rに分けています。

ui.R

llibrary(shiny)

shinyUI(fluidPage(
  
  titlePanel("esa記事をWord or PDFで出力"),
  
  sidebarLayout(
    sidebarPanel(
      numericInput("numericInput_data",
                   "esaの記事IDを入力",
                   min = 1,
                   value = 1),
      actionButton("render_md", "Markdownを取得"),
      br(),
      h3("テンプレート用のファイルをアップロードしてください"),
      a(href="reference.docx", target="blank", "サンプルのテンプレートファイル", download="reference.docx"),
      p("上のファイルをダウンロードして、Wordで開き見出し等のフォントを変更してください"),
      fileInput("template", "Choose Template Docx File",
                multiple = FALSE,
                accept = c(".docx")),
      uiOutput("download_files")
    ),
    mainPanel(
      verbatimTextOutput("text")
    )
  )
))

前回との違いとして、ユーザーが作成したオリジナルのテンプレートファイルを入力できるように、fileInput()関数を使っています。
fileInputでファイルを受け取り、その内容を元にPandocでdocxファイルを作成します。

fileInputについては、以下書籍や

RとShinyで作るWebアプリケーション

RとShinyで作るWebアプリケーション

以下記事が参考になると思います。
www.randpy.tokyo

server.R

library(shiny)
library(httr)
library(dplyr)

esa_info <- read.csv("esa_info.csv")
headers <- httr::add_headers(`Authorization` = paste("Bearer", esa_info[1, 1]),
                           `Content-Type`  = "application/json")
team_name <- esa_info[1, 2]

shinyServer(function(input, output, session) {  
  output$text <- renderText({    
    paste(markdown_text())
  })

  markdown_text <- reactive({
    input$render_md
    input_id <- isolate(input$numericInput_data)

    api_result <- GET("http://api.esa.io",
           path = paste("v1/teams", team_name, "posts", input_id, sep = "/"),
           config = headers)
    
    if (api_result %>% status_code() != 200){
        return(paste("API Error:", api_result %>% status_code()))
    }

    contents <- api_result %>% content()
    write(contents$body_md, paste("www/sample_", input_id, ".md", sep=""))

    if (reference_word_file() == ""){
      reference_word_file_path <- "www/reference.docx"
    }
    else {
      reference_word_file_path <- reference_word_file()
    }

    system(paste("pandoc -f markdown -t docx www/sample_", input_id, ".md -o www/sample_", input_id, ".docx --reference-doc=", reference_word_file_path, sep=""))
    system(paste("pandoc -t latex www/sample_", input_id, ".docx -o www/sample_", input_id, ".pdf  --pdf-engine=xelatex -V documentclass=bxjsarticle -V classoption=pandoc", sep=""))
    
    return(contents$body_md)
  })

  reference_word_file <- reactive({
    inFile <- input$template
    
    if (is.null(inFile)){
      return("")
    }

    return(inFile$datapath)
  })

  output$download_files <- renderUI({
    input_id <- input$numericInput_data
    tagList(
      h3(tags$a(href=paste("sample_", input_id, ".docx", sep=""), target="blank", "Download Word", download=paste("sample_", input_id, ".docx", sep=""))),
      h3(tags$a(href=paste("sample_", input_id, ".pdf", sep=""), target="blank", "Download PDF", download=paste("sample_", input_id, ".pdf", sep=""))),
    )
  })
})


前回との違いとして、ui.Rからテンプレートファイルを受け取れるように、以下の処理を追加しています。

reference_word_file <- reactive({
...
})

また、pandocのコマンドに、「--reference-doc=」オプションを付けています。
これをすることで、ユーザーが用意したテンプレートファイルが適用されます。

コンテナ起動方法

コンテナの起動方法は前回と同様で、以下を実行するだけです。

$ docker-compose up -d

ブラウザで、【localhost:3838/esa-word-template】、もしくは 【今起動しているパソコンのIPアドレス:3838/esa-word-template】にアクセス(例 192.168.1.100:3838/esa-word-template)してください。

オリジナルのテンプレートファイルを用意し、生成されるWordファイルが変化することを確認してください。

まとめ

ということで、前回に引き続き、esaの記事をAPI経由で取得して、WordやPDFファイルを生成するツールを作ってみました。
すべてDocker内に完結されているので、導入しやすいのではと思います。

ニーズとして狭いかもしれませんが、割とこのようなツールがずっと欲しかったという人も多いのではと思います。
もし、これをベースに何か別のツールを作ってみたなどあったら、是非教えてください。