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

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

ただいまの
回答率

90.49%

  • JavaScript

    16960questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • XPath(XML Path)

    80questions

    XML Path Language (XPath; XMLパス言語)は、マークアップ言語 XML に準拠した文書の特定の部分を指定する言語構文の事をいいます。XPathはXMLとは別の構文を使用します。XMLドキュメントの抽象、論理ストラクチャ上で動作します。

XPath 式でテキストノード値を指定してフィルタするには?

解決済

回答 1

投稿 編集

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

think49

score 12161

前提・実現したいこと

次のHTMLからテキストノード値が「JavaScript」となる「XPath 式」を作りたい。

<div id="sample">
  <p>JavaScript<span>test</span>JavaScript<span>JavaScript</span>test<span>test</span>JavaScript<span>test</span>JavaScript</p>
  <p>JavaScript<span>test</span>JavaScript<span>JavaScript</span>test<span>test</span>JavaScript<span>test</span>JavaScript</p>
</div>

発生している問題・エラーメッセージ

document.evaluate で実装出来ましたが、テキストノード値の照合は for ループ内でフィルタする方法しか思いつきませんでした。

ソースコード

XPathResult.ORDERED_NODE_ITERATOR_TYPE を指定すれば for-of や Array.from でループコストが安くなりますが、Polyfill が難しいので snapShot で妥協しています。

TreeWalker は代替案です。
document.createTreeWalker() 時点で「テキストノード値 === "JavaScript"」を得られるのは優秀ですが、クロージャが形成されている点がもやもやします。

/**
 * XPath 式
 */
function getTextNodes1 (contextNode, data) {
  var doc = contextNode.nodeType === Node.DOCUMENT_NODE ? contextNode : contextNode.ownerDocument,
      xpathResult = doc.evaluate('descendant::text()', contextNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null),
    i = 0,
    l = xpathResult.snapshotLength,
    textNodes = [],
    textNode;

  while (i < l) {
    textNode = xpathResult.snapshotItem(i++);

    if (textNode.data === data) {
      textNodes.push(textNode);
    }
  }

  return textNodes;
}

/**
 * TreeWalker
 */
function getTextNodes2 (contextNode, data) {
  function filter (textNode) {
    return textNode.data === data ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
  }

  var doc = contextNode.nodeType === Node.DOCUMENT_NODE ? contextNode : contextNode.ownerDocument,
      treeWalker = doc.createTreeWalker(contextNode, NodeFilter.SHOW_TEXT, filter, false),
      textNodes = [];

  while (treeWalker.nextNode()) {
    textNodes.push(treeWalker.currentNode);
  }

  return textNodes;
}

console.log(getTextNodes1(document.getElementById('sample'), 'JavaScript'));
console.log(getTextNodes2(document.getElementById('sample'), 'JavaScript'));

質問

XPath 式のみで目的のテキストノードの XpathResult を参照する方法はないでしょうか。

回答

@ryls-nmm さんの回答により下記 XPath 式で解決できる事がわかりました(1つめの XPath 式は短縮形)。

descendant::text()[.="JavaScript"]
descendant::text()[self::node()="JavaScript"]

 参考リンク

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • eripong

    2016/04/10 06:56

    回答依頼をいただいたまま、回答があったのに受付中で変わっていないのが気になっています。ryls-nmmさんの回答に満足されていないと言うことでしょうか?その場合、どの点に疑問、不足を感じているか書いていただかないと分かりません。

    キャンセル

  • think49

    2016/04/10 09:19 編集

    すみません。本題は解決済みですが、2点の問題に取り組んでおりました。ORDERED_NODE_ITERATOR_TYPE が ES6 のイテレータとして機能する為に @@iterator(Symbol.iterator) を利用できないか。任意の String 値を指定する為に " (ダブルクォート) をエスケープ出来ないか。とはいえ、当初の問題は解決済みなので本質問はクローズして新たに質問を立ち上げたいと思います。心配をおかけして申し訳ありません。

    キャンセル

回答 1

checkベストアンサー

+1

xpath は書いたことも読んだことも無いですが今簡単にググって出てきたことで答えてみます

複数結果を返すのはイテレータかスナップショットの2種類なようです
どちらもこれ専用な形式のようで、リスト(配列)で欲しいということであれば自分で変換するしかないと思います
MDN

また、イテレータとはいってもES6のものとはまた別物みたいで、次のものの取得は iterateNext メソッドで、Array.from や for of では取得できませんでした

それと、 getTextNodes1 のほう data つかわれてないようです

var xp = document.evaluate(`descendant::text()[. = '${data}']`, contextNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)

のようにすれば良いと思います

修正
xpath 以外の引数を質問文に合わせました

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/04/01 22:13 編集

    失礼しました。適切な表現ではなかったので質問内容を修正しました。
    - リストはXPathResultを意図しており、XPath式だけで目的のテキストノード郡を得る事を期待しています。
    - getTextNodes1 の方は完全にコードミスで配列化する際の if (textNode.data === data) が抜け落ちていました。

    > var xp = document.evaluate(`descendant::text()[. = '${data}']`, $0, null, 5)
    ありがとうございます。
    そのまま実行すると「ReferenceError: $0 is not defined」になりましたが、document.evaluate('descendant::text()[.="' + data + '"]', contextNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null) に修正したら期待通りの動作しました。
    https://jsfiddle.net/46rvso59/1/

    下記文書によれば ". はコンテキスト・ノードを選択します" とありますが、"." を非短縮シンタックスにするとどのように書けるでしょうか。
    http://xmlconsortium.org/wg/tech/WD-xpath20-20020816-Japan-without-Appendix.htm#abbrev

    キャンセル

  • 2016/04/01 23:41 編集

    すみません、xpath式のところだけ意識していたので他の引数は質問文にあわせてなかったです

    $0 は xpath 特有な何かではなくて、単に devtools の選択中の要素への参照が入った変数です
    jsのコード書くときに一々jsファイルつくったりはせず、 devtools でやってしまうのでそれをそのままコピペしてしまいました

    > 下記文書によれば ". はコンテキスト・ノードを選択します" とありますが、"." を非短縮シンタックスにするとどのように書けるでしょうか。

    「.」 って短縮系だったのですね
    「.」 は自分自身を表すって stackoverflow で見て使ってみたものなのでそこまで把握してませんでした

    自分自身のテキストのことなのでこれのことではないかと思います

    ```
    var xp = document.evaluate(`descendant::text()[self::text() = '${data}']`, contextNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
    ```

    感覚でかいてみたものですが、うまく動きました
    とりあえず動くものではなく、ドキュメントにあるレベルの正確な情報が欲しいなら私ではあまり役に立たないかもしれません

    キャンセル

  • 2016/04/02 21:32

    ありがとうございます。
    改めて XPath 2.0 を読んでみましたが、「.. で構成されるステップはparent::node()の短縮形です」と説明が見つかりました。
    "." には言及されていませんでしたが、原文を読むと「// は/descendant-or-self::node()/の短縮形です」とあるので、「. は self::node() の短縮形」なのかもしれません。
    実際に descendant::comment()[self::node()="JavaScript"] も期待通りに動作しました。
    http://xmlconsortium.org/wg/tech/WD-xpath20-20020816-Japan-without-Appendix.htm#abbrev

    キャンセル

  • 2016/04/10 09:27

    遅れて申し訳ありません。下記2点の問題に取り組んでいましたが、本題は解決済みなので別途質問致します。
    - document.evaluate('descendant::text()[.="' + data + '"]') において、data でダブルクォート(") を含む場合エスケープしたい
    - XPathResult.ORDERED_NODE_ITERATOR_TYPE 及び XPathResult.ORDERED_NODE_SNAPSHOT_TYPE を ES6 のイテレータとして機能させたい
    大変参考になりました。ありがとうございました。

    キャンセル

  • 2016/04/10 15:32

    2 つめの質問が見つけられなかったのでここに書いておきます
    もしかすると解決済みかもしれませんけど

    ```
    <body>
        <p>abc</p>
        <p>abcd</p>
        <p>abcde</p>
    </body>

    XPathResult.prototype[Symbol.iterator] = function(){
        var self = this
        var count = 0
        return {
            next() {
                if(self.resultType === 5){
                    var node = self.iterateNext()
                    return {done: node === null, value: node || undefined}
                }else if(self.resultType === 7){
                    if(count === self.snapshotLength){
                        return {done: true, value: undefined}
                    }else{
                        var node = self.snapshotItem(count++)
                        return {done: false, value: node}
                    }
                }else{
                    throw new Error("ORDERED_NODE_ITERATOR_TYPE と ORDERED_NODE_SNAPSHOT_TYPE 以外は未対応です")
                }
            }
        }
    }

    var xpResultSnapshot = document.evaluate(`descendant::text()[starts-with(., "a")]`, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE , null)
    var arrSnapshot = [...xpResultSnapshot]
    console.log(arrSnapshot, arrSnapshot.map(e => e.textContent))
    // [text, text, text] ["abc", "abcd", "abcde"]


    var xpResultIterator = document.evaluate(`descendant::text()[starts-with(., "a")]`, document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE , null)
    var arrIterator = [...xpResultIterator]
    console.log(arrIterator, arrIterator.map(e => e.textContent))
    // [text, text, text] ["abc", "abcd", "abcde"]
    ```

    自分で Symbol.iterator 定義してるだけです

    キャンセル

  • 2016/04/10 15:53

    普通に generator でよかったです

    ```
    XPathResult.prototype[Symbol.iterator] = function*(){
        if(this.resultType === 5){
            var node
            while(node = this.iterateNext()){
                yield node
            }
        }else if(this.resultType === 7){
            for(var i=0;i<this.snapshotLength;i++){
                yield this.snapshotItem(i)
            }
        }else{
            throw new Error("ORDERED_NODE_ITERATOR_TYPE と ORDERED_NODE_SNAPSHOT_TYPE 以外は未対応です")
        }
    }
    ```

    キャンセル

  • 2016/04/11 10:07

    ありがとうございます。
    イテレータはもう少し自分で努力してから質問しようと思っていましたが、参考になりました。
    (yield は IE11- で SyntaxError になるので扱いづらい印象があります。Babelを使えばいいのかもしれませんが…。)

    キャンセル

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

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

関連した質問

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

  • JavaScript

    16960questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • XPath(XML Path)

    80questions

    XML Path Language (XPath; XMLパス言語)は、マークアップ言語 XML に準拠した文書の特定の部分を指定する言語構文の事をいいます。XPathはXMLとは別の構文を使用します。XMLドキュメントの抽象、論理ストラクチャ上で動作します。