web-dev-qa-db-ja.com

HTMLエンコーディング(URLエンコーディングではない)を行うJDKクラスはありますか?

もちろん、Java.net.URLEncoderおよびJava.net.URLDecoder クラス。ただし、HTMLスタイルのエンコーディングのみが必要です。 (私は欲しくない' ' 交換された '+'など)。 HTMLエンコーディングだけを行うJDK組み込みクラスについては知りません。ありますか?他の選択肢(たとえば Jakarta Commons Lang 'StringEscapeUtils' など)は知っていますが、これが必要なプロジェクトに別の外部依存関係を追加したくありません。

最近のJDK(別名5または6)に私が知らない何かが追加されていることを願っています。そうでなければ私は自分で転がさなければなりません。

31
Eddie

どうやら、答えは「いいえ」です。残念ながら、これは私が何かをしなければならず、短期的にはできなかった新しい外部依存関係を追加する必要がある場合でした。 Commons Langを使用することが最善の長期的解決策であるということは、誰もが同意します。これが、プロジェクトに新しいライブラリを追加できるようになった後の作業です。

Java APIにはそのような一般的な使用法が存在しないのは残念です。

9
Eddie

これを行うためのJDKビルトインクラスはありませんが、Jakarta commons-langライブラリの一部です。

String escaped = StringEscapeUtils.escapeHtml3(stringToEscape);
String escaped = StringEscapeUtils.escapeHtml4(stringToEscape);

JavaDoc を確認してください。

依存関係の追加は、通常、jarをどこかにドロップするのと同じくらい簡単で、commons-langには非常に多くの有用なユーティリティがあり、それを搭載する価値があることがよくあります。

45
johnmcase

簡単な方法はこれのようです:

public static String encodeHTML(String s)
{
    StringBuffer out = new StringBuffer();
    for(int i=0; i<s.length(); i++)
    {
        char c = s.charAt(i);
        if(c > 127 || c=='"' || c=='<' || c=='>')
        {
           out.append("&#"+(int)c+";");
        }
        else
        {
            out.append(c);
        }
    }
    return out.toString();
}

ソース: http://forums.thedailywtf.com/forums/p/2806/72054.aspx#72054

13
Rawton Evolekam

私が確認したすべての既存のソリューション(ライブラリ)は、以下の問題の1つまたはいくつかに苦しんでいることがわかりました。

  • それらはJavadocでそれらが置き換えるものを正確に伝えていません。
  • 彼らはあまりにもエスケープします...それはHTMLを読むのをはるかに難しくします。
  • 彼らは文書化しませんwhen戻り値が安全に使用できます(HTMLエンティティに使用しても安全ですか?、HTML属性に使用しますか?など)
  • 彼らは速度のために最適化されていません。
  • ダブルエスケープを回避する機能はありません(既にエスケープされているものをエスケープしないでください)。
  • 一重引用符を&apos;に置き換えます(間違っています!)

その上、外部ライブラリを持ち込めないという問題もありました。少なくとも、ある程度の量のテープがなければできません。

それで、私は自分で転がしました。有罪。

以下はその例ですが、最新バージョンは常に this Gist にあります。

/**
 * HTML string utilities
 */
public class SafeHtml {

    /**
     * Escapes a string for use in an HTML entity or HTML attribute.
     * 
     * <p>
     * The returned value is always suitable for an HTML <i>entity</i> but only
     * suitable for an HTML <i>attribute</i> if the attribute value is inside
     * double quotes. In other words the method is not safe for use with HTML
     * attributes unless you put the value in double quotes like this:
     * <pre>
     *    &lt;div title="value-from-this-method" &gt; ....
     * </pre>
     * Putting attribute values in double quotes is always a good idea anyway.
     * 
     * <p>The following characters will be escaped:
     * <ul>
     *   <li>{@code &} (ampersand) -- replaced with {@code &amp;}</li>
     *   <li>{@code <} (less than) -- replaced with {@code &lt;}</li>
     *   <li>{@code >} (greater than) -- replaced with {@code &gt;}</li>
     *   <li>{@code "} (double quote) -- replaced with {@code &quot;}</li>
     *   <li>{@code '} (single quote) -- replaced with {@code &#39;}</li>
     *   <li>{@code /} (forward slash) -- replaced with {@code &#47;}</li>
     * </ul>
     * It is not necessary to escape more than this as long as the HTML page
     * <a href="https://en.wikipedia.org/wiki/Character_encodings_in_HTML">uses
     * a Unicode encoding</a>. (Most web pages uses UTF-8 which is also the HTML5
     * recommendation.). Escaping more than this makes the HTML much less readable.
     * 
     * @param s the string to make HTML safe
     * @param avoidDoubleEscape avoid double escaping, which means for example not 
     *     escaping {@code &lt;} one more time. Any sequence {@code &....;}, as explained in
     *     {@link #isHtmlCharEntityRef(Java.lang.String, int) isHtmlCharEntityRef()}, will not be escaped.
     * 
     * @return a HTML safe string 
     */
    public static String htmlEscape(String s, boolean avoidDoubleEscape) {
        if (s == null || s.length() == 0) {
            return s;
        }
        StringBuilder sb = new StringBuilder(s.length()+16);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
                case '&':
                    // Avoid double escaping if already escaped
                    if (avoidDoubleEscape && (isHtmlCharEntityRef(s, i))) {
                        sb.append('&');
                    } else {
                        sb.append("&amp;");
                    }
                    break;
                case '<':
                    sb.append("&lt;");
                    break;
                case '>':
                    sb.append("&gt;");
                    break;
                case '"':
                    sb.append("&quot;"); 
                    break;
                case '\'':
                    sb.append("&#39;"); 
                    break;
                case '/':
                    sb.append("&#47;"); 
                    break;
                default:
                    sb.append(c);
            }
        }
        return sb.toString();
  }

  /**
   * Checks if the value at {@code index} is a HTML entity reference. This
   * means any of :
   * <ul>
   *   <li>{@code &amp;} or {@code &lt;} or {@code &gt;} or {@code &quot;} </li>
   *   <li>A value of the form {@code &#dddd;} where {@code dddd} is a decimal value</li>
   *   <li>A value of the form {@code &#xhhhh;} where {@code hhhh} is a hexadecimal value</li>
   * </ul>
   * @param str the string to test for HTML entity reference.
   * @param index position of the {@code '&'} in {@code str}
   * @return 
   */
  public static boolean isHtmlCharEntityRef(String str, int index)  {
      if (str.charAt(index) != '&') {
          return false;
      }
      int indexOfSemicolon = str.indexOf(';', index + 1);
      if (indexOfSemicolon == -1) { // is there a semicolon sometime later ?
          return false;
      }
      if (!(indexOfSemicolon > (index + 2))) {   // is the string actually long enough
          return false;
      }
      if (followingCharsAre(str, index, "amp;")
              || followingCharsAre(str, index, "lt;")
              || followingCharsAre(str, index, "gt;")
              || followingCharsAre(str, index, "quot;")) {
          return true;
      }
      if (str.charAt(index+1) == '#') {
          if (str.charAt(index+2) == 'x' || str.charAt(index+2) == 'X') {
              // It's presumably a hex value
              if (str.charAt(index+3) == ';') {
                  return false;
              }
              for (int i = index+3; i < indexOfSemicolon; i++) {
                  char c = str.charAt(i);
                  if (c >= 48 && c <=57) {  // 0 -- 9
                      continue;
                  }
                  if (c >= 65 && c <=70) {   // A -- F
                      continue;
                  }
                  if (c >= 97 && c <=102) {   // a -- f
                      continue;
                  }
                  return false;  
              }
              return true;   // yes, the value is a hex string
          } else {
              // It's presumably a decimal value
              for (int i = index+2; i < indexOfSemicolon; i++) {
                  char c = str.charAt(i);
                  if (c >= 48 && c <=57) {  // 0 -- 9
                      continue;
                  }
                  return false;
              }
              return true; // yes, the value is decimal
          }
      }
      return false;
  } 


  /**
   * Tests if the chars following position <code>startIndex</code> in string
   * <code>str</code> are that of <code>nextChars</code>.
   * 
   * <p>Optimized for speed. Otherwise this method would be exactly equal to
   * {@code (str.indexOf(nextChars, startIndex+1) == (startIndex+1))}.
   *
   * @param str
   * @param startIndex
   * @param nextChars
   * @return 
   */  
  private static boolean followingCharsAre(String str, int startIndex, String nextChars)  {
      if ((startIndex + nextChars.length()) < str.length()) {
          for(int i = 0; i < nextChars.length(); i++) {
              if ( nextChars.charAt(i) != str.charAt(startIndex+i+1)) {
                  return false;
              }
          }
          return true;
      } else {
          return false;
      }
  }
}

TODO:連続する空白を保持します。

4
peterh

自分で巻かないでください。 Jakarta Commons Langを使用します。テストされ、動作することが証明されています。必要になるまでコードを記述しないでください。 「ここで発明されていない」または「別の依存関係ではない」は、何を選択して書き込むかを決定するための非常に良いベースではありません。

1
bitboxer

いいえ。私はあなたが言及したStringEscapeUtils、または例えばJTidy( http://jtidy.sourceforge.net/multiproject/jtidyservlet/apidocs/org/w3c/tidy/servlet/utilの使用をお勧めします。 /HTMLEncode.html )。

0
simon