or1ko's diary

日々を書きます

ConvertTo-*コマンドの動作

ConvertTo-で始まるPowerShellのコマンドはいくつかあるので、動作確認結果。
ConvertTo-Xmlはちょっとだけ期待はずれ。
最初はcsv

> ([PSCustomObject]@{id=1;name="Jhon"},[PSCustomObject]@{id=2;name="Tom"}) | ConvertTo-Csv
#TYPE System.Management.Automation.PSCustomObject
"id","name"
"1","Jhon"
"2","Tom"

次にjson

> ([PSCustomObject]@{id=1;name="Jhon"},[PSCustomObject]@{id=2;name="Tom"}) | ConvertTo-Json
[
    {
        "id":  1,
        "name":  "Jhon"
    },
    {
        "id":  2,
        "name":  "Tom"
    }
]

XML。デフォルトはxmlのオブジェクトになってしまうのでstringで出力。型情報も除去する。
Propertyでなく、idやnameにしたいが、方法は分からなかった。

> ([PSCustomObject]@{id=1;name="Jhon"},[PSCustomObject]@{id=2;name="Tom"}) | ConvertTo-Xml -As string -NoTypeInformation
<?xml version="1.0" encoding="utf-8"?>
<Objects>
  <Object>
    <Property Name="id">1</Property>
    <Property Name="name">Jhon</Property>
  </Object>
  <Object>
    <Property Name="id">2</Property>
    <Property Name="name">Tom</Property>
  </Object>
</Objects>

ちなみにHTMLは下記の通り。

> ([PSCustomObject]@{id=1;name="Jhon"},[PSCustomObject]@{id=2;name="Tom"}) | ConvertTo-Ht
ml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>HTML TABLE</title>
</head><body>
<table>
<colgroup><col/><col/></colgroup>
<tr><th>id</th><th>name</th></tr>
<tr><td>1</td><td>Jhon</td></tr>
<tr><td>2</td><td>Tom</td></tr>
</table>
</body></html>

Xmlを手軽に処理するならPowerShell

xmlのデータを集計など処理したい場合はPowerShellがおすすめ。
[xml]を使えば、解析してスクリプト上で簡単に扱えるようになる。

例えば、下記のような呼び出し方。
xmlファイルのhoge.fooという値にアクセスできる。

[xml]$(Get-Content *.xml -Encoding utf-8 -Raw).hoge.foo 

配列のshuffle

以前、shuffleをPowerShellで実装したが冗長だっため、再び調べてみたら、すごく簡単な方法が見つかった。

配列のshuffle - or1ko's diary

下記のページに記載されている。
Get-Random

どうも -Count パラメータを指定すると、入力をshuffleするようだ。

PS > 1..10 | Get-Random -Count ([int]::MaxValue)
6
7
5
2
4
1
8
10
3
9

ちなみに、shuffleはリストをランダムでいくつか取得した際に使うため、たまに使いたくなる。

Outlook の Web APIを呼び出す例

Outlook の Web APIを呼び出すサンプルコードを書いた。

起動して http://localhost:8080/ にアクセスすると、
OAuth2のためにOutlookに転送され、許可すると、
http://localhost:8080に戻ってきて、
メッセージのタイトル一覧を表示する。

Outlook の Web APIを呼び出す方法は下記に記載あり。OAuth2でアクセストークンを取得し、APIを呼び出す。
Get Started with the Outlook REST APIs - Outlook Dev Center


使用したパッケージ
- base >= 4.7 && < 5
- Spock >= 0.11
- mtl
- text
- hoauth2
- http-types
- http-client
- bytestring
- http-client-tls
- aeson

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Web.Spock
import Web.Spock.Config
import Network.OAuth.OAuth2

import Control.Monad
import Control.Monad.Trans
import Data.IORef
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString as B
import qualified Network.HTTP.Types.URI as URI
import qualified Network.HTTP.Types.Header as Header
import qualified Network.HTTP.Client as HTTPClient
import qualified Network.HTTP.Client.TLS as TLS
import qualified Data.Aeson as Aeson


data MySession = EmptySession
data MyAppState = DummyAppState (IORef Int)

main :: IO ()
main =
    do ref <- newIORef 0
       spockCfg <- defaultSpockCfg EmptySession PCNoDatabase (DummyAppState ref)
       runSpock 8080 (spock spockCfg app)

oauth2Param :: OAuth2
oauth2Param = OAuth2 {
  oauthClientId = "<client_id>",
  oauthClientSecret = "<client_secret>",
  oauthOAuthorizeEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
  oauthAccessTokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token",
  oauthCallback = Just "http://localhost:8080/"
}

scope = "https://outlook.office.com/mail.read"

redirectHotmail = redirect $ TE.decodeUtf8 $ appendQueryParam (authorizationUrl oauth2Param) [("scope", scope)]

getAccessToken code =
    do man <- HTTPClient.newManager TLS.tlsManagerSettings
       at <- fetchAccessToken man oauth2Param $ TE.encodeUtf8 code
       return $ TE.decodeUtf8 $ either BL.toStrict accessToken at

mail = "https://outlook.office.com/api/v2.0/me/messages"

mailparam = [
  ("$select", Just "Subject,From,ReceivedDateTime"),
  ("$top", Just "25"),
  ("$orderby", Just "ReceivedDateTime DESC")]

getMailBox :: T.Text -> IO BL.ByteString
getMailBox accessToken =
    do
      man <- HTTPClient.newManager TLS.tlsManagerSettings
      requestUrl <- HTTPClient.setQueryString mailparam <$> HTTPClient.parseRequest mail
      let request = requestUrl { HTTPClient.method = "GET",
                                 HTTPClient.secure = True,
                                 HTTPClient.requestHeaders = [(Header.hAuthorization, B.append "Bearer " $ TE.encodeUtf8 accessToken)]}
      res <- HTTPClient.httpLbs request man
      return $ HTTPClient.responseBody res

data MailMessage = MailMessage {
  subject :: T.Text
} deriving Show

instance Aeson.FromJSON MailMessage where
    parseJSON (Aeson.Object v) = MailMessage <$> (v Aeson..: "Subject")
    parseJSON _          = mzero

data MailBox = MailBox {
  value :: [MailMessage]
} deriving Show

instance Aeson.FromJSON MailBox where
    parseJSON (Aeson.Object v) = MailBox <$> (v Aeson..: "value")
    parseJSON _          = mzero

getMailMessages code = liftIO (getAccessToken code >>= getMailBox) >>= toHtml
  where
     toHtml body = html (maybe "" (T.unwords . map (T.append "<BR>" . subject) . value) (Aeson.decode body))

app :: SpockM () MySession MyAppState ()
app =
    get root $
        param "code" >>= maybe redirectHotmail getMailMessages

24

言語処理100本ノック 2015

24. ファイル参照の抽出
記事から参照されているメディアファイルをすべて抜き出せ.

メディアファイルというのが何を指しているのかわからなかったが、
ファイル参照の抽出というタイトルから、File:を抜き出すことにした。
中身を見ていると、日本語のファイルでも良さそうだったが、面倒だったためFileのみとした。

import Data.Aeson as A
import Control.Applicative
import System.Environment
import Data.Maybe
import Data.ByteString.Lazy.UTF8 as B
import GHC.Generics
import Text.Regex.Posix
import Prelude as P
import GHC.IO.Encoding

data Article = Article {
  text :: String,
  title :: String
} deriving (Eq, Show, Generic)
instance FromJSON Article

main = do
  setLocaleEncoding utf8
  filename <- head <$> getArgs
  body <- fromString <$> readFile filename
  writeFile "24.out.txt" $ unlines $ map (P.drop 5) $ filter (\x -> P.length x /= 0 ) $ concatMap concat $ map f $ map (text . fromJust . A.decode) $ B.lines $ body 
  where
    f = ((=~ ("File:[^]|]*" :: String)) :: String -> [[String]]) 

[^]|]と[^|]]で異なる解釈となることが勉強になった。