Как я могу отображать ссылки на объекты XML-символов в Ruby?

Я читаю некоторые данные из веб-службы XML с Ruby, что-то вроде этого:

<phrases>
  <phrase language="en_US">&iexcl;I&#39;m highly&nbsp;annoyed with character references!</phrase>
</phrases>

Я разбираю XML и хватаю массив фраз. Как вы можете видеть, текст фразы содержит некоторые ссылки на символы XML-символа. Я хотел бы заменить их ссылкой на фактический символ. Это достаточно просто с числовыми ссылками, но противными с XML и HTML. Я бы хотел избежать большого хэша в моем коде, который содержит символ для каждой ссылки на XML или HTML-символ, т.е. http://www.java2s.com/Code/Java/XML/Resolvesanentityreferenceorcharacterreferencetoitsvalue.htm

Конечно, там есть библиотека для этого, не так ли?

Обновление

Да, есть библиотека, и она называется HTMLEntities:

: [email protected]; sudo gem install htmlentities
Successfully installed htmlentities-4.2.4
: [email protected];  irb
irb(main):001:0> require 'htmlentities'
=> []
irb(main):002:0> HTMLEntities.new.decode "&iexcl;I&#39;m highly&nbsp;annoyed with character references!"
=> "¡I'm highly annoyed with character references!"

Ответ 1

Это не попытка предоставить решение, это связано с некоторыми из моих собственных опытов, связанных с XML из дикой природы. Сначала я использовал Perl, а затем использовал Ruby, и опыт - это то, с чем вы легко столкнетесь, если хватаете достаточное количество каналов XML или RDF/RSS/Atom.

Я часто видел, что XML CDATA содержит HTML, как закодированные, так и незашифрованные. Закодированный HTML, вероятно, был результатом того, что кто-то делает все правильно, через некоторый API или библиотеку для генерации XML. Некодированный HTML, вероятно, был кем-то с помощью script, чтобы обернуть HTML тегами, что привело к недопустимому XML, но мне все равно пришлось иметь дело с ним.

Я также видел XML CDATA, содержащий HTML, который был закодирован несколько раз, требуя, чтобы я unencode все, даже после того, как движок XML сделал свое дело. Иногда во время промежуточного прохода у меня внезапно появлялись символы не-UTF8 в строке вместе с закодированными, в результате того, что кто-то добавлял комментарии или соединял несколько потоков HTML вместе, которые были из разных наборов символов. По какой-то причине это было действительно уродливо и вызвало синтаксический анализ XML, чтобы сломать или издавать много предупреждений. Мне пришлось бы перебирать содержимое, расшифровывать и проверять, был ли предыдущий проход таким же, как и текущий пропуск для декодирования, и ругаться, если ничего не изменилось. Не было никакой гарантии, что в то время у меня была бы строка с допустимым набором символов, поэтому я должен был бы сказать iconv, чтобы преобразовать ее в UTF8 и выбросить символы, которые не будут преобразовываться чисто.

Nokogiri может декодировать содержимое node различными способами, используя творческие методы to_xml и to_html. Вы также можете взглянуть на драгоценный камень HTMLEntities, Loofah и другие, чтобы пойти после содержимого CDATA. Loofah - это хорошо, потому что он предназначен для ярлыков белого или черного списка, с которыми вы можете столкнуться.

Предполагается, что спецификация XML защитит нас от таких махинаций, но, как сказал мне один из моих сотрудников: "Мы можем сделать это безумным, но не проклятым". Люди так изобретательны, и спецификации ничего не значат для тех, кто не удосужился их прочитать или не заботится.

Ответ 2

REXML может это сделать, хотя он не будет обрабатывать "& iexcl;" или "& nbsp;". Список предопределенных XML-объектов (кроме числовых объектов Unicode) на самом деле довольно мал. См. http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references

Учитывая этот ввод XML:

<phrases>
  <phrase language="en_US">&quot;I&#39;m highly annoyed with character references!&#x00a9;</phrase>
</phrases>

вы можете проанализировать XML и встроенные объекты, подобные этому (например):

require 'rexml/document'

doc = REXML::Document.new(File.open('/tmp/foo.xml').readlines.join(''))
phrase = REXML::XPath.first(doc, '//phrases/phrase')
text = phrase.first # Type is REXML::Text
puts(text.value)

Очевидно, что в этом примере предполагается, что XML находится в файле /tmp/foo.xml. Вы можете так же легко передать строку XML. На моих компьютерах Mac и Ubuntu его запуск:

$ ruby /tmp/foo.rb
"I'm highly annoyed with character references!©