42309


rubyの正規表現を扱う際の挙動についてよくわからないことがあります。  
HTMLのパースをしているのですが、

[code]

    Regexp.new("スタックオーバーフロー")
    => /スタックオーバーフロー/
    Regexp.new("<h1>スタックオーバーフロー</h1>")
    => /<h1>\u30B9\u30BF\u30C3\u30AF\u30AA\u30FC\u30D0\u30FC\u30D5\u30ED\u30FC<\/h1>/
    
[/code]

というような、上はUTF-8そのままで正規表現化できるのですが下の場合はunicodeがそのまま出てきてしまうという動きになっており、なぜこのようになるのかわかりません。  
理想としては

[code]

    => /<h1>スタックオーバーフロー<\/h1>/
    
[/code]

という正規表現がほしいのですが、どのようにすればよいでしょうか？  
あまり文字コードや文字セットなどに詳しくないので見当ハズレなことを聞いているのかもしれませんが、よろしくお願いします。


CC BY-SA 3.0
2018-03-09T07:29:11.753
42260
2018-03-11T09:23:12.423
27675
question
ruby 正規表現
rubyの正規表現を扱う際の文字セットについて


### 何がおきているか

`ri Regexp#to_s` を見ると以下のように表示されます。

[code]

    = Regexp#to_s
    
    (from ruby core)
    ------------------------------------------------------------------------------
      rxp.to_s   -> str
    
    ------------------------------------------------------------------------------
    
    Returns a string containing the regular expression and its options (using the
    (?opts:source) notation. This string can be fed back in to Regexp::new to a
    regular expression with the same semantics as the original. (However,
    Regexp#== may not return true when comparing the two, as the source of the
    regular expression itself may differ, as the example shows).  Regexp#inspect
    produces a generally more readable version of rxp.
    
      r1 = /ab+c/ix           #=> /ab+c/ix
      s1 = r1.to_s            #=> "(?ix-m:ab+c)"
      r2 = Regexp.new(s1)     #=> /(?ix-m:ab+c)/
      r1 == r2                #=> false
      r1.source               #=> "ab+c"
      r2.source               #=> "(?ix-m:ab+c)"
    
[/code]

特に、 `Regexp#== may not return true when comparing the two` が重要だと考えていて、`to_s`
メソッドによって返されるのは、 `Regexp.new`
に引数として与えた時に、同じ文字列セットに一致するような正規表現を返すような文字列である、以外に期待してはいけないと考えられます。

### 今回ほしかったもの

おそらく、今回欲しいのは `Regexp#source` だと思います。

[code]

    regex = Regexp.new("スタックオーバーフロー")
    puts regex.source
    # => スタックオーバーフロー
    
    regex = Regexp.new("<h1>スタックオーバーフロー</h1>")
    puts regex.source
    # => <h1>スタックオーバーフロー</h1>
    
[/code]


CC BY-SA 3.0
2018-03-10T07:45:22.590
42291
2018-03-10T07:45:22.590
754
42260
answer


>
[code]

>     Regexp.new("<h1>スタックオーバーフロー</h1>")
>     =>
> /<h1>\u30B9\u30BF\u30C3\u30AF\u30AA\u30FC\u30D0\u30FC\u30D5\u30ED\u30FC<\/h1>/
>  
[/code]

これはirbやpryで上記コードを実行した結果か`p`の類いで表示した結果だと思います。表現上\uxxxx形式になっているだけで、渡した文字列に対する正しい正規表現オブジェクトが生成されてます。

[code]

    Regexp.new("<h1>スタックオーバーフロー</h1>") == /<h1>スタックオーバーフロー<\/h1>/
    => true
    
[/code]

こうなってしまう理由はよくわかりませんが、`/`を`/regexp/`形式に含めるにはエスケープが必要なので、巻き添えで非ASCII文字もエスケープされてるんではないかなと想像します。

なおHTMLを正規表現でパースするのはたいていの場合よくない方法です。Nokogiriという定番のHTML/XMLパーサライブラリがあるので、そちらを使うことも検討した方がよいとおもいます。


CC BY-SA 3.0
2018-03-11T01:18:43.527
42309
2018-03-11T01:18:43.527
5793
42260
answer


irbは式の評価結果に対して`inspect`メソッドを呼び出して得た文字列を出力します。つまり、そのような表示になっているのは[`Regexp#inspect`](https://docs.ruby-
lang.org/ja/latest/method/Regexp/i/inspect.html)の動作です。  
※
irbのコマンドオプションや`conf.inspect_mode`に代入することで`to_s`を使う等に変更できます。詳しくは[irbのドキュメント](https://docs.ruby-
lang.org/ja/latest/library/irb.html)を確認して下さい。  
※ `p`も同様に`inspect`を使用しますので、`p`を使った場合も同じになります。

どうしてそうなるかはソースコードを見る必要があります。`Regexp#inspect`の通常処理で文字列の生成を行っているのは[rb_reg_expr_str()](https://github.com/ruby/ruby/blob/ruby_2_5/re.c#L357)になります。この関数では、"/"が含まれていない場合はそのまま出力します。しかし、"/"が含まれている場合は、前後を"/"で囲む関係上"\/"とエスケープする必要があるため、分岐した別処理になります。問題はこの分岐した処理で、"/"以外の通常の文字は[rb_str_buf_cat_escaped_char](https://github.com/ruby/ruby/blob/ruby_2_5/string.c#L5721)で処理を行うと言うことです。rb_str_buf_cat_escaped_charではASCII文字以外は全てエスケープ(\uや\xを使う)するため、日本語の部分はエスケープされる事になります。  
※ 実際はさらに細かい処理をしています。上はだいたいの動作概要ですので、ご注意下さい。

注意して欲しいのは、Regexpの中に保存されている正規表現の文字列(`Regexp#source`で確認できる)では"/"が"\/"にエスケープされていないと言うことです。このエスケープは`/.../`というリテラル表記のみで必要なものであるため、内部処理としてはエスケープしておく必要がありません。しかし、`Regexp#inspect`ではわかりやすいリテラル表記になるようにするため、出力時のエスケープ処理が必要になっており、その副作用で、ASCII以外は全てエスケープするとなったということです。

"/"をエスケープするには、一旦文字列をバラバラに解釈していかなくてはいけないため、間違った分割をしないようにこのような処理になっているのかも知れません。ただ、エスケープせずに出す方法がないわけではないでしょうから、[Ruby
Issue](https://bugs.ruby-lang.org/)に投げるなり、自分でパッチを作って送りつけるなりすると直してくれるかも知れません。


CC BY-SA 3.0
2018-03-11T09:23:12.423
42321
2018-03-11T09:23:12.423
7347
42260
answer