Используя Ruby, как я могу подтвердить, что фрагмент XML действителен?

Как некоторые из вас знают, я работаю над интеграцией XMPP (Jabber) для чат-системы StackOverflow. , как компонент XMPP, написанный на Ruby с использованием пакета xmpp4r.

Я борюсь с одной проблемой (ну, много проблем, но одна проблема на данный момент :-) Я беру фид JSON из чата и извлекаю HTML для сообщений. Я использую привязки Ruby TidyHTML для преобразования HTML из JSON в XHTML, чтобы я мог отправлять это как сообщение XMPP - поскольку сообщения XMPP - это просто XML, преобразование HTML в XHTML должно сделать его действительным XML, который я могу просто вставить в строфу <message>.

Для большинства сообщений это работает отлично!

Мой мозг взорвался

Однако для других сообщений он полностью задыхается - сервер XMPP закрывает поток, и скрипт останавливается. (И Рчерн и другие в Таверне расстраиваются. Ну, может быть, не расстраиваются, но они смеются надо мной. Это меня огорчает!)

Я почти уверен, что по той или иной причине сообщения не являются допустимым XML, и поэтому сервер XMPP закрывает соединение, потому что он обнаруживает ошибку синтаксического анализа в потоке XML от компонента Ruby. Вот пример одного из таких сообщений:

<message to='[email protected]/Token' type='groupchat' xmlns='jabber:client'><body>&lt;div class=&quot;onebox ob-message&quot;&gt;&lt;a class=&quot;roomname&quot; href=&quot;/transcript/message/263372#263372&quot;&gt;&lt;span title=&quot;2010-11-04 19:20:23Z&quot;&gt;1 hour ago&lt;/span&gt;&lt;/a&gt;, by &lt;span class=&quot;user-name&quot;&gt;Fosco&lt;/span&gt; &lt;br/&gt;&lt;div class=&quot;quote&quot;&gt;&lt;div class=&quot;room-mini&quot;&gt;&lt;div class=&quot;room-mini-header&quot;&gt;&lt;h3&gt;&lt;img class=&quot;small-site-logo&quot; title=&quot;Gaming&quot; alt=&quot;Gaming&quot; width=&quot;16&quot; height=&quot;16&quot; src=&quot;http://sstatic.net/gaming/img/favicon.ico&quot; /&gt;&amp;nbsp;&lt;span class=&quot;room-name&quot;&gt;&lt;a href=&quot;http://chat.stackexchange.com/rooms/28/minecraft-talk&quot;&gt;Minecraft Talk&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;div class=&quot;room-mini-description&quot;&gt;Everything Minecraft, including classic and survival mode&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;room-current-user-count&quot; title=&quot;current users&quot;&gt;9&lt;/div&gt;&lt;div class=&quot;mspark&quot; style=&quot;height:25px;width:205px&quot;&gt;
&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:13px;left:0px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:9px;left:8px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:2px;left:16px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:8px;left:24px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:1px;left:32px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:1px;left:56px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:0px;left:64px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:0px;left:88px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:0px;left:96px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:11px;left:104px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:7px;left:112px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:7px;left:120px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:25px;left:128px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:14px;left:136px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:4px;left:144px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:7px;left:152px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:19px;left:160px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:19px;left:168px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:12px;left:176px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar&quot; style=&quot;width:8px;height:11px;left:184px;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;mspbar now&quot; style=&quot;height:25px;left:154px;&quot;&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;clear-both&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</body><html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'><div class="onebox ob-message"><a class="roomname" href="/transcript/message/263372#263372"><span title="2010-11-04 19:20:23Z">1 hour ago</span></a>, by <span class="user-name">Fosco</span><br />
<div class="quote">
<div class="room-mini"><div class="room-mini-header">
<h3><img class="small-site-logo" title="Gaming" alt="Gaming" width="16" height="16" src="http://sstatic.net/gaming/img/favicon.ico" />&nbsp;<span class="room-name"><a href="http://chat.stackexchange.com/rooms/28/minecraft-talk">Minecraft Talk</a></span></h3>
<div class="room-mini-description">Everything Minecraft, including classic and survival mode</div>
</div>
<div class="room-current-user-count" title="current users">9</div>
<div class="mspark" style="height:25px;width:205px">
<div class="mspbar" style="width:8px;height:13px;left:0px;"></div>
<div class="mspbar" style="width:8px;height:9px;left:8px;"></div>
<div class="mspbar" style="width:8px;height:2px;left:16px;"></div>
<div class="mspbar" style="width:8px;height:8px;left:24px;"></div>
<div class="mspbar" style="width:8px;height:1px;left:32px;"></div>
<div class="mspbar" style="width:8px;height:1px;left:56px;"></div>
<div class="mspbar" style="width:8px;height:0px;left:64px;"></div>
<div class="mspbar" style="width:8px;height:0px;left:88px;"></div>
<div class="mspbar" style="width:8px;height:0px;left:96px;"></div>
<div class="mspbar" style="width:8px;height:11px;left:104px;"></div><div class="mspbar" style="width:8px;height:7px;left:112px;"></div><div class="mspbar" style="width:8px;height:7px;left:120px;"></div><div class="mspbar" style="width:8px;height:25px;left:128px;"></div><div class="mspbar" style="width:8px;height:14px;left:136px;"></div>
<div class="mspbar" style="width:8px;height:4px;left:144px;"></div>
<div class="mspbar" style="width:8px;height:7px;left:152px;"></div>
<div class="mspbar" style="width:8px;height:19px;left:160px;"></div>
<div class="mspbar" style="width:8px;height:19px;left:168px;"></div><div class="mspbar" style="width:8px;height:12px;left:176px;"></div>
<div class="mspbar" style="width:8px;height:11px;left:184px;"></div>
<div class="mspbar now" style="height:25px;left:154px;"></div>
</div>
<div class="clear-both"></div>
</div>
</div>
</div>
</body></html></message>

(Это сообщение оказалось цитатой ссылки на чат)

Вот ошибка, которую мне дал Руби:

IOError: stream closed
/usr/lib/ruby/1.8/xmpp4r/stream.rb:594:in `empty?'
/usr/lib/ruby/1.8/rexml/parsers/baseparser.rb:153:in `empty?'
/usr/lib/ruby/1.8/rexml/parsers/baseparser.rb:193:in `pull'
/usr/lib/ruby/1.8/rexml/parsers/sax2parser.rb:92:in `parse'
/usr/lib/ruby/1.8/xmpp4r/streamparser.rb:79:in `parse'
/usr/lib/ruby/1.8/xmpp4r/stream.rb:75:in `start'
/usr/lib/ruby/1.8/xmpp4r/stream.rb:72:in `initialize'
/usr/lib/ruby/1.8/xmpp4r/stream.rb:72:in `new'
/usr/lib/ruby/1.8/xmpp4r/stream.rb:72:in `start'
/usr/lib/ruby/1.8/xmpp4r/connection.rb:119:in `start'
/usr/lib/ruby/1.8/xmpp4r/component.rb:70:in `start'
/usr/lib/ruby/1.8/xmpp4r/connection.rb:77:in `connect'
/usr/lib/ruby/1.8/xmpp4r/component.rb:47:in `connect'
./classes/SOXMPP_Bridge.rb:20:in `initialize'
./soxmpp.rb:81:in `new'
./soxmpp.rb:81

Наконец, мой вопрос!

Учитывая, что отправка недопустимого XML на сервер XMPP меня отталкивает, есть ли способ с помощью Ruby проверить (и, желательно, исправить) XML перед отправкой на сервер XMPP? Скорее всего, исправление будет связано с тем, что я буду писать дополнительный код для каждого случая, когда Tidy не создает корректный XML, но я бы, по крайней мере, хотел предотвратить сбой скрипта. Итак, как я могу проверить XML перед отправкой на сервер XMPP?


person Josh    schedule 04.11.2010    source источник
comment
Спасибо Майклу Мрозеку за скриншоты!   -  person Josh    schedule 05.11.2010
comment
@TreyE: Я не уверен, что конкретно вы спрашиваете ... На данный момент теста нет. Код просто с радостью берет каждое HTML-сообщение из чата, анализирует его в XHTML и отправляет на сервер XMPP. Я могу опубликовать соответствующий код, если это поможет.   -  person Josh    schedule 05.11.2010
comment
Полный исходный код доступен в trac или в подрывной деятельности   -  person Josh    schedule 05.11.2010
comment
Я не знаю, как создать клиент ejabber с помощью рубинового кода, пожалуйста, помогите мне, если можете.   -  person Jigar Bhatt    schedule 09.07.2014


Ответы (4)


Фактическая ошибка в этом случае - это ваш &nbsp;. Согласно XEP-0071, раздел 8, пункт 5:

Раздел 11.1 ядра XMPP предусматривает, что символьные объекты, отличные от пяти общих объектов, определенных в разделе 4.6 спецификации XML (т. е., , , , и ), НЕ ДОЛЖНЫ отправляться через поток XML. Поэтому реализации XHTML-IM НЕ ДОЛЖНЫ включать предопределенные объекты XHTML 1.0, такие как -- вместо этого, реализации ДОЛЖНЫ использовать эквивалентные ссылки на символы, как указано в разделе 4.1 спецификации XML (даже в неочевидных местах, таких как URI, включенные в атрибут href).

Таким образом, эта проблема касается большего, чем просто создание правильно сформированного XML, что является предварительным условием. Вы также должны убедиться, что используете XHTML только из утвержденного набора в разделе 6. .

Короче говоря, вам нужно прочитать XEP-0071.

person Joe Hildebrand    schedule 04.11.2010
comment
@html_body.gsub!(/&nbsp;/,' '), кажется, сделал конкретное сообщение о сбое XMPP, прежде чем остановить его сейчас, и это здорово! Однако это явно только одна сущность - мне нужно более надежное решение, чтобы компонент XMPP оставался активным. Большое спасибо! Я упомяну вас в журнале изменений :-) - person Josh; 05.11.2010

Может быть, на самом деле поможет преобразование в XML с помощью Nokogiri? Затем вы можете повторно сериализовать поток XMPP. Кроме того, если вы хотите, чтобы ваши файлы немного масштабировались и избегали раздувания памяти, переключитесь на Blather вместо XMPP4r. Также DSL довольно удивительным!

person Julien Genestoux    schedule 04.11.2010
comment
Спасибо, посмотрю Нокогири! Может ли Блатер создать компонент XMPP? То есть прямо сейчас скрипт создает новый поддомен моего сервера XMPP и создает несколько комнат MUC на этом поддомене. Это не просто способ подключения к серверу XMPP с использованием существующего JID... - person Josh; 05.11.2010
comment
Хо, да, особенно если вы хотите использовать компонент, я бы рекомендовал Blather или даже Skates. XMPP4R, вероятно, довольно быстро выйдет из строя по мере увеличения трафика. - person Julien Genestoux; 05.11.2010

Не используйте Тиди. Воспользуйтесь парсером HTML5, а затем создайте дамп DOM, который он генерирует в XML. Если вы можете создавать модель DOM, вы можете каждый раз создавать из нее правильно сформированный XML. Кроме того, у него будет преимущество в том, что он будет создавать примерно такой же DOM, что и большинство современных браузеров.

person Bob Aman    schedule 04.11.2010
comment
Это звучит чрезвычайно многообещающе, и я с нетерпением жду возможности попробовать это! - person Josh; 05.11.2010
comment
Тем не менее, Джо прав, и, кроме того, html5lib не решит проблему, на которую он указал. - person Bob Aman; 05.11.2010

Вы работаете на *nix? Если это так, я бы делегировал проблему xmllint, программе, которая является частью libxml2. Я работаю с системой, которая генерирует xml перед отправкой по сети; мы проверяем наш xml с помощью xmllint, например:

    command = "xmllint #{temp_file_path} --schema #{schema_file_path} --noout 2>&1"
    output = `#{command}`
    if $? != 0
      temp_dir.keep
      $stderr.puts "Error validating xml: running command #{command.inspect}"
      $stderr.puts output
      exit(1)
    end

Конечно, вам нужно будет адаптировать это к вашей ситуации, но основная идея работает хорошо. Если у вас нет DTD, оставьте бит "--schema".

person Wayne Conrad    schedule 04.11.2010
comment
Я работаю в Linux, но я бы предпочел, чтобы система была независимой от ОС, поскольку я знаю, что в сообществе много пользователей, использующих только Windows. Кроме того, запуск внешней команды для каждого сообщения в чате будет ужасно с точки зрения производительности... вы недавно были в чате? ;-) - person Josh; 05.11.2010
comment
Если для этого нет рубиновой жемчужины...? Не поймите меня неправильно, это хорошая идея, но... - person Josh; 05.11.2010