Arduinoでネットワーク通信する際に必要だったのですが、どれも凝った実装ばかりで、ブラックボックスなままコピペするのも癪だったので、勉強も兼ねて簡単な実装をしてみました。
日本語には対応してません、悪しからず。
テーブル
エンコード/デコードを行う際の、文字コード識別テーブルを用意します。
エンコード用
const char encTable[16] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
エンコード対象識別用
const char safeTable[128] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
/* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 2 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 3 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
/* 4 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 5 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
/* 6 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 7 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
};
デコード用
const char decTable[128] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
/* 0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 1 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 2 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 3 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
/* 4 */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 5 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 6 */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 7 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
エンコーダ/デコーダ
エンコーダ
String urlEncode(const char* msg){
String encoded = "";
while(*msg != '\0'){
if(safeTable[*msg]){
encoded += *msg;
}
else{
encoded += '%';
encoded += encTable[*msg >> 0x04];
encoded += encTable[*msg & 0x0F];
}
msg++;
}
return encoded;
}
[0-9a-zA-Z]
ならば変換は不要なので、そのままencoded
へ追加します。
それ以外の文字は、文字コード値の上位4bitと下位4bitをそれぞれテーブルに掛け、16進表記へ変換しています。
例: '#' (0x23)
の場合
-
safeTable[0x23] == 0
の為、変換対象 -
0x02
と0x03
に分割 encTable[0x02] == '2'
encTable[0x03] == '3'
- 出力は
%23
となる
デコーダ
String urlDecode(const char* msg){
String decoded = "";
while(*msg != '\0'){
if(*msg == '%'){
char dec1 = decTable[*(msg + 1)];
char dec2 = decTable[*(msg + 2)];
if(dec1 != -1 && dec2 != -1){
decoded += (dec1 << 4) + dec2;
}
msg += 3;
}
else{
decoded += *msg;
msg++;
}
}
return decoded;
}
文字が%
でなければ、そのままdecoded
へ追加し次の文字へ移ります。
%
がヒットしたら、1個先と2個先の文字コード値をそれぞれテーブルに掛け、1個先の値を4ビット左シフト後に2個先の値を足して出力します。
%1z
など、%
の先に16進値以外の文字列が来た場合は、何も出力せずスキップします。
例: '#' ("%23")
の場合
-
%
の1個先は'2'
decTable['2'] == 2 ('2' == 0x32)
-
2
を4ビット左シフトすると0x20
-
%
の2個先は'3'
decTable['3'] == 3 ('3' == 0x33)
-
0x20 + 3 == 0x23
となる
まとめ
普段は便利なライブラリに頼りがちですが、こうして自力で実装してみると、こんな短いコードでも勉強になりました。
余談: '\0'
とは
char*
型の配列は、自身の配列終了を識別する為、暗黙的に配列末尾へ\0
が付与されます。
ポインタを増やして行き、ポインタが指すアドレスの中身が\0
ならば、そこで配列は終了(文字列終了)と判別可能なのです。