Как извлечь вложенный элемент сообщения ejabberd из события MucSub в Erlang - PullRequest
0 голосов
/ 07 июня 2019

Я хочу найти элемент сообщения в пакете 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.

Ответы [ 2 ]

1 голос
/ 10 июня 2019

Оказалось, мне не нужно делать все эти вычисления.это метод unwrap_mucsub_message, который делает именно то, что мне нужно.

get_message(Packet) ->
    case misc:unwrap_mucsub_message(Packet) of
        #message{} = Msg ->
            Msg;
        _ ->
            Packet
    end.
0 голосов
/ 09 июня 2019

Упс, это эрланг!Вот решение erlang, использующее xmerl, который является встроенным модулем синтаксического анализа xlan для erlang:

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>

my.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> 

Чтобы получить тег body (или любой другой вложенный тег) в сообщении:

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 возвращает charlist (строка в одинарных кавычках) вместострока в двойных кавычках (что, вероятно, то, что вы хотите).Если вы добавите s ко второму вызову xpath(), возвращаемое значение будет строкой в ​​двойных кавычках:

|> 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

output:

[
  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

output:

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