20200518のHTMLに関する記事は4件です。

ZONeチャレンジ第一弾をJavaScriptでハックした話

要約

キャンペーンサイトへPCからスマホサイズでアクセスし、WebブラウザのコンソールからJavaScriptでフリック入力を再現した。

ソースコード: https://github.com/r-yanyo/flick-challenge/blob/master/newfuga.js

キャンペーンサイト: https://cp.zone-energy.jp/challenge/kuma/

キャンペーンサイトのconsoleにソースコードを貼り付けると(途中まで)フリック入力が自動的に入力されます(途中までの理由は最後の方に書いてます)。

PCでスマートフォン専用サイトにアクセスする

PCでアクセスした図

PCでキャンペーンページを普通に開くと、スマートフォン専用サイトである旨が表示されます。
このままではPCでフリック入力ゲームで遊ぶことが出来ません。

同じWebサイトでも、PCでアクセスした時とスマートフォンでアクセスした時にWebページのデザインが違う経験があるのではないでしょうか?これは、デバイスに依存してWebサイトの表示を変更する場合に、一般的にメディアクエリ が基準として使用されるからです。

メディアクエリはユーザが使用しているデバイスのViewport(ウィンドウのサイズ)を基準として、Webサイトの表示を変更する方法です。つまり、実際にPCやスマートフォンのどちらを使用しているかは検証しておらず、ウィンドウのサイズだけで判断しています。よって、何らかの方法でウィンドウサイズを変更すれば、PCでもスマートフォン専用ページにアクセスすることが出来ます。

Google Chromeでは、デベロッパーツールを開き、Device Toolbarを使用することでウィンドウのサイズをスマートフォンサイズに変更できます。

フリック入力をプログラミングで再現する

大きく分けて工程は3つあります。
1. ひらがなボタンのElementを取得
2. 取得したElementに対してフリック入力を再現
3. 答えを機械が認識できる形で入力&変換

1. ひらがなボタンのElementを取得

ボタンをインスペクト

デベロッパーツールで見てみると、どうやら.css-xx1yz5-buttonStyleというクラスが各「あかさたなはまやらわ」ボタンに付与されているようです。
さらに、そのボタンをタップすると出現するフリック入力部分("あ"をタップした場合は「あいうえお」)には.css-2rzvda-wingStyleというクラスが付与されています。

このことから、例えばキーボードの「さ」を取得したいときは、
[0]: "あ", [1]: "か", [2]: "さ"
なので

document.getElementsByClassName("css-xx1yz5-buttonStyle")[2]

フリック入力の「す」を取得したいときは、「す」は上から13番目なので

document.getElementsByClassName("css-2rzvda-wingStyle")[12]

とします。

ひらがな以外の文字に関しては、デベロッパーツールでその要素が上から何個目にあるかを見れば分かります。

2. 取得したElementに対してフリック入力を再現

初めはClickイベントでフリック入力が再現されると思ったのですが、
document.getElementsByClassName("css-xx1yz5-buttonStyle")[2].click()
のようにしても何も起こりませんでした。

そこで次に、TouchEvent を試したところ、キーボードのボタンがタッチされるイベントが発生しました。よって、このTouchEventを使うことにしました。

touchEvent.ts
//elmはフリックの起点となるelement。「す」を入力するときは「さ」
//moveElmはフリック先のelement。「す」を入力するときは「す」

touchMove = function (elm, moveElm) {
  if (!moveElm) return;
  rect = moveElm.getBoundingClientRect();
  X = rect.x + rect.width / 2;
  Y = rect.y + rect.height / 2;
  let touch = new Touch({
    identifier: Date.now(),
    target: elm,
    clientX: X,
    clientY: Y,
    force: 1,
    pageX: X,
    pageY: Y,
    radiusX: 41.66666793823242,
    radiusY: 41.66666793823242,
    rotationAngle: 0,
  });
  touchMoveEvent = new TouchEvent("touchmove", {
    bubbles: true,
    touches: [touch],
  });
  elm.dispatchEvent(touchMoveEvent);
};

TouchEventに関してはあまり詳しくなかったため、実際にイベントを発生させて必要な部分だけを変更する戦略を取りました。
結果的には clientX,clietnY,pageX,pageY の数値をフリック先のelementの座標に変えることで、フリック入力を再現することが出来ました。

フリック入力をするときにTouchEventで発生するイベントは、touchstart -> touchmove -> touchend の順で発生します。また、それぞれのイベントの発生元は全てフリックの起点となるelementで発火します。1

//elmはフリックの起点となるelement。「す」を入力するときは「さ」
//moveElmはフリック先のelement。「す」を入力するときは「す」
function touchEmulate(startElement, moveElm) {
  touchStart(elm);
  touchMove(elm, moveElm); //イベント発火元はelm
  touchEnd(elm);
}

3. 答えを機械が認識できる形で入力&変換

「ひらがな」から数字への変換は愚直に一つ一つ入力しました。

"あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもや(ゆ)よらりるれろわをんー 、。?!".split("")

例えば「す」をフリック入力したい場合は、

flick.ts
//「さ」ボタン
const button = document.getElementsByClassName("css-xx1yz5-buttonStyle")[2];
//「す」フリック
const flick = document.getElementsByClassName("css-xx1yz5-buttonStyle")[12];

このように「あ」〜「ん」や「!?」の記号などは入力できるようになりました。
ここで問題となるのが、濁音や半濁音、促音(「が」、「ぱ」、「っ」など)の入力です。これらはフリック入力を2回行う必要があります。例えば、「が」であれば、「か」をフリックした後に「゛」をフリックする必要があります。
これを解決する方法は色々あるのですが、今回は全部条件分岐で書きました(具体的にはソースコード参照)。2

「これで晴れてフリック入力がエミュレート出来た・・・」と思ったのですが、もう1つ問題があります。
フリック入力では同じボタンを2回押すと次の文字が表れるという特性上、連続で同じ文字を入力しようとすると正しい文字が入力されません。
例えば、「けたたましく」を入力しようとすると「けちましく」になってしまいます。
この問題はキーボードにある「→」ボタンを入力すれば解決できますが、当時の私は気付かなかったため手動で入力を分けて解決しました(ダサい・・・)。

おわりに

以上でフリック入力をエミュレートすることが出来ました。便宜上「ハックした」と書いていますが、JavaScriptでフリック入力をエミュレートしただけです。
キャンペーンは終了してしまいましたが、まだゲームで遊ぶことはできるようなので、是非実際に試してみてください。


  1. フリック入力周りの動作は実際のイベントを複製して改変したため、あまり理解していない部分が多々あります。 

  2. 「が」を「か」と「゛」に分割する方法は、もっと良い方法があると思います。ひらがなの部分も含め、文字コードを上手く使えばもっと綺麗に書けるかも? 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ファイルをダウンロードさせる方法

リンククリックでファイルをダウンロードさせる方法についてまとめました。

右クリック→リンク先を保存ではなく、リンクボタンをクリックしただけでダウンロードさせるという方法です。

画像などの単体ファイルをダウンロードさせる

index.html
<a href="ファイル名" download>ファイルをダウンロード</a>

リンクしたファイルがダウンロードされます。

ダウンロードされるファイルに名前をつけたい場合

index.html
<a href="ファイル名" download="ダウンロードした後のファイル名">ファイルをダウンロード</a>

リンクするファイルのファイル名と、ダウンロードされるファイルのファイル名を変えたい場合は、download=""にファイル名を指定するとできます。

フォルダ内をまとめてダウンロードする

サーバーに上がっているファルダの中身をフォルダごとzipにまとめてダウンロードする場合、zip化するphpを叩いてダウンロードする、がシンプルかなと思います。

phpのZipArchiveクラスを使います。

zip.php
<?php

$zipName = $_GET['dl']; // 圧縮するフォルダ名

$dist = $zipName.'.zip'; // 生成する圧縮ファイル名
$path = 'フォルダまでのパス'.$zipName; // 圧縮するフォルダのパス

//DLするファイルのフォルダ構成を維持するか否か 0:しない/1:する
//維持する場合、ダウンロードしたファイルは $path の構成になります
$filePath = 0;
//DLファイルが1つの場合、フォルダに入れるか否か 0:いれない/1:いれる
$folderIn = 1;

$zip = new ZipArchive();
$zip->open($dist, ZipArchive::CREATE | ZipArchive::OVERWRITE);

if (is_dir($path)) {
    $files = array_diff(scandir($path), ['.', '..']);
    $filesNum = count($files);
    foreach ($files as $file){
        if($filePath >= 1){ //フォルダ構成を維持する
            $zip->addFile($path.'/'.$file);
        }else{ //維持しない
            if(($folderIn >= 1) && ($filesNum <= 1)){ //ファイルが1つの時、フォルダに収める場合の処理
                $localFile = $zipName.'/';
            }else{
                $localFile = '';
            }
            $zip->addFile($path.'/'.$file, $localFile.$file);
        }
    }
}
$zip->close();

// ストリームに出力
header('Content-Type: application/zip; name="' . $dist . '"');
header('Content-Disposition: attachment; filename="' . $dist . '"');
header('Content-Length: '.filesize($dist));
echo file_get_contents($dist);

// 一時ファイルを削除しておく
unlink($dist);

exit;

?>
index.html
<a href="zip.php?dl=フォルダ名">ファイルをダウンロード</a>

圧縮するファイルにフォルダを指定していても、フォルダ内のファイルが1つしかない場合、解凍するとファイルだけ、ということが起きるので、$zip->addFileの第2引数でフォルダ内に収めるようにしています。

ダウンロードされたファイルに特定の名前などを指定したい場合も、$zip->addFileの第2引数で指定してあげると可能です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaはExcelを画像、html、XPS、XML、CSV、PostScript、PCLに変換します

Excel文書の日常の運用では、さまざまな作業場面のニーズを満たすために、文書の形式を変換する必要があることがよくあります。この記事では、Javaプログラムでコードを使用してExcelを画像、html、XPS、XML、CSVに変換する方法を示します。

使用ツール: Free Spire.XLS for Java(無料版)

JARファイルのインポート方法
方法1: Free Spire.XLS for Javaパッケージをダウンロードして解凍し、Spire.Xls.jarパッケージをlibフォルダーからJavaアプリケーションにインポートします。
方法2: mavenを使用している場合は、pom.xmlファイルに次の依存関係を追加する必要があります。

<repositories>
        <repository>
            <id>com.e-iceblue</id>
            <name>e-iceblue</name>
            <url>http://repo.e-iceblue.com/nexus/content/groups/public/</url>
        </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>e-iceblue</groupId>
        <artifactId>spire.xls.free</artifactId>
        <version>2.2.0</version>
    </dependency>
</dependencies>

Javaコード例:

import com.spire.xls.*;

public class ExcelToImg {
    public static void main(String[] args) {
        //Excelワークシートを読み込む
        Workbook wb = new Workbook();
        wb.loadFromFile("input.xlsx");

        //ワークシートを取得
        Worksheet sheet = wb.getWorksheets().get(0);

        //画像としてExcelワークシートを保存
        sheet.saveToImage("ToImg.png");
        //Excelで指定したセルのデータ範囲を画像として保存する
        //sheet.saveToImage("ToImg2.png",8,1,30,7);

        //ExcelをHTMLとして保存
        sheet.saveToHtml("ToHtml.html");

        //ExcelをXPSとして保存
        sheet.saveToFile("ToXPS.xps", String.valueOf(FileFormat.XPS));

        //ExcelをCSVとして保存
        sheet.saveToFile("ToCSV.csv", String.valueOf(FileFormat.CSV));

        //ExcelをXMLとして保存
        sheet.saveToFile("ToXML.xml", String.valueOf(FileFormat.XML));

        //ExcelをPostScriptとして保存
        sheet.saveToFile("ToPostScript.postscript", String.valueOf(FileFormat.PostScript));

        //ExcelをPCLとして保存
        sheet.saveToFile("ToPCL.pcl", String.valueOf(FileFormat.PCL));

    }
 }

ドキュメントの変換結果:
convert.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 319日目

100日チャレンジの319日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
319日目は、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む