WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

JavaScript で全角数字を半角に変換

by WebSurfer 2017年9月24日 14:12

先の記事「全角数字を半角に変換」は C# のコードで書きましたが、それと同等なコードを JavaScript で実装してみました。

JavaScript で全角数字を半角に変換

JavaScript の String.prototype.replace(), String.fromCharCode(), String.prototype.charCodeAt() の使い方の備忘録ですので、記事としては面白くないと思います。スミマセン(汗)

上記のメソッドを利用して、テキストボックスに全角数字交じりの文字をタイプしてフォーカスを外すと全角数字 ⇒ 半角数字に変換するサンプルを以下に書いておきます。上の画像はその実行結果です。

<%@ Page Language="C#" %>

<!DOCTYPE html>

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>JavaScript で全角数字 ⇒ 半角数字</title>
  <script src="/Scripts/jquery-1.10.2.js"></script>
  <script type="text/javascript">
  //<![CDATA[
    var zen2han = function(str) {
      str = str.replace(/[0-9]/g, function (s) {
        return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
      })
      return str;
    }

    $(function () {
      $("#text1").on("change", function () {
        var str = $(this).val();
        $(this).val(zen2han(str));
      });
    });
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
    <h2>JavaScript で全角数字 ⇒ 半角数字</h2>
    <p>入力してフォーカスを外すと全角数字 ⇒ 半角数字に変換</p>
    <input id="text1" type="text" style="width: 350px;" />
  </form>
</body>
</html>

テキストボックスの change イベントでの書き換えのコーディングが面倒なので、その部分は jQuery を使ってますが、変換するためのメソッドは上記の JavaScript のメソッドを使っています。

replace メソッドは、第一引数の正規表現 /[0-9]/g にマッチした文字列の一部または全てを、第二引数に指定される文字列で置き換えた新しい文字列を返します。

第二引数には新しい部分文字列を生成するために実行される関数を指定することができます。上の例では匿名関数 function (s) { ... } を使っています。

全角数字の UTF-16 コードから 0xFEE0 を引くと半角数字の UTF-16 コードになるので、匿名関数 function (s) { ... } は JavaScript の charCodeAt メソッドと fromCharCode メソッドを利用して全角数字を半角数字の文字列に変換します。

具体的には (1) charCodeAt メソッドでインデックス 0 に位置する文字の UTF-16 コードを表す整数を取得、(2) それから 0xFEE0 を引いて半角数字の UTF-16 コードを取得、(3) fromCharCode メソッドで指定された UTF-16 値の文字列を取得して戻り値として返しています。

Tags: , ,

JavaScript

日付時刻と JSON 文字列

by WebSurfer 2017年8月27日 15:01

JavaScript の Date オブジェクトや .NET Framework の DataTime オブジェクトの JSON 文字列への変換について調べましたので備忘録として書いておきます。

ASP.NET ajax によるパース結果

JSON の紹介よると、その記事に書いてある value に直接設定できるのは string, number, object, array, true, false, null だけということですので、JavaScript の Date オブジェクトや .NET Framework の DataTime オブジェクトは string に変換して JSON 文字列に設定せざるを得ません。

変換は自力で行わなくても、例えば、JavaScript なら JSON.stringify() メソッド、.NET Framework なら DataContractJsonSerializer クラスを利用すれば自動的に変換してくれます。

その他、JavaScriptSerializer クラス、ASP.NET Web サービス、WCF、ASP.NET Web API でも DateTime 型を自動的に文字列に変換してくれますが、それぞれどのような結果になるかを以下にまとめておきます。

No. 方法 結果
(1) JSON.stringify() 2017-02-01T03:15:45.000Z
(2) DataContractJsonSerializer \/Date(1503727168573+0900)\/
(3) WCF 上記 (2) の結果と同じ
(4) JavaScriptSerializer \/Date(1030806000000)\/
(5) ASP.NET Web サービス 上記 (4) の結果と同じ
(6) ASP.NET Web API 2017-08-26T15:39:32.6330349+09:00

以下に上記 (1) ~ (6) のそれぞれの検証方法や備忘録として残しておいた方がよさそうな情報を書いておきます。

(1) JSON.stringify() メソッド

ECMAScript 2017 Language Specification (ECMA-262, 8th edition, June 2017) の 20.3.1.16 Date Time String Format のセクションによると、ISO 8601 Extended Format 即ち:

YYYY-MM-DDTHH:mm:ss.sssZ

という形式に変換することになっているそうです。

全てのブラウザで確認したわけではありませんが、IE11, Chrome 60.0.3112.113, Firefox 55.0.3 では確かにその通りになるのを確認しました。

なお、JavaScript の Data オブジェクトはローカルタイムとして扱われまずが、JSON.stringify() メソッドで文字列に変換すると UTC になるので注意してください(YYYY-MM-DDTHH:mm:ss.sssZ の末尾の Z は UTC であることを意味します)。

例えば、以下のコードを実行すると、

var dateTime = new Date(2017, 1, 1, 12, 15, 45);
var dateObject = { DateTime: dateTime };
var dateJson = JSON.stringify(dateObject);

dateJson は {"DateTime":"2017-02-01T03:15:45.000Z"} という JSON 文字列になります。

Date コンストラクタの引数で月を表す整数値は 0 (1月) から 11 (12月) なので、1 ⇒ 02 という結果になっています。時間 12 が 03 になるのは東京ローカルタイムが UTC に変換されるためです。

(2) DataContractJsonSerializer クラス

DataContractJsonSerializer クラスを使うと、別の記事「w2ui Grid」に書きましたように、.NET Framework の DateTime 型は:

\/Date(1503727168573+0900)\/

のような文字列に変換されます。

この例の最初の数字 1503727168573 は UTC 1970 年 1 月 1 日午前 0 時からカウントしたミリ秒、+0900 はオプションで、ローカルタイムの UTC からの時差だそうです。

詳しくは以下の MSDN ライブラリの記事「スタンドアロン JSON のシリアル化」の「高度な情報 / DateTime ワイヤ形式」のセクションの説明を見てください。

(3) WCF

先の記事「WCF と jQuery AJAX」の CarService.svc.cs のコードに以下のメソッドを追加して試してみました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;

namespace WebApplication1
{
    // web.config で aspNetCompatibilityEnabled="true" と設定され
    // ているので [AspNetCompatibilityRequirements(...)] が必要
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = 
        AspNetCompatibilityRequirementsMode.Allowed)]
    public class CarService
    {
        // ・・・中略・・・

        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.Wrapped)]
        public DateTimeJsonTest GetToday()
        {
            DateTimeJsonTest today = new DateTimeJsonTest();
            today.Today = DateTime.Now;
            return today;
        }
    }

    // ・・・中略・・・

    [DataContract]
    public class DateTimeJsonTest
    {
        [DataMember]
        public DateTime Today { set; get; }
    }
}

ブラウザから上記コードの GetToday を要求すると以下のような JSON 文字列が返されます:

{"GetTodayResult":{"Today":"\/Date(1503727168573+0900)\/"}}

すなわち、上記「(2) DataContractJsonSerializer クラス」の場合と同じになります。(内部的に DataContractJsonSerializer クラスを使って変換しているのであろうと思います。未確認ですが・・・)

(4) JavaScriptSerializer クラス

ASP.NET MVC の Controller クラスの Json メソッドの内部で .NET Framework のオブジェクトを JSON 文字列にシリアライスずるために JavaScriptSerializer クラスが用いられているそうです。

先の記事「jQuery.ajax で JSONP」に JavaScriptSerializer クラスを使ってシリアル化を行っている例がありますので、これに少し手を加えて検証してみました。

その記事のコードを見てください。Controller の Jsonp アクションメソッドでは Address テーブルから AddressID, AddressLine1, City フィールドのデータを取得して匿名クラスのオブジェクトを作り、それを JavaScriptSerializer クラスの Serialize メソッドで JSON 文字列にシリアライズしています。

Address テーブルには ModifiedDate という datetime 型のフィールドがありますのでそれを匿名クラス address に追加してシリアライスしてみました。

Serialize(address) の結果は以下のような文字列となります。

{"AddressID":25,"AddressLine1":"9178 Jumping St.","City":"Dallas","ModifiedDate":"\/Date(1030806000000)\/"}

即ち、DateTime 型の変換結果は上記「(2) DataContractJsonSerializer クラス」とほぼ同じで、オプションのローカルタイムの UTC からの時差(上の例では +0900)が省略された形になっています。

(5) ASP.NET Web サービス

先の記事「ASP.NET AJAX と Web サービス」のコードに以下のメソッドを追加して試してみました。

<%@ WebService Language="C#" Class="CarService" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
using System.Collections.Generic;
using System.Linq;

// ・・・中略・・・

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class CarService : WebService
{
    // ・・・中略・・・

    [WebMethod]
    public DateTimeJsonTest GetToday()
    {
        DateTimeJsonTest today = new DateTimeJsonTest();
        today.Today = DateTime.Now;
        return today;
    }
}

public class DateTimeJsonTest
{
    public DateTime Today { set; get; }
}

上記 GetToday() メソッドを、要求ヘッダに Content-Type: application/json を追加して POST 要求すると(注)以下のような JSON 文字列が返されます:

{"d":{"__type":"DateTimeJsonTest","Today":"\/Date(1503712714770)\/"}}

(注)ASP.NET Web サービスの制約上 POST 要求しないとエラーになります。Content-Type を指定しない、または application/x-www-form-urlencoded とすると XML 形式で返されます。XML の場合は形式は上記とは異なり 2017-08-26T10:36:27.0809765+09:00 のようになります。

即ち、上記「(4) JavaScriptSerializer クラス」と同じ形式の文字列に変換されます(+0900 も同じく省略されています)。

ASP.NET AJAX を利用すると、\/Date(1503712714770)\/ という形式の文字列は、Date 型の JavaScript オブジェクトに自動的に変換されます。具体例は以下の通りです。

記事「ASP.NET AJAX と Web サービス」の「(2) JavaScript (097_ASPNETAjaxAndWebService.js)」のセクションに記載されているコードを以下のように書き換えます。

var serviceProxy;

// プロキシの初期化とコールバック関数の設定
function pageLoad() {
    serviceProxy = new CarService();
    serviceProxy.set_defaultSucceededCallback(Succeeded);
    serviceProxy.set_defaultFailedCallback(Failed);
}

// ボタンクリックで呼び出されるサービスメソッド  
function getToday() {
    serviceProxy.GetToday();
}

// AJAX 通信が成功したときに呼び出され、戻ってきたデータ
// を処置するコールバック関数。
// 引数 data は JSON 文字列ではなく、DateTimeJsonTest 型
// オブジェクト。それから Today プロパティで取得できるの
// は、文字列ではなく Date 型に変換済みのオブジェクト。
function Succeeded(data) {
    $('#output').empty();
    var now = data.Today;  // now は Date 型オブジェクト
    $('#output').append(
        "<p>" + now.getFullYear() + "年" +
        (now.getMonth() + 1) + "月" + now.getDate() + "日" +
        now.getHours() + "時" + now.getMinutes() + "分" +
        now.getSeconds() + "秒</p>"
    );
}

// ・・・以下略・・・

上の getToday() メソッドを「(3) apsx ページ (097_ASPNETAjaxAndWebService.aspx) 」のセクションに記載されているコードと同等な aspx ページから呼び出すと、Date 型オブジェクトが上記コードの now に取得でき、この記事の一番上の画像のとおり表示されます。

MSDN ライブラリの記事「スタンドアロン JSON のシリアル化」の「高度な情報 / DateTime ワイヤ形式」のセクションの説明によると、"UTC からの時差を示す部分(例: +0900)が指定されていない場合、時刻は UTC として逆シリアル化されます" とありますが、上記コードの結果はローカル時刻になります。

ASP.NET AJAX に代えて jQuery ajax を使う場合は自動的に Date オブジェクトには変換してくれませんので、自力でコードを書いて変換することになります。

具体例は以下の通りです。数字の部分を抜き出して JavaScript の Date コンストラクタの引数として渡し、Date オブジェクトを生成すれば ASP.NET AJAX と同じ結果が得られます。

function getDateByJqueryAjax() {
  $.ajax({
    type: "POST",

    url: "097_jQueryAjaxAndWebService.asmx/GetToday",

    contentType: "application/json; charset=utf-8",

    success: function (data) {
      if (data.hasOwnProperty('d')) {
          data = data.d;
      }
      $('#output').empty();
      var re = /^\/Date\(([0-9]+)\)\/$/;
      var results = re.exec(data.Today);
      var now = new Date(Number(results[1]));
      $('#output').append(
        "<p>" + now.getFullYear() + "年" +
        (now.getMonth() + 1) + "月" + now.getDate() + "日" +
        now.getHours() + "時" + now.getMinutes() + "分" +
        now.getSeconds() + "秒</p>"
      );
    },

    error: function (jqXHR, textStatus, errorThrown) {
      $('#output').text('textStatus: ' + textStatus +
                  ', errorThrown: ' + errorThrown);
    }
  });
}

(6) ASP.NET Web API

Visual Studio 2010 Professional のテンプレートを使って自動生成される ASP.NET Web API アプリのコードに、以下のメソッドを追加して試してみました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using WebApi1.Models;

namespace WebApi1.Controllers
{
    public class HeroesController : ApiController
    {
        public DateTimeJsonTest Get()
        {
            DateTimeJsonTest today = new DateTimeJsonTest();
            today.Today = DateTime.Now;
            return today;
        }

        // ・・・中略・・・
    }
}

jQuery ajax を利用して上の Get() メソッドを呼び出すと以下のような JSON 文字列が返されます:

{"Today":"2017-08-26T15:39:32.6330349+09:00"}

DateTime 型の文字列への変換は上の「(5) ASP.NET Web サービス」の場合の XML 形式の応答の場合と同じです。

これは ASP.NET Web API 独自実装なのか、ISO 8601 などに準拠しているのかは調べ切れてません。

なお、この形式で、JavaScript の Date 型へのパースは IE11, Chrome 60.0.3112.113, Firefox 55.0.3 のいずれも可能です。例えば、jQuery ajax を使うと、success オプションに設定したコールバックの引数に上の JSON 文字列を JavaScript オブジェクトに変換して代入してくれますので、それから以下のようにして Date オブジェクトにパースできます。

success: function (data, textStatus, jqXHR) {
    var now = new Date(data.Today);
    $('#heroes').append(
        "<p>" + now.getFullYear() + "年" +
        (now.getMonth() + 1) + "月" + now.getDate() + "日" +
        now.getHours() + "時" + now.getMinutes() + "分" +
        now.getSeconds() + "秒</p>"
    );
},

Tags: , ,

AJAX

IE11 と Referer

by WebSurfer 2017年8月13日 17:02

ASP.NET 4 の Web Forms アプリで、クエリ文字列に日本語を含めて要求をかけた場合、Windows 10 の IE11 と Edge ではポストバックの際 Referer が送信されないという話を書きます。(ASP.NET 4.5 では問題は解消されています。ASP.NET 3.5 以下は未確認です)

Fiddler によるキャプチャ

そもそもの原因は Windows 10 の IE11 や Edge ではなく、ASP.NET 4(3.5 以前も?・・・未確認です)が応答の from 要素に設定する action 属性にあるようです。

例えば、IE11 のアドレスバーに直接以下の URL(クエリ文字列は "日本語" を UTF-8 のパーセントエンコードしたものです)を入力して直接ページを GET 要求したとします。

http://aspnet4site/test.aspx?P1=%e6%97%a5%e6%9c%ac%e8%aa%9e

そうすると、ASP.NET により応答の form 要素の action 属性に設定される URL は以下のようになります。

action="./test.aspx?P1=%u65e5%u672c%u8a9e"

つまり、要求 URL のクエリ文字列の "日本語" は UTF-8 のパーセントエンコーディングなのに、応答の form 要素の action 属性に設定されるのは Unicode のパーセントエンコーディングになってしまいます。そこに問題があるようです。

具体的には下にアップした簡単なサンプルのページ test.aspx で再現できます。

上の画像は、UTF-8 でパーセントエンコーディングした "日本語" をクエリ文字列に含めた URL を IE11 のアドレスバーに直接入力して test.aspx を要求し、表示されたページで 2 回ポストバックを行ったときの Fiddler によるキャプチャ画像です。

上の画像の #1 が初回の要求・応答で、#3 が 1 回目のポストバック、#4 が 2 回目のポストバックの際の要求・応答です。

問題は 2 回目のポストバックのときで、ブラウザからリファラが送信されません。Edge も同様で 2 回目のポストバックの際はリファラは送信されません。

どのような動きになるかと言うと・・・

#1 の初回要求に対して ASP.NET が返す応答の form 要素の action 属性に同じページの URL が設定されますが、URL に含まれるクエリ文字列の "日本語" は Unicode のパーセントエンコーディング %u65e5%u672c%u8a9e となってしまいます。

なので、1 回目のポストバックの時は要求 URL に含まれるクエリ文字列は %u65e5%u672c%u8a9e となります。上の画像の #3 を見てください。

ただし、1 回目のポストバックの際にブラウザから送信されるリファラは、最初の要求の URL(すなわちアドレスバーに直接入力した URL)になります。つまり、クエリ文字列の "日本語は" UTF-8 のパーセントエンコーディングなので問題なくリファラとして送信されます。問題は 2 回目以降です。

2 回目のポストバックの時(上の画像の #4)は、1 回目のポストバックの際の要求 URL(すなわちクエリ文字列の "日本語" が %u65e5%u672c%u8a9e)をリファラとして送信するはずですが、Windows 10 の IE11 と Edge では送信されません。3 回目、4 回目も同様です。

Microsoft の公式文書が見つけられないので想像ですが、Windows 10 の IE11 と Edge ではセキュリティ対策が強化され、URL に UTF-8 として解釈できないコードがあるとリファラを送信しないということではないかと思っています。

ちなみに、Windows 7 の IE11 では %u65e5%u672c%u8a9e となってしまっていてもリファラは送信されるそうです(聞いた話で未検証・未確認)。Chrome 60.0.3112.90, Firefox 54.0.1 では %u65e5%u672c%u8a9e でも送信されることは確認しました。

やはり、URL にはクエリ文字列を含めて ASCII 文字のみ使用することで徹底するのがよさそうです。

サンプルページ test.aspx

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.UrlReferrer != null)
        {
            Label1.Text = Request.UrlReferrer.ToString();
        }
        else
        {
            Label1.Text = "無し";
        }
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <h1>Page B</h1>
    <asp:Button ID="Button1" runat="server" Text="Button" />
    <br />
    UrlReferrer: 
    <asp:Label ID="Label1" runat="server"></asp:Label>
    </form>
</body>
</html>

Tags: , ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2017年10月  >>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar