使用Ruby,我该如何确认一个XML片段是否有效?

3
一些人可能已经知道,我正在为StackOverflow聊天系统工作,开发XMPP(Jabber)集成,使用xmpp4r包编写的XMPP组件。

我正在处理一个问题(好吧,有很多问题,但是目前只有一个问题 :-) 我正在提取聊天记录的JSON源并提取消息的HTML。我使用Ruby TidyHTML绑定将JSON中的HTML转换为XHTML,以便我可以将其作为XMPP消息发送--因为XMPP消息只是XML,将HTML转换为XHTML应该使其成为有效的XML,我只需将其放入<message>标签中即可。

对于大多数消息来说,它都很好用!

My Mind Is Blown

然而,对于其他消息,它完全瘫痪 - XMPP服务器关闭了流并且脚本停止运行。(rchen和The Tavern中的其他人会感到不安。好吧,也许不是“不安”,但他们会嘲笑我。这让我很难过!) 我几乎可以确定发生的事情是,由于某种原因,消息不是有效的XML,因此XMPP服务器正在关闭连接,因为它在Ruby组件的XML流中遇到解析错误。以下是其中一条消息的示例:
<message to='jeswah@smart-safe-secure.com/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>

我收到的信息是一个引用聊天室链接的消息。

Ruby 给出的错误如下:

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在将其发送到XMPP服务器之前验证(并且最好是 更正)XML?很可能,更正它将是我为每种情况编写额外代码的问题,其中Tidy未生成有效XML,但我至少想阻止脚本崩溃。那么,在将XML发送到XMPP服务器之前,如何验证XML?


感谢Michael Mrozek提供的截图素材! - Josh
@TreyE:我不确定你具体在问什么……目前没有测试。代码只是愉快地接收聊天中的每个HTML消息并将其解析为XHTML,然后将其发送到XMPP服务器。如果有帮助,我可以发布相关代码。 - Josh
完整的源代码可在 trac 或者 subversion 中获取。 - Josh
我不知道如何通过Ruby代码创建ejabber客户端,请帮帮我,如果您能的话。 - Jigar Bhatt
4个回答

3
在这种情况下,实际的错误是您的&nbsp;。根据XEP-0071,第8节,第5点:
“XMPP核心的第11.1节规定,除了XML规范第4.6节中定义的五个通用实体(即&lt;、&gt;、&amp;、&apos;和&quot;)之外,不得发送字符实体到XML流。因此,XHTML-IM的实现不能包括预定义的XHTML 1.0实体,例如&nbsp; - 相反,实现必须使用XML规范第4.1节中指定的等效字符引用(即使在包含在“href”属性中的URI这样的非明显位置也是如此)。”
因此,这个问题不仅涉及生成格式良好的XML,这是先决条件。您还需要确保仅使用第6节中批准的XHTML集。
简而言之,您需要阅读XEP-0071。

@html_body.gsub!(/&nbsp;/,' ') 看起来已经成功解决了之前导致 XMPP 崩溃的特定消息,这太好了!然而,这只是一个实体 -- 我需要更强大的解决方案来确保 XMPP 组件保持活动状态。非常感谢!我会在变更日志中给你署名 :-) - Josh

1

你是在*nix上运行吗?如果是的话,我建议将问题委托给xmllint,这是libxml2的一部分。我们使用一个系统在发送xml之前生成xml;我们使用xmllint验证我们的xml,如下所示:

    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”部分。


我正在使用Linux,但我希望系统是独立于操作系统的,因为我知道社区中有许多仅限于Windows的用户。此外,为每个聊天消息启动外部命令将会对性能产生可怕的影响...你最近参加过聊天吗?;-) - Josh
除非有RubyGem支持,否则怎么办?别误会,这是个好主意,但是... - Josh

1

或许使用Nokogiri将其转换为XML会有所帮助?然后您可以重新序列化XMPP流。 此外,如果您想要使您的内容更具可扩展性并避免内存膨胀,请改用Blather而不是XMPP4r。此外,该DSL非常棒!


谢谢,我会去看看Nokogiri!Blather能创建一个XMPP组件吗?也就是说,现在这个脚本会创建我的XMPP服务器的一个新子域,并在该子域上创建多个MUC房间。这不仅仅是使用现有JID连接到XMPP服务器的方法... - Josh
嗯,是的,特别是如果你想使用组件,我会推荐Blather甚至Skates。随着流量的增加,XMPP4R可能很快就会出现问题。 - Julien Genestoux

1
不要使用Tidy。使用HTML5解析器,然后将其生成的DOM转储为XML。如果您可以生成DOM,则每次都可以从中生成格式良好的XML。它还具有产生大多数现代浏览器提供的相同DOM的优点。

这听起来非常有前途,我期待着尝试它! - Josh
话虽如此,Joe是正确的,此外,html5lib也无法解决他指出的问题。 - Bob Aman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接