Pythonでクリップボードをリンク付き(HTML形式)加工する。

# はじめに
　クリップボード操作をするPythonライブラリは```pyperclip```が有名だが、このライブラリは文字ベースでしか扱えず、リンク付きのHTML形式で扱うことができない。この記事では```win32clipboard```を使ってHTML形式のクリップボードを扱う方法をまとめる。シチュエーションはSharepoint等のURLコピーしたクリップボードデータを、リンク付きのHTMLデータとして加工することを想定する。Windows限定であることは注意。

# 目次
+ [クリップボードのHTML形式ってどういうフォーマット？](#クリップボードのhtml形式ってどういうフォーマット)
+ [win32clipboard を使う。](#win32clipboardを使う)
+ [コピーしたURLリンクを加工する例。](#コピーしたurlリンクを加工する例)
　
## クリップボードのHTML形式ってどういうフォーマット？
　URLコピーしたデータを、Excel、PowerPointやTeams等に貼り付けると文字リンク付きのデータになる。この文字リンク付きのクリップボードデータがどういうものか？は、[クリップ見え窓](https://www.officedaytime.com/clipmm/)を使うと確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1299802/4ddb723a-ff11-aeb6-4531-2e672995f572.png)

　```https://qiita.com/timeline```をHTML形式にすると下記のようになっている。本体は```<a href= ~ </a>```の部分で、```<!--StartFragment-->```と```<!--EndFragment-->```で囲まれている。先頭の4行の10桁の数字は、それぞれのオフセット位置を10進数で表記したものである。このフォーマットの詳細はMicrosoftのサイトにまとまっている。
https://learn.microsoft.com/ja-jp/windows/win32/dataxchg/html-clipboard-format

```html:クリップボードHTML形式
Version:0.9
StartHTML:0000000105
EndHTML:0000000244
StartFragment:0000000141
EndFragment:0000000208
<html>
<body>
<!--StartFragment--><a href="https://qiita.com/timeline">タイムライン - Qiita</a><!--EndFragment-->
</body>
</html>
```
[戻る](#目次)


## win32clipboardを使う。
　では、本題の```win32clipboard```について。```pip install pywin32```でモジュールをインストール。名称が違うので注意。

### クリップボード取得
```win32clipboard.GetClipboardData()```でクリップボード取得する。前後にOpen/Closeの処理が必要。この操作で、テキストベースのクリップデータを取得できる。
```python:クリップボード取得
import win32clipboard

win32clipboard.OpenClipboard()
clipdata = win32clipboard.GetClipboardData()
win32clipboard.CloseClipboard()

#クリップデータ確認
print(clipdata)
```

### クリップボード設定
```win32clipboard.SetClipboardData()```で設定する。クリップボード取得と同じく前後にOpen/Closeの処理が必要。また設定前の情報をクリアするためEmptyも実施する。Emptyは必須でないが、実施しておいた方が無難。パラーメータを設定することで、クリップボードに設定するフォーマットを選択できる。```win32clipboard.CF_UNICODETEXT```とすることで日本語のマルチバイトをTEXT形式で設定ができる。
```python:クリップボード設定(TEXT形式)
import win32clipboard

#clipdataにクリップボードに設定したい文字列を入れる。
clipdata = 'テスト'
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()	
win32clipboard.SetClipboardData(win32clipboard.CF_UNICODETEXT, clipdata.encode('UTF-8'))
win32clipboard.CloseClipboard()
```

HTML形式でクリップボードに設定する場合は```CF_HTML```というパラメータがないので、代わりに
```win32clipboard.RegisterClipboardFormat('HTML Format')```を設定する。設定するHTMLデータは [クリップボードのHTML形式ってどういうフォーマット？](#クリップボードのhtml形式ってどういうフォーマット)のフォーマットで記述が必要。「 https://qiita.com/ 」のリンク文字を 「Qiitaのリンク」という文字にする例は下記の通り。```clipboard_to_html_format()```は各種ヘッダやオフセットの計算をする自作関数。


```python:クリップボード設定(HTML形式)
import win32clipboard

#------------------------------
# HTML形式に変換する
#------------------------------
def clipboard_to_html_format(in_html_data):
	#HTML形式用の前後データ
	header     = 'Version:0.9\nStartHTML:0000000000\nEndHTML:0000000000\nStartFragment:0000000000\nEndFragment:0000000000\n'
	html_start = '<html><body><!--StartFragment-->'
	html_end   = '<!--EndFragment--></body></html>'
	
	#オフセット計算
	offset_s_html = '{:010d}'.format(len(header))											# StartHTML
	offset_e_html = '{:010d}'.format(len(header + html_start + in_html_data + html_end))	# EndHTML
	offset_s_frag = '{:010d}'.format(len(header + html_start))								# StartFragment
	offset_e_frag = '{:010d}'.format(len(header + html_start + in_html_data))				# EndFragment

	#オフセットデータ書き換え
	header = 'Version:0.9\nStartHTML:'+offset_s_html+'\nEndHTML:'+offset_e_html+'\nStartFragment:'+offset_s_frag+'\nEndFragment:'+offset_e_frag+'\n'
	
	#結合
	return header + html_start + in_html_data + html_end


#------------------------------
# main
#------------------------------
#「https://qiita.com/」のリンク文字を「Qiitaのリンク」とする
clipdata = '<a href="https://qiita.com/">Qiitaのリンク</a>'

#html形式に変更
clipdata = clipboard_to_html_format(clipdata)

# html形式で設定
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()	
win32clipboard.SetClipboardData(win32clipboard.RegisterClipboardFormat('HTML Format'), clipdata.encode('UTF-8'))
win32clipboard.CloseClipboard()
```
TEXT形式とHTML形式両方設定したい場合は```win32clipboard.SetClipboardData()```を2行並列して記述すればよい。
[戻る](#目次)


## コピーしたURLリンクを加工する例。
コピーしたURLを下記のように　「リンク」と「URL」併記したクリップボードに加工する例。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1299802/7d166ffa-3496-6f1e-67c1-239c348e921d.png)

<details><summary><b>👆のPythonコードはこちら。</summary><div>

```python:clipboard_html.py
import win32clipboard	#クリップボード処理 pip install pywin32
import urllib.parse		#URLのデコード処理

#------------------------------
#URLの日本語デコード
#------------------------------
def url_decode(in_url):
	return urllib.parse.unquote(in_url)

#------------------------------
# HTML形式に変換する
#------------------------------
def clipboard_to_html_format(in_html_data):
	#HTML形式用の前後データ
	header     = 'Version:0.9\nStartHTML:0000000000\nEndHTML:0000000000\nStartFragment:0000000000\nEndFragment:0000000000\n'
	html_start = '<html><body><!--StartFragment-->'
	html_end   = '<!--EndFragment--></body></html>'
	
	#オフセット計算
	offset_s_html = '{:010d}'.format(len(header))											# StartHTML
	offset_e_html = '{:010d}'.format(len(header + html_start + in_html_data + html_end))	# EndHTML
	offset_s_frag = '{:010d}'.format(len(header + html_start))								# StartFragment
	offset_e_frag = '{:010d}'.format(len(header + html_start + in_html_data))				# EndFragment

	#オフセットデータ書き換え
	header = 'Version:0.9\nStartHTML:'+offset_s_html+'\nEndHTML:'+offset_e_html+'\nStartFragment:'+offset_s_frag+'\nEndFragment:'+offset_e_frag+'\n'
	
	#結合
	return header + html_start + in_html_data + html_end

#------------------------------
# クリップボードに設定
# cliptype     ：'HTML'(HTML形式とTEXT形式)/'TEXT'(TEXT形式のみ)
# clipdata_text：TEXT形式データ
# clipdata_html：HTML形式データ
#------------------------------
def clipboard_set(clipdata_text, clipdata_html='', cliptype='TEXT'):
	win32clipboard.OpenClipboard()
	win32clipboard.EmptyClipboard()
	
	if cliptype == 'HTML':
		win32clipboard.SetClipboardData(win32clipboard.RegisterClipboardFormat('HTML Format'), clipdata_html.encode('UTF-8'))
		win32clipboard.SetClipboardData(win32clipboard.CF_UNICODETEXT, clipdata_text.encode('UTF-8'))
	elif cliptype == 'TEXT':
		win32clipboard.SetClipboardData(win32clipboard.CF_UNICODETEXT, clipdata_text.encode('UTF-8'))
	else:
		print('The format is not defined.')
	
	win32clipboard.CloseClipboard()

#------------------------------
# クリップボードから取得
# cliptype     ：'HTML'(HTML形式)/'TEXT'(TEXT形式のみ)
#------------------------------
def clipboard_get(cliptype='TEXT'):
	win32clipboard.OpenClipboard()
	
	if cliptype == 'HTML':
		clipdata = win32clipboard.GetClipboardData(win32clipboard.RegisterClipboardFormat('HTML Format'))
	elif cliptype == 'TEXT':
		clipdata = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
	else:
		clipdata = 'The format is not defined.'

	win32clipboard.CloseClipboard()
	
	return clipdata


#------------------------------
# main
#------------------------------

#クリップボードから取得し日本語デコード
url_data = url_decode(clipboard_get())

#下記のようになるようHTML記述
#---------------
#リンク
#http://xxxxxxxx
#---------------
htmldata = '<a href="'+url_data+'">リンク<br>'+url_data+'</a><br>'

#クリップボード設定
clipboard_set(url_data, clipboard_to_html_format(htmldata), 'HTML')
```
</div></details>


[戻る](#目次)