業務備忘録

備忘録です

PowerShellで文字列を機械的に埋め込んで出力する

2024-04-23 21:47:28 | 日記

インストールの許可がないためにPHPなど他言語でサクッとスクリプトを書くこともできないがエクセルで設計書を書くことを強いられるような過酷な状況の時のためのメモ書き

# 文字列を埋め込みたい場所に{0}を指定(複数埋め込みたい場合は都度数字をインクリメント)
$templates = @("テンプレ1({0})", "テンプレ2({0})" )
# 埋め込むための大量の文字列が1行ずつ書き込まれているファイルがあると想定
$attributes = (Get-Content -Encoding utf8 -Path C:\test.txt) -as [string[]]
#StringBuilderで効率よく結合
$formatedSb = [System.Text.StringBuilder]::new()

foreach ($att in $attributes) {
    foreach ($temp in $templates) {
        $formatedSb.AppendLine([string]::format($temp -f $att))
    }
}

#多分いらない $formatedSb.ToString() | Set-Content -Encoding utf8 -Path C:\result.txt で大丈夫
$encoder = [System.Text.Encoding]::UTF8.GetBytes($formatedSb.ToString())
#パイプで流し込む
[System.Text.Encoding]::UTF8.GetString($encoder) | Set-Content -Encoding utf8 -Path C:\result.txt

C:\result.txtに一行ずつフォーマットされた文字列が出力される。
VSCodeのPowerShell拡張から実行したらテンプレ部分が文字化けしてしまったけど、PowerShellのターミナルから実行したら文字化けしなかったので多分大丈夫。


aタグとPointerEvent

2024-01-24 22:31:32 | 日記

aタグをコンソールから押す方法についてやっと理解できたのでメモ。

1.やりたいこと

こんなHTML内のaタグをJavaScriptからクリックしたい。

<body>
    <a href="https://www.google.com" id="target">Google.com</a>
</body>

2.ダメな例

function log(id){
    const target = document.getElementById(id);
    console.log(e);
    target.dispatchEvent(new Event('click'));
}

element.dispatchEvent()を使ってみる。dispatchEventは引数にEventオブジェクトを取って、要素に任意のイベントを割り当てることができる。
上記の場合は、Eventオブジェクトのコンストラクタに'click'を指定して、aタグにクリックイベントを発生させようとしている。

が、画面遷移は発生しない。

3.これならできるけど

function log(id){
    const target = document.getElementById(id);
    console.log(e);
    target.click();
}

click()メソッドを実行して、クリック動作をシミュレーションすることができ、aタグによる遷移を発生させることができる。

4.DispatchEventにこだわるなら

aタグのhref属性を削除したうえで、イベントオブジェクトを確認してみる。

function log(id){
    const target = document.getElementById(id);
    target.addEventListener('click', function (e) {
        console.log(e);
    });
    target.click();
}

Eventオブジェクトではなく、PointerEventオブジェクトが割り当てられている模様。

PointerEvent インターフェイスは、接触点の形状、イベントを生成した機器の種類、接触面に加えられた圧力の量など、ポインターによって生成された DOM イベントの状態を表します。

-----mdc web docs

クラス図

PointerEventはEventオブジェクトをプロトタイプとして継承しているので(多分)、Eventオブジェクトで指定できる'click'イベントも指定可能。
PointerEventによる発火であればログ出力して画面遷移。

function log(id){
    const target = document.getElementById(id);
    target.addEventListener('click', function (e) {
        if(e.constructor.name === 'PointerEvent'){
            console.log('PointerEvent!!');
        }
    });
    target.dispatchEvent(new PointerEvent('click'));
}

🥰🥰🥰

読めたら 読んでおく

 


バイナリの制御文字と'BOM'でハマった

2023-10-22 11:28:13 | 日記

最近部屋の片づけをしており、大量の本やCDを整理しています。
折角だから捨てる前に全部CDを取り込んでおこう…ということでアルバムを一つ一つasunderから取り込んでいるのですが、面倒な課題にぶつかってしまいました。

1. 曲・アルバム・作曲者の表記揺れが多すぎる問題

昔聞いていた曲には19世紀の古典的な曲が多く、再演・再奏された録音を収録している場合、アルバムが違うだけで「同じ曲なのにタイトルが違う」ということが頻発します。

J.Strauss Ⅱ Wo die Zitronen blühen

→ヨハンシュトラウス2世 シトロンの花咲くところ

→ヨハン・シュトラウスⅡ世 レモンの花咲くところ

→ヨハン・シュトラウス2世 シトロンの花咲く国

曲名が違うだけならそこまで神経質にならずともよいですが、作曲者名に揺れがあると「その作曲者の曲だけ聞きたい」という場合不便を極めます。

ということで、表記揺れを一括で直せるように楽曲ファイルのメタデータを編集できるようにしたいと思います。類似のMP3タグ編集ソフトで同等のことはできるのかもしれませんが、調べていません。どうせ勉強だし。

2. バイナリを読む

とりあえず、AACとかその他の音楽ファイルの形式は考えず、MP3のタグ(ID3V2)を編集することだけ考えます。
タグを編集する場合、バイナリ(0と1で構成された、テキスト以外のデータ)の読み書きを直接行うことになります。

ID3V2タグにはヘッダーも存在しますが、ここでは最初にタグの本体の中身を見ることにします。
バイナリエディタの左側のパネルの各セルには、1バイトの情報が表示されており、右側のパネルにはバイトに対応する文字がエンコードされて表示されています。

言わでものことかもしれませんが、1バイトはここでは16進表記されているので、'54'なら16 * 5 * 4で10進数の84となります。16進数をリテラル表記する際の'0x'という接頭辞をつけて表記するなら、0x54となります。
さて、上掲の画像の中で、エンコードされた文字列を見ていると、「TSSE」や「TRCK」、「TPE1」という大文字の文字列が目につきます。
この文字列からID3V2タグの「フレーム」と呼ばれる、楽曲のメタデータのより具体的な情報を格納する部分が開始します。「TSSE」などは各フレームの名称で、「TSSE」はエンコード設定に関する情報、「TRCK」はトラック番号、「TPE1」は主なるアーティストを指します。

今回は、「TRCK」について見ていきたいと思います。上掲の画像のうち、赤枠で囲った部分がフレーム名の「TRCK」を表す部分。
水色の枠で囲った部分が、フレームの本体サイズを表す部分で、今回は「00 00 00 05」なので、5バイトであることがわかります。本来はSyncsafe Integerという表記法に対応したサイズの計算が必要ですが、今回は割愛。
緑色の枠で囲った部分が、フラグを表す部分ですが、通常は使用されません。
そして、最後に、オレンジの枠で囲った部分が、フレームの本体で、ここにトラック番号が格納されています。サイズは先ほどフレームの本体サイズを表記していた箇所に従って5バイトとなります。

さて、フレームのサイズに従ってトラック番号を表す箇所を見てみると、「01 FF FE 31 00」と記されています。
31 = 0x31はUnicode(文字コードの標準規格)では'1'を指します。バイナリエディタで見ていた曲はアルバムの1曲目なので、正しそうですね。
ただ、0x31の前には、0x01,0xFF,0xFEというバイト列が並んでいます。0x01や0xFFや0xFEには対応する文字が無いようですが…。

3. 0x01,0xFFFEとは

Unicodeエンコーディング(UTF-16またはUTF-8)が使用されているデータ・ファイルには、ファイルの最初の数バイトにバイト順序マーク(BOM)が含まれている場合があります。キャラクタ・セットUTF-16が使用されているデータ・ファイルでは、ファイルの最初の2バイトの値{0xFE,0xFF}は、ファイルがビッグ・エンディアンのデータを含んでいることを示すBOMです。{0xFF,0xFE}という値は、ファイルにリトル・エンディアンのデータが含まれていることを示すBOMです。
https://docs.oracle.com/cd/E57425_01/121/SUTIL/GUID-CFEED713-D459-42F4-A777-7AAA654451AC.html

0xFF,0xFEが使用されている場合、続くデータはリトルエンディアン(=多バイトをメモリに格納する際の方式1つ。リトルエンディアンの場合下位バイトを先に格納する)ことを明示しています。このような記号をBOM(Byte Order Mark)と呼びます。
(ただし、UTF-8のBOMは0xEFBBBFで、これはUTF-8で書き込まれていることを明示するために使われるのであって、今見ているファイルがUTF-8で書き込まれているはずなのに0xFFEが書き込まれているのはなぜなのかはよくわかりません。)

せんずるに、0XFFFEは文字コードとしては定義されない値であり、今回はUTF-8のファイルとしてバイナリデータを扱うので、楽曲に関するメタデータとして考慮する必要はないということです。
ただし、読み込んだバイト列をエンコードする際に弾きだす必要はありますね。

0x01については、unicodeの制御文字のようで、意味としては'START OF HEADING'=ヘッダ開始を表す符号にすぎないようで、これも文字列として扱わないように弾く必要あり。そのほかunicodeには0x000~0x001Fまでの制御文字があるようです。

 


【Java】XMLからオブジェクトを生成する

2023-10-01 22:05:19 | 日記

書くことが思いつかず前の記事から2週間以上空いてしまいました。
前々回まで、StreamAPIの練習として適当に作成していた図書館の貸し出しをモデルにしたプロジェクトを書いていましたが、今回はStreamAPIとは別方向の改修を行いたいと思います。

1.前回までのコード

public class BookList {
        Map<String, Book> bookList = new HashMap<>();

    public void setBookList() {
        bookList.put("こころ", new Book("こころ", "夏目漱石", 200));
        bookList.put("斜陽",new Book("斜陽", "太宰治", 180));
        bookList.put("想像の共同体",new Book("想像の共同体", "アンダーソン", 350));
        bookList.put("近代文学論争",new Book("近代文学論争", "臼井吉見", 300));
        bookList.put("近代天皇像の形成",new Book("近代天皇像の形成", "安丸良夫", 400));
        bookList.put("天皇の肖像",new Book("天皇の肖像", "多木浩二", 190));
        bookList.put("ドストエフスキーの詩学",new Book("ドストエフスキーの詩学", "バフチン", 500));
        bookList.put("『キング』の時代",new Book("『キングの時代』", "佐藤卓己", 530));
       
    }

面倒なのでDBは使わない方針でしたが、そのため、書籍一覧を保持するBookListクラスのオブジェクトは以上のように初期化されていました。

上記のコードでbookListが保持しているBookクラスのオブジェクトには、貸出履歴や貸出回数を管理するメンバ変数が存在しますが、当然のことながら、JVM(Java Virtual Machine)が終了した時点でそれらの変数は解放されてしまいます。
そこで今回は、DBを使わずにXMLファイルを読み込むことでBookListの初期化を行いたいと思います。
XMLファイルを読み込む練習がしたいだけなので、DB使えよ…みたいな指摘はなしで。

2.XMLファイルとは

.xmlファイルとは、ファイル名の拡張子(末尾部分)が「.xml」のファイルで、XML(Extensible Markup Language)で記述されたテキストファイルのこと。ソフトウェアの設定ファイルなどによく見られる。

IT用語辞典 e-Words(https://e-words.jp/w/.xml%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.html)


xmlファイルでは、自分で任意のタグを用意することでデータを定義することができます。
例えば今回改修範囲であるBookクラス(書籍名・著者・ページ数などを保持するクラス)をXMLで表現すると以下のようになります。

<books>
    <book id="0001">
        <title>こころ</title>
        <author>夏目漱石</author>
        <year>1914</year>
        <lentCount>0</lentCount>
        <lastLent></lastLent>
        <pages>250</pages>
    </book>
    <book id="0002">
        <title></title>
        <author>夏目漱石</author>
        <year>1910</year>
        <lentCount>0</lentCount>
        <lastLent></lastLent>
        <pages>270</pages>
    </book>

3.JAXBの利用

さて、XML形式で外出しした書籍一覧情報ですが、これを実際のコード内で活用するには、

  1. XML形式の構文解析
  2. 毎にBookクラスのオブジェクトに変換

という手順を踏む必要があります。
こういった場合に使用できるAPIに、JAXB(Java Architecture XML Binding)があります。

JAXB(Java Architecture XML Binding)は、[…]XMLファイルを読み込むというよりも、「XMLファイルとJavaのオブジェクトを結び付ける」動作をします。JAXBを利用することで、XMLとJavaオブジェクトを相互変換できます。

Java本格入門p.262

JAXBはかつてはJavaの標準ライブラリ(Eclipseのパッケージエクスプローラ内のJRE システムライブラリーに格納されているパッケージ)であったが、現在は削除されているため、自力で導入する必要があります。導入については割愛。

【BookList.java】

   Books bookList = null;
 
   private Books makeBookList() {
       
        try(InputStream is = Files.newInputStream(Paths.get("./src/main/resources/xml/Book.xml"))){
            bookList = JAXB.unmarshal(is, Books.class);
           
        }catch(IOException e) {
            System.err.println(e);
        }
        return bookList;
    }

JAXBクラスのunmarshalメソッド(XMLからJavaオブジェクトへの変換を行うメソッド)を使用しています。引数にInputStreamを渡していますが、InputStreamの代わりにReaderクラスオブジェクトが指定されているオーバーロードもあるので、BufferedReaderでも大丈夫です。
第二引数にはBooksクラスのクラスリテラル(そのクラスを表すClassクラスのオブジェクト)が指定されます。
では、肝心のBooksクラスはどうなっているかというと、

【Books.java】

@XmlRootElement(name = "books")

public class Books {
    private List<Book> bookList;
   
    @XmlElement(name = "book")
    public List<Book> getBookList(){
        return bookList;
    }
   
    public void setBookList(List<Book> bookList) {
        this.bookList = bookList;
    }
   
}

@XmlRootElementは、オブジェクトをXML要素として表現するための注釈で、XMLのルートタグ(最上位の要素)と、ツリー構造で表現されるJavaのオブジェクトの、最上位のクラスを結び付けます。ここでは、Bookオブジェクトを格納するArrayListではなく、クラスに注釈を付与している点に注意しておきます。
@XmlElementも同様に、XML要素とオブジェクトを結び付ける注釈です。
このように、注釈型を用いるだけで、XML要素がJavaオブジェクトにマッピング(関連づけ・割り当て)されます。

4.実際に動かす

先掲のBookList.java内のbookListをwatch式で見てみます。
個人的には、@XmlRootElement注釈はBooksクラスに付与したのに、Booksクラスのメンバ変数であるbookListにバインドされているのが親切なような不気味なような…。

ともかく、XMLの各要素がBook型のリストに格納され、各BookオブジェクトはXMLタグで囲われた値を保持していることがわかります。ただし、

    <book id="0001">
        <title>こころ</title>
        <author>夏目漱石</author>
        <year>1914</year>
        <lentCount>0</lentCount>
        <lastLent></lastLent>
        <pages>250</pages>
    </book>

<book>タグの属性であるidは、Bookクラスのメンバ変数であるidにはバインドされていません。
タグ内の属性をバインドするには、@XmlAttribute注釈型を用います。

    @XmlAttribute(name = "id")
    public int getId() {
        return id;
    }

getterに@XmlAttributeを付与していることに注意します。メンバ変数に直接付与してもバインドされません。注意しましょう。

きちんとidもバインドされています。もっとも、XMLで"0001"だったidはint型の1として扱われ、0埋めではなくなっていますが…。

 


JavaのPathsクラスとURIクラス(Windows)

2023-09-14 01:13:55 | 日記

1.そもそもパスとURIってなんだっけ

パス…ファイルシステムのリソースの在処を示す。 標準のDOSパスの場合、パスは3つの部分から構成される。

  1. ボリュームまたはドライブ文字とそれに続くボリューム区切り記号 (:)。
    =>Cドライブ直下の場合はパスの先頭の"C:"
  2. ディレクトリ名。ディレクトリ区切り文字によって、入れ子になっているディレクトリ階層内でサブディレクトリが分割されます。
    =>ディレクトリ区切り文字はWindowsの場合はバックスラッシュ(\)。
  3. 任意のファイル名。
    =>readme.txtとか。

https://learn.microsoft.com/ja-jp/dotnet/standard/io/file-path-formats

URI

  1. スキーム
    =>http:、ftp:、data:、file:など通信手段を示す
  2. authority =>スキームのあと"//"で始まる部分。ユーザ情報、ホスト、ポートから構成される。 ホストは"www.google.com"、ポートはポート番号。
  3. パス
    =>指定したauthority内のリソースの在処を表す。 上で説明したパスに同じ。
  4. クエリ 指定したauthority内のリソースの在処を表す。 上で説明したパスに同じ。
  5. フラグメント ??

 

2.PathsクラスとURIクラスを利用する

JavaのURIクラスには、上で説明したスキームやクエリを取得するメソッドのほか、URIオブジェクトを生成するcreateメソッドが用意されています。

public static URI create(String str)

パラメータには、URIとして分析可能な文字列を渡します。

2.1. エラーになる表現もある

String pattern = "パスは{0}.\nURIは{1}";
 
URI uri2 = URI.create("file:/pleiades/workspace/JavaStudy/text/sample/test2.txt");
Path path2 = Paths.get(uri2);
System.out.println(MessageFormat.format(pattern,path2.toString() ,uri2.toString()));
 
URI uri3 = URI.create("C:/pleiades/workspace/JavaStudy/text/sample/test2.txt");
Path path3 = Paths.get(uri3);
System.out.println(MessageFormat.format(pattern,path3.toString(), uri3.toString()));
 
URI uri4 = URI.create("./text/test/txt");
Path path4 = Paths.get(uri4);
System.out.println(MessageFormat.format(pattern, uri4.toString(), path4.toString()));
 
URI uri5 = URI.create("file:\\pleiades\\workspace\\JavaStudy\\text\\sample\\test2.txt");
Path path5 = Paths.get(uri5);
System.out.println(MessageFormat.format(pattern, path5.toString(), uri5.toString()));
 

URIクラスのcreateメソッド※を利用して、URIクラスのオブジェクトを作成し、さらに、URIクラスのオブジェクトを利用してPathsクラスのgetメソッドからPathを指定します。
最後に、MessageFormatクラスのformatメソッド※でURIクラスオブジェクトとPathクラスオブジェクトの文字列表現を出力。
適当に4つ並べてみなしたが、この中で正しくPathクラスオブジェクトを取得できるのは1つだけです

※MessageFormat.formatメソッド…第一引数のpatternに変数を埋め込む書式パターン、第二引数以降に埋め込む変数を指定して、メッセージの指定部分のみを変更する。

2.2. 解答編

//パスは\pleiades\workspace\JavaStudy\text\sample\test2.txt.
//URIはfile:///pleiades/workspace/JavaStudy/text/sample/test2.txt
URI uri2 = URI.create("file:/pleiades/workspace/JavaStudy/text/sample/test2.txt");
Path path2 = Paths.get(uri2);
 
//FileSystemNotFoundException:Privider"C"not installed
URI uri3 = URI.create("C:/pleiades/workspace/JavaStudy/text/sample/test2.txt");
Path path3 = Paths.get(uri3);
 
//IllegalArgumentException:missing scheme
URI uri4 = URI.create("./text/test/txt");
Path path4 = Paths.get(uri4);
 
//IllegalArgumentException:Illegal character in opaque part at index 5
URI uri5 = URI.create("file:\\pleiades\\workspace\\JavaStudy\\text\\sample\\test2.txt");
Path path5 = Paths.get(uri5);
 

【失敗例1】

//FileSystemNotFoundException:Privider"C"not installed
URI uri3 = URI.create("C:/pleiades/workspace/JavaStudy/text/sample/test2.txt");
Path path3 = Paths.get(uri3);

ドライブの"C"をスキームとして分析してしまうためエラー。

【失敗例2】

//IllegalArgumentException:missing scheme
URI uri4 = URI.create("./text/test/txt");
Path path4 = Paths.get(uri4);

スキーム(https:など)を指定しない相対パスで生成したURIクラスオブジェクトを引数にしてgetを呼び出すとillegalArgumentExceptionがスローされます。
なお、

Path path4 = Paths.get("./text/test/txt");

のように、URIクラスオブジェクトを利用せず、Pathsクラスのgetメソッドに相対パスを文字列で指定した場合は、正しくリソースのパスを指定することができます。
相対パスで指定した場合、ルートはプロジェクトのフォルダになります(上掲の例だとJavaStudy)。

【失敗例3】

URI uri5 = URI.create("file:\\pleiades\\workspace\\JavaStudy\\text\\sample\\test2.txt");
Path path5 = Paths.get(uri5);

windowsのディレクトリ区切り文字であるバックスラッシュ(/)を利用しているパターン。
"Illegal character in opaque part at index 5"
=>"opaque"はURIのデータ格納部分。バックスラッシュで区切りを表現してはいけない。

【成功例】

//パスは\pleiades\workspace\JavaStudy\text\sample\test2.txt.
//URIはfile:/pleiades/workspace/JavaStudy/text/sample/test2.txt
URI uri2 = URI.create("file:/pleiades/workspace/JavaStudy/text/sample/test2.txt");
Path path2 = Paths.get(uri2);

パスはwindowsの区切り文字であるバックスラッシュで表現されていることがわかります。また、URIのスキーム部はパスとしては表現されないこともわかります。
なお、Ubuntuで実行した場合、

//パスは/pleiades/workspace/JavaStudy/text/sample/test2.txt.
//URIはfile:/pleiades/workspace/JavaStudy/text/sample/test2.txt

パスの区切り文字はスラッシュとなります。