質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.34%

特定の文字列Aの間にある指定した文字列BをカウントしてAに追加

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,715

flat

score 607

HTMLの形をしたテキスト(すべて文字列なのでDOMやNodeなどは存在しない)にあるnbspをカウントしてp要素に該当する文字列にクラスとして追加したいのですが、この様に文字列の部分的な処理を連続して行うにはどうしたら良いでしょうか?

ちなみに、この処理はMeryというテキストエディタのマクロとして利用するつもりなのですが、マクロ機能でJavaScriptがそのまま使える様なので、テキストエディタの事は特に考慮せず、普通のJavaScriptの質問として見て頂いて構いません。

<!-- HTMLの形をしていますが実際にはテキスト(文字列)です -->
<!-- beforeはdiv直下のみで後は全てafter -->
<div>
&nbsp;
<p>段落</p>
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
<p>段落</p>
&nbsp;
&nbsp;
</div>

<div>
&nbsp;
&nbsp;
<p>段落</p>
&nbsp;
&nbsp;
&nbsp;
&nbsp;
<p>段落</p>
&nbsp;
</div>

<div>
<p>段落</p>
&nbsp;
<p>段落</p>
&nbsp;
&nbsp;
&nbsp;
</div>


↑を↓にしたい。

<!-- HTMLの形をしていますが実際にはテキスト(文字列)です -->
<!-- beforeはdiv直下のみで後は全てafter -->
<div>
&nbsp;
<p class="before-1 after-6">段落</p>
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
<p class="after-2">段落</p>
&nbsp;
&nbsp;
</div>

<div>
&nbsp;
&nbsp;
<p class="before-2 after-4">段落</p>
&nbsp;
&nbsp;
&nbsp;
&nbsp;
<p class="after-1">段落</p>
&nbsp;
</div>

<div>
<p class="after-1">段落</p>
&nbsp;
<p class="after-3">段落</p>
&nbsp;
&nbsp;
&nbsp;
</div>
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+3

/<div[^>]*>([&nbsp;|\s| ]*)([\s\S]*)<\/div>/ig

でdivを分割して、(ついでに先頭のスペース群をキャッチ)

/<p[^>]*>[^<]+<\/p>([&nbsp;|\s| ]*)/ig

それをまた分割して、(ついでに後方のスペース群をキャッチ)
それぞれスペースをカウントして<pの後(or class=""内)にクラスを追加する感じでしょうか。


追記:

書き方が悪いのだと思いますがRegExpで取得すると空白が無視されてしまうので未完成ですが、splitやreplaceで処理すれば何とかなると思います。

動くサンプル:https://jsfiddle.net/mLwwq58g/1/

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/11 17:05

    回答して下さりありがとうございます。
    正規表現が苦手なのでとても勉強になります。

    >それぞれスペースをカウントして<pの後(or class=""内)にクラスを追加する

    実はここの処理がずっと思いつかなかった事が質問に踏み切った理由だったりします…。

    キャンセル

  • 2016/06/11 18:53

    少しコードを書いてみました。正直未完成なのですが、方向性はわかるかと思います。(回答に追記しています)

    キャンセル

  • 2016/06/11 21:03

    サンプルを提示して下さりありがとうございます。
    早速チャレンジします。

    キャンセル

checkベストアンサー

+2

 String.prototype.replace

String.prototype.replace の第二引数に関数を指定して分岐処理すれば良いと思います。

<script>
'use strict';
function addClassByStartTag (startTag, token /* [...token] */) {
  if (arguments.length < 2) {
    return startTag;
  }

  startTag = /^(<\w+)((\s[^>]*class\s*=\s*")([^"]*)("[^>]*>)|[^>]*>)$/.exec(startTag);

  if (startTag[3]) {
    var tokens = startTag[4].trim().split(/\s+/);

    for (var i = 1, l = arguments.length; i < l; ++i) {
      token = arguments[i];

      if (tokens.indexOf(token) === -1) {
        tokens.push(token);
      }
    }

    return startTag[1] + startTag[3] + tokens.join(' ') + startTag[5];
  }

  var tokens = [];

  for (var i = 1, l = arguments.length; i < l; ++i) {
    tokens.push(arguments[i]);
  }

  return startTag[1] + ' class="' + tokens.join(' ') + '"' + startTag[2];
}

function replaceSample (string) {
  var before;

  return string.replace(/(<p(?: [^>]*)?>)([^<>]*<\x2Fp>)([^<>]*)|(<[^>]*>)|([^<>]+)/g, function (match, p1, p2, p3, p4, p5) {
    if (p1) {
      var tokens = [p1], after, length;

      if (before) {
        tokens.push('before-' + before);
      }

      after = p3.match(/&nbsp;/g);

      if (after) {
        tokens.push('after-' + after.length);
        before = after.length;
      }

      return addClassByStartTag.apply(null, tokens) + p2 + p3;
    }

    if (p4) {
      before = 0;
    }

    if (p5) {
      before = p5.match(/&nbsp;/g);
      before = before ? before.length : 0;
    }

    return match;
  });
}

function handleSubmit (event) {
  var elements = event.target.elements;

  event.preventDefault();
  elements['output'].value = replaceSample(elements['input'].value);
}
</script>

<Form onsubmit="handleSubmit(event);">
<p>
<textarea name="input">&lt;!-- HTMLの形をしていますが実際にはテキスト(文字列)です --&gt;
&lt;!-- beforeはdiv直下のみで後は全てafter --&gt;
&lt;div&gt;
&amp;nbsp;
&lt;p&gt;段落&lt;/p&gt;
&amp;nbsp;
&amp;nbsp;
&amp;nbsp;
&amp;nbsp;
&amp;nbsp;
&amp;nbsp;
&lt;p&gt;段落&lt;/p&gt;
&amp;nbsp;
&amp;nbsp;
&lt;/div&gt;

&lt;div&gt;
&amp;nbsp;
&amp;nbsp;
&lt;p&gt;段落&lt;/p&gt;
&amp;nbsp;
&amp;nbsp;
&amp;nbsp;
&amp;nbsp;
&lt;p&gt;段落&lt;/p&gt;
&amp;nbsp;
&lt;/div&gt;

&lt;div&gt;
&lt;p&gt;段落&lt;/p&gt;
&amp;nbsp;
&lt;p&gt;段落&lt;/p&gt;
&amp;nbsp;
&amp;nbsp;
&amp;nbsp;
&lt;/div&gt;

&lt;!-- div要素をネストする --&gt;
&lt;div&gt;
&lt;div&gt;
&lt;!-- class属性のあるp要素 --&gt;
&lt;p class="hoge"&gt;段落&lt;/p&gt;
&amp;nbsp;
&lt;p&gt;段落&lt;/p&gt;
&amp;nbsp;
&amp;nbsp;
&amp;nbsp;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;!-- テキストノードを挟まない場合 --&gt;&lt;p&gt;段落&lt;/p&gt;
&amp;nbsp;
&lt;p&gt;段落&lt;/p&gt;
&amp;nbsp;
&amp;nbsp;
&amp;nbsp;
&lt;/div&gt;
</textarea>
</p>
<p><input type="submit" value="変換"></p>
<p>
<textarea name="output"></textarea>
</p>
</Form>

 更新履歴

  • 2016/06/12 06:36 連続するp要素の2つ目以降のp要素の before-n が期待通りに動作しなかった不具合を修正。before-n における &nbsp; の合計算出時に要素を乗り越えて記録していた不具合を修正(今までのサンプルではホワイトスペースノードで数値がクリアされていたので表面化されていませんでした)。

Re: flat さん

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/11 22:00 編集

    回答して下さりありがとうございます。
    提示して下さったコードは完璧でした。
    しかし、どうやらMeryはJavaScriptをそのまま実行できるのではなく、JavaScriptに準拠した仕様という形になっていて String.prototype.replace がサポートされていませんでした…。
    これ以上はJavaScriptというよりはMeryが主体の質問になってしまいそうなので、この質問は終了させます。
    後は皆さんの回答を参考に試行錯誤してみようと思います。

    キャンセル

  • 2016/06/11 22:22 編集

    replaceがサポートされていないというのは私の勘違いでした。

    また、一部意図した通りに置換されないようです。
    具体的にはdiv内の2つ目以降のp要素にも同じbefore-xが追加されていってしまいます。

    これを改善するには「div内の一番最初のp要素かどうか」という条件を判定できるようにしなければいけないと思うのですが、replaceSample()内の正規表現を変更する事でこれに対応出来るでしょうか…?

    キャンセル

  • 2016/06/12 06:42

    修正しました。
    正規表現とは関係ない部分でのバグなのでこのコードがどう動いているのか、を一つ一つ追ってみてください。

    キャンセル

  • 2016/06/12 14:48

    修正して下さりありがとうございます。

    修正前
    次の p4(before部分の&nbsp;) をキャッチしてカウントするまで before の数値がクリアされていなかった

    修正後
    before に手前のp要素の after の数値を代入して実質的に正常な数値にした上で、念の為にdiv要素をキャッチしたときに before の数値をクリア

    コードを読み解く力が自分にはまだまだ不足していると実感したと同時にすごく勉強になりました。

    キャンセル

0

ごめんなさい,これやってること逆でした… ↓
(後で直します)

<!DOCTYPE html>
<meta charset="UTF-8">
<title>Example</title>

<div>
    <p class="before-1 after-6">段落</p>
    <p class="after-2">段落</p>
</div>
<div>
    <p class="before-2 after-4">段落</p>
    <p class="after-1">段落</p>
</div>
<div>
    <p class="after-1">段落</p>
    <p class="after-3">段落</p>
</div>

<script>
'use strict';

addEventListener('DOMContentLoaded', function () {
    Array.prototype.slice.call(document.getElementsByTagName('p')).forEach(function (p) {
        var reg = /\b(before|after)-(\d+)\b/g;
        var cls = p.getAttribute('class');
        var m;
        while ((m = reg.exec(cls)) !== null) {
            var pos = m[1] === 'before' ? 'beforebegin' : 'afterend';
            var txt = new Array(parseInt(m[2]) + 1).join('&nbsp;');
            p.insertAdjacentHTML(pos, txt);
        }
    });
});
</script>

イメージ説明

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/11 17:06

    回答して下さりありがとうございます。
    質問にあるコードなのですが、HTMLの形をしてはいるものの実際にはテキストなのです。
    私の質問内容が分かりにくいので書き直します。

    キャンセル

  • 2016/06/11 17:16

    処理系はNode.jsではなくフロントエンドですか?

    キャンセル

  • 2016/06/11 17:19 編集

    なるほどMeryですか.ElectronベースのAtomであれば無理して正規表現で頑張らなくてもDOMParserが使えるのですが,Meryはちょっと怪しいですね…

    http://caniuse.com/#search=DOMParser

    キャンセル

  • 2016/06/11 17:58

    やはりDOMとして扱えると便利ですね…。
    Atomも利用してはいるのですが、テキストエディタとしては動作が遅いので、ちょっとした編集をしたい時には使いにくいのが難点です…。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る