2023/12/20

Outlook のメールを体裁よくノーツに貼り付け

ノーツを利用しつつもメールは Outlook を使用している環境はたくさんありますよね。私がお世話になっているお客様でもメールが Outlook に移行しました。とはいっても、現時点ではノーツアプリは残っており、作業報告や問い合わせの対応履歴などはノーツで書いています。

メールがノーツで届いている環境であれば、メールをリッチテキストに転載することは簡単でした。ところが、Outlook からメールをコピペすると改行が吹っ飛んでしまう現象にみまわれ、レポートにならなくなってしまいました。原因は、Outlook 内の改行とノーツリッチテキスト内の改行が一致しないためだと思われます。

そこで、ノーツ - ノーツほどではないにしろ、Outlook のメールをそれなりの体裁を維持しつつリッチテキストに貼り付ける方法を教えてもらいました。私自身の備忘録を兼ねて整理しておきます。

手順は次の通りです。

  1. Outlook でメールを開き、[転送] ボタンを押す
  2. 閲覧ウィンドウの場合は [ポップアウト] ボタンを押す
  3. 『検索と置換』ウィンドウを開く(Ctrl+H)
  4. [あいまい検索] のチェックを外す( [特殊文字] のボタンがアクティブになる)
  5. [検索する文字列] に「任意指定の行区切り」を指定
  6. [置換後の文字列] に「段落記号」を指定
  7. [すべて置換] をクリック(本文内の”↓”が改行マークに代わる)
  8. 本文全体を選択して、コピー(CTRL+C)
  9. ノーツのリッチテキストに貼り付け
  10. ホップアウトウィンドウを閉じる(下書き保存しない) 


手順で操作する Outlook の『検索と置換』ウィンドウと操作する箇所は次の通りです。

また、特殊文字の選択肢はたくさんありますが、今回の操作で使用するのは次の2つです。


ノーツで貼り付けたときのように返答スレッドがセクション形式とはなりません。しかし、メール内の画像にも対応しています。少し手順が多いですが、これぐらいならギリギリ耐えられますね...


2023/12/18

つないでみよう:#7)お天気アイコンの表示

Web 系アプリ開発のど素人が、チャレンジする WebAPI 連携日記の第 7 回です。

気象庁の天気予報取得シリーズの最終回です。前回まででお天気アイコンの準備ができたので、いよいよ天気マークを表示します。完成すると、次のように天気予報が表示できます。


天気コードの取得

第 4 回 で気象庁の Web サイトから天気予報取得のサンプルコードを紹介しました。ただ、シンプルなコードにするため、天気の名称だけを取得していました。まずは、天気コードを取得するように修正します。

修正する個所は次の通りです。修正箇所のみを抽出して赤字で表示します。

Sub Initialize
            ・・・
   Dim vCD As Variant
            ・・・
   '大阪の天気予報を取得
   sAreaCD = "270000"
   Call xGetForecast(sAreaCD, vCD, vWeather)
End Sub

Function xGetForecast(ByVal vsAreaCD As String, rvCD As Variant, rvWeather As Variant)
            ・・・
   '明後日までの天気予報
   Set je3 = jnav.GetFirstElement()
   Call xGetForecast_3Days(je3, rvCD, rvWeather)
End Function

"weatherCodes" というオブジェクト内に天気コードがありますので、それを取得して新設した変数にセットします。

Function xGetForecast_3Days(vje3Days As NotesJSONElement, rvCD As Variant, rvWeather As Variant)
            ・・・
      If jeTmp.Type = Jsonelem_type_array Then
         '配列だが1つしか要素がない
         Set jaTmp = jeTmp.Value
         Set jeTmp = jaTmp.GetFirstElement()
         Set jobjTmp = jeTmp.Value

         '天気 CD
         Set jeTmp = jobjTmp.GetElementByName("weatherCodes")
         rvCD = xGetArray_String(jeTmp)


         '天気
         Set jeTmp = jobjTmp.GetElementByName("weathers")
         rvWeather = xGetArray_String(jeTmp)
      End If
   End If
End Function


天気予報の表示

続いて天気予報を表示する機能をエージェントに追加します。

まず、表示する関数は次の通りです。新規文書を作成し、天気コードと天気名称をセットして、文書を表示するだけのシンプルなプログラムです。

Function xShowForecast_3Dyas(vvCD As Variant, vvWeather As Variant)
   Dim ndb As NotesDatabase
   Dim nd As NotesDocument
   Dim nuiw As New NotesUIWorkspace
   Dim i As Integer

   Set ndb = xns.CurrentDatabase
   Set nd = ndb.CreateDocument()

   nd.Form = "fForecast"
   For i = 0 To UBound(vvCD)
      Call nd.ReplaceItemValue("Days3_WeatherCode_" & CStr(i+1), vvCD(i))
      Call nd.ReplaceItemValue("Days3_WeatherText_" & CStr(i+1), vvWeather(i))
   Next

   Call nd.ComputeWithForm(False, False)
   Call nuiw.EditDocument(False, nd)
End Function

天気コードは Days3_WeatherCode_n 、天気の名称は  Days3_WeatherText_n フィールドにセットします。n = 1 が今日、n = 2 が明日、n = 3 が明後日となります。


関数ができたらメインルーチンから関数をコールします。これでエージェントは完成です。

Sub Initialize
            ・・・
   '大阪の天気予報を取得
   sAreaCD = "270000"
   Call xGetForecast(sAreaCD, vCD, vWeather)

   '天気予報を表示
   Call xShowForecast_3Dyas(vCD, vWeather)
End Sub


フォームの作成

続いて、天気予報を表示するフォームを作成します。3列の表を作成し、上記エージェントで作成されるフィールドを各列に配置し、3日分の天気を表示します。値はエージェントでセットするので作成時の計算結果で、式には "" をセットします。

エージェントでセットしていない Days3_WeatherIcon_n がイメージリソース名を保持するフィールドです。計算結果フィールドとして作成し、以下の式をセットします。この式は自身のフィールド名を利用して記述しているので n = 1 ~ 3 のすべてのフィールドで同じ式となります。

xFld := "Days3_WeatherCode_" + @RightBack(@ThisName; "_");
xWCD := @GetField(xFld);
xSVG := @DbLookup("":""; "":""; "vWeather"; xWCD; "WeatherFileName");
@If(@IsError(xSVG);
   "";
   @Left(xSVG; ".") + ".png"
)

イメージリソースのファイル名は、@DbLookup を使用して、天気コードマスタから取得しています。今回は、昼用のアイコンだけを対象としています。


最後に天気アイコンの表示です。表の背景を@式で設定する機能を利用して作成しました。表の内側にカスケードの表を作成し幅や高さを設定し、罫線を 0 にします。

イメージのソースを計算結果に設定し、式にリソース名が入っているフィールド Days3_WeatherIcon_1 を記述します。この設定はフィールド内ではないので、列ごとにそれぞれのフィールド名をしています。


以上で完成です。エージェントを実行すると、その時点の天気を Web API から取得して、フォームに表示します。これを応用すればノーツのポータル画面にも天気予報が表示できますね。


前回 連載:つないでみよう 次回

2023/12/16

つないでみよう:#6)お天気アイコンの取得

Web 系アプリ開発のど素人が、チャレンジする WebAPI 連携日記の第 6 回です。

前回に続いて気象庁の天気予報でお天気マークを表示する作業をすすめます。今回はお天気マークの画像を取得します。


アイコンファイル

天気予報 を開きアイコン画像の URL を確認します。例えば、晴れのアイコンの場合は次のようになっており、ファイル名がわかります。

https://www.jma.go.jp/bosai/forecast/img/100.svg

このファイル名は、前回 JSON より取得した天気コードマスタ内にありましたね。これら情報を利用して画像ファイルをダウンロードするプログラムを作成しましょう。


ところで、ファイル名には2種類あり、昼と夜のアイコンとなっています。例えば、411 の「雪後晴」の場合は、411.svg と 811.svg となっており、アイコンは次の通りとなっています。


アイコンファイルのダウンロード

ダウンロードしたアイコンファイルは、天気コードマスタ内に添付することとし、次のように添付用のリッチテキストを用意します。


HTTP でファイルをダウンロードするので、以前作成した関数を使用します。ファイルサイズが万一 64 KB を越えても動作するよう、LotusScript の NotesHTTPRequest クラスを使用しない方法を使います。詳しくは以下を参照してください。

HTTP でファイルをダウンロード(いにしえの呪文)


また、ダウンロードしたファイルを一時的に保存するため、Windows のテンポラリフォルダを利用します。フォルダ名の取得には、先日公開した関数を使用します。

Windows のテンポラリフォルダの取得


サンプルプログラム

天気コードマスタのアイコンを取得するためのエージェントを新規作成します。

まずはメインルーチンです。すべて天気コードマスタを取得するためのビュー  vWeather に接続し、1件ずつ順に取得して処理しています。1マスタにつき2つのアイコンを取得するので、サブ関数 xSetIconFile で処理させています。

Option Declare
Use "lsWindows"
Use "lsHttpDownload"

Sub Initialize
   Dim ns As New NotesSession
   Dim nuiw As New NotesUIWorkspace
   Dim nd As NotesDocument
   Dim ndb As NotesDatabase
   Dim nv As NotesView
   Dim i As Integer

   Set ndb = ns.CurrentDatabase
   Set nv = ndb.GetView("vWeather") '天気コードマスタ取得ビュー
   nv.AutoUpdate = False

   Set nd = nv.GetFirstDocument()
   While Not (nd Is Nothing)
      '昼のアイコン
      Call xSetIconFile(nd, "WeatherIcon_SVG", nd.WeatherFileName(0))
      '夜のアイコン
      Call xSetIconFile(nd, "WeatherIcon2_SVG", nd.WeatherFileName2(0))

      Call nd.Save(True, False)
      i = i + 1

      Set nd = nv.GetNextDocument(nd)
   Wend
   MsgBox CStr(i) & " マスタのファイルを取得しました。"

   'ビュー更新
   Call nuiw.ViewRefresh()
End Sub


アイコンファイルをマスタ文書に添付する処理は次の通りです。Use したライブラリ内の関数を使用して、ダウンロードしたファイルをテンポラリフォルダに保存しています。添付が完了したらファイルは削除しています。

Function xSetIconFile(vnd As NotesDocument, ByVal vsFld As String, ByVal vsFN As String)
   Dim sURL As String
   Dim sFP As String
   Dim nrti As NotesRichTextItem

   'アイコンのURL生成
   sURL = "https://www.jma.go.jp/bosai/forecast/img/" & vsFN

   'ファイルダウンロード
   sFP = GetWinTmpPath() & vsFN
   Call HttpDownload(sURL, sFP)

   'フィールドに添付
   Call vnd.RemoveItem(vsFld)
   Set nrti = vnd.CreateRichTextItem(vsFld)
   Call nrti.EmbedObject(EMBED_ATTACHMENT, "", sFP)

   'ダウンロードしたファイルは削除
   Kill sFP
End Function


実行結果

実行すると次のように各マスタ文書にアイコンファイルが添付されます。


アイコンをイメージリソースに登録

天気コードマスタは天気予報の種類を詳細に分類していますが、アイコン複数の天気コードで共通となっています。例えば、昼用にファイル名でカテゴライズしたビューを作成してみると複数の天気コードマスタに同じファイル名が割り当てられていることがわかります。

これで使用されているアイコンの種類がわかりますので、それぞれをイメージリソースに登録します。ノーツは現時点で SVG ファイルに対応していないので、インターネット上の変換サイトなどを利用して、PNG 形式に変換してからリソースに保存します。

本当であればこの処理も自動で行いたかったのですが、うまく変換できなかったので現時点では手作業としています。よい方法が見つかれば別途記事にしたいと思います。


前回 連載:つないでみよう 次回

2023/12/14

つないでみよう:#5)天気コードマスタの作成

Web 系アプリ開発のど素人が、チャレンジする WebAPI 連携日記の第 5 回です。

前回は気象庁の Web サイトから天気予報を取得するサンプルを紹介しました。ただ、テキストで表示するだけだと面白くないですよね。そこでお天気マークの表示にチャレンジしたいと思います。

利用するのは、天気コードです。


天気コードの定義

上記 JSON では 天気コードと天気のテキストがセットで提供されています。ですが、この天気コードは気象庁の Web サイトではマスタ化されているようです。

マスタデータをまるっと取得する URL は発見できなかったのですが、以下の Web ページに紹介されている手順で、JSON を取得できるようです。

気象庁JSON ファイルにある weatherCode 一覧

手順としては次の通りです。

  1. 天気予報の Web ページ を Chrome で開く
  2. デバッグツールを開く(F12)
  3. コンソールを開き ”Forecast.Const.TELOPS” と入力して、Enter を押す
  4. 出力された結果を右クリックして [object をコピー] を選択

これをメモ帳などに貼り付けると天気コードが JSON 形式で確認できます。この情報から天気コードと名称(日英)、アイコンの画像ファイル名(.svg)が確認できます。

{
   "100": [
      "100.svg",
      "500.svg",
      "100",
      "晴",
      "CLEAR"
   ],
   "101": [
      "101.svg",
      "501.svg",
      "100",
      "晴時々曇",
    ・・・


天気コードマスタの作成

では、この JSON から nsf 内に天気マスタを作成しましょう。

まずは、JSON を入力する画面を作成します。手順をすぐに忘れちゃうので、取得手順を含めて画面を作成してみました。

実行すると以下のように手順や URL リンク、Console に入力するコマンドが表示されます。これを利用して JSON を取得して、入力欄に貼りつけるという算段です。

作成処理が完了すると天気コードマスタが作成されます。

参考までですが、マスタフォームの設計は次のような感じです。


サンプルコード

実際にマスタを一括登録するためのエージェントを作成します。

まずはメインルーチンです。ここでは、初期設定とダイアログボックスを表示して、入力した JSON を取得、天気マスタを作成する関数 xCreateAllWeather をコールしています。

Option Declare
Private xns As NotesSession
Private xasFld(5) As String

Sub Initialize
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument
   Dim ndb As NotesDatabase
   Dim nv As NotesView

   Dim sJSON As String
   Dim iCount As Integer

   'フィールド名の定義
   xasFld(0) = "WeatherCode"
   xasFld(1) = "WeatherFileName"
   xasFld(2) = "WeatherFileName2"
   xasFld(3) = "WeatherGroup"
   xasFld(4) = "WeatherName"
   xasFld(5) = "WeatherNameEN"

   Set xns = New NotesSession
   Set ndb = xns.CurrentDatabase
   Set nuid = nuiw.CurrentDocument
   Set nd = ndb.CreateDocument()

   'JSON の入力
   If nuiw.Dialogbox("dlgInputWeatherMst", True, True, False, False, False, False, "天気マスタ登録", nd, True) Then
      'JSON の取得
      sJSON = nd.JSON(0)

      'JSON を解析してマスタを作成
      iCount = xCreateAllWeather(ndb, sJSON)

      'ビュー更新
      Call nuiw.ViewRefresh()

      MsgBox CStr(iCount) & " 件の天気マスタを登録しました。"
   End If
End Sub


天気マスタを作成する関数 xCreateAllWeather では、JSON から天気コード1件分のオブジェクトを取得して、マスタ文書を作成する関数 xCreateWeather をコールしています。

Function xCreateAllWeather(vndb As NotesDatabase, ByVal vsJSON As String) As Integer
   Dim jnav As NotesJSONNavigator
   Dim jelm As NotesJSONElement
   Dim i As Integer

   '天気コードを1件ごとに取得
   Set jnav = xns.CreateJSONNavigator(vsJSON)
   Set jelm = jnav.GetFirstElement()
   While Not (jelm Is Nothing)
      If xCreateWeather(vndb, jelm) Then i = i + 1
      Set jelm = jnav.GetNextElement()
   Wend

   xCreateAllWeather = i
End Function


xCreateWeather 関数では、実際にマスタ文書を作成しています。今回の JSON の構造では、天気名称などの詳細な情報は配列として保存されていました。そこで、ループを利用してセットする単純な構造となっています。

Function xCreateWeather(vndb As NotesDatabase, vjelm As NotesJSONElement) As Boolean
   Dim jelm As NotesJSONElement
   Dim jarr As NotesJSONArray
   Dim jobj As NotesJSONObject
   Dim nd As NotesDocument
   Dim i As Integer

   xCreateWeather = False
   If vjelm.Type <> Jsonelem_type_array Then Exit Function

   'マスタ文書作成
   Set nd = vndb.CreateDocument()
   nd.Form = "fWeather"

   'コードをフィールドにセット
   Call nd.ReplaceItemValue(xasFld(0), vjelm.Name)

   '配列内の各要素をフィールドにセット
   Set jarr = vjelm.Value
   For i = 1 To jarr.Size
      Set jelm = jarr.GetNthElement(i)
      Call nd.ReplaceItemValue(xasFld(i), jelm.Value)
   Next

   xCreateWeather = nd.Save(True, False)
End Function


前回 連載:つないでみよう 次回

2023/12/12

DXL や JSON の日付値の変換

連載:DXL Step-by-Step では DXL(Domino XML Language)、連載:つないでみよう では JSON が頻繁に登場します。DXL(XML)も JSON もデータ構造を表す言語で、それぞれ標準化されていますが、日付/時刻値については共通の仕様になっています。

以下の画像は DXL と JSON 内の日付/時刻値の例です。


DXL 20230913T110634,71+09
気象庁 JSON 2023-12-01T11:00:00+09:00

この表記は ISO 8601 として標準化されています。この仕様について整理しつつ、変換プログラムを LotusScript で作成してみましょう。


ISO 8601 の仕様

ISO 8601 は、日付と時刻、タイムゾーンを文字列で表す仕様です。基本的には、日付を年 4 桁、月と日を 2 桁で表し、時刻の時分秒をそれぞれ 2 桁で表します。日時を表す各要素は、大から小で固定されており、月日年などの順序は認められていません。

表記方法として、基本形式と拡張形式が定義されています。基本形式は各要素を区切り文字なしで、拡張形式が区切り文字ありの書式と覚えればよいと思います。上記事例では、DXL が基本形式、JSON が拡張形式となります。


◇ 日付値

日付値は年月日の順で、年 4 桁、月と日を 2 桁で表します。

基本形式では 19970713 と 8 桁の連続した数値で表します。拡張形式は 1999-07-13 となり、区切り文字に "-" を使用します。"/" など他の区切り文字は認められていません。

なお、仕様としては、日付を省略し年月を表現したり、その年の何日目というような特殊な表現も定義されています。


◇ 時刻値

時刻値は、時分秒をそれぞれ 2 桁で表します。基本形式では T060328、拡張形式は T06:03:28 のようになります。区切り文字は ":" を使用します。

時刻の前には必ず "T" の文字が付加されます。また、時刻は 24 時制のみサポートしており、12 時制の定義はありません。

時刻値は、少数で表現できる仕様となっています(拡張形式の場合は最後の桁のみ)。T060328.50 のようにミリ秒を表すことができます。また、T06:03.12 で秒以下を少数表記したり、T06.123 で分以下を少数表記できるなど特殊な表記も定義されています。

なお、小数点は日本で使用されている "."(ピリオド)以外に ","(カンマ)も利用できます。上記 DXL の例ではカンマになっていますね。


◇ タイムゾーン

時刻の後ろに "Z" を指定すると、協定世界時(UTC)を表します。UTC より進んでいる時間帯の場合は "+"、遅れている場合は "-" を付加し、時差を 2 桁、もしくは 4 桁の数値で記述します。

上位2桁が時間を表します。4 桁の指定は一部地域で採用されている分単位の時差で必要となります。拡張形式での時間と分の区切り文字は ":" を使用します。


サンプルプログラム

ISO 8601 の日付/時刻値(文字列)をノーツで使用できる日付/時刻値(Variant 型)に変換する関数を作成してみました。

機能的には次の通りです。

  • 日付のみ、時刻のみ、日付/時刻値に対応
  • 標準形式と拡張形式に対応
  • 時刻はノーツのタイムゾーンに合わせて時差を換算

また、機能的に省略した点は次の通りです。

  • 日付の省略や時刻の少数表記などの特殊な指定
  • ミリ秒の計算(Variant 型の日付/時刻値で取り扱えないため)
  • 分単位の時差(NotesDateTime の TimeZone プロパティがサポートしていないため)

引数に ISO 8601 の日付/時刻値(文字列)を指定し、戻り値が日付/時刻値(Variant 型)の関数となっています。

Public Function ISO8601ToDateTime(ByVal vsString As String) As Variant
   Dim vTmp As Variant
   Dim vReturn As Variant

   If InStr(vsString, "T") = 0 Then
      '日付のみ
      vReturn = xISO8601ToDate(vsString)
   Else
      '時刻を含む
      If Left(vsString, 1) = "T" Then
         '時刻のみ
         vReturn = xISO8601ToTime(Replace(vsString, "T", ""))

         'オバーフロー対応
         If vReturn < 0 Then
            '時差計算で前日(負の値)になった場合、24時間を加算
            vReturn = vReturn + 1
         ElseIf vReturn >= 1 Then
            '時差計算で翌日になった場合、24時間を減算
            vReturn = vReturn - 1
         End If
      Else
         '日付/時刻値
         '時刻部/日付部を準備

         vTmp = Split(vsString, "T")
         vReturn = xISO8601ToDate(vTmp(0))
         vReturn = vReturn + xISO8601ToTime(vTmp(1))
      End If
   End If

   ISO8601ToDateTime = vReturn
End Function

ISO 8601 の文字列が時刻のみ指定されている場合、時差の計算を行った結果、前日になったり、翌日になったりオーバーフローする場合の調整を行っています。時刻のみを扱う事例に出くわしたことがないため、このインプリが正しいのかは判断できていません。予めご了承ください。


日付部、時刻部の変換はそれぞれサブ関数化しています。まず、日付値に変換する関数は次の通りです。

Function xISO8601ToDate(ByVal vsDate As String) As Variant
   Dim sTmp As String
   Dim iY As Integer
   Dim iM As Integer
   Dim iD As Integer

   '区切り文字を消去
   sTmp = Replace(vsDate, "-", "")

   '日付値に変換
   iY = CInt(Left(sTmp,4))
   iM = CInt(Mid(sTmp, 5, 2))
   iD = CInt(Right(sTmp, 2))
   xISO8601ToDate = DateNumber(iY, iM, iD)
End Function

続いて、時刻部の変換関数です。

Function xISO8601ToTime(ByVal vsTime As String) As Variant
   Dim sTime As String
   Dim vTmp As Variant
   Dim iH As Integer
   Dim iM As Integer
   Dim iSec As Integer

   Dim iTZ As Integer
   Dim bTZ As Boolean

   bTZ = True
   If InStr(vsTime, "Z") > 0 Then
      'UTC
      iTZ = 0
      sTime = Replace(vsTime, "Z", "")
   ElseIf InStr(vsTime, "+") > 0 Then
      'UTC より進んでいる
      vTmp = Split(vsTime, "+")
      iTZ = CInt(Left(vTmp(1), 2))
      sTime = vTmp(0)
   ElseIf InStr(vsTime, "-") > 0 Then
      'UTC より遅れている
      vTmp = Split(vsTime, "-")
      iTZ = - CInt(Left(vTmp(1), 2))
      sTime = vTmp(0)
   Else
      'タイムゾーン指定なし
      sTime = vsTime
      bTZ = False
   End If

   '時差計算
   If bTZ = True Then
      Dim ndt As New NotesDateTime(Now)
      iTZ = -(iTZ + ndt.TimeZone)
   End If

   '少数を削除
   vTmp = Split(sTime, ",")
   sTime = vTmp(0)
   vTmp = Split(sTime, ".")
   sTime = vTmp(0)

   '区切り文字を削除
   sTime = Replace(sTime, ":", "")

   '時刻値に変換
   iH = CInt(Left(sTime,2)) + iTZ
   iM = CInt(Mid(sTime, 3, 2))
   iSec = CInt(Right(sTime, 2))
   xISO8601ToTime = TimeNumber(iH, iM, iSec)
End Function

なお、日本の時差は UTC より 9 時間早いですが、LotusSript の TimeZone プロパティは -9、 ISO 8601 では +09 となり、± が逆転している点に注意が必要です。


2023/12/10

Windows のテンポラリフォルダの取得

LotusScript でプログラムを作成していると PC 内にファイルを一時的に保存したくなることがあります。ノーツ的にはデータディレクトリ内に利用しやすいのですが、私はずぼらなので、ついつい削除し忘れて他のファイルと混ざり収拾がつかなくなりがちです。

こうならないよう一時的なファイルは、Windows のテンポラリフォルダに保存するべきだと思います。そこで、LotusScript からそのフォルダを取得する方法を紹介します。

さまざまな方法があるのですが、今回はいにしえの呪文(?)である Windows API を利用して実現します。


利用する API

Kernel32.dll の GetTempPathA という関数を利用します。Microsoft Learn によると次の順序で確認し、見つかった最初のパスを返します。詳細は、GetTempPathA 関数 (fileapi.h) を参照ください。

  1. TMP 環境変数で指定されたパス
  2. TEMP 環境変数で指定されたパス
  3. USERPROFILE 環境変数で指定されたパス
  4. Windows ディレクトリ


サンプルプログラム

テンポラリフォルダの取得はさまざまなシーンでの利用が想定できるので、スクリプトライブラリ lsWindows を新規作成します。将来、Windows に関係する関数を作成することがあれば、ここに追加したいと思います。

Option Declare
Declare Function xGetTempPath Lib "kernel32.dll" Alias "GetTempPathA" (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long

Public Function GetWinTmpPath() As String
   Dim sPath As String
   Dim lStrLen As Long

   sPath = Space(255)
   lStrLen = xGetTempPath(255, sPath)

   GetWinTmpPath = Left(sPath, lStrLen)
End Function


作成した関数は、GetWinTmpPath() 関数で、戻り値は、テンポラリフォルダのパスです。結果をメッセージボックスで表示すると、次の通り最後に "\" が付加されています。


2023/12/08

つないでみよう:#4)天気予報取得のサンプルコード

Web 系アプリ開発のど素人がチャレンジする WebAPI 連携日記の第 4 回です。

前回に引き続き、気象庁の天気予報に関してです。前回は、Web サイトの仕組みやデータの取得方法、構造について整理しました。今回はその情報を使用して、ノーツで天気予報を取得するサンプルを紹介します。


サンプルプログラム

指定したエリアの天気予報を取得し、メッセージボックスで表示するだけの単純なエージェントです。メインルーチンは次の通りです。

Option Declare
Private xns As NotesSession

Sub Initialize
   Dim sAreaCD As String
   Dim vWeather As Variant

   Set xns = New NotesSession

   '大阪の天気予報を取得
   sAreaCD = "270000"
   Call xGetForecast(sAreaCD, vWeather)


   MsgBox Join(vWeather, Chr(10)), 64, "大阪の天気予報"
End Sub

エリアコードを指定して天気予報を取得する関数をコールします。戻り値の vWeather は文字列型の配列となる前提で、それをメッセージボックスで改行区切りで表示します。


天気予報の取得

天気予報を取得する関数は次の通りです。

引数で指定したエリアコードを使用してコールする URL を生成してから HTTP リクエストを実行しています。今回の API では、メソッドは Get となります。また、レスポンスは JSON を前提としています。

Function xGetForecast(ByVal vsAreaCD As String, rvWeather As Variant)
   Dim sURL As String
   Dim http As NotesHTTPRequest
   Dim jnav As NotesJSONNavigator
   Dim je3 As NotesJSONElement

   '天気予報の JSON を取得
   sURL = "https://www.jma.go.jp/bosai/forecast/data/forecast/" & vsAreaCD & ".json"
   Set http = xns.CreateHttpRequest()
   http.PreferJSONNavigator = True

   Set jnav = http.Get(sURL)

   '明後日までの天気予報の取得
   Set je3 = jnav.GetFirstElement()
   Call xGetForecast_3Days(je3, rvWeather)

End Function

返される JSON には、2つのオブジェクトがあり、明後日までの天気予報と週間天気予報となっていました。今回は、明後日までの天気予報を取得するので、1つ目のオブジェクト(エレメント)を取得しています。

実際に天気予報を取得するプログラムは、サブ関数 xGetForecast_3Days で行います。


明後日までの天気予報の取得

この部分は JSON の構造が少し複雑なので、取得したい天気予報(weathers)までの構造を改めて掲載します。

取得までの流れとしては、まず timeSeries を取得して、次に areas を取得、その中に目的の weathers が存在します。それぞれが配列の構造となっているので、段階を踏んで取得する必要があります。

具体的なプログラムは下記の通りですが、各段階の処理は理解しやすいようにコードをそろえるようにしました。流れとしては、

  1. エレメントを名称(キー)で検索し、NotesJSONElement オブジェクトとして取得
  2. 配列であるか確認
  3. 1つ目の要素を取得し、NotesJSONObject オブジェクトとして取得

としています。

Function xGetForecast_3Days(vje3Days As NotesJSONElement, rvWeather As Variant)
   Dim jeTmp As NotesJSONElement
   Dim jobjTmp As NotesJSONObject
   Dim jaTmp As NotesJSONArray

   Set jobjTmp = vje3Days.Value
   Set jeTmp = jobjTmp.GetElementByName("timeSeries")
   If jeTmp.Type = Jsonelem_type_array Then

      '配列の1つ目が天気予報
      Set jaTmp = jeTmp.Value
      Set jeTmp = jaTmp.GetFirstElement()
      Set jobjTmp = jeTmp.Value


      'エリア(areas)を取得
      Set jeTmp = jobjTmp.GetElementByName("areas")
      If jeTmp.Type = Jsonelem_type_array Then

         '配列だが1つしか要素がない
         Set jaTmp = jeTmp.Value
         Set jeTmp = jaTmp.GetFirstElement()
         Set jobjTmp = jeTmp.Value


         '天気
         Set jeTmp = jobjTmp.GetElementByName("weathers")
         rvWeather = xGetArray_String(jeTmp)
      End If
   End If
End Function

目的のオブジェクト weathers を取得した後は、その中身を配列に変換する関数を xGetArray_String をコールしています。

Function xGetArray_String(vje As NotesJSONElement) As Variant
   Dim jaVal As NotesJSONArray
   Dim jeVal As NotesJSONElement
   Dim i As Integer
   Dim vTmp As Variant

   If vje.Type = Jsonelem_type_array Then
      Set jaVal = vje.Value
      ReDim vTmp(jaVal.Size-1)
      For i = 1 To jaVal.Size
         Set jeVal = jaVal.Getnthelement(i)
         vTmp(i-1) = CStr(jeVal.Value)
      Next
   End If

   xGetArray_String = vTmp
End Function


まとめ

実行すると次のように、取得した天気予報を表示します。

サンプルがシンプルになるよう、天気予報をテキストで取得しているだけですが、このサンプルを応用すれば、降水確率や週間天気予報なども取得できるようになりますね。


今回は、気象庁の Web サイトから天気予報を取得するサンプルを紹介しました。

プログラム的には、

  1. HTTP リクエストを送信
  2. レスポンスが JSON となっている
  3. JSON を解析し、データを利用する

と、一般的な WebAPI と同じでしたね。このような背景から『つないでみよう』の連載で取り上げました。


前回 連載:つないでみよう 次回

2023/12/06

つないでみよう:#3)気象庁の天気予報

Web 系アプリ開発のど素人が、チャレンジする WebAPI 連携日記の第 3 回です。今回からは、気象庁の天気予報を題材にします。


ノーツで作るポータル画面に天気予報を表示できないかとネットを調べていると、気象庁の Web サイトから取得できることがわかりました。

今回はその調査結果をレポートします。


天気予報データの取得

次のリンクをクリックすると気象庁の天気予報の Web ページが確認できます。

気象庁 天気予報(大阪)


このサイトから天気予報データを抽出できる構造になっているようです。次の URL をクリックすると、天気予報のデータがテキストデータ(JSON 形式)で表示されます。これをノーツで利用しようということですね。

https://www.jma.go.jp/bosai/forecast/data/forecast/270000.json


また、天気予報以外では、明後日までと1週間の天気概況がテキストデータで取得できます。

https://www.jma.go.jp/bosai/forecast/data/overview_forecast/270000.json

https://www.jma.go.jp/bosai/forecast/data/overview_week/270000.json


なお、これら URL はリクエストした時点での最新情報が返される仕様になっており、固定された URL でいつでもデータ取得が可能です。


JSON の確認

それでは、天気予報の JSON を確認しましょう。表示されたテキストを VS code で開き、JSON として成形して構造を確認します。

まず大枠として、2つのオブジェクトがあり、明後日までの天気予報と週間天気予報があります。

明後日までの天気予報はさらに3分割されていて、天気、降水確率と気温の予報が入っています。

天気予報のオブジェクトは、"timeDefines" に予報日時が配列で入っており、各予報が同じ要素数の配列で記録されています。

なお、この例では翌日の予報までしか含まれていませんが、JSON を取得する時刻によっては明後日を含む場合があります。コーディングする際には、このあたりを柔軟に行う必要があそうですね。


降水確率のオブジェクトも同様の構造になっていますが、"timeDefines" が天気予報とは合致していません。降水確率は 6 時間ごとの予報が提供されるようです。


気温予想に関しては、日ごとの最低/最高気温が取得できます。ただ、当日の最低気温は通常不要です。Web ページでも以下のように省略されています。

JSON では、以下のようにデータが存在しているのですが、最高気温と同じ数値が入っていて、配列の並び順が逆転しています。このあたりで省略する条件を表していると想定しているのですが、不明瞭ですね。仕様がわからないので判断が難しいところです。


週間天気予報についても同様の構造になっていて、7 日分の天気予報だけでなく、降水量や平均気温まで、Web ページに表示されている情報が含まれています。


エリアコード

URL の最後の数字部分が取得するエリアのコードです。例えば、東京の情報を取得するなら 130000.json となります。エリアの定義は、以下の URL の JSON で確認できます。

https://www.jma.go.jp/bosai/common/const/area.json

天気予報の上記3 URL では、”offices” に定義されているエリアコードが利用できるようです。


まとめ

今回は、気象庁の Web サイトから天気予報を取得する方法についてまとめました。

ただ、気象庁として正式な API サービスではないとのことなので、仕様変更や最悪提供中止があるかもしれません。とはいっても、特定の URL にアクセスすると JSON 形式で天気予報や概況が取得できる仕様になっていますので、インターフェース的には WebAPI といっても問題ないと思います。

正式なサービスではないため、仕様が公開されていません。手探りの部分があり、不明瞭な点は存在はしますが、必要十分な情報は取得できそうです。とはいっても、公的は Web サイトから取得できるのはありがたいですね。

なお、以下のリンクに利用規約などが記載されております。実用前にはご確認ください。

気象庁ホームページについて


前回 連載:つないでみよう 次回

2023/12/04

NRPC ポートの接続確認

Domino サーバと Notes クライアント、Domino サーバ間の通信では、一般的に NRPC(Notes Remote Procedure Call)を使用します。NRPC は通常 1352 のポートを使用します。

追加のサーバやクライアントのセットアップをしていると、Ping は通るのにサーバに接続できない症状に出くわすことがあります。原因は Windows ファイアウォールなどにより NRPC ポートで通信できないことによるのですが、これが把握できないと、ネットワーク的な問題か Notes/Domino の問題か切り分けできません。

そこで今回は、NRPC での疎通確認を Notes を使わずに行う方法を整理します。


Telnet の利用

コマンドプロンプトを起動して以下のコマンドを入力します。[IP address] は接続確認を行うサーバの IP アドレスです。

   telnet [IP address] 1352


接続できない場合は、エラーが表示されます。


接続できた場合は、次のようにウィンドウタイトルが "Telnet" となり真っ黒な画面となります。

この画面が確認できれば、指定したサーバに対して NRPC で接続できることになります。


Telnet の設定

上記の Telnet を使用するには、最近の Windows では追加の設定が必要です。

Windows 10 の場合、コントロールパネルの [プログラムと機能] を開き [Windows の機能の有効化または無効化] をクリックします。

表示された [Windows の機能] ダイアログのリストから『Telnet クライアント』にチェックを入れます。

これでコマンドプロンプトで Telnet コマンドが利用可能となります。


Windows PowerShell の利用

スタートメニューより Windows PowerShell を起動し、次のコマンドを入力します。もちろん、[IP address] は接続確認を行うサーバの IP アドレスです。

   Test-NetConnection -ComputerName [IP address] -Port 1352


接続できない場合は、警告メッセージが表示され、テスト結果の TcpTestSucceeded が False と出力されます。


接続できた場合は、TcpTestSucceeded が True となります。


接続確認時の注意

NRPC のポートは Domino サーバが起動しないと開きません。起動前にテストしても、今回の確認は、すべてエラーとなりますので、ご注意ください。

2023/12/02

命名規則の例(LotusScript)

先日投稿した ビジネスアプリ開発で大切なこと の記事の中で、コーディングルールについて紹介しました。その具体例として、今回は、命名規則についてまとめます。

命名規則は、プログラム内の変数や関数、フォームやビューなどの設計要素など用途は様々ですが、今回は一番わかりやすい LotusScript の変数宣言に絞って記述します。

ほとんどの開発者は、ご自身で何らかのルールに基づき命名されていると思います。学生の頃、すべての変数を某ガン〇ムのモビルスーツ名を変数にしているプログラムに出くわしたことがあり、変数の用途を理解するのに四苦八苦したことを覚えています。しばらくして、数が足りなかったのでしょうね、途中からキャラ名に切り替わっており、ますます混とんとしたコードになっていました...

ビジネスでは、このようなことがないよう、他のメンバーでもガ〇ダムにくわしくない人でもわかりやすい名前を付ける必要があります。

その一例として、私が通常使用している命名規則を紹介します。


命名規則の構成

変数は、値やオブジェクトを一時的に保管しておく器のようなものです。命名するときはその変数の中身が想像できるような名前を付けます。短い名前で用途が明確になるとすっきり、わかりやすいプログラムになります。

私が利用している命名規則では、1つの変数名は4つのパートに分けて考えます。

[接頭語][タグ][基本名][接尾語]

各パートの用途や役割は次の通りとしています。

接頭語 スコープやコーディング上の用途を指定
タグ 変数の型を指定
基本名 変数名の本体
用途や役割を簡潔に表した一つまたは複数の単語で構成
接頭語やタグと区別するため単語の大文字で始める
接尾語 類似する変数が複数必要となるなど、基本名が重複する場合に指定
アンダースコアを使って、基本名と区別するとわかりやすい

ただ、このルールを必須とするとすべての変数が長くなり、コードが見づらくなります。そこで、ループ変数(ex. i, n など)のようにコーディング上短い変数名の方が可読性が上がる場合は例外としています。

また、変数の数が少ない場合は、タグだけでもコードの理解に支障はないので、基本名も必須とはしてません。要は、変数の用途がわかれば OK という比較的 ”ゆるい” ルールとしています。


接頭語

スコープは、その変数が有効な範囲のことです。例えば、スクリプトライブラリ内に宣言された変数が Public の場合、そのライブラリを利用するプログラムからアクセスできます。これは、外部から値が変更されることがあり得ることになり、この変数を利用する場合に注意する必要があります。

関数の引数の場合、値が関数内でセット(または変更)されるのか、呼び出し元から値提供されるだけなのかを明示すると仕様がわかりやすくなります。入出力インターフェースを明確にする効果があり、関数の利用者に仕様を伝えやすくなります。

このように、変数がプログラム上でどのようなふるまいをするのか指定する部分が接頭語となります。


なお、スコープや関数の引数に関しては、上記の説明だけではわかりにくいですよね。別の機会に掘り下げて説明したいと思います。


タグ

タグは変数の型を表します。

例えば、コードを表す変数を定義する場合、sCD だと文字列(string)、iCD だと数値(integer)が入っていると表現できます。Notes Object Class の場合であれば、NotesDocument は nd、NotesDatabase は ndb と決めておけば、その変数の中身(値の種類)が明確になります。

このタグに当たる部分は、実践されている方は多いかと思います。


基本名

変数の用途や役割を表す部分です。先ほどのコードを表す変数 sCD を例にすると、CD が基本名となります。

末尾で紹介する命名規則では、各パートの役割と並び順を規定するにとどめており、基本名の付け方までは規定していません。ただ、この基本名は開発者によりばらけやすく、用途を不鮮明になりやすい部分です。

例えば、”事業所” を表す変数を考えます。変数名を英語でつける場合には、次のような単語が想定できます。

  • office
  • shop
  • location
  • place
  • branch

ただ、その企業にとって ”事業所” という言葉は工場や営業所を含む総称であった場合、そぐわない単語もあります。また、ローマ字で Jigyosho するかもしれませんし、Jigyousyo とする開発者がいるかもしてません。開発メンバーに外注が入るとこのような事象が起こりやすいといえます。

これを避けるため ”事業所” は "JIS" を基本名とすると事前に決めておき共有することで、的外れな命名やブレを抑制できます。社内で共通認識がある単語の場合、用語集のような命名表を事前に用意しておくのもいいかもしれませんね。


接尾語

同様の用途の変数を複数作成する場合、基本名までが同じとなります。

例えば、ドミノディレクトリのユーザ文書をビューから順に取得するプログラムを作っていたとします。処理中の文書が ndUser という変数だったとすると次の文書を保存する変数を定義するなら ndUser_Next、 前の文書を表す変数は  ndUser_Prev とします。この ”_” 以降が接尾語となります。


命名規則のサンプル

このブログを書くにあたり使用している命名規則を別途まとめました。特に重要視している接頭語とタグについて整理しています。

この投稿と重複する部分もありますが、以下のリンクより確認ください。

命名規則:LotusScript (変数)


例えば、先日投稿した『Notes - Excel 連携:#26)罫線の設定 ②』という記事で作成した関数を例に挙げると、変数を見るだけで次のような情報が判別できます。


今後掲載するサンプルコードもこの規則に従い作成しますので参考にしてください。なお、このリンク先の命名規則は、将来改変する可能性があります。場合によっては、この記事と食い違うことになるかもしれません。予めご了承ください。