SSブログ

[Visual Basic]HTMLオブジェクトをクリップボードに転送する [プログラミング]

C++版はこちら
文字列だけをクリップボードに転送するコードとか、逆にクリップボードにある
HTML Formatからテキストだけをコピーするやり方は調べればいくらでも出てくるが、
HTMLデータをクリップボードにコピーするやり方についてはほとんど見当たらなかったので、
VBで書いてみる。

調べてもこういうのとかでサンプルコードを全然書かなかったり
まともに解説している人もいないので、
作成手順も含めてちゃんとサンプルコードを書き散らして簡単に解説しておく。

仕様上単体のテキストのみを囲うタグにしか対応していないが、
設計変更すれば複数にもできると思う。
簡単なhrefとfontでしか動作は確認していないが、
おそらくよっぽど大きかったり変なデータでなければ大体は行けるはず。

手順
※VS使ってプログラム作ったことある人は②から

①Visual Studio Express 2017をダウンロード・インストール
以下は2018/12/01時点でのリンクなので、今後変わる可能性あり。
Visual Studio Express | 今すぐ Visual Studio Community
このページがものすごく鬱陶しいのがExpressではなく
Communityを勧めてくるレイアウトになっている。
惑わされずページ下部の「それでも Visual Studio Express を希望されますか?」から
exeファイルをダウンロード。
Express 2017 for Windows Desktop

10GBくらい容量消費するので、空き領域の多いディスクにインストールすることが推奨。
また、起動時にサインインを求めてくるかもしれないが、特にしなくても今回の範囲であれば
普通に使える。


②プロジェクトを作成
ファイル>新しいプロジェクト>Visual Basic>Windows デスクトップ>コンソール アプリ (.Net Framework)
を選択。
適当な名前をつける(既定では実行ファイルの名前になる)。
フレームワークのバージョンは4.5あたりでも動く。
01.png

②参照を追加
初期状態だとクリップボード操作に必要なフレームワークが含まれていないので、
プロジェクトに追加する。

ソリューション エクスプローラー>参照を右クリックし、参照の追加。
02.png

アセンブリ>フレームワーク>System.Windows.Forms にチェックを入れてOK。
03.png

③ソースコードを貼り付け
モジュール名などを変更していなければ、そのままModule1.vbを以下のコードに差し替える。
※関数化しなかったりハードコーディングを放置しているので、良い設計にはなっていない。

Imports System.Windows
Imports System.Text

Module Module1
Sub Main(ByVal args() As String)
If args.Length = 0 Then
Forms.MessageBox.Show("引数が1つもありません。" & vbCrLf & "処理を中止します。")
ElseIf args.Length < 3 Then
Forms.MessageBox.Show("引数が少ないです。" & vbCrLf &
"空白区切りで3つ指定してください。" & vbCrLf & Args2Str(args))
Else
If args.Length > 3 Then
Forms.MessageBox.Show("引数の数が多いです。" & vbCrLf &
"処理は実行しますが4つ目以降は無視されます。" & vbCrLf & Args2Str(args))
End If
Dim padNum = 8
Dim padStr = "0"
Dim replacingLengthStr = "0".PadLeft(padNum, padStr)
Dim dispText = args(1)
Dim htmlFragment = String.Join("", args)
Dim htmlVersion = "Version:0.9" & vbCrLf
Dim startHtmlPosition = "StartHTML:" & replacingLengthStr & vbCrLf
Dim endHtmlPosition = "EndHTML:" & replacingLengthStr & vbCrLf
Dim startFragmentPosition = "StartFragment:" & replacingLengthStr & vbCrLf
Dim endFragmentPosition = "EndFragment:" & replacingLengthStr & vbCrLf
Dim contextStart = "<html><body>" & vbCrLf & "<!--StartFragment-->"
Dim contextEnd = "<!--EndFragment-->" & vbCrLf & "</body>" & vbCrLf & "</html>"

Dim descriptionLength = UTF8ByteLength(htmlVersion & startHtmlPosition & endHtmlPosition &
startFragmentPosition & endFragmentPosition)
Dim contextStartLength = UTF8ByteLength(contextStart)
Dim htmlFragmentLength = UTF8ByteLength(htmlFragment)

startHtmlPosition = startHtmlPosition.Replace(
replacingLengthStr,
descriptionLength.ToString().PadLeft(padNum, padStr))

endHtmlPosition = endHtmlPosition.Replace(
replacingLengthStr, (descriptionLength + contextStartLength + htmlFragmentLength +
UTF8ByteLength(contextEnd)).ToString().PadLeft(padNum, padStr))

startFragmentPosition = startFragmentPosition.Replace(
replacingLengthStr,
(descriptionLength + contextStartLength).ToString().PadLeft(padNum, padStr))

endFragmentPosition = endFragmentPosition.Replace(
replacingLengthStr,
(descriptionLength + contextStartLength + htmlFragmentLength).ToString().PadLeft(padNum, padStr))

Dim clipData = htmlVersion & startHtmlPosition & endHtmlPosition & startFragmentPosition &
endFragmentPosition & contextStart & htmlFragment & contextEnd

Dim clipObj = New Forms.DataObject
clipObj.SetData(Forms.DataFormats.Html, True, Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(clipData)))
clipObj.SetText(dispText)
Forms.Clipboard.Clear()
Forms.Clipboard.SetDataObject(clipObj, True)

Console.WriteLine("生成されたFragment:""" + htmlFragment + """")
End If

End Sub

Function UTF8ByteLength(ByVal target As String)
Return Encoding.UTF8.GetBytes(target).Length
End Function

Function Args2Str(ByVal args() As String)
Dim str = ""
For i = 0 To args.Length - 1
str &= "第" & (i + 1) & "引数:""" + args(i) + """" & vbCrLf
Next
Return str.TrimEnd()
End Function

End Module

04.png


④実行ファイルの生成
ビルド>構成マネージャー>アクティブ ソリューション構成 をReleaseに変更する。
05.png

ビルド>ソリューションのビルド もしくはF7を押してビルド実行。
ビルドが成功していれば、ワークスペース\プロジェクト名\プロジェクト名\bin\Release
にプロジェクト名.exeが生成されているはず。
06.png

⑤実行(クリップボードにHTMLデータを生成)
4で生成されたexeファイルを、任意の場所においておく。
コマンドプロンプトを立ち上げ、実行ファイルを画面にドラッグドロップ or フルパスを入力。
第1引数にHTML開始タグ、第2引数に囲むテキスト、第3引数にHTML終了タグ
を入力してEnter。
例) "<a href=""https://www.google.co.jp/"">" "HTML テスト" "</a>"
※各引数をダブルクォーテーションで囲えば、空白や<>といった文字が
エスケープシーケンスではなく文字列として認識してもらえる。
 ダブルクォーテーションを文字列としたい場合は、「"」を1個に付き「""」とする。
 引数の数が3つあれば「生成されたFragment: ・・・」とコンソールに出力され、
 HTML Formatでクリップボードにコピーされる。
 また、第2引数のHTMLタグは対応していないので、改行(<br>)などを囲むテキストに入れても反映されない。
07.png

HTML貼り付けに対応したアプリでの貼り付けの例:Wordの場合
HTMLデータとして貼り付けが行われる。
08.png


HTML貼り付けに対応していないアプリの例:メモ帳の場合
第2引数で指定した囲みテキストだけがペーストされる。
09.png


※引数の指定の仕方を間違えて3個にならなかった場合にはメッセージを表示する仕様としている。

10.png

⑤'vbsで実行

VBScriptではHTMLを転送するために必要なモジュールをインポートする方法がない
(調査した範囲では)ので、
もしVBSで実行したい場合は間接的に上記VBで作ったバイナリを呼ぶことで一応動作する。

以下のコードでexecPathにexeのフルパスを指定して、適当なファイル名.vbsで保存。

Dim execPath
execPath = "C:\フルパス\ファイル名.exe"
Set ws = CreateObject("WScript.Shell")

If Wscript.Arguments.Count > 0 Then
Set args = CreateObject("System.Collections.ArrayList")
For Each arg In Wscript.Arguments
args.Add Replace(arg, " ", """ """)
Next
ws.run execPath & " " & Join(args.ToArray, " "), 0, true
Else
WScript.Echo "引数が1つもありません。"
End If

11.png

execと同様に、コマンドプロンプトから呼び出し、同様の引数の指定の仕方で実行する。
12.png


成功時にはコンソールには何も出ない。
また、ダブルクォーテーションはVBScriptの仕様上、
文字列として認識されないようなので使えない。

以下のようにシングルクォーテーションで代用する必要がある。
"<a href='https://www.google.co.jp/'>" "HTML テスト" "</a>"



ここから以下は、クリップボードの仕様やソースコードについて簡単に触れる。

通常、ブラウザ等からHTMLデータをコピーすると、HTML FormatとCF_TEXTなどといった
テキストベースのデータがクリップボードのオブジェクトに格納される。
つまり、普通のテキストデータのコピーのときとは違い、複数の異なるオブジェクトが
クリップボードに転送される。
※正確にはテキストデータのコピーでも複数オブジェクトはあるが、ややこしくなるので端折る。

以下はGooogleのリンクをブラウザでコピーした場合のクリップボードの内容
13.png
14.png

HTML貼り付けが可能なアプリはデータフォーマットが"HTML Format"と
指定しているオブジェクトを探し、
オブジェクトの文字列をバイトデータに変換して貼り付けるように動くようである。
逆にHTML非対応のアプリはCF_TEXTなどのオブジェクトを探し、
そのまま文字列を貼り付けるように動くようである。
つまりアプリごとに見に行く先を変えているように思える。

HTMLの方の実際の文字列は以下のようになっている。
Version:0.9
StartHTML:00000224
EndHTML:00000502
StartFragment:00000258
EndFragment:00000466
SourceURL:https://www.google.co.jp/search?num=100&source=hp&ei=zx4CXNyuOcaO8wXP_rawAw&q=google&btnK=Google+%E6%A4%9C%E7%B4%A2
<html><body>
<!--StartFragment--><a href="https://www.google.co.jp/" onmousedown="return rwt(this,'','','','1','AOvVaw0-P_L5nSi7IPHfyZFbFj0T','','2ahUKEwi5xoDX9f3eAhVlMH0KHev8COMQFjAAegQIBBAC','','',event)"><h3 class="LC20lb">Google</h3></a><!--EndFragment-->
</body>
</html>

詳しいフォーマットについては解説しないが、HTMLデータや実際のHTML部分(=Fragment)が
何バイト目~何バイト目まであるかというのをStartHTMLなどのキーで指定し、
貼り付け側のアプリで解析できるようにしている。
今回のサンプルプログラムでは、まず8桁の0埋めで初期化し、
実際にデータを結合した後にどこからどこまでが
各データかを算出して、結果を0埋めした部分と置き換えている。

以上の通り、HTMLとテキストの両方の貼り付けに対応させるためにはHTML Formatと
テキストフォーマットの2種類用意する必要がある。

今回のサンプルコードではDataObjectに2つのデータをSetすることで対応している。
SetData(DataFormats.Html, True, … でHTMLデータのバイト配列をSetし、
SetText(dispText)でテキストデータをSetしている。

ちなみにSetDataだけしか行わないとHTML非対応の方に何も貼り付けられない。
SetDataでHTMLオブジェクトのみをセットしたときはあくまでHTML FormatだけがSetされ、
CF_TEXTなどは作られない。
なので、SetDataしただけだとメモ帳とかにペーストしても何も起こらない。
だから両方のSetが必要になると思われる。

この辺はClipboardのオブジェクト仕様などを正しく理解していないとわからないことだが、
あまり取り上げているページは見当たらなかった。


以上。

参考にしたサイト
クリップ見え窓 クリップボード/D&Dデータ内容ダンプソフト(開発者専用ツール)
書いて忘れる: クリップボードにhtml形式でコピー
.net - How to set HTML to clipboard in C#? - Stack Overflow
c# - How to copy both - HTML and text to the clipboard? - Stack Overflow

nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:[必須]
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。