如何从 Erlang 中的 MucSub 事件中提取嵌套的 ejabberd 消息元素
How to extract nested ejabberd message element from MucSub event in Erlang
我想在 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>
在第二个示例中,我想找到内部消息元素。
第二种情况结构并不总是相同的。所以我需要遍历数据包并尝试找到名称为 message 的任何子元素。
它不能是两个消息子元素,所以如果我找到第一个,我就不需要再继续了。如果没有带有名称消息的子元素,我想 return 原始数据包。
这是我目前的代码:
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.
哎呀,这是二郎!这是一个使用 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>
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
将是一个包含以下内容的列表:
- 一条消息,如果没有嵌套消息,或
- 如果有嵌套消息,则为两条消息。嵌套消息将是列表中的最后一条消息。
这意味着lists:last()
将return嵌套消息,或者当没有嵌套消息时是根消息。
在shell:
~/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 来解析您的 "packets":
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()
调用 return 一个 (l)ist,即所有匹配项,而不仅仅是第一个匹配项。该列表将包含一个或两个消息标记——取决于数据包。 Enum.at(-1)
将 return 列表中的最后一个消息标记,它将是嵌套消息标记,或者在没有嵌套消息标记时是根消息标记。第二个 xpath()
调用 return 消息标记的 from
属性,在嵌套数据包的情况下会产生:
'coven@muc.shakespeare.example/secondwitch'
我注意到 SweetXml returns 是一个字符列表(单引号字符串)而不是双引号字符串(这可能是您想要的)。如果将 s
添加到第二个 xpath()
调用,则 return 值将是双引号字符串:
|> 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)
./
在当前标签中查找,即return被Enum.at(-1)编辑的标签,@*
选择所有属性。再一次,l
需要 xpath()
return 所有匹配项(如果忘记 l
会非常令人沮丧!),而 e
代表对于 "entity",这会导致 xpath() return 每个属性的 "entities",如下所示:
[
{: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'
}
原来我不需要做所有这些计算。这些是一种名为 unwrap_mucsub_message 的方法,它完全满足我的需要。
get_message(Packet) ->
case misc:unwrap_mucsub_message(Packet) of
#message{} = Msg ->
Msg;
_ ->
Packet
end.
我想在 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>
在第二个示例中,我想找到内部消息元素。 第二种情况结构并不总是相同的。所以我需要遍历数据包并尝试找到名称为 message 的任何子元素。 它不能是两个消息子元素,所以如果我找到第一个,我就不需要再继续了。如果没有带有名称消息的子元素,我想 return 原始数据包。
这是我目前的代码:
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.
哎呀,这是二郎!这是一个使用 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>
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
将是一个包含以下内容的列表:
- 一条消息,如果没有嵌套消息,或
- 如果有嵌套消息,则为两条消息。嵌套消息将是列表中的最后一条消息。
这意味着lists:last()
将return嵌套消息,或者当没有嵌套消息时是根消息。
在shell:
~/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 来解析您的 "packets":
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()
调用 return 一个 (l)ist,即所有匹配项,而不仅仅是第一个匹配项。该列表将包含一个或两个消息标记——取决于数据包。 Enum.at(-1)
将 return 列表中的最后一个消息标记,它将是嵌套消息标记,或者在没有嵌套消息标记时是根消息标记。第二个 xpath()
调用 return 消息标记的 from
属性,在嵌套数据包的情况下会产生:
'coven@muc.shakespeare.example/secondwitch'
我注意到 SweetXml returns 是一个字符列表(单引号字符串)而不是双引号字符串(这可能是您想要的)。如果将 s
添加到第二个 xpath()
调用,则 return 值将是双引号字符串:
|> 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)
./
在当前标签中查找,即return被Enum.at(-1)编辑的标签,@*
选择所有属性。再一次,l
需要 xpath()
return 所有匹配项(如果忘记 l
会非常令人沮丧!),而 e
代表对于 "entity",这会导致 xpath() return 每个属性的 "entities",如下所示:
[
{: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'
}
原来我不需要做所有这些计算。这些是一种名为 unwrap_mucsub_message 的方法,它完全满足我的需要。
get_message(Packet) ->
case misc:unwrap_mucsub_message(Packet) of
#message{} = Msg ->
Msg;
_ ->
Packet
end.