【问题标题】:How to extract nested ejabberd message element from MucSub event in Erlang如何从 Erlang 中的 MucSub 事件中提取嵌套的 ejabberd 消息元素
【发布时间】:2019-10-23 00:57:03
【问题描述】:

我想在 ejabberd 数据包中查找消息元素。 数据包本身是一个消息元素,但有时(延迟消息或其他情况)实际消息嵌套在数据包内:

普通消息:

<message from="hag66@shakespeare.example"
         to="coven@muc.shakespeare.example"
         type="groupchat">
  <body>Test</body>
</message>

其他结构示例:

<message from="coven@muc.shakespeare.example"
         to="hag66@shakespeare.example/pda">
  <event xmlns="http://jabber.org/protocol/pubsub#event">
    <items node="urn:xmpp:mucsub:nodes:messages">
      <item id="18277869892147515942">
        <message from="coven@muc.shakespeare.example/secondwitch"
                 to="hag66@shakespeare.example/pda"
                 type="groupchat"
                 xmlns="jabber:client">
          <archived xmlns="urn:xmpp:mam:tmp"
                    by="muc.shakespeare.example"
                    id="1467896732929849" />
          <stanza-id xmlns="urn:xmpp:sid:0"
                     by="muc.shakespeare.example"
                     id="1467896732929849" />
          <body>Hello from the MUC room !</body>
        </message>
      </item>
    </items>
  </event>
</message>

在第二个示例中,我想找到内部消息元素。 第二种情况的结构并不总是相同的。所以我需要遍历数据包并尝试找到任何带有名称消息的子元素。 它不能是两个消息子元素,所以如果我找到第一个,我就不需要再继续了。如果没有带有名称消息的子元素,我想返回原始数据包。

这是我到现在为止的代码:

get_message(Packet) ->
    Els = xmpp:get_els(Packet),

    Found =
        case Els of
            [] ->
                <<>>;
            _ ->
                El = find_file(Els, fun(El) ->
                ElementName = io_lib:format("~s",[xmpp:get_name(El)]),
                string:equal(ElementName,"message") end, <<>>),

                Fe = 
                    case El of
                        <<>> -> 
                            Elements = xmpp:get_els(El),
                            lists:foreach(fun(Element) ->
                                FoundElement = get_message(Element),
                                case FoundElement of
                                    <<>> ->
                                        ok;
                                    _ -> 
                                        % stop foreach and return FoundElement
                                        FoundElement
                                end
                            end, Elements);
                        _ ->
                            El
                    end,
                Fe
        end,
    Found.


    find_file(L, Condition, Default) ->
      case lists:dropwhile(fun(E) -> not Condition(E) end, L) of
        [] -> Default;
        [F | _] -> F
      end.

【问题讨论】:

    标签: erlang ejabberd


    【解决方案1】:

    原来我不需要做所有这些计算。这是一个名为 unwrap_mucsub_message 的方法,它完全符合我的需要。

    get_message(Packet) ->
        case misc:unwrap_mucsub_message(Packet) of
            #message{} = Msg ->
                Msg;
            _ ->
                Packet
        end.
    

    【讨论】:

      【解决方案2】:

      哎呀,这是二郎!这是一个使用xmerl的erlang解决方案,这是erlang的内置xml解析模块:

      xml.xml:

      <message from="coven@muc.shakespeare.example"
               to="hag66@shakespeare.example/pda">
        <event xmlns="http://jabber.org/protocol/pubsub#event">
          <items node="urn:xmpp:mucsub:nodes:messages">
            <item id="18277869892147515942">
              <message from="coven@muc.shakespeare.example/secondwitch"
                       to="hag66@shakespeare.example/pda"
                       type="groupchat"
                       xmlns="jabber:client">
                <archived xmlns="urn:xmpp:mam:tmp"
                          by="muc.shakespeare.example"
                          id="1467896732929849" />
                <stanza-id xmlns="urn:xmpp:sid:0"
                           by="muc.shakespeare.example"
                           id="1467896732929849" />
                <body>Hello from the MUC room !</body>
              </message>
            </item>
          </items>
        </event>
      </message>
      

      我的.erl:

      -module(my).
      -compile(export_all).
      -include_lib("./xmerl.hrl").
      
      get_doc() ->
          {ParsedDoc, _Rest} = xmerl_scan:file("./message.xml"),
          ParsedDoc.
      
      get_message() ->
          Messages = xmerl_xpath:string("//message", get_doc()),
          %io:format("~p~n", [Messages]),
          lists:last(Messages).
      
      get_attributes(Node) ->
          xmerl_xpath:string("./@*", Node).
      
      convert_to_map(Attrs) ->
      
          lists:foldl(
              fun({xmlAttribute,Name,_,_,_,_List,_,_,Value,_}, Acc) ->
                  Acc#{Name => Value}
              end,
              #{},  % initial value for Acc
              Attrs
          ).
      

      如果您已经将消息作为字符串,还有一个名为 xmerl_scan:string/1 的函数,例如:

      {ParsedMessage, _RemainingText = ""} = xmerl_scan:string(Message)
      

      您还需要文件xmerl.hrl

      在这个函数中:

      get_message() ->
          Messages = xmerl_xpath:string("//message", get_doc()),
          lists:last(Messages).
      

      Messages 将是一个包含以下内容的列表:

      1. 如果没有嵌套消息,则为一条消息,或者
      2. 如果有嵌套消息,则有两条消息。嵌套消息将是列表中的最后一条消息。

      这意味着lists:last() 将返回嵌套消息,或者当没有嵌套消息时返回根消息。

      在外壳中:

      ~/erlang_programs/xmerl$ erl
      Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
      Eshell V9.3  (abort with ^G)
      
      1> Msg = my:get_message().        
      {xmlElement,message,message,[],
                  {xmlNamespace,'jabber:client',[]},
                  [{item,2},{items,2},{event,2},{message,1}],
                  2,
                  [{xmlAttribute,from,[],[],[],
                                 [{message,2},{item,2},{items,2},{event,2},{message,1}],
                                 1,[],"coven@muc.shakespeare.example/secondwitch",false},
                   {xmlAttribute,to,[],[],[],
                                 [{message,2},{item,2},{items,2},{event,2},{message,1}],
                                 2,[],"hag66@shakespeare.example/pda",false},
                   {xmlAttribute,type,[],[],[],
                                 [{message,2},{item,2},{items,2},{event,2},{message,1}],
                                 3,[],"groupchat",false},
                   {xmlAttribute,xmlns,[],[],[],
                                 [{message,2},{item,2},{items,2},{event,2},{message,1}],
                                 4,[],"jabber:client",false}],
                  [{xmlText,[{message,2},
                             {item,2},
                             {items,2},
                             {event,2},
                             {message,1}],
                            1,[],"\n          ",text},
                   {xmlElement,archived,archived,[],
                               {xmlNamespace,'urn:xmpp:mam:tmp',[]},
                               [{message,2},{item,2},{items,2},{event,2},{message,1}],
                               2,
                               [{xmlAttribute,xmlns,[],[],[],
                                              [{archived,2},{message,...},{...}|...],
                                              1,[],
                                              [...],...},
                                {xmlAttribute,by,[],[],[],
                                              [{archived,...},{...}|...],
                                              2,[],...},
                                {xmlAttribute,id,[],[],[],[{...}|...],3,...}],
                               [],[],".",undeclared},
                   {xmlText,[{message,2},
                             {item,2},
                             {items,2},
                             {event,2},
                             {message,1}],
                            3,[],"\n          ",text},
                   {xmlElement,'stanza-id','stanza-id',[],
                               {xmlNamespace,'urn:xmpp:sid:0',[]},
                               [{message,2},{item,2},{items,2},{event,2},{message,1}],
                               4,
                               [{xmlAttribute,xmlns,[],[],[],[{...}|...],1,...},
                                {xmlAttribute,by,[],[],[],[...],...},
                                {xmlAttribute,id,[],[],[],...}],
                               [],[],".",undeclared},
                   {xmlText,[{message,2},
                             {item,2},
                             {items,2},
                             {event,2},
                             {message,1}],
                            5,[],"\n          ",text},
                   {xmlElement,body,body,[],
                               {xmlNamespace,'jabber:client',[]},
                               [{message,2},{item,2},{items,2},{event,2},{message,1}],
                               6,[],
                               [{xmlText,[{body,...},{...}|...],1,[],...}],
                               [],".",undeclared},
                   {xmlText,[{message,2},
                             {item,2},
                             {items,2},
                             {event,2},
                             {message,1}],
                            7,[],"\n        ",text}],
                  [],".",undeclared}
      
      2> Attrs = my:get_attributes(Msg).
      [{xmlAttribute,from,[],[],[],
                     [{message,2},{item,2},{items,2},{event,2},{message,1}],
                     1,[],"coven@muc.shakespeare.example/secondwitch",false},
       {xmlAttribute,to,[],[],[],
                     [{message,2},{item,2},{items,2},{event,2},{message,1}],
                     2,[],"hag66@shakespeare.example/pda",false},
       {xmlAttribute,type,[],[],[],
                     [{message,2},{item,2},{items,2},{event,2},{message,1}],
                     3,[],"groupchat",false}]
      
      3> my:convert_to_map(Attrs).          
      #{from => "coven@muc.shakespeare.example/secondwitch",
        to => "hag66@shakespeare.example/pda",type => "groupchat"}
      
      4> 
      

      获取消息中的正文标签(或任何其他嵌套标签):

      get_body(Message) ->
          [Body] = xmerl_xpath:string(".//body", Message),
          Body.
      

      获取消息的所有直接子标签:

      get_direct_children(Message) ->
          xmerl_xpath:string("./*", Message).
      

      获取标签单个属性的值:

      get_attribute(Attr, Node) ->
          % {xmlObj,string,"coven@muc.shakespeare.example"}
          {xmlObj, string, Value} = xmerl_xpath:string("string(./@" ++ Attr ++ ")", Node),
          Value.
      

      ===灵丹妙药===

      您可以使用SweetXml 来解析您的“数据包”:

      defmodule XmlExample do
        import SweetXml
      
        def sweet(path) do
          File.read!(path)
          |> xpath(~x"//message"l)
          |> Enum.at(-1) 
          |> xpath(~x"//@from")
        end
      
      end
      

      第一个 xpath() 调用返回一个 (l)ist,即所有匹配项,而不仅仅是第一个匹配项。该列表将包含一个或两个消息标签——取决于数据包。 Enum.at(-1) 将返回列表中的最后一个消息标签,该标签要么是嵌套消息标签,要么是没有嵌套消息标签时的根消息标签。第二个xpath() 调用返回消息标签的from 属性,在嵌套数据包的情况下会产生:

      'coven@muc.shakespeare.example/secondwitch'
      

      我注意到 SweetXml 返回一个字符列表(单引号字符串)而不是双引号字符串(这可能是您想要的)。如果在第二个xpath() 调用中添加s,则返回值将是双引号字符串:

      |> xpath(~x"//@from"s)
      

      输出:

      ~/elixir_programs/xml_example$ iex -S mix
      Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
      Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
      
      iex(1)> XmlExample.sweet("./lib/xml.xml") 
      "coven@muc.shakespeare.example/secondwitch"
      

      我不知道是否有更好的方法,但是要获取标签的所有属性,您可以这样做:

        def sweet(path) do
          File.read!(path)
          |> xpath(~x"//message"l)
          |> Enum.at(-1)
          |> xpath(~x"./@*"le)
          |> Enum.map(fn {:xmlAttribute,name,_,_,_,_list,_,_,value,_} ->
               {name, value} 
             end)
      
        end
      

      输出:

      [
        from: 'coven@muc.shakespeare.example/secondwitch',
        to: 'hag66@shakespeare.example/pda',
        type: 'groupchat'
      ]
      

      在这一行:

      xpath(~x"./@*"le)
      

      ./在当前标签中搜索,也就是Enum.at(-1)返回的标签,@*选择所有属性。再一次,l 需要让xpath() 返回所有匹配项(如果你忘记了l,那会非常令人沮丧!),e 代表“实体”,这会导致 xpath() 返回每个属性的“实体”,如下所示:

      [
        {:xmlAttribute, :from, [], [], [],
         [message: 2, item: 2, items: 2, event: 2, message: 1], 1, [],
         'coven@muc.shakespeare.example/secondwitch', false},
      
        {:xmlAttribute, :to, [], [], [],
         [message: 2, item: 2, items: 2, event: 2, message: 1], 2, [],
         'hag66@shakespeare.example/pda', false},
      
        {:xmlAttribute, :type, [], [], [],
         [message: 2, item: 2, items: 2, event: 2, message: 1], 3, [],
         'groupchat', false}
      ]
      

      然后代码模式匹配元组以挑选出每个属性的name及其value

      如果您希望获取地图中的所有属性:

        def sweet(path) do
      
          attr_entities = File.read!(path)
            |> xpath(~x"//message"l)
            |> Enum.at(-1)
            |> xpath(~x"./@*"le)
      
          for {:xmlAttribute,name,_,_,_,_list,_,_,value,_} <- attr_entities, into: %{} do
               {name, value} 
          end
      
        end
      

      输出:

      %{
        from: 'coven@muc.shakespeare.example/secondwitch',
        to: 'hag66@shakespeare.example/pda',
        type: 'groupchat'
      }
      

      【讨论】:

      • 听起来您是 Ejabberd 的新手。 Ejabberd 使用自己的名为 fast_xml 的 XML 库。 IIRC 可以使用 fxml:get_subtags/2 来完成
      • 哇.. 非常感谢您提供所有这些详细信息.. 但是我这里的数据包已经使用 Ejabberd 附带的 fast_xml 库进行了解析,我只是不知道如何获取嵌套消息从这个已经解析的xml。我会再读一次你的答案,如果它让我知道如何实现这一目标,我会给你竖起大拇指。再次感谢您为此付出的时间。
      猜你喜欢
      • 2016-07-17
      • 1970-01-01
      • 2015-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多