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

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

ただいまの
回答率

90.45%

  • JavaScript

    21050questions

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

  • XPath(XML Path)

    93questions

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

XPath 式の文字列リテラルでダブルクォートをエスケープするには?

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,543

think49

score 13729

 質問

上記質問に引き続き、任意の文字列値を持つテキストノードを得る関数を考えます。

<ul id="sample">
  <li>test</li>
  <li>"hoge"</li>
  <li>'foo'</li>
  <li>"piyo'</li>
</ul>
<script>
'use strict';
function getFirstTextNode (contextNode, string) {
  return document.evaluate('descendant::text()[.="' + String(string).replace(/"/g, '""') + '"]', contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

var ul = document.getElementById('sample');
console.log(getFirstTextNode(ul, 'test'));    // "test"
console.log(getFirstTextNode(ul, '"hoge"'));  // SyntaxError: Failed to execute 'evaluate' on 'Document': The string 'descendant::text()[.=""hoge""]' is not a valid XPath expression.
</script>

XPath では文字列リテラルはダブルクォート(")もしくはシングルクォート(')で括りますが、ダブルクォートで括るとダブルクォートを含める事が出来ず、シングルクォートで括るとシングルクォートを含める事が出来ません。
エスケープ方法について調査したところ、XPath 2.0 では "" でダブルクォートをエスケープできる事がわかりました。

ところが、Google Chrome 49.0.2623.112 m, Firefox 45.0.1 では期待に反して SyntaxError になってしまいます。

'use strict';
function getFirstTextNode (contextNode, string) {
  return document.evaluate('descendant::text()[.="' + String(string).replace(/"/g, '""') + '"]', contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

var ul = document.getElementById('sample');
console.log(getFirstTextNode(ul, '"hoge"'));  // SyntaxError: Failed to execute 'evaluate' on 'Document': The string 'descendant::text()[.="""hoge"""]' is not a valid XPath expression.

XPath でダブルクォート(")をエスケープするにはどんな方法が考えられるでしょうか。

 (解決案) concat() で連結する

CertaiN さんに XPath 式の concat() 関数で連結する方法を教えて頂きました。

<ul id="sample">
  <li>test</li>
  <li>"</li>
  <li>"""</li>
  <li>"""x"""</li>
  <li>"hoge"</li>
  <li>'foo'</li>
  <li>"piyo'</li>
</ul>

<script>
'use strict';
var toXPathStringLiteral = (function () {
  function replacefn (match, p1) {

    if (p1) {
      return ',\u0027' + p1 + '\u0027';
    }

    return ',"' + match + '"';
  }

  return function toXPathStringLiteral (string) {
    string = String(string);

    if (/^"+$/g.test(string)) {
      return '\u0027' + string + '\u0027';
    }

    switch (string.indexOf('"')) {
      case -1:
        return '"' + string + '"';
      case 0:
        return 'concat(' + string.replace(/("+)|[^"]+/g, replacefn).slice(1) + ')';
      default:
        return 'concat(' + string.replace(/("+)|[^"]+/g, replacefn) + ')';
    }
  };
}());


function getFirstTextNode (contextNode, string) {
  return document.evaluate('descendant::text()[.=' + toXPathStringLiteral(string) + ']', contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

var ul = document.getElementById('sample');

console.log(toXPathStringLiteral('test'));             // "test"
console.log(toXPathStringLiteral('"'));                // '"'
console.log(toXPathStringLiteral('"""'));              // '"""'
console.log(toXPathStringLiteral('"""x"""'));          // concat('"""',"x",'"""')
console.log(toXPathStringLiteral('"hoge"'));           // concat('"',"hoge",'"')
console.log(toXPathStringLiteral('\u0027foo\u0027'));  // "'foo'"
console.log(toXPathStringLiteral('"piyo\u0027'));      // concat('"',"piyo'")

console.log(getFirstTextNode(ul, 'test'));             // test
console.log(getFirstTextNode(ul, '"'));                // "
console.log(getFirstTextNode(ul, '"""'));              // """
console.log(getFirstTextNode(ul, '"""x"""'));          // """x"""
console.log(getFirstTextNode(ul, '"hoge"'));           // "hoge"
console.log(getFirstTextNode(ul, '\u0027foo\u0027'));  // 'foo'
console.log(getFirstTextNode(ul, '"piyo\u0027'));      // "piyo'
</script>

 更新履歴

  • 2016/04/10 10:01 getFirstTextNode における evaluate の第2引数が ul になっていたので contextNode に修正
  • 2016/04/10 12:05 concat() で連結するコードを追記 (CertaiN さん発案)
  • 2016/04/11 09:50 concat() で連結するコードで """ を指定すると concat() がエラーを返すバグ修正
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

+3

CertaiNさんが書かれているように XPath 1.0 では concat する方法しかなさそうです
色々調べてみましたがみんながみんな concat してました

2.0 ではエスケープができて解決されたようですが、cssセレクタなどに需要あって、xpath使う人がぜんぜんいないからか、ブラウザでは未だに2.0サポートされてないらしいです

試してみてはいないですがライブラリで 2.0 を使うという手もありかもです
https://github.com/ilinsky/xpath.js

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/04/10 17:06

    興味深いですね!ただ,ユーザランドの実装になるとどうしてもパフォーマンス面で不利になってしまうのが残念です…(concatが妥協点としては優秀かも

    キャンセル

  • 2016/04/11 09:58

    やはり、concat() しかないのでしょうか。悩ましいですね…。

    > ブラウザでは未だに2.0サポートされてないらしいです
    2010/12/10に勧告されているXPath2.0が未だに実装されない状況を見るにサポートされる事に期待できないのは残念です…。
    XPathはテキストノードを扱える上にコンテキストノードを指定可能でCSSセレクタよりもよっぽど融通が利くと思います。

    キャンセル

checkベストアンサー

+2

リテラルを分割してXPathのconcat関数で結合する,という方法があるようです.但し分割パートが1つとなってしまった場合にエラーになるので,concat関数の引数が必ず2つ以上になるように空文字列のリテラルを渡す必要があります.

<ul id="sample">
  <li>test</li>
  <li>"hoge"</li>
  <li>'foo'</li>
  <li>"piyo'</li>
</ul>
<script>
'use strict';

function xPathLiteralize(str) {
    var r = "concat('" + String(str).replace(/'/g, "', \"'\", '") + "', '')";
    console.log(r); // just for debugging
    return r;
}

function getFirstTextNode (contextNode, string) {
    return document.evaluate(
        'descendant::text()[.=' + xPathLiteralize(string) + ']',
        ul,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
    ).singleNodeValue;
}

var ul = document.getElementById('sample');
console.log(getFirstTextNode(ul, 'test'));
console.log(getFirstTextNode(ul, '"hoge"'));
console.log(getFirstTextNode(ul, "'foo'"));
console.log(getFirstTextNode(ul, '"piyo\''));
</script>

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/04/10 12:32

    ありがとうございます。concat() で望む結果を得ることが出来ましたので質問文に私なりのコードを追記しました。
    (今気がつきましたが、"" を入力すると concat() がエラーを返しそうですね。帰宅後に修正します。)
    回答を頂いたところで恐縮ですが、もう少しエレガントな方法はないものでしょうか。感覚的なものですが、ダブルクォートとシングルクォートを併用する事に居心地の悪さを感じます。
    別の解決方法があるかも知れないので1週間ほど待ってからベストアンサーを決めたいと思います。

    キャンセル

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

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

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

  • JavaScript

    21050questions

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

  • XPath(XML Path)

    93questions

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