2016年12月1日木曜日

cleanwork -hostmatch は使いにくい

使っていないWORKを削除するcleanworkですが-hostmatchオプションで、少し嵌りました。共有ファイルサーバ上にWORKを配置し、複数サーバから使うことはままあります。I/O性能高いディスクを使ってリソースの一元管理をしたい、Gridの環境ときには良くある話です。

で、cleanworkのオプションに-hostmatch オプションがあってホスト名を指定して、他のサーバが作ったWORKも削除できるのですが、これは使っているWORKも削除する恐れがあります。straceで動きを追うと、PIDの存在チェックをしています。他サーバなので意味のないチェックです。で、あっさりとWORKを削除してしまいます。

ホスト名の指定も仕様が良く分かりません。FQDNで書くと削除されたり、短いホスト名だと削除されなかったり理解できない動きをしています。使用を細かく追うのは時間が惜しいので、-hostmatchは使わず、削除は各ノードから行うのが固い手順だと結論付けます。

2016年11月28日月曜日

Excel Page Setupの修正案

機能追加を検討しています。

  • セルの値が値か数式かによってフォントの色を変更する
  • 中味がない空のシートを削除する
  • 行の高さを調整する
  • シート全体のフォントを設定する
これは最近読んだ本の中に書かれていたことです。実装して良いのか?

setup.sh -consoleでエラー

エラーのメモです。久しぶりにUNIX上で、SAS9.3のインストールを行っています。
デポの作成を、GUI無しでコンソールモードでやろうとしたら、正しく動かなかった。インストールのログをチェックすると、Disk2のメディア交換のところで、エラー終了している。

setup.sh -console

どうにもこうにも正しく動かないかった。結局、Windows7 のPC上でデポを作成、UNIX側に転送してDepotCheckerで検証してエラーを回避しました。いつもの手順が手堅い。下は参考記事です。

http://support.sas.com/kb/38/258.html

2016年11月16日水曜日

ホスト名の最後にドットをつけてはいけません。

SASのネタというよりかは、ホスト名の命名のメモです。
エラーの問い合わせあって、よく見るとホスト名の後ろにドットが付いています。
開発者はPINGが通るから大丈夫ということでしたが、これがエラーの原因でした。
RFC952のルールから外れるので、この命名だと認識されないケースあります。
参考記事はコチラです。

最初にログを見たときに指摘したものの、「大丈夫!」という意見で遠回りしてしまった。

追記します。
何故か、ホスト名の最後のドットがダメってどこに書いてあるのかと質問受けました。RRC952の原文を見ると、ホスト名の定義は以下のようになっています。

 <hname> ::= <name>*["."<name>]
 <name>>  ::= <let>[*[<let-or-digit-or-hyphen>]<let-or-digit>]

これみると、アンダースコアはNGなのね。

2016年11月13日日曜日

Enterprise GuideのMap Chartの例

SAS Enterprise Guideでマップチャートを使った例をメモしておきます。資料を探すと海外のネタがいくつか転がっていますが、今回は埼玉県のマップを描きます。さて、ここで迷ったのが日本の地図のデータをどこから取ってくるかです。東京都のデータなら、NLSのライブラリMAPSの下にあります。東京以外のデータも描きたいということで、別なところから地図のデータを探して取ってきます。地図データの検索キーワードは"地図 シェープファイル”で手繰ります。以下、マップを描く手順です。
  1. 全国市区町村界データをesriジャパンから取ってくる
  2. PROC MAPIMPORTでシェイプを読み込んでデータセットにする
  3. データセットから埼玉県だけを抽出
  4. エリア毎の濃淡をつけるために乱数で変数を作る
  5. マップチャートで描く
出力はこんなイメージです。



最初にesriジャパンのWebサイトを検索して、全国市区町村界データをダウンロードします。ダウンロードしたら、ZIPファイルを展開してc:\tempの下に置きます。

次にコードタスクで、ダウンロードしたシェイプのデータを読み取ります。埼玉県だけの情報に絞って、乱数で適当な変数を作ります。この変数はマップチャートのタスクで集計します。

データセット選択して、タスク>グラフ>マップチャートを選択します。2D塗り分け地図を選びます。因みに、シェープデータだけで「空白の地図を作成」することができます。シェープデータの中味を確認するときに使ってください。


データの役割を下の図のように設定します。ID変数役割で間違った変数を指定すると、思わぬ方向に線が引かれることがあります。そのときには、ID変数を疑ってください。


変数RANDOMを集計するので、「グラフに使用する統計量」に合計を指定します。それと凡例の箇所もチェックしてください。


プロセスフローは以下のようになるはずです。

シェイプファイルですが、様々なフォーマットで色んなところから提供されています。メッシュ上に区切った形状のものや、海岸線、避難地域など国交省のネタがあることを知りました。シェイプファイルについて、私が参照したリンクはコチラです。

2016年10月20日木曜日

Object Spawnerのログが英語で出力される

またレアな事象にぶつかったのでメモしておきます。Object Spawnerのログが何故か英語で出力される問題にぶつかりました。OSのロケールは日本語にしているにも関わらずです。
Object Spawnerの設定に言語やエンコーディングは無かったので、1時間ほど調べながら悩みました。

応えは、Local System Accountの言語が英語(EN)になっていたためでした。構築した環境はSAS9.4、Windows Server 2012で、AWS上に構築しています。土台が英語環境だったのであとから、日本語に切り替えたのですがこれが原因でした。

調べたのはレジストリエディタで、Local System Accountのユーザ環境を手繰って、Control Panel, Languagesのエントリの下を見て言語が英語になっていることを確かめました。また、Objspawn.exeをコマンドプロンプトから直接叩いて、他のアカウントではメッセージが日本語ででることを確かめました。

回避策として、他のローカルアカウントをAdministratorsに加えて、ローカルポリーシーでLocal System Accountが持っているのと同じ権限を付与しました。Local System Account自体の言語の設定を変えることも考えましたが、レジストリをいじるぐらいしか手が思いつかず、危険なのでやめました。

2016年9月28日水曜日

SAS9.4ではASYNCHIO、 SYNCHIOはObsolete(廃止)されています。

SAS9.4でSYNCHIOの設定を確認しました。

 ASYNCHIO, NOASYNCHIOのオプションは指定できるものの、設定値としては表示されず効きません。NOSYNCHIOのオプションは指定できるものの、設定値としてはSYNCHIOで変わりません。

 ASYNCIOがObsoleteというのは、SAS9.2のWhat'sに記述ありましたが、AliasのSYNCHIOについては記述が見つかりませんでした。ばっちり明記して欲しいところです。

2016年9月26日月曜日

EG7.1 で credentials.xml の位置が変わってます。

EGで credentials.xml を使う人向けの情報です。
バッチ実行で、credentials.xml を使うネタは、以下のNote:に記述があります。
が、7.1の情報が書かれていません。

Usage Note 30917: Scheduling projects in SAS® Enterprise Guide®


EG7.1のパスは変更されています。
%APPDATA%\SAS\SharedSettings\7.1\credentials.xml で作成してください。
あと、server nameの値はプロファイルの名前でした。これ一時間ほど悩みました。


2016年9月13日火曜日

Workspace ServerのログのエンコーディングをUTF-8 に変更する。

Intelligent Platform製品を使っていて困ることのひとつが、UTF-8を前提として開発されているので、エンコーディングを従来と同じSJISにすると、SAS Environment Managerでエラーがでたりします。前に書いたPostgreのDBも内部はUTF-8なので、SJISのログを入れると不都合が生じます。

で、悩んで試したところ、SASのエンコーディングはSJIS, ワークスペースサーバのログはUTF-8に変更することができました。logconfig.xml に一行書き加えるだけです。

     param name="Encoding" value="UTF-8"/>

こんな感じです。ただ、ここだけ変更してシステムとして矛盾しないかという検証が未だです。このログをACM, APMで吸い上げる以外に何か落とし穴がないかこれから検証です。


2016年8月24日水曜日

WIP Data Serverのエラーと再構成

トラブル対応のメモ書きです。
WIP(Web Infrastructture Platform) Data Serverが起動後にエラーを起こしていた事象にぶつかりました。sas.servers start だと問題なく起動できているように見えて、裏側ではエラーでていました。手掛かりないなか、同僚がPostgre DBの破損をみつけて復旧できました。

エラーのログは、/SAS/SAS_CONFIG/Lev1/WebInfrastructurePlatformDataServer/Logs/webinfdsvrc-日時.logあたりに出力されていました。

> WARNING:  could not write block 1530 of base/16384/8260301
>DETAIL:  Multiple failures --- write error might be permanent.
>ERROR:  xlog flush request 110/4CA5DF8C is not satisfied --- flushed only to 110/4C98BCE4
>CONTEXT:  writing block 1530 of relation base/16384/8260301

上のエラーメッセージは、「仕掛中トランザクションの物理ログ出力エラーと推測」とのことで、仕掛中トランザクションの物理ログを強制出力する、以下コマンドを実行して復旧しました。


/SAS/SAS_HOME/SASWebInfrastructurePlatformDataServer/9.4/bin/pg_resetxlog f /Lev1/WebInfrastructurePlatformDataServer/data

原因はSAS GridのCompute Nodeでサービスを起動したことらしい。これは私のオペミスだが、そもそもサービス起動/停止が壊れないような実装になっていて欲しい。

助かりました。

2016年8月15日月曜日

SAS University Edition

インストールしてみました、SAS University Edition。手間取ったのはPCのバイオスの設定ぐらいでした。BISOのメニューで、セキュリティの下にある仮想化のオプションをENABLEにするのに迷いました。のんびり使ってみます。


2016年8月7日日曜日

ローカルのサーバで、RPMのスコアリングを行う方法

デスクトップ版のEnterprise MinerとEnterprise GuideのRPMの組合せですが、スコアリングは、Metadata Serverに接続しないとできない仕様になっています。わざわざ筆を持ち換えて、Enterprise Minerに移るのは面倒です。

そんなときは、コードタスクでスコアのデータセット名を書き換えればスコアリングできます。RPMの「前回実行したコード」を表示してコードタスクにコピーして、SCORE=とSCOREOUT=の部分を書き換えます。

%EM_RPM(
 FLOW=INTERMEDIATE, 
 PROJPATH=%NRBQUOTE(C:\Temp\RPM),
 PROJNAME=%NRBQUOTE(RPM),
 TASKID=efb7cb31-d183-4282-81d6-d938435074fd,
 DATA=&em_traindata, 
 VARIABLEDELTA=&em_vardelta, 
 TARGETLEVEL=,
    DECDATA=&em_decdata, 
    PRIOR=NONE, 
    DECISION=INVERSE, 
    EVENT=%NRBQUOTE()
    ,
    SCORE=WORK.TEST,
    SCOREOUT=WORK.SCORED_TEST
    );            

ERROR: トランスコード時に文字データが一部損失しました。

文字のエンコーディングで良くあるエラーですが、改めて問題を定義します。このエラーは、異なるエンコーディングへ文字が変換できなかったときに発生します。

理解を深めるために、具体的にエラーを引き起こすコードを示します。このコードはEUCのテキストファイルをわざとUTF-8で読み取ってデータセット化し、SJISのエンコーディングで保存しています。
data data1(encoding='utf-8');
 length name $16;
 input name;
 cards;
与太郎
助六
正蔵
円楽
八雲
;;
run;

data _null_;
 set data1;
 file 'c:\temp\euc.txt' encoding='jeuc';
 put name;
run;

data data2(encoding='utf-8');
 infile 'c:\temp\euc.txt' encoding='jeuc';
 length name $16;
 input name;
run;

data data3(encoding='utf-8');
 infile 'c:\temp\euc.txt' encoding='utf-8';
 length name $16;
 input name;
run;

data data4(encoding='sjis');
 set data3(encoding='utf-8');
run;

エラーは、SJISのデータセットを出力するときに発生します。
362  data data1(encoding='utf-8');
363      length name $16;
364      input name;
365      cards;

NOTE: データセットWORK.DATA1は5オブザベーション、1変数です。
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.03 秒
      CPU時間            0.01 秒


371  ;;
372  run;
373
374  data _null_;
375      set data1;
376      file 'c:\temp\euc.txt' encoding='jeuc';
377      put name;
378  run;

NOTE: 出力ファイル'c:\temp\euc.txt' :
      ファイル名=c:\temp\euc.txt,
      レコードフォーマット=V,
      論理レコード長=65534,
      ファイルサイズ (バイト)=0,
      更新日時=2016年08月07日 13時28分31秒,
      作成日時=2016年08月07日 13時06分49秒

NOTE: 5レコードを出力ファイル'c:\temp\euc.txt'に書き込みました。
      最小レコード長は6です。
      最大レコード長は9です。
NOTE: データセットWORK.DATA1から5オブザベーションを読み込みました。
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.01 秒
      CPU時間            0.01 秒


379
380  data data2(encoding='utf-8');
381      infile 'c:\temp\euc.txt' encoding='jeuc';
382      length name $16;
383      input name;
384  run;

NOTE: 入力ファイル'c:\temp\euc.txt' :
      ファイル名=c:\temp\euc.txt,
      レコードフォーマット=V,
      論理レコード長=65534,
      ファイルサイズ (バイト)=32,
      更新日時=2016年08月07日 13時28分31秒,
      作成日時=2016年08月07日 13時06分49秒

NOTE: 5レコードを入力ファイル'c:\temp\euc.txt'から読み込みました。
      最小レコード長は6です。
      最大レコード長は9です。
NOTE: データセットWORK.DATA2は5オブザベーション、1変数です。
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.01 秒
      CPU時間            0.01 秒


385
386  data data3(encoding='utf-8');
387      infile 'c:\temp\euc.txt' encoding='utf-8';
388      length name $16;
389      input name;
390  run;

NOTE: 入力ファイル'c:\temp\euc.txt' :
      ファイル名=c:\temp\euc.txt,
      レコードフォーマット=V,
      論理レコード長=131068,
      ファイルサイズ (バイト)=32,
      更新日時=2016年08月07日 13時28分31秒,
      作成日時=2016年08月07日 13時06分49秒

NOTE: 5レコードを入力ファイル'c:\temp\euc.txt'から読み込みました。
      最小レコード長は4です。
      最大レコード長は6です。
NOTE: データセットWORK.DATA3は5オブザベーション、1変数です。
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.03 秒
      CPU時間            0.01 秒


391
392  data data4(encoding='sjis');
393      set data3(encoding='utf-8');
394  run;

NOTE: データファイルWORK.DATA4.DATAは別なホストにネイティブな形式が使用
      されているか、またはエンコーディングがセッションエンコーディ
      ングと一致していません。クロス環境データアクセスが使用される
      ため、追加のCPUリソースが必要となり、パフォーマンスが低下しま
      す。
ERROR: データセットWORK.DATA4のトランスコード時に文字データが一部損失
       しました。新しいエンコーディングで表せない文字がデータに含ま
       れていたか、またはトランスコード時に切り捨てが発生しました。

NOTE: DATAステップは異常終了しました。
NOTE: エラーが発生したため、このステップの処理を中止しました。
NOTE: データセットWORK.DATA3から3オブザベーションを読み込みました。
WARNING: データセットWORK.DATA4は未完成です。このステップは、
         2オブザベーション、1変数で停止しました。
WARNING: このステップを中止したため、データセットWORK.DATA4を置き換えて
         いません。
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.03 秒
      CPU時間            0.01 秒


work.data3は以下のように文字化けしています。EUCのファイルをUTF-8で読み込んだときに文字化けを起こしています。これをSJISで保存しようとしてエラーになっています。


今回、仕事ではまったのが、SASのインストール、構成において途中でエンコーディングの設定を変えてこのエラーにぶつかりました。しかも、文字化けして格納されたのがPostgreのデータベースの中でした。ログファイルや、データセットの中を見ても見つからずにSOURCE, MPRINTで元データを探しまわり時間を費やしました。

教訓ですが、SASをインストした後にエンコーディングを変えてから、構成しましょう。SAS Environment Managerの設定で時間が掛かったのは、このエラーのためです。

2016年8月3日水曜日

SAS Metadata API

調べ物のメモです。
メターデータのAPIを使うと、ユーザのDescriptionやメールアドレスを設定できます。

rc = metadata_setattr("omsobj:Person?@Name='ogawan'","Desc", "hogehoge");


2016年7月13日水曜日

次に作りたいツール, Metadata Serverに登録されているユーザにグループを設定/解除するスクリプト

決定版のスクリプトがないので、自分で作ることにします。
欲しい機能を書きとめておきます。

  • ユーザを登録する機能
  • ユーザを削除する機能
  • 全ユーザとそのグループとロールの一覧を表示する機能
  • 全グループの一覧を表示する機能
  • ユーザに対して1つ以上のグループを設定する機能
  • ユーザに対して1つ以上のロールを設定する機能
  • 変更前にバックアップ用のデータをファイルに保存する機能
  • UNIXのシェルから実行できること
  • エラーのログファイルを指定できること
  • 指定されたファイルを読み込んで、まとめて複数のユーザを登録できること
  • 指定されたファイルを読み込んで、まとめて複数のユーザを削除できること
GUIを組みたいが、APIとしてはJavaかC#を使う必要がある。
C#はDelphiを選ぶ前に試しで画面組んだが、もう5年以上も触っていない。
UNIXのログインしてスクリプトたたかずに、PCクライアントからツールを起動して、登録と確認できたら良いです。

まずは、スクリプトを目標にします。

2016年4月15日金曜日

ERROR: The LRECL= value is out of range, found 0.00000E-309.

レアケースのエラーにぶつかったのでメモしておきます。PROC OPTMODELのエラーです。

ERROR: The LRECL= value is out of range, found 0.00000E-309. 

これLRECLを指定する箇所ないのに、レンジがおかしいとエラーになっています。広く事例を調べてみると、この極端に小さな値0.00000E-309.は、ひょこひょことOPTMODEL以外でも出ています。

原因は、入力データセットの中に必要な変数がなかったことによるものでした。よく分からない論理レコード長のエラーがでたら、入力データの変数を疑うという経験則を得ました。

2016年4月8日金曜日

Enterprise Guide, UACダイアログが出る現象

トラブル対応のメモです。 EGを実行すると必ずUACのダイアログが表示され、管理者のパスワード入力を促されるという事象にぶつかりました。 原因は、SEGuide.exeのプロパティで、[管理者としてこのプログラムを実行する」というオプションが設定されていたためです。 このプロパティ、他のPCでは設定されていないので、何故チェックが入ったのかは不明です。



Keyword: UAC dialog, User Account Contorol, EG, Enterprise Guide, SEGuide.exe

2016年3月27日日曜日

VBScriptでタスクスケジューラの複数のタスクをXMLに出力、削除、作成

EGで登録したスケジュールのタスクを、他のユーザへ定期的に引き渡すためにVBScriptを組みました。特定のタスクをXMLファイルに出力、削除、登録します。

SCHTASKSを使っていますが、これ1つの操作で1タスクしか定義できません。じゃあPowerShellで書こうかと思ったのですが、お客様先の環境だと使えないことが多いためVBScriptで書きました。
  • tasks_list.vbs でXMLファイルを作成
  • tasks_delete.vbs でタスクを削除
  • tasks_create.vbs でタスクを作成
キーワードを含む名前のタスクをXML形式で出力します。

'----
' Windowsのタスク スケジューラから指定された名前のタスクをXML形式で表示します。
' 標準出力にはログ、標準エラー出力にはXMLを出力します。
' 使用例: C:\Windows\System32\cscript //b c:\temp\tasks_list.vbs 1> tasks.log 2> tasks.xml
'----

Option Explicit
Dim objShell
Dim objExec 
Dim objDictionary
Dim objFso
Dim strLine
Dim strCmd
Dim Count
Dim Name
Dim QuitCode
Const MINIMIZE_WINDOW = 2
Const SCHTASKS = "C:\Windows\System32\SCHTASKS.EXE"
Const KEYWORD = "スケジュール - "

'----
' チェック関数
'----

Function CheckError(fnName)
    CheckError = False
    
    Dim strmsg
    Dim errNum
    
    If Err.Number <> 0 Then
        strmsg = "ERROE: #" & Hex(Err.Number) & " in module " & fnName & " " & Err.Description
        WScript.Stdout.WriteLine strmsg
        CheckError = True
    End If
         
End Function

'----
' オブジェクト生成
'----

Set objShell = WScript.CreateObject("WScript.Shell")
Set objDictionary = WScript.CreateObject("Scripting.Dictionary")
Set objFso = WScript.CreateObject("Scripting.FileSystemObject")

'----
' タスク一覧表示
'----

Sub Main()
    Dim ExitCode
    Dim ErrCount

    '----
    ' タスク一覧から削除対象のタスク名を取得
    '----

    Set objExec = objShell.Exec(SCHTASKS & " /query /fo:csv")
    Count = 0
    Do Until objExec.StdOut.AtEndOfStream
        strLine = objExec.StdOut.ReadLine
        If InStr(strLine, KEYWORD) <> 0 Then
            Count = Count + 1
            objDictionary.Add Count, Mid(strLine, 1, InStr(strLine, ""","))
        End If
    Loop
    If objExec.ExitCode <> 0 Then
        Do Until objExec.StdErr.AtEndOfStream
            WScript.StdOut.WriteLine objExec.StdErr.ReadLine
        Loop
        Err.Raise(51)
    End If
    Set objExec = Nothing

    '----
    ' 件数を出力
    '----

    WScript.StdOut.WriteLine "NOTE: found " & Count & " tasks to export."

    '----
    ' タスクをXML形式で表示
    '----

    For Each Name In objDictionary.Keys
        strCmd = SCHTASKS & " /query /xml /tn:" & objDictionary(Name)
        WScript.StdOut.WriteLine strCmd
        Set objExec = objShell.Exec(strCmd)
        WScript.StdErr.WriteLine("")
        Do Until objExec.StdOut.AtEndOfStream
            WScript.StdErr.WriteLine(objExec.StdOut.ReadLine)
        Loop
        WScript.StdErr.WriteLine("")

        If objExec.ExitCode <> 0 Then
            Do Until objExec.StdErr.AtEndOfStream
                WScript.StdOut.WriteLine objExec.StdErr.ReadLine
            Loop
            Err.Raise(51)
        End If
        Set objExec = Nothing
    Next

End Sub

'----
' 処理実行
'----

Sub Try()
    Call Main()
End Sub

'----
' エラー捕捉
'----

Sub Catch()
    On Error Resume Next
    Call Try()
End Sub

'----
' 処理実行
'----

QuitCode=0
WScript.StdOut.WriteLine "NOTE: script=" & WScript.ScriptFullName & " date=" & Now()
Call Catch()
If CheckError("Catch") Then
    QuitCode = 1
End If

'----
' 終了
'----

Set objShell = Nothing
Set objDictionary = Nothing
Set objFso = Nothing
WScript.StdOut.WriteLine "NOTE: script finished with exit code " & QuitCode
WScript.Quit QuitCode



特定の名前のタスクを削除します。
'----
' Windowsのタスク スケジューラから指定された名前のタスクを削除します。
' 標準出力に削除のログを出力します。
' 使用例: C:\Windows\System32\cscript //b c:\temp\tasks_delete.vbs > tasks_delete_%date:~-2,2%.log
'----

Option Explicit
Dim objShell
Dim objExec 
Dim objDictionary
Dim objFso
Dim strLine
Dim strCmd
Dim Count
Dim Name
Dim objFile
Dim QuitCode
Const SCSHTASKS = "C:\Windows\System32\SCHTASKS.EXE"

'----
' 削除するタスク名の名前
'----

Const KEYWORD = "スケジュール - "

'----
' チェック関数
'----

Function CheckError(fnName)
    CheckError = False
    
    Dim strmsg
    Dim errNum
    
    If Err.Number <> 0 Then
        strmsg = "ERROE: #" & Hex(Err.Number) & " in module " & fnName & " " & Err.Description
        WScript.Stdout.WriteLine strmsg
        CheckError = True
    End If
         
End Function

'----
' オブジェクト生成
'----

Set objShell = WScript.CreateObject("WScript.Shell")
Set objDictionary = WScript.CreateObject("Scripting.Dictionary")
Set objFso = WScript.CreateObject("Scripting.FileSystemObject")

'----
' タスク削除
'----

Sub Main()
    Dim ExitCode
    Dim ErrCount

    '----
    ' タスク一覧から削除対象のタスク名を取得
    '----

    Set objExec = objShell.Exec(SCSHTASKS & " /query /fo:csv")
    Count = 0
    Do Until objExec.StdOut.AtEndOfStream
        strLine = objExec.StdOut.ReadLine
        If InStr(strLine, KEYWORD) <> 0 Then
            Count = Count + 1
            objDictionary.Add Count, Mid(strLine, 1, InStr(strLine, ""","))
        End If
    Loop
    If objExec.ExitCode <> 0 Then
        Do Until objExec.StdErr.AtEndOfStream
            WScript.StdOut.WriteLine objExec.StdErr.ReadLine
        Loop
        Err.Raise(51)
    End If
    Set objExec = Nothing

    '----
    ' 件数を出力
    '----

    WScript.StdOut.WriteLine "NOTE: found " & Count & " tasks to delete."

    '----
    ' タスクを削除
    '----

    For Each Name In objDictionary.Keys
        strCmd = SCSHTASKS & " /delete /f /tn " & objDictionary(Name)
        WScript.StdOut.WriteLine strCmd
        Set objExec = objShell.Exec(strCmd)

        Do Until objExec.StdOut.AtEndOfStream
            WScript.StdOut.WriteLine objExec.StdOut.ReadLine
        Loop

        If objExec.ExitCode <> 0 Then
            Do Until objExec.StdErr.AtEndOfStream
                WScript.StdOut.WriteLine objExec.StdErr.ReadLine
            Loop
            Err.Raise(51)
        End If
        Set objExec = Nothing
    Next

End Sub

'----
' 処理実行
'----

Sub Try()
    Call Main()
End Sub

'----
' エラー捕捉
'----

Sub Catch()
    On Error Resume Next
    Call Try()
End Sub

'----
' 処理実行
'----

QuitCode = 0
WScript.StdOut.WriteLine "NOTE: script=" & WScript.ScriptFullName & " date=" & Now()
Call Catch()
If CheckError("Catch") Then
    QuitCode = 1
End If

'----
' 終了
'----

Set objShell = Nothing
Set objDictionary = Nothing
Set objFso = Nothing
WScript.StdOut.WriteLine "NOTE: script finished with exit code " & QuitCode
WScript.Quit QuitCode

XMLファイルからタスクを作成します。
'----
' WindowsのタスクをXMLファイルから定義します。
' 引数に tasks_list.vbs で作成したXMLファイルを指定します。
' タスクの実行ユーザは、スクリプトの実行者に置き換えられます。
' 使用例: C:\Windows\System32\cscript //b c:\temp\tasks_create.vbs tasks.xml
'----

Option Explicit
Dim objShell
Dim objExec 
Dim objDictionary
Dim objFso
Dim strLine
Dim strCmd
Dim Count
Dim Name
Dim objFile
Dim xmlFile
Dim QuitCode
Const SCSHTASKS = "C:\Windows\System32\SCHTASKS.EXE"

'----
' 実行ユーザのキーワード
'----

Const KEYWORD = "      "

'----
' チェック関数
'----

Function CheckError(fnName)
    CheckError = False
    
    Dim strmsg
    Dim errNum
    
    If Err.Number <> 0 Then
        strmsg = "ERROE: #" & Hex(Err.Number) & " in module " & fnName & " " & Err.Description
        WScript.Stdout.WriteLine strmsg
        CheckError = True
    End If
         
End Function

'----
' XMLのコメントからユーザ名を取得
'----

Function XmlUserName(Buf)
    Dim Pos1, Pos2
    Const Keyword1 = "username="
    Const Keyword2 = " -->"

    XmlUserName = "N/A"

    Pos1 = InStr(Buf, Keyword1)
    Pos2 = InStr(Buf, Keyword2)
    If Pos1 <> 0 And Pos2 <> 0 Then
        XmlUserName = Mid(Buf, Pos1 + Len(Keyword1), Pos2 - (Pos1 + Len(Keyword1)))
    End If
End Function

'----
' XMLのコメントからタスク名を取得
'----

Function XmlTaskName(Buf)
    Dim Pos1, Pos2
    Dim AryStrings
    Const Keyword1 = "<!-- end "
    Const Keyword2 = "username="

    XmlTaskName = "N/A"

    Pos1 = InStr(Buf, Keyword1)
    Pos2 = InStr(Buf, Keyword2)
    If Pos1 <> 0 And Pos2 <> 0 Then
        XmlTaskName = Trim(Mid(Buf, Pos1 + Len(Keyword1), Pos2 - (Pos1 + Len(Keyword1))))
        XmlTaskName = Mid(XmlTaskName, 2, Len(XmlTaskName) - 2)

        AryStrings = Split(XmlTaskName, "\")
        XmlTaskName = AryStrings(UBound(AryStrings))
    End If
End Function

'----
' オブジェクト生成
'----

Set objShell = WScript.CreateObject("WScript.Shell")
Set objDictionary = WScript.CreateObject("Scripting.Dictionary")
Set objFso = WScript.CreateObject("Scripting.FileSystemObject")

'----
' タスク定義
'----

Sub Main()
    Dim UserName
    Dim TaskName
    Dim FilePath

    '----
    ' テンポラリファイルのパスを設定
    '----
    FilePath = objShell.ExpandEnvironmentStrings("%TEMP%") & "\task.xml"

    '----
    ' 引数のXMLファイルを開く
    '----
    If WScript.Arguments.Count <> 1 Then
        Err.Rase(51)
    End If
    Set objFile = objFso.OpenTextFile(WScript.Arguments.Item(0))

    Count = 0
    strLine = objFile.ReadLine
    Do Until objFile.AtEndOfStream
        If InStr(strLine, "<!-- begin") <> 0 Then
            Set xmlFile = objFSO.OpenTextFile(FilePath, 2, True)
            strLine = objFile.ReadLine
            Do Until objFile.AtEndOfStream or (InStr(strLine, "<!-- end") <> 0)
                ' ユーザIDを書き換える
                If InStr(strLine, KEYWORD) = 1 Then
                    strLine = "      " & objShell.ExpandEnvironmentStrings("%USERDOMAIN%") & "\" & objShell.ExpandEnvironmentStrings("%USERNAME%") & ""
                End If
                xmlFile.WriteLine strLine
                strLine = objFile.ReadLine
            Loop
            UserName = xmlUserName(strLine)
            TaskName = xmlTaskName(strLine)
            xmlFile.Close

            '----
            ' タスク登録
            '----
            Count = Count + 1
            strCmd = SCSHTASKS & " /create /xml """ & FilePath & """ /tn:""EG\" & UserName & "." & Right("000" & Count, 3) & " " & TaskName & """"
            WScript.StdOut.WriteLine strCmd
            Set objExec = objShell.Exec(strCmd)

            Do Until objExec.StdOut.AtEndOfStream
                WScript.StdOut.WriteLine objExec.StdOut.ReadLine
            Loop
            If objExec.ExitCode <> 0 Then
                Do Until objExec.StdErr.AtEndOfStream
                    WScript.StdOut.WriteLine objExec.StdErr.ReadLine
                Loop
                Err.Raise(51)
            End If
            Set objExec = Nothing

        End If
        If objFile.AtEndOfStream = False Then
            strLine = objFile.ReadLine
        End If
    Loop

    '----
    ' 件数を出力
    '----

    WScript.StdOut.WriteLine "NOTE: create " & Count & " tasks."

    '----
    ' テンポラリのXMLファイルを削除
    '----
    objFso.DeleteFile FilePath, True

End Sub

'----
' 処理実行
'----

Sub Try()
    Call Main()
End Sub

'----
' エラー捕捉
'----

Sub Catch()
    On Error Resume Next
    Call Try()
End Sub

'----
' 処理実行
'----

QuitCode = 0
WScript.StdOut.WriteLine "NOTE: script=" & WScript.ScriptFullName & " date=" & Now()
Call Catch()
If CheckError("Catch") Then
    QuitCode = 1
End If

'----
' 終了
'----

Set objShell = Nothing
Set objDictionary = Nothing
Set objFso = Nothing
WScript.StdOut.WriteLine "NOTE: script finished with exit code " & QuitCode
WScript.Quit QuitCode




2016年3月22日火曜日

RPM - Rapid Predictive Modeler

10年ぶりのEnterprise Miner

久しぶりにEnterprise Minerを使ったら、RPMなる便利な機能が付いてました。10年以上前にSAS Enterprise Minerのトレーニングを受けたものの、仕事ではSPSS Modeler(Clementine)ばかりを使っていました。しばらく会えなかった友人(又は恋人)に再開したような気持ちです。



RPMとは何か?

Enterprise Guideのデータに対して目的変数、説明変数を設定し、予測モデルを作ってくれる機能です。データ加工はEGの上で行って、EMのプロジェクトはRPMで作ることができます。モデル作成の機能が割り切りよく、基本、中間、詳細の3段階というのが良いです。

できたモデルをEGから参照して、スコアリングできます。細かい調整をしたい人は、EMからプロジェクトを開いて修正します。モデル作成で詳細を指定すると、回帰、決定木と複数のモデルを作成して比較してくれます。とはいえ、実際に使うモデルの手法は、業務や分析者で決まってしまうとは思いますが。

Rev up your RPMs; A Modeling Sampler動画

RPMがどんなものかは、下の動画を見れば、一目瞭然かと思います。
Part 1
Part 2
Part 3

2016年2月21日日曜日

SAS Log Utility 1.8.1.1, ログファイルのエンコーディングを判定

Vectorに新しいバージョンの公開を依頼しました。今回の修正内容は以下のとおりです。

  • 不具合の修正
  • SASログファイルのエンコーディングを自動判定
  • SJIS, JIS, EUC, UTF8N, UTF8, UTF7など複数のエンコーディングに対応
  • Optionsのメニューの構成を変更
  • 日本語のマッチングパターン(keyword.ini)を訂正



ログファイルのエンコーディング判定は、ココの情報が役立ちました。これが無ければ、短時間で実装できなかったです。

ListViewのソートは、DEBUGのシンボルを定義したときと、外したときで動作が異なっていました。原因分からず。機能的には、だいぶ落ち着いてきたと思います。そろそろメジャーバージョンを上げて、処理構造の無駄を取り除きたいです。

2016年2月17日水曜日

EncodeDetect.pas

開発のメモです。
ログファイルのエンコーディング判定の関数、下にネタがあります。

EncodeDetect : EncodeDetectリファレンス

http://www.watercolor-city.net/ct_delphi/delphi_tiburon/doc_thirdparty/tp_ed_ref.htm

これを使ってコードを判定、TEncodeingを生成した後します。
StringListからEncodingを指定して、読み取ると正しく読み取れました。
試しにElapsed Timeの表示に組み込んだらあっさりと動きました。

2016年2月15日月曜日

ZIP圧縮のファイルを書く

投稿を修正して再度掲載します。

HULFT転送されてきたテキストファイルを読み込んでデータセット化するときに、元のファイルは残しておきたいことがあります。そのときに、圧縮して保存しとくと容量削減できて良いです。下のサンプルは、お馴染みCLASSのデータを使ったものです。名前と年齢をZIPファイルに出力し、後半のDATAステップでZIPファイルから読み取っています。


23   filename foo ZIP 'C:\temp\testzip.zip';
24
25   /* write to zip file */
26   data _null_;
27       file foo(class);
28       set sashelp.class;
29       put name age;
30   run;
 
NOTE: ファイルライブラリFOO :
      Directory=C:\temp\testzip.zip
 
NOTE: 出力ファイルFOO(class) :
      Filename=C:\temp\testzip.zip,
      Member Name=class
 
NOTE: 19レコードをファイルライブラリFOOに書き込みました。
      最小レコード長は9です。
      最大レコード長は15です。
NOTE: 19レコードを出力ファイルFOO(class)に書き込みました。
      最小レコード長は9です。
      最大レコード長は15です。
NOTE: データセットSASHELP.CLASSから19オブザベーションを読み込みました。
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.01 秒
      CPU時間            0.01 秒
 
 
31
32   /* read from zip file */
33   data work.class;
34       infile foo(class);
35       input name $ age;
36   run;
 
NOTE: 入力ファイルライブラリFOO:
      Directory=C:\temp\testzip.zip
 
NOTE: 入力ファイルFOO(class) :
      Filename=C:\temp\testzip.zip,
      Member Name=class
 
NOTE: 19レコードを入力ファイルライブラリFOOから読み込みました。
      最小レコード長は9です。
      最大レコード長は15です。
NOTE: 19レコードを入力ファイルFOO(class)から読み込みました。
      最小レコード長は9です。
      最大レコード長は15です。
NOTE: データセットWORK.CLASSは19オブザベーション、2変数です。
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.01 秒
      CPU時間            0.01 秒

便利!

UTF-8のログ

読み込めないSASログがあった。調べてみたらUTF-8だった。特に機能仕様には明記していなかったが、扱えるのはANSI, SJISのファイルです。

UTF-8はBOMあり、BOMなしで扱いが異なる。
別途サンプルのProjectを作って、検証から始める。

仕方ないので、メモ帳でSJISに変換してしのぎます。

実装の参考記事は こちら です。

2016年2月14日日曜日

SAS Log Utilityの概要

2009年に書いたツールの概要を加筆します。
SAS Log Utilityの概要について説明します。

概要
SAS Log Utilityは、SASプログラムが出力するログファイルを分析して、パフォーマンス診断や入出力の解析に役立つ情報を作成します。SAS Log Utilityを使うと以下のことができます。
  • SASプログラムの処理時間をDATAステップで集計して、ボトルネックとなっている処理を見つけることができます。
  • SASプログラムがどのようなデータを読み書きしているか、DATAステップ又はプロシージャ単位で捉えて、データの流れを理解することができます。
  • MPRINTのログを捉えて、マクロ単位の処理時間を集計します。
  • 複数のSASプログラムがどのようなデータを読み書きし、プログラム間でのデータの連携を可視化します。
機能
  • SASログからDATAステップ/プロシージャ単位の処理時間を抽出
  • SASログから処理時間を抽出し、データセット単位で処理に要した時間を集計
  • SASログからDATAステップ/プロシージャの入出力を抽出し、データの生成、読み取り、更新、削除の流れを表形式に出力
  • 設定ファイルの切り替えで、日本語と英語のSASログに対応
  • エラーにつながるNOTE:のメッセージをSASログから捕捉
  • INIファイルを追加、修正して日本語、英語以外のSASログを解析可能
  • SASログからMPRINTのログを抽出し、マクロ単位の処理時間を集計
  • SASログの分析結果をExcelに出力

SAS Log Utility 1.7.1.16, 不具合修正と設定ファイルの切り替え機能を追加

Vectorに新しいバージョンの公開を依頼しました。今回の修正内容は以下のとおりです。

  • 不具合の修正
  • 日本語と英語の設定ファイルを分けて、nlsのサブフォルダ下に移動
  • システムのデフォルトの言語から、設定ファイルを切り替えるように修正
  • Optionsのメニューの構成を変更

今回、日本語、英語の設定を分けたのが大きな変更点です。仕組みとしてはnlsフォルダの下に、ISO言語省略名のサブフォルダを定義して、INIファイルを作成すれば、他の言語も追加できます。が、まだ検証できていません。

修正した不具合は以下のものです。

  • FILENAMEを使わないで直接ファイルのパスを指定しているときに、入出力を捕捉できない
  • データセット名の変更が正しくCRUD表に反映されない
  • 英語のSASログで、データセットの書き込み(W)が正しく反映されない

他の言語の対応は、時間のあるときに試したいです。
開発で謎なのが、Patterns... のボタンにアイコンを表示していたのですが、いつのまにか表示されなくなりました。IDEの中だと、鉛筆のマークが出ているのですが、実行すると消えてしまいます。この検証はまた時間のあるときに行います。

2016年2月13日土曜日

Delphi 条件付コンパイルの設定

開発のメモです。
リリース、デバッグ版とは別にインターナルのバージョンを作りました。条件付コンパイルの設定が分からなかったので、設定画面を貼り付けておきます。Vectorに載せるリリース版から分岐して、仕事で使うためのバージョンを作成しました。


土曜日の作業で、英語版の設定ファイルが8割がたできました。
明日、まとめてVectorに新しいバージョンを載せたいです。



2016年2月9日火曜日

NLS下のフォルダを探して、メニューを設定する(2)

開発のメモです。
設定ボタンの配置を換えました。
設定ボタンに16x16のアイコンを設定しました。
ISO言語省略名の選択は、ラジオボタンのタイプに変更しました。
潜在的なバグを見つけ出すパターンの設定は、Potential Problemのタブの下に移動しました。
言語の設定を切り替えるたびに、設定ファイルを読み直すように実装しました。





今日は、有給休暇でした。
銀行の手続きで3時間も掛かりました。

2016年2月7日日曜日

NLS下のフォルダを探して、メニューを設定する

開発のメモ。SASに倣って、NLSのフォルダ下にある言語のサブフォルダを見て、設定を切り替えられるように作成中です。



2016年2月6日土曜日

転職に弾みをつけたできごと、疲労臭

2014年12月の臭い
数年前から妻に「仕事を変えて」と言われていました。転職の理由は色々で、ひとつには絞れないけど、記憶に残ることがありました。人間、えらく疲れると通常とは異なる臭いを発するらしい。それが疲労臭という。2014年12月はそんな臭いを撒き散らしていたらしい。

症状
酷く足が臭く、脹脛が硬くなった。この臭いが強烈で、履いていた靴がやたらと臭くなった。メレルのごつい靴を履いていたが、臭いが取れずに「靴選科」で4500円のクリーニングに出した記憶があります。

その頃は仕事の追い込みで、客先にカジュアルウェアで土日、昼夜関係なく仕事をしていました。2日連続で徹夜したとき、靴を脱いで、靴下で歩き回ると嫌な臭いがカーペットに残りました。他の人がファブリーズで、私が歩いたところを消臭して回ったぐらいです。

疲労臭
あとから知ったのですが、これは疲労臭という症状でした。腎臓、肝臓の機能が低下して、アンモニア臭を発するという状況とぴたりと一致します。このころ、リフレクソロジーに行ったときも、ふくらはぎが異様にかたいとしてきされていました。身体が悲鳴を上げていたのは自覚していましたが、この仕事の切れ目までと無理を続けていました。

なんだか臭う…それはお疲れのサイン「疲労臭」かも?

妻の指摘は正しかったわけで、鼻を膨らませて「だからいったでしょ」とドヤ顔決めてます。あれを突き抜けると、過労死が待っていたのかもと思うと少し寒くなります。身体がダウンする手前まで頑張った。故に清々として、次の職場を探すことができたと思うのは不健康でしょうか。

2016年2月1日月曜日

SAS Log Utility, 多言語対応の仕組みを考える

多言語対応で必要なものは何?

設定ファイルは、ISO言語省略名ごとに保持できるようにしたい。

どこに、設定ファイルを保持するか?
!SASROOT\nls\ja の形に倣って設定ファイルを配置するか?
それとも、設定ファイルの後ろにISO言語省略名を付与するのか?

LOCALEの取得は、Windows.GetLocaleInfoでできる。

言語の切り替えは、メニューから行う。
切り替えはツールの再起動なしで、反映できるようにしたい。

言語を切り替えて、ダイアログの表示を切り替えられるか?
reinit.ReinitializeFormsは使えるのか?
reinit.LoadNewResourceModuleは使えるのか?

他に使い出のあるものは何?

マイグレーション案件、面倒なことが色々ある。
何か効率化できるネタはないか。
VAの構成って面倒、設定だけではなくエビデンスの取得もできる何かが欲しい。

移行で毎回手を焼くのが、過去データの移送と変換です。
量が多いので、並列化したり、夜間に差分検出して移送する、中断、再開の制御を入れています。

他には?
BI Serverの構成が面倒です。
SMCの管理は苦手なのです、私はね。



2016年1月30日土曜日

きっと役立つ、バグになりそうなNOTE:のチェック

SAS Log Utilityには、エラーでもなく、ワーニングでもないけど、バグになりそうなメッセージをチェックする機能があります。2013年頃投稿、2016年1月30日更新です。

  • 変数 ABC は初期化されていません
  • 以下の箇所で文字値を数値に変換しました
  • 以下の箇所で数値を文字値に変換しました
  • ステートメントに BY値を繰り返すデータセットが複数あります
  • 欠損値を含んだ計算により、以下の箇所で欠損値が生成されました
  • 切り捨てられた行があります
  • カラム999で0による割り算がありました
  • 関数XYZ(行999 カラム999の引数は無効です
  • LOST CARD.
  • NOEXECオプションが指定
  • ループが発生したため、DATAステップの実行を中止しました
  • 引用符で囲まれた文字列の後の識別子の意味は
  • 数値をプリントするには小さすぎるW.D出力形式
  • ライブラリABCは存在しません
  • クエリは元のデータに要約統計量の結果を再マージします
  • データセットABCにオブザベーションがありません
  • ファイルABC(memtype=DATA)は見つかりませんが、DELETEステートメントに存在します
  • ABCは未参照のラベル定義です
  • ABCに対して、無効なデータが行999カラム999にあります
  • ステートメントが行の終端に達したので、次の行を読み込みます
  • 要約化中にディスク処理が発生しました
  • CASE式にELSE句がありません

関数の引数無効とかは、意外と気がつきにくいです。

SAS Log Utility 1.6.1.8, 不具合修正

Vectorに登録の申請をしました。
修正したのは以下の点です。

  • NOTE:でチェックするパターンを追加
  • ステータスバーにISO言語省略名を表示
  • ログの行末に追加されている空白又はタブを削除して処理
  • ListViewのソートの不具合を修正

ステータスバーに言語省略名を表示したのは、多言語対応の準備です。
行末のタブ又は空白を削除するようにしたのは、SASログをEXCELファイルに貼って渡す人がいて、処理の区切りを正確に判定できなかったためです。これがメモリリークの原因のひとつになっていました。ちなみに、EXCELに貼り付けて渡すとダブルクォートがエスケープされて、正しくパースできないことがありますので、私はWinZipで圧縮して渡します。

ステータスバーの左に言語省略名追加


記録まで

2016年1月28日木曜日

SAS Log Inspector 1.3.1.2, リリース


機能は変えず、組み込みのマッチングパターンを24種類に増やしました。どんなパターンが定義されているかは、以下の画像をご覧ください。Vectorに公開の依頼を投げました。



SAS Log Inspectorの機能
  • 複数のログファイルからエラー、バグに繋がるNOTEを集計
  • 日本語と英語版のメッセージに対応
  • Excelファイルにログをチェックした結果をエクスポート
  • 任意のチェック項目を検査対象から外すことができる
  • Notepadを起動して、該当のログファイルを参照できる
  • マッチングパターンを画面から追加、修正できる

2016年1月27日水曜日

やること

ログを検査するパターン追加

NOTE: CASE式にELSE句がありません。すべてのWHEN句の条件に合わなかった場合、CASE式の結果は欠損値になります。

APPENDのパターン対応
NOTE: WORK.ABCをWORK.DEFに追加します。
NOTE: データセットWORK.ABCから0オブザベーションを読み込みました。
NOTE: 0オブザベーションが追加されました。
NOTE: データセットWORK.DEFは0オブザベーション、20変数です。
NOTE: PROCEDURE APPEND処理(合計処理時間):
      処理時間           0.00 秒
      CPU時間            0.01 秒

NOTE: Appending WORK.ONE to WORK.BASE.
WARNING: Variable c was not found on BASE file. The variable will not be added to the BASE file.
WARNING: Variable a has different lengths on BASE and DATA files (BASE 3 DATA 4).
WARNING: Variable b has different lengths on BASE and DATA files (BASE 5 DATA 4).
WARNING: Variable d was not found on DATA file.
NOTE: FORCE is specified, so dropping/truncating will occur.
NOTE: There were 4 observations read from the data set WORK.ONE.
NOTE: 4 observations added.
NOTE: The data set WORK.BASE has 4 observations and 4 variables.
NOTE: PROCEDURE APPEND used (Total process time):
      real time           0.15 seconds
      cpu time            0.06 seconds

2016年1月17日日曜日

Windows.GetLocaleInfoでLanguage Tagを取得

ツールのI18Nの検討メモです。
設定ファイルの言語を区別するため、Language Tag(ja, JP, en-usとか)を取得する方法を探しています。ログ解析の設定を日本語、英語と切り替えられるようにするための調査です。ネット上を探すとそのまま使えるサンプルが見つからないため、DelphiForFun HomeにあったDemoのコードを参考にしてサンプルコードを作りました。



試すときは、Formの上に、ButtonとListViewを配置し、ListViewの形式をvsReport、カラムを5つ定義してください。参考にしたのは以下のURLの情報です。
Locale Constants Demo

Language Tagの定義を探していたら、以下の情報が見つかりました。昔々、VAX Notes上でお世話になった記憶が微かにあります。
国際化プログラミング

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, shellapi, Vcl.ComCtrls;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    Button1: TButton;
    procedure FormActivate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  TRec = Record
    Code: Cardinal;
    Name: String;
    Text: String;
  end;

const
  rSet: Array [0 .. 79] of TRec = (
    (Code: LOCALE_ILANGUAGE; Name: '言語ID'; Text: 'LOCALE_ILANGUAGE'),
    (Code: LOCALE_SLANGUAGE; Name: '言語名'; Text: 'LOCALE_SLANGUAGE'),
    (Code: LOCALE_SENGLANGUAGE; Name: '言語英語名'; Text: 'LOCALE_SENGLANGUAGE'),
    (Code: LOCALE_SABBREVLANGNAME; Name: '言語省略名'; Text: 'LOCALE_SABBREVLANGNAME'),
    (Code: LOCALE_SNATIVELANGNAME; Name: '言語固有名'; Text: 'LOCALE_SNATIVELANGNAME'),
    (Code: LOCALE_ICOUNTRY; Name: '国コード'; Text: 'LOCALE_ICOUNTRY'),
    (Code: LOCALE_SCOUNTRY; Name: '国名'; Text: 'LOCALE_SCOUNTRY'),
    (Code: LOCALE_SENGCOUNTRY; Name: '国英語名'; Text: 'LOCALE_SENGCOUNTRY'),
    (Code: LOCALE_SABBREVCTRYNAME; Name: '国省略名'; Text: 'LOCALE_SABBREVCTRYNAME'),
    (Code: LOCALE_SNATIVECTRYNAME; Name: '国固有名'; Text: 'LOCALE_SNATIVECTRYNAME'),
    (Code: LOCALE_IDEFAULTLANGUAGE; Name: 'デフォルト言語ID'; Text: 'LOCALE_IDEFAULTLANGUAGE'),
    (Code: LOCALE_IDEFAULTCOUNTRY; Name: 'デフォルト国コード'; Text: 'LOCALE_IDEFAULTCOUNTRY'),
    (Code: LOCALE_IDEFAULTCODEPAGE; Name: 'デフォルトOEMコードページ'; Text: 'LOCALE_IDEFAULTCODEPAGE'),
    (Code: LOCALE_IDEFAULTANSICODEPAGE; Name: 'デフォルトANSIコードページ'; Text: 'LOCALE_IDEFAULTANSICODEPAGE'),
    (Code: LOCALE_IDEFAULTMACCODEPAGE; Name: 'デフォルトMACコードページ'; Text: 'LOCALE_IDEFAULTMACCODEPAGE'),
    (Code: LOCALE_FONTSIGNATURE; Name: 'フォント署名'; Text: 'LOCALE_FONTSIGNATURE'),
    (Code: LOCALE_SISO639LANGNAME; Name: 'ISO言語省略名'; Text: 'LOCALE_SISO639LANGNAME'),
    (Code: LOCALE_SISO3166CTRYNAME; Name: 'ISO国省略名'; Text: 'LOCALE_SISO3166CTRYNAME'),
    (Code: LOCALE_SLIST; Name: '区切り記号'; Text: 'LOCALE_SLIST'),
    (Code: LOCALE_IMEASURE; Name: '単位'; Text: 'LOCALE_IMEASURE'),
    (Code: LOCALE_SDECIMAL; Name: '小数点の記号'; Text: 'LOCALE_SDECIMAL'),
    (Code: LOCALE_STHOUSAND; Name: '桁区切り記号'; Text: 'LOCALE_STHOUSAND'),
    (Code: LOCALE_SGROUPING; Name: '区切る桁数'; Text: 'LOCALE_SGROUPING'),
    (Code: LOCALE_IDIGITS; Name: '小数点以下の桁数'; Text: 'LOCALE_IDIGITS'),
    (Code: LOCALE_ILZERO; Name: '少数前ゼロの桁数'; Text: 'LOCALE_ILZERO'),
    (Code: LOCALE_INEGNUMBER; Name: '負の値の形式'; Text: 'LOCALE_INEGNUMBER'),
    (Code: LOCALE_SNATIVEDIGITS; Name: '0から9の表記'; Text: 'LOCALE_SNATIVEDIGITS'),
    (Code: LOCALE_SPOSITIVESIGN; Name: '正の記号'; Text: 'LOCALE_SPOSITIVESIGN'),
    (Code: LOCALE_SNEGATIVESIGN; Name: '負の記号'; Text: 'LOCALE_SNEGATIVESIGN'),
    (Code: LOCALE_IPOSSIGNPOSN; Name: '正の記号の位置'; Text: 'LOCALE_IPOSSIGNPOSN'),
    (Code: LOCALE_INEGSIGNPOSN; Name: '負の記号の位置'; Text: 'LOCALE_INEGSIGNPOSN'),
    (Code: LOCALE_SCURRENCY; Name: '通貨記号'; Text: 'LOCALE_SCURRENCY'),
    (Code: LOCALE_SINTLSYMBOL; Name: '国際通貨記号'; Text: 'LOCALE_SINTLSYMBOL'),
    (Code: LOCALE_SMONDECIMALSEP; Name: '小数点の記号'; Text: 'LOCALE_SMONDECIMALSEP'),
    (Code: LOCALE_SMONTHOUSANDSEP; Name: '桁区切り記号'; Text: 'LOCALE_SMONTHOUSANDSEP'),
    (Code: LOCALE_SMONGROUPING; Name: '区切る桁数'; Text: 'LOCALE_SMONGROUPING'),
    (Code: LOCALE_ICURRDIGITS; Name: '小数点以下の桁数'; Text: 'LOCALE_ICURRDIGITS'),
    (Code: LOCALE_IINTLCURRDIGITS; Name: '小数点以下の桁数'; Text: 'LOCALE_IINTLCURRDIGITS'),
    (Code: LOCALE_ICURRENCY; Name: '正の値の形式'; Text: 'LOCALE_ICURRENCY'),
    (Code: LOCALE_INEGCURR; Name: '負の値の形式'; Text: 'LOCALE_INEGCURR'),
    (Code: LOCALE_IPOSSYMPRECEDES; Name: '正の通貨記号の位置'; Text: 'LOCALE_IPOSSYMPRECEDES'),
    (Code: LOCALE_IPOSSEPBYSPACE; Name: '正の通貨記号の分離位置'; Text: 'LOCALE_IPOSSEPBYSPACE'),
    (Code: LOCALE_INEGSYMPRECEDES; Name: '負の通貨記号の位置'; Text: 'LOCALE_INEGSYMPRECEDES'),
    (Code: LOCALE_INEGSEPBYSPACE; Name: '負の通貨記号の分離位置'; Text: 'LOCALE_INEGSEPBYSPACE'),
    (Code: LOCALE_SDATE; Name: '区切り記号'; Text: 'LOCALE_SDATE'),
    (Code: LOCALE_SSHORTDATE; Name: '短い形式'; Text: 'LOCALE_SSHORTDATE'),
    (Code: LOCALE_SLONGDATE; Name: '長い形式'; Text: 'LOCALE_SLONGDATE'),
    (Code: LOCALE_IDATE; Name: '短い形式の年月日順'; Text: 'LOCALE_IDATE'),
    (Code: LOCALE_ILDATE; Name: '長い形式の年月日順'; Text: 'LOCALE_ILDATE'),
    (Code: LOCALE_ICENTURY; Name: '年の桁数'; Text: 'LOCALE_ICENTURY'),
    (Code: LOCALE_IDAYLZERO; Name: '日前ゼロの有無'; Text: 'LOCALE_IDAYLZERO'),
    (Code: LOCALE_IMONLZERO; Name: '月前ゼロの有無'; Text: 'LOCALE_IMONLZERO'),
    (Code: LOCALE_ICALENDARType; Name: 'カレンダの種類'; Text: 'LOCALE_ICALENDARType'),
    (Code: LOCALE_IOPTIONALCALENDAR; Name: '追加カレンダの種類'; Text: 'LOCALE_IOPTIONALCALENDAR'),
    (Code: LOCALE_IFIRSTDAYOFWEEK; Name: '週の先頭日'; Text: 'LOCALE_IFIRSTDAYOFWEEK'),
    (Code: LOCALE_IFIRSTWEEKOFYEAR; Name: '年の先頭月'; Text: 'LOCALE_IFIRSTWEEKOFYEAR'),
    (Code: LOCALE_SDAYNAME1; Name: '週の第1日名'; Text: 'LOCALE_SDAYNAME1'),
    (Code: LOCALE_SDAYNAME2; Name: '週の第2日名'; Text: 'LOCALE_SDAYNAME2'),
    (Code: LOCALE_SDAYNAME3; Name: '週の第3日名'; Text: 'LOCALE_SDAYNAME3'),
    (Code: LOCALE_SDAYNAME7; Name: '週の第7日名'; Text: 'LOCALE_SDAYNAME7'),
    (Code: LOCALE_SABBREVDAYNAME1; Name: '週の第1日省略名'; Text: 'LOCALE_SABBREVDAYNAME1'),
    (Code: LOCALE_SABBREVDAYNAME2; Name: '週の第2日省略名'; Text: 'LOCALE_SABBREVDAYNAME2'),
    (Code: LOCALE_SABBREVDAYNAME3; Name: '週の第3日省略名'; Text: 'LOCALE_SABBREVDAYNAME3'),
    (Code: LOCALE_SABBREVDAYNAME7; Name: '週の第7日省略名'; Text: 'LOCALE_SABBREVDAYNAME7'),
    (Code: LOCALE_SMONTHNAME1; Name: '年の第1月名'; Text: 'LOCALE_SMONTHNAME1'),
    (Code: LOCALE_SMONTHNAME2; Name: '年の第2月名'; Text: 'LOCALE_SMONTHNAME2'),
    (Code: LOCALE_SMONTHNAME3; Name: '年の第3月名'; Text: 'LOCALE_SMONTHNAME3'),
    (Code: LOCALE_SMONTHNAME12; Name: '年の第12月名'; Text: 'LOCALE_SMONTHNAME12'),
    (Code: LOCALE_SMONTHNAME13; Name: '年の第13月名'; Text: 'LOCALE_SMONTHNAME13'),
    (Code: LOCALE_SABBREVMONTHNAME1; Name: '年の第1月省略名'; Text: 'LOCALE_SABBREVMONTHNAME1'),
    (Code: LOCALE_SABBREVMONTHNAME2; Name: '年の第2月省略名'; Text: 'LOCALE_SABBREVMONTHNAME2'),
    (Code: LOCALE_SABBREVMONTHNAME3; Name: '年の第3月省略名'; Text: 'LOCALE_SABBREVMONTHNAME3'),
    (Code: LOCALE_SABBREVMONTHNAME12; Name: '年の第12月省略名'; Text: 'LOCALE_SABBREVMONTHNAME12'),
    (Code: LOCALE_STIME; Name: '区切り記号'; Text: 'LOCALE_STIME'),
    (Code: LOCALE_STIMEFORMAT; Name: '時間の形式'; Text: 'LOCALE_STIMEFORMAT'),
    (Code: LOCALE_ITIME; Name: '時間制(12/24)'; Text: 'LOCALE_ITIME'),
    (Code: LOCALE_ITIMEMARKPOSN; Name: '午前午後記号の位置'; Text: 'LOCALE_ITIMEMARKPOSN'),
    (Code: LOCALE_ITLZERO; Name: '時刻前ゼロの有無'; Text: 'LOCALE_ITLZERO'),
    (Code: LOCALE_S1159; Name: '午前の記号'; Text: 'LOCALE_S1159'),
    (Code: LOCALE_S2359; Name: '午後の記号'; Text: 'LOCALE_S2359')
  );

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form1.Close;
end;

procedure TForm1.FormActivate(Sender: TObject);
var
  buf: array [1 .. 100] of char;
  i: integer;
  AItem: TListItem;
begin
  for i := Low(rSet) to High(rSet) do
  begin
    Windows.GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, rSet[i].Code, @buf, 100);

    AItem := ListView1.Items.Add;
    AItem.Caption := Format('%.3d', [i + 1]);
    AItem.SubItems.Add(rSet[i].Name);
    AItem.SubItems.Add(rSet[i].Text);
    AItem.SubItems.Add(Format('%d', [rSet[i].Code]));
    AItem.SubItems.Add(string(buf));
  end;

end;

end.

2016年1月4日月曜日

AdvListViewのソートを使う方法

実装を何度も忘れたのでメモします。
AdvListViewでソートを使うためには以下の設定が必要です。

  • SortShow := True を設定
  • AdvListViewの各カラムにTagの番号を設定します。
  • イベント OnColumnClick のハンドラを定義しその中で
  • SortColumnをカラムのタグから指定

procedure TForm1.AdvListView1ColumnClick(Sender: TObject; Column: TListColumn);
var
  tag: integer;
begin

  tag := Column.Tag;
  AdvListView1.SortColumn := tag;

end;
便利!

SAS Log Utility 1.6.1.5, 文字列のパターンをINIファイルで定義

年末年始の休みを使って、SAS Log Utilityを更新しました。
  • 潜在的な問題のパターンをINIファイルに定義
  • ログを解析するためのパターンをINIファイルに定義

Vectorに新バージョンの登録を依頼しました。SAS Log Utilityは仕事でもっとも良く使うツールです。これまで、SASのバージョンアップの度に、マッチングのパターンを変えてはビルドしていました。とはいえ、いつも同じバージョンのSASを使っていないので、その都度直してビルドするのは面倒です。そんな理由で、設定のフォームを作って修正できるようにしました。

追加したOptionsのメニュー


バグに繋がりそうなNOTE:のパターン


処理時間、データセット名を捕捉するためのパターン定義
マッチングのパターンですが、"Keyword Setting"では、データセット名やファイル名などのメタ文字を定義できるようにしました。たとえば"[:dsn:]"でデータセット名のパターンを定義します。これは、Nリテラルのデータセット名をすっきりと定義、参照するために実装した機能です。


定義しているパターン(Keyword)の件数は固定です。このパターンは、行の種類を特定するためと、行の中に含まれる情報を抽出するための2種類があります。情報を抽出するとは、「999 オブザベーション」とかのデータ数、データセット名、ファイル名などを取り出すことです。

それと英語のパターン定義は、外しました。将来、多言語対応で設定を切り替えられるようにするため、いったんパターンから外しました。

INIファイルの書式が、桁固定で分かりにくいが、それはゆくゆく直します。いまは使えることの方が大事なので後回しです。