Textwellで正規表現を検索 → マッチ結果のハイライト → 置換プレビュー → 置換

正規表現で検索・置換したいことがよくあるので、TextwellからTextforceにデータを送り、Textforceの機能を使っていました。できればTextwell内で完結すべく、

  • 正規表現を与えるとマッチする部分をハイライトしてくれて、
  • g、m、i のフラグを動的に付け外しできて、
  • ()でキャプチャしたサブマッチを使った置換も可能で、
  • 置換後のイメージもハイライトしてくれて、
  • iPhoneでもiPadでも使えて、
  • ポートレイト(縦)でもランドスケープ(横)でも違和感のないデザインの、

アクションの作成をめざしました。

マッチ前こんな感じで、1行目にマッチさせたい正規表現を書き、2行目以降に本文を書きます。1行目は「行頭から任意の文字列+スペース」という意味。

起動直後アクションを実行すると、1行目を検索窓に取り込んでマッチを行い、結果をハイライトします。
1行目が検索系、2行目が置換系機能。正規表現は記号をたくさん使って読みづらいのでmonospaceフォントで表示。それに合わせてボタン群もmonospaceです。
g、i、m のボタンで正規表現のフラグをトグルします。ピンクがON、グレーがOFF。gフラグ(該当する全結果にマッチ)はよく使うのでデフォルトで付けています。今回は行ごとにこの正規表現をマッチさせたいので"m"(^や$を行単位でマッチ)をタップしてから"Srch"をタップすると……

Srchによる再ハイライトそのときの検索窓(とフラグ)の内容がハイライトに反映されます。
文章だけでなくhtmlなどの編集にも使えるよう、内部的にhtmlエスケープしています。
また()でのキャプチャにも対応していて、しかも何組でも使えます。ただしハイライト結果を表示する際には、最初のサブマッチ箇所にハイライト処理を施し、その結果に対してまたハイライト処理を施し……と処理を重ねていくので、ときどきうまく表示されないことがあります。できるだけ壊れないように工夫してはみましたが、完全に治す方法を見つけていません。
マッチした全体がハイライトされます。つまりabcという文字列を(a)b(c)でマッチさせると、abc全体がハイライトされます。

Landscapeちなみに検索窓のテキストフィールドは画面幅に合わせて伸び縮みするので、長い正規表現を書くときは適宜iphoneを横にして確認できます。
iPhoneでもiPadでも、それぞれの縦でも横でもレイアウトが崩れず、できれば検索窓の幅をいつも最大限大きく取りたい。display:tableを使ったりボタン群を描画してから幅を測って再調整したり、このあたりの実装に苦労しました。

置換プレビュー今回は、見出し(スペースより手前)を【】で囲み、さらに行末に句点(。)を打ちます。そこでしかるべく正規表現を書いて"Test"をタップすると、こんな感じで置換後のイメージを表示します。
左図のように、サブマッチの結果は$1、$2のように扱えます。

終了問題なければ"Go!"をタップ。テキストビューの2行目以降を置換し、戻ります。"Go!"をタップする前に左上の×をタップすれば、テキストビューは変更されません。もし置換後に戻したい場合は標準のUNDO機能(機をシェイクするなど)でOK。

<html>
<head>
<meta name=viewport content=initial-scale=1,user-scalable=no>
<title>RegExp Search & Replace</title>
<style>
body {
  font-family: 'Hiragino Kaku Gothic ProN';
}
input, button, a {
  display: inline-block;
  font-family: monospace;
  font-size: large;
  margin: 0.1em;
  padding: 0.5em;
  border: 1px solid #333;
  line-height: 1.5em;
}
input{
  width: 100%;
}
button {
  -webkit-appearance: none; /* Erase orig. look */
  border-radius: 0.2em;
}
a {
  border-color: #ddd; /* Overwrite */
  background-color: #ddd;
  border-radius: 0.2em;
}
.matched {
  background-color: yellow;
}
.preview {
  background-color: pink;
}
.active {
  background-color: pink;
  border-color: pink;
}
.inBox {
  display: table;
  table-layout: fixed;
  width: 100%;
}
.textElastic {
  display: table-cell;
  width: 100%;
}
.btnsFixed{
  display: table-cell;
  padding-left: 5px;
  width: 12em; /* will be resized, but is necessary to be defined */
}
</style>
</head>
<body>
<div class='inBox'>
  <div class='textElastic'>
    <input type='text' id='sTxt'>
  </div>
  <div class='btnsFixed'>
    <span class='btns'><!-- To calculate width of buttons -->
    <a id='gBtn' class='active'>g</a><!-- g is ON by default
    --><a id='mBtn'>m</a><!-- Comments are to kill whitespace
    --><a id='iBtn'>i</a><!--
    --><button id='sBtn'>Srch</button>
    </span>
  </div>
</div>
<div class='inBox'>
  <div class='textElastic'>
    <input type='text' id='rTxt'>
  </div>
  <div class='btnsFixed'>
    <span class='btns'>
    <button id ='prvBtn'>Test</button><!--
    --><button id ='goBtn'>Go!</button>
    </span>
  </div>
</div>
<p id='theP'></p>
<script>
// Temp. placeholder for highlighting <span>s.
// Any (unusual) combination of HTML- & RegExp-safe characters will work
var S = '__%~%~%__', regS = new RegExp( S, 'g' ),
E = '__%~%__', regE = new RegExp( E, 'g' );

// Adjust the width of divs for buttons
var bF = document.getElementsByClassName( 'btnsFixed' ),
b = document.getElementsByClassName( 'btns' );
bF[0].style.width = b[0].offsetWidth;
bF[1].style.width = b[1].offsetWidth;

// Copy 1st line of text-view to searchbox. put sample if none.
$( 'sTxt' ).value = ( T.line( 1 ) ) ? T.line( 1 ) : '^([a-z]{1,3})';

// Highlight matches on startup
highlight();

// Change flag
$( 'gBtn' ).addEventListener( 'touchstart', setSearchFlag, false );
$( 'mBtn' ).addEventListener( 'touchstart', setSearchFlag, false );
$( 'iBtn' ).addEventListener( 'touchstart', setSearchFlag, false );

// Highlight mathes
$( 'sBtn' ).addEventListener( 'click', highlight, false );

// Replacement preview
$( 'prvBtn' ).addEventListener( 'click',
  function(){
    $( 'theP' ).innerHTML = escapeHTML(
      T.lines( 2, null )
      .replace( getSearchRegExp(), S + $( 'rTxt' ).value + E )
    )
    .replace( regS, '<span class="preview">' )
    .replace( regE, '</span>' )
    .replace( /\n/g, '<br>' );
  }, false
);

// Replace text-view
$( 'goBtn' ).addEventListener('click',
  function(){
    T( 'replace',
      {
        text: T.line( 1 ) + '\n' + T.lines( 2, null )
        .replace( getSearchRegExp(), $( 'rTxt' ).value )
      }
    );
  }, false
);

/*
 * Functions
 */
// Highlight matches
function highlight(){
  $( 'theP' ).innerHTML = escapeHTML(
    T.lines( 2, null ).replace( getSearchRegExp(), S + '\$&' + E )
  )
  .replace( regS, '<span class="matched">' )
  .replace( regE, '</span>' )
  .replace( /\n/g, '<br>' );
}

// Toggle regexp flags
function setSearchFlag( e ){
  e.currentTarget.className = ( e.currentTarget.className ) ? '' : 'active';
}

// Create regexp for searchbox
// Flag is regarded as ON if classname is assigned
function getSearchRegExp(){
  return new RegExp( $( 'sTxt' ).value,
    ( $( 'gBtn' ).className ? 'g' : '' )
    + ( $( 'mBtn' ).className ? 'm' : '' )
    + ( $( 'iBtn' ).className ? 'i' : '' )
  );
}

// HTML escape
function escapeHTML(str) {
  return str.replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;')
  .replace(/"/g, '&quot;')
  .replace(/'/g, '&#039;');
}

// Shortner
function $(e) {
  return document.getElementById(e);
}
</script>
</body>
</html>

Import Textwell ActionRegExp S&R

(修正: 2015-06-30)


友達に伝える
タグ: Textwell
作成: 2015/6/27 by:koji
更新: 2015/6/30 by:koji


日付に曜日を入れるTextwellアクション

Tech

1タップで定型句を貼り付けるTextwellアクション"Stamp"

新規ユーザー登録(無料)

  • メールニュース[週刊起-動線]の購読
  • コメントなどの投稿
  • ココロミの利用
  • 一部コンテンツの購読

ログイン

コメント

タグ(キーワード)