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

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

新規登録して質問してみよう
ただいま回答率
85.50%
XPath(XML Path)

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

JavaScript

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

Q&A

解決済

1回答

4205閲覧

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

think49

総合スコア18156

XPath(XML Path)

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

JavaScript

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

0グッド

0クリップ

投稿2016/03/31 15:02

編集2016/04/02 12:36

###前提・実現したいこと

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

HTML

1<div id="sample"> 2 <p>JavaScript<span>test</span>JavaScript<span>JavaScript</span>test<span>test</span>JavaScript<span>test</span>JavaScript</p> 3 <p>JavaScript<span>test</span>JavaScript<span>JavaScript</span>test<span>test</span>JavaScript<span>test</span>JavaScript</p> 4</div>

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

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

###ソースコード

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

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

JavaScript

1/** 2 * XPath 式 3 */ 4function getTextNodes1 (contextNode, data) { 5 var doc = contextNode.nodeType === Node.DOCUMENT_NODE ? contextNode : contextNode.ownerDocument, 6 xpathResult = doc.evaluate('descendant::text()', contextNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null), 7 i = 0, 8 l = xpathResult.snapshotLength, 9 textNodes = [], 10 textNode; 11 12 while (i < l) { 13 textNode = xpathResult.snapshotItem(i++); 14 15 if (textNode.data === data) { 16 textNodes.push(textNode); 17 } 18 } 19 20 return textNodes; 21} 22 23/** 24 * TreeWalker 25 */ 26function getTextNodes2 (contextNode, data) { 27 function filter (textNode) { 28 return textNode.data === data ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; 29 } 30 31 var doc = contextNode.nodeType === Node.DOCUMENT_NODE ? contextNode : contextNode.ownerDocument, 32 treeWalker = doc.createTreeWalker(contextNode, NodeFilter.SHOW_TEXT, filter, false), 33 textNodes = []; 34 35 while (treeWalker.nextNode()) { 36 textNodes.push(treeWalker.currentNode); 37 } 38 39 return textNodes; 40} 41 42console.log(getTextNodes1(document.getElementById('sample'), 'JavaScript')); 43console.log(getTextNodes2(document.getElementById('sample'), 'JavaScript'));

###質問

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

###回答

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

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

参考リンク

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

eripong

2016/04/09 21:56

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

2016/04/10 00:21 編集

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

回答1

0

ベストアンサー

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

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

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

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

javascript

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

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

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

投稿2016/03/31 17:24

編集2016/04/01 14:42
ryls-nmm

総合スコア633

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2016/04/01 13:14 編集

失礼しました。適切な表現ではなかったので質問内容を修正しました。 - リストは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
ryls-nmm

2016/04/01 14:44 編集

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

2016/04/02 12: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
think49

2016/04/10 00:27

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

2016/04/10 06: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 定義してるだけです
ryls-nmm

2016/04/10 06: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 以外は未対応です")     } } ```
think49

2016/04/11 01:07

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問