Javanese Online

Хакерский способ экранирования символов в XML/HTML

В JDK я не нашёл публичного метода для XML/HTML-экранирования. Разные варианты есть в Apache Commons, Spring, Guava. Большинство из них стремится заменить все возможные символы: Ⓒ на &copy;, ™ на &trade; и так далее. Желание нездоровое и приводит к разбуханию текста и выполнению лишней работы. Стоит отдать должное варианту HtmlEscapers#htmlEscaper из Guava — он заменяет только <>&"'.

Но есть один нюанс. В контексте значения атрибута, взятого в двойные кавычки, незачем экранировать одинарные. А в контексте тела тега нет смысла вообще экранировать кавычки.

Итак, нужно каждое вхождение любого из «опасных» символов заменить на определённую «безопасную» строку. Найти символ можно замечательным шустрым методом indexOf, но меня интересует не один конкретный символ, а несколько. Поэтому придётся итерировать строку.

Как проверить, что данный символ принадлежит ко множеству опасных? if (c == '<' || c == '>' || …? Скука. Несколько условий. Данные в коде. Не расширяемо. setOfCharacters.contains(c)? Неплохо, но готового такого set в JDK нет. Arrays.binarySearch(unsafeChars, c) >= 0? Вообще, довольно бестолково для массива из пяти элементов.

Душа требует приключений, а статья об оптимизациях, которые умеет делать LLVM, оставила в памяти неизгладимый след. Присматриваюсь к символам: "=34, &=38, '=39, <=60, >=62 — все они меньше чем 64, каждому можно подарить по битику в общем лонге: long UNSAFE = 1L << '"' | 1L << '&' | 1L << '\'' | 1L << '<' | 1L << '>'. Теперь для каждого символа можно проверить, является ли он одним из них: ((c & 63) == c) && (((1L << c) & UNSAFE) != 0). Если да, значит, это один из интересующих нас символов. Но какой именно? Считаем количество единичек в UNSAFE с любой стороны от нашего символа: int which = Long.bitCount(UNSAFE & ((1L << c) - 1)). Теперь нужно в StringBuilder написать достойную замену этому символу. REPLACEMENTS[which] решило бы проблему, но у меня же так всё красиво было без массивов! А тут двумерный массив (массив строк, каждая из которых — массив байт). Спекаем всё в одну строку! Теперь надо куда-то сохранить, в какой части строки́ лежат какие замены: String REPLACEMENTS = "&#34;&amp;&#39;&lt;&gt;" — [0,5); [5,10); [10,15); [15,19); [19,23). «Массив» {0, 5, 10, 15, 19, 23} состоит из шести чисел. Каждое меньше чем 32, то есть влезает в пять бит, 5×6=30, весь массив помещается в один инт: int SLICES = 5<<5 | 10<<10 | 15<<15 | 19<<20 | 23<<25. Энное число достаётся по формуле SLICES >>> 5n & 31. Дописываем: .append(REPLACEMENTS, SLICES >>> 5 * which & 31, SLICES >> 5 * (which + 1) & 31).

Готово: исходный код с комментариями.

Комментарии к статье

{"type":"articleComments","id":"ec3db731-1a51-43bc-8791-f23d5f74025e","comments":[]}

Javanese.Online в GitHub

Чаты и каналы в Telegram

RSS-лента