]> granicus.if.org Git - ejabberd/commitdiff
Add xdata generator and make some code using it
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 7 Oct 2016 07:31:03 +0000 (10:31 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 7 Oct 2016 07:31:03 +0000 (10:31 +0300)
58 files changed:
Makefile.in
include/flex_offline.hrl [new file with mode: 0644]
include/muc_register.hrl [new file with mode: 0644]
include/muc_request.hrl [new file with mode: 0644]
include/muc_roomconfig.hrl [new file with mode: 0644]
include/muc_roominfo.hrl [new file with mode: 0644]
include/pubsub_get_pending.hrl [new file with mode: 0644]
include/pubsub_node_config.hrl [new file with mode: 0644]
include/pubsub_publish_options.hrl [new file with mode: 0644]
include/pubsub_subscribe_authorization.hrl [new file with mode: 0644]
include/pubsub_subscribe_options.hrl [new file with mode: 0644]
include/xmpp_codec.hrl
specs/flex_offline.cfg [new file with mode: 0644]
specs/flex_offline.xdata [new file with mode: 0644]
specs/muc_register.cfg [new file with mode: 0644]
specs/muc_register.xdata [new file with mode: 0644]
specs/muc_request.cfg [new file with mode: 0644]
specs/muc_request.xdata [new file with mode: 0644]
specs/muc_roomconfig.cfg [new file with mode: 0644]
specs/muc_roomconfig.xdata [new file with mode: 0644]
specs/muc_roominfo.cfg [new file with mode: 0644]
specs/muc_roominfo.xdata [new file with mode: 0644]
specs/pubsub_get_pending.cfg [new file with mode: 0644]
specs/pubsub_get_pending.xdata [new file with mode: 0644]
specs/pubsub_node_config.cfg [new file with mode: 0644]
specs/pubsub_node_config.xdata [new file with mode: 0644]
specs/pubsub_publish_options.cfg [new file with mode: 0644]
specs/pubsub_publish_options.xdata [new file with mode: 0644]
specs/pubsub_subscribe_authorization.cfg [new file with mode: 0644]
specs/pubsub_subscribe_authorization.xdata [new file with mode: 0644]
specs/pubsub_subscribe_options.cfg [new file with mode: 0644]
specs/pubsub_subscribe_options.xdata [new file with mode: 0644]
specs/xmpp_codec.spec [moved from tools/xmpp_codec.spec with 99% similarity]
src/ejabberd_captcha.erl
src/flex_offline.erl [new file with mode: 0644]
src/mod_caps.erl
src/mod_mam.erl
src/mod_muc.erl
src/mod_muc_room.erl
src/mod_offline.erl
src/mod_pubsub.erl
src/muc_register.erl [new file with mode: 0644]
src/muc_request.erl [new file with mode: 0644]
src/muc_roomconfig.erl [new file with mode: 0644]
src/muc_roominfo.erl [new file with mode: 0644]
src/node_flat.erl
src/node_flat_sql.erl
src/pubsub_get_pending.erl [new file with mode: 0644]
src/pubsub_node_config.erl [new file with mode: 0644]
src/pubsub_publish_options.erl [new file with mode: 0644]
src/pubsub_subscribe_authorization.erl [new file with mode: 0644]
src/pubsub_subscribe_options.erl [new file with mode: 0644]
src/xdata_codec.erl [new file with mode: 0644]
src/xmpp_codec.erl
src/xmpp_util.erl
test/ejabberd_SUITE.erl
test/suite.erl
test/suite.hrl

index 07502f06e262badb69d6dcae6d80be6416935e96..728b2fa98420f0b224f1cb6fe5e92bef1a7ec9fe 100644 (file)
@@ -110,7 +110,11 @@ edoc:
 
 spec:
        $(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \
-       'case fxml_gen:compile("tools/xmpp_codec.spec", [{add_type_specs, xmpp_element}, {erl_dir, "src"}, {hrl_dir, "include"}]) of ok -> halt(0); _ -> halt(1) end.'
+       'case fxml_gen:compile("specs/xmpp_codec.spec", [{add_type_specs, xmpp_element}, {erl_dir, "src"}, {hrl_dir, "include"}]) of ok -> halt(0); _ -> halt(1) end.'
+
+xdata:
+       $(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \
+       'case xdata_codec:compile("specs", [{erl_dir, "src"}, {hrl_dir, "include"}]) of ok -> halt(0); _ -> halt(1) end.'
 
 JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1))
 
diff --git a/include/flex_offline.hrl b/include/flex_offline.hrl
new file mode 100644 (file)
index 0000000..74a38fb
--- /dev/null
@@ -0,0 +1,10 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: flex_offline.xdata
+%% Form type: http://jabber.org/protocol/offline
+%% Document: XEP-0013
+
+
+-type property() :: {'number_of_messages', non_neg_integer()}.
+-type result() :: [property()].
+
+-type form() :: [property() | xdata_field()].
diff --git a/include/muc_register.hrl b/include/muc_register.hrl
new file mode 100644 (file)
index 0000000..0cfc928
--- /dev/null
@@ -0,0 +1,16 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: muc_register.xdata
+%% Form type: http://jabber.org/protocol/muc#register
+%% Document: XEP-0045
+
+
+-type property() :: {'allow', boolean()} |
+                    {'email', binary()} |
+                    {'faqentry', [binary()]} |
+                    {'first', binary()} |
+                    {'last', binary()} |
+                    {'roomnick', binary()} |
+                    {'url', binary()}.
+-type result() :: [property()].
+
+-type form() :: [property() | xdata_field()].
diff --git a/include/muc_request.hrl b/include/muc_request.hrl
new file mode 100644 (file)
index 0000000..bc14be3
--- /dev/null
@@ -0,0 +1,16 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: muc_request.xdata
+%% Form type: http://jabber.org/protocol/muc#request
+%% Document: XEP-0045
+
+
+-type property() :: {'role', participant} |
+                    {'jid', jid:jid()} |
+                    {'roomnick', binary()} |
+                    {'request_allow', boolean()}.
+-type result() :: [property()].
+
+-type options(T) :: [{binary(), T}].
+-type property_with_options() ::
+      {'role', participant, options(participant)}.
+-type form() :: [property() | property_with_options() | xdata_field()].
diff --git a/include/muc_roomconfig.hrl b/include/muc_roomconfig.hrl
new file mode 100644 (file)
index 0000000..89cfc33
--- /dev/null
@@ -0,0 +1,55 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: muc_roomconfig.xdata
+%% Form type: http://jabber.org/protocol/muc#roomconfig
+%% Document: XEP-0045
+
+-type 'allow_private_messages_from_visitors'() :: nobody | moderators | anyone.
+-type 'maxusers'() :: none | non_neg_integer().
+-type 'presencebroadcast'() :: moderator | participant | visitor.
+-type 'whois'() :: moderators | anyone.
+
+-type property() :: {'maxhistoryfetch', binary()} |
+                    {'allowpm', binary()} |
+                    {'allow_private_messages', boolean()} |
+                    {'allow_private_messages_from_visitors', 'allow_private_messages_from_visitors'()} |
+                    {'allow_visitor_status', boolean()} |
+                    {'allow_visitor_nickchange', boolean()} |
+                    {'allow_voice_requests', boolean()} |
+                    {'allow_subscription', boolean()} |
+                    {'voice_request_min_interval', non_neg_integer()} |
+                    {'captcha_protected', boolean()} |
+                    {'captcha_whitelist', [jid:jid()]} |
+                    {'allow_query_users', boolean()} |
+                    {'allowinvites', boolean()} |
+                    {'changesubject', boolean()} |
+                    {'enablelogging', boolean()} |
+                    {'getmemberlist', [binary()]} |
+                    {'lang', binary()} |
+                    {'pubsub', binary()} |
+                    {'maxusers', 'maxusers'()} |
+                    {'membersonly', boolean()} |
+                    {'moderatedroom', boolean()} |
+                    {'members_by_default', boolean()} |
+                    {'passwordprotectedroom', boolean()} |
+                    {'persistentroom', boolean()} |
+                    {'presencebroadcast', ['presencebroadcast'()]} |
+                    {'publicroom', boolean()} |
+                    {'public_list', boolean()} |
+                    {'roomadmins', [jid:jid()]} |
+                    {'roomdesc', binary()} |
+                    {'roomname', binary()} |
+                    {'roomowners', [jid:jid()]} |
+                    {'roomsecret', binary()} |
+                    {'whois', 'whois'()} |
+                    {'mam', boolean()}.
+-type result() :: [property()].
+
+-type options(T) :: [{binary(), T}].
+-type property_with_options() ::
+      {'allowpm', binary(), options(binary())} |
+      {'allow_private_messages_from_visitors', 'allow_private_messages_from_visitors'(), options('allow_private_messages_from_visitors'())} |
+      {'getmemberlist', [binary()], options(binary())} |
+      {'maxusers', 'maxusers'(), options('maxusers'())} |
+      {'presencebroadcast', ['presencebroadcast'()], options('presencebroadcast'())} |
+      {'whois', 'whois'(), options('whois'())}.
+-type form() :: [property() | property_with_options() | xdata_field()].
diff --git a/include/muc_roominfo.hrl b/include/muc_roominfo.hrl
new file mode 100644 (file)
index 0000000..cf4f4eb
--- /dev/null
@@ -0,0 +1,18 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: muc_roominfo.xdata
+%% Form type: http://jabber.org/protocol/muc#roominfo
+%% Document: XEP-0045
+
+
+-type property() :: {'maxhistoryfetch', non_neg_integer()} |
+                    {'contactjid', [jid:jid()]} |
+                    {'description', binary()} |
+                    {'lang', binary()} |
+                    {'ldapgroup', binary()} |
+                    {'logs', binary()} |
+                    {'occupants', non_neg_integer()} |
+                    {'subject', binary()} |
+                    {'subjectmod', boolean()}.
+-type result() :: [property()].
+
+-type form() :: [property() | xdata_field()].
diff --git a/include/pubsub_get_pending.hrl b/include/pubsub_get_pending.hrl
new file mode 100644 (file)
index 0000000..4ddf9ba
--- /dev/null
@@ -0,0 +1,13 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_get_pending.xdata
+%% Form type: http://jabber.org/protocol/pubsub#subscribe_authorization
+%% Document: XEP-0060
+
+
+-type property() :: {'node', binary()}.
+-type result() :: [property()].
+
+-type options(T) :: [{binary(), T}].
+-type property_with_options() ::
+      {'node', binary(), options(binary())}.
+-type form() :: [property() | property_with_options() | xdata_field()].
diff --git a/include/pubsub_node_config.hrl b/include/pubsub_node_config.hrl
new file mode 100644 (file)
index 0000000..e1519cd
--- /dev/null
@@ -0,0 +1,60 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_node_config.xdata
+%% Form type: http://jabber.org/protocol/pubsub#node_config
+%% Document: XEP-0060
+
+-type 'access_model'() :: authorize | open | presence | roster | whitelist.
+-type 'children_association_policy'() :: all | owners | whitelist.
+-type 'itemreply'() :: owner | publisher | none.
+-type 'node_type'() :: leaf | collection.
+-type 'notification_type'() :: normal | headline.
+-type 'publish_model'() :: publishers | subscribers | open.
+-type 'send_last_published_item'() :: never | on_sub | on_sub_and_presence.
+
+-type property() :: {'access_model', 'access_model'()} |
+                    {'body_xslt', binary()} |
+                    {'children_association_policy', 'children_association_policy'()} |
+                    {'children_association_whitelist', [jid:jid()]} |
+                    {'children', [binary()]} |
+                    {'children_max', binary()} |
+                    {'collection', [binary()]} |
+                    {'contact', [jid:jid()]} |
+                    {'dataform_xslt', binary()} |
+                    {'deliver_notifications', boolean()} |
+                    {'deliver_payloads', boolean()} |
+                    {'description', binary()} |
+                    {'item_expire', binary()} |
+                    {'itemreply', 'itemreply'()} |
+                    {'language', binary()} |
+                    {'max_items', non_neg_integer()} |
+                    {'max_payload_size', non_neg_integer()} |
+                    {'node_type', 'node_type'()} |
+                    {'notification_type', 'notification_type'()} |
+                    {'notify_config', boolean()} |
+                    {'notify_delete', boolean()} |
+                    {'notify_retract', boolean()} |
+                    {'notify_sub', boolean()} |
+                    {'persist_items', boolean()} |
+                    {'presence_based_delivery', boolean()} |
+                    {'publish_model', 'publish_model'()} |
+                    {'purge_offline', boolean()} |
+                    {'roster_groups_allowed', [binary()]} |
+                    {'send_last_published_item', 'send_last_published_item'()} |
+                    {'tempsub', boolean()} |
+                    {'subscribe', boolean()} |
+                    {'title', binary()} |
+                    {'type', binary()}.
+-type result() :: [property()].
+
+-type options(T) :: [{binary(), T}].
+-type property_with_options() ::
+      {'access_model', 'access_model'(), options('access_model'())} |
+      {'children_association_policy', 'children_association_policy'(), options('children_association_policy'())} |
+      {'itemreply', 'itemreply'(), options('itemreply'())} |
+      {'language', binary(), options(binary())} |
+      {'node_type', 'node_type'(), options('node_type'())} |
+      {'notification_type', 'notification_type'(), options('notification_type'())} |
+      {'publish_model', 'publish_model'(), options('publish_model'())} |
+      {'roster_groups_allowed', [binary()], options(binary())} |
+      {'send_last_published_item', 'send_last_published_item'(), options('send_last_published_item'())}.
+-type form() :: [property() | property_with_options() | xdata_field()].
diff --git a/include/pubsub_publish_options.hrl b/include/pubsub_publish_options.hrl
new file mode 100644 (file)
index 0000000..3b04b48
--- /dev/null
@@ -0,0 +1,14 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_publish_options.xdata
+%% Form type: http://jabber.org/protocol/pubsub#publish-options
+%% Document: XEP-0060
+
+-type 'access_model'() :: authorize | open | presence | roster | whitelist.
+
+-type property() :: {'access_model', 'access_model'()}.
+-type result() :: [property()].
+
+-type options(T) :: [{binary(), T}].
+-type property_with_options() ::
+      {'access_model', 'access_model'(), options('access_model'())}.
+-type form() :: [property() | property_with_options() | xdata_field()].
diff --git a/include/pubsub_subscribe_authorization.hrl b/include/pubsub_subscribe_authorization.hrl
new file mode 100644 (file)
index 0000000..fb67ab4
--- /dev/null
@@ -0,0 +1,13 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_subscribe_authorization.xdata
+%% Form type: http://jabber.org/protocol/pubsub#subscribe_authorization
+%% Document: XEP-0060
+
+
+-type property() :: {'allow', boolean()} |
+                    {'node', binary()} |
+                    {'subscriber_jid', jid:jid()} |
+                    {'subid', binary()}.
+-type result() :: [property()].
+
+-type form() :: [property() | xdata_field()].
diff --git a/include/pubsub_subscribe_options.hrl b/include/pubsub_subscribe_options.hrl
new file mode 100644 (file)
index 0000000..9a05822
--- /dev/null
@@ -0,0 +1,25 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_subscribe_options.xdata
+%% Form type: http://jabber.org/protocol/pubsub#subscribe_options
+%% Document: XEP-0060
+
+-type 'show-values'() :: away | chat | dnd | online | xa.
+-type 'subscription_type'() :: items | nodes.
+-type 'subscription_depth'() :: 1 | all.
+
+-type property() :: {'deliver', boolean()} |
+                    {'digest', boolean()} |
+                    {'digest_frequency', binary()} |
+                    {'expire', binary()} |
+                    {'include_body', boolean()} |
+                    {'show-values', ['show-values'()]} |
+                    {'subscription_type', 'subscription_type'()} |
+                    {'subscription_depth', 'subscription_depth'()}.
+-type result() :: [property()].
+
+-type options(T) :: [{binary(), T}].
+-type property_with_options() ::
+      {'show-values', ['show-values'()], options('show-values'())} |
+      {'subscription_type', 'subscription_type'(), options('subscription_type'())} |
+      {'subscription_depth', 'subscription_depth'(), options('subscription_depth'())}.
+-type form() :: [property() | property_with_options() | xdata_field()].
index 8ffb3780805fc26f49310fcea8279f4b4b7ebf56..443769bb78917eb6f7b7d5751fc9c6640a13d02f 100644 (file)
@@ -8,7 +8,7 @@
 -record(ps_affiliation, {xmlns = <<>> :: binary(),
                         node = <<>> :: binary(),
                         type :: member | none | outcast |
-                                owner | publisher | 'publish-only',
+                                owner | publisher | publish_only,
                         jid :: jid:jid()}).
 -type ps_affiliation() :: #ps_affiliation{}.
 
                       type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
                       var = <<>> :: binary(),
                       required = false :: boolean(),
-                      desc :: binary(),
+                      desc = <<>> :: binary(),
                       values = [] :: [binary()],
                       options = [] :: [#xdata_option{}],
                       sub_els = [] :: [xmpp_element() | fxml:xmlel()]}).
diff --git a/specs/flex_offline.cfg b/specs/flex_offline.cfg
new file mode 100644 (file)
index 0000000..5d5b268
--- /dev/null
@@ -0,0 +1 @@
+[{decode, [{<<"number_of_messages">>, {dec_int, [0, infinity]}}]}].
diff --git a/specs/flex_offline.xdata b/specs/flex_offline.xdata
new file mode 100644 (file)
index 0000000..d0c7862
--- /dev/null
@@ -0,0 +1,12 @@
+<form_type>
+  <name>http://jabber.org/protocol/offline</name>
+  <doc>XEP-0013</doc>
+  <desc>
+    Service Discovery extension for number of messages
+    in an offline message queue.
+  </desc>
+  <field
+      var='number_of_messages'
+      type='text-single'
+      label='Number of Offline Messages'/>
+</form_type>
diff --git a/specs/muc_register.cfg b/specs/muc_register.cfg
new file mode 100644 (file)
index 0000000..3efd0cf
--- /dev/null
@@ -0,0 +1,2 @@
+[{prefix, <<"muc#register_">>},
+ {required, [<<"muc#register_roomnick">>]}].
diff --git a/specs/muc_register.xdata b/specs/muc_register.xdata
new file mode 100644 (file)
index 0000000..e808b44
--- /dev/null
@@ -0,0 +1,37 @@
+<form_type>
+  <name>http://jabber.org/protocol/muc#register</name>
+  <doc>XEP-0045</doc>
+  <desc>
+    Forms enabling user registration with a
+    Multi-User Chat (MUC) room or admin approval
+    of user registration requests.
+  </desc>
+  <field 
+     var='muc#register_allow'
+     type='boolean'
+     label='Allow this person to register with the room?'/>
+  <field
+      var='muc#register_email'
+      type='text-single'
+      label='Email Address'/>
+  <field
+      var='muc#register_faqentry'
+      type='text-multi'
+      label='FAQ Entry'/>
+  <field
+      var='muc#register_first'
+      type='text-single'
+      label='Given Name'/>
+  <field
+      var='muc#register_last'
+      type='text-single'
+      label='Family Name'/>
+  <field
+      var='muc#register_roomnick'
+      type='text-single'
+      label='Nickname'/>
+  <field
+      var='muc#register_url'
+      type='text-single'
+      label='A Web Page'/>
+</form_type>
diff --git a/specs/muc_request.cfg b/specs/muc_request.cfg
new file mode 100644 (file)
index 0000000..4811b32
--- /dev/null
@@ -0,0 +1,2 @@
+[{prefix, <<"muc#">>},
+ {required, [<<"muc#role">>]}].
diff --git a/specs/muc_request.xdata b/specs/muc_request.xdata
new file mode 100644 (file)
index 0000000..64fa138
--- /dev/null
@@ -0,0 +1,31 @@
+<form_type>
+  <name>http://jabber.org/protocol/muc#request</name>
+  <doc>XEP-0045</doc>
+  <desc>
+    Forms enabling voice requests in a 
+    Multi-User Chat (MUC) room or admin
+    approval of such requests.
+  </desc>
+  <field var='muc#role'
+         type='list-single'
+         label='Requested role'>
+    <option label='Participant'>
+      <value>participant</value>
+    </option>
+  </field>
+  <field var='muc#jid'
+         type='jid-single'
+         label='User JID'/>
+  <field var='muc#roomnick'
+         type='text-single'
+         label='Nickname'/>
+  <field var='muc#request_allow'
+         type='boolean'
+         label='Grant voice to this person?'/>
+</form_type>
+
+<!--
+Local Variables:
+mode: xml
+End:
+-->
diff --git a/specs/muc_roomconfig.cfg b/specs/muc_roomconfig.cfg
new file mode 100644 (file)
index 0000000..de16099
--- /dev/null
@@ -0,0 +1,11 @@
+[{prefix, <<"muc#roomconfig_">>},
+ {prefix, <<"muc#">>},
+ {decode, [{<<"muc#roomconfig_maxusers">>,
+           {dec_enum_int, [[none], 0, infinity]}},
+          {<<"voice_request_min_interval">>,
+           {dec_int, [0, infinity]}}]}].
+
+%% Local Variables:
+%% mode: erlang
+%% End:
+%% vim: set filetype=erlang tabstop=8:
diff --git a/specs/muc_roomconfig.xdata b/specs/muc_roomconfig.xdata
new file mode 100644 (file)
index 0000000..cab6465
--- /dev/null
@@ -0,0 +1,192 @@
+<form_type>
+  <name>http://jabber.org/protocol/muc#roomconfig</name>
+  <doc>XEP-0045</doc>
+  <desc>
+    Forms enabling creation and configuration of
+    a Multi-User Chat (MUC) room.
+  </desc>
+  <field
+      var='muc#maxhistoryfetch'
+      type='text-single'
+      label='Maximum Number of History Messages Returned by Room'/>
+  <field
+      var='muc#roomconfig_allowpm'
+      type='list-single'
+      label='Roles that May Send Private Messages'/>
+  <field
+      var='allow_private_messages'
+      type='boolean'
+      label='Allow users to send private messages'/>
+  <field
+      var='allow_private_messages_from_visitors'
+      type='list-single'
+      label='Allow visitors to send private messages to'>
+    <option label='nobody'>
+      <value>nobody</value>
+    </option>
+    <option label='moderators only'>
+      <value>moderators</value>
+    </option>
+    <option label='anyone'>
+      <value>anyone</value>
+    </option>
+  </field>
+  <field
+      var='allow_visitor_status'
+      type='boolean'
+      label='Allow visitors to send status text in presence updates'/>
+  <field
+      var='allow_visitor_nickchange'
+      type='boolean'
+      label='Allow visitors to change nickname'/>
+  <field
+      var='allow_voice_requests'
+      type='boolean'
+      label='Allow visitors to send voice requests'/>
+  <field
+      var='allow_subscription'
+      type='boolean'
+      label='Allow subscription'/>
+  <field
+      var='voice_request_min_interval'
+      type='text-single'
+      label='Minimum interval between voice requests (in seconds)'/>
+  <field
+      var='captcha_protected'
+      type='boolean'
+      label='Make room CAPTCHA protected'/>
+  <field
+      var='captcha_whitelist'
+      type='jid-multi'
+      label='Exclude Jabber IDs from CAPTCHA challenge'/>
+  <field
+      var='allow_query_users'
+      type='boolean'
+      label='Allow users to query other users'/>
+  <field
+      var='muc#roomconfig_allowinvites'
+      type='boolean'
+      label='Allow users to send invites'/>
+  <field
+      var='muc#roomconfig_changesubject'
+      type='boolean'
+      label='Allow users to change the subject'/>
+  <field
+      var='muc#roomconfig_enablelogging'
+      type='boolean'
+      label='Enable logging'/>
+  <field
+      var='muc#roomconfig_getmemberlist'
+      type='list-multi'
+      label='Roles and Affiliations that May Retrieve Member List'/>
+  <field
+      var='muc#roomconfig_lang'
+      type='text-single'
+      label='Natural Language for Room Discussions'/>
+  <field
+      var='muc#roomconfig_pubsub'
+      type='text-single'
+      label='XMPP URI of Associated Publish-Subscribe Node'/>
+  <field
+      var='muc#roomconfig_maxusers'
+      type='list-single'
+      label='Maximum Number of Occupants'>
+    <option label='No limit'>
+      <value>none</value>
+    </option>
+    <option><value>5</value></option>
+    <option><value>10</value></option>
+    <option><value>20</value></option>
+    <option><value>30</value></option>
+    <option><value>50</value></option>
+    <option><value>100</value></option>
+    <option><value>200</value></option>
+    <option><value>500</value></option>
+    <option><value>1000</value></option>
+    <option><value>2000</value></option>
+    <option><value>5000</value></option>
+  </field>
+  <field
+      var='muc#roomconfig_membersonly'
+      type='boolean'
+      label='Make room members-only'/>
+  <field
+      var='muc#roomconfig_moderatedroom'
+      type='boolean'
+      label='Make room moderated'/>
+  <field
+      var='members_by_default'
+      type='boolean'
+      label='Default users as participants'/>
+  <field
+      var='muc#roomconfig_passwordprotectedroom'
+      type='boolean'
+      label='Make room password protected'/>
+  <field
+      var='muc#roomconfig_persistentroom'
+      type='boolean'
+      label='Make room persistent'/>
+  <field
+      var='muc#roomconfig_presencebroadcast'
+      type='list-multi'
+      label='Roles for which Presence is Broadcasted'>
+    <option label='Moderator'>
+      <value>moderator</value>
+    </option>
+    <option label='Participant'>
+      <value>participant</value>
+    </option>
+    <option label='Visitor'>
+      <value>visitor</value>
+    </option>
+  </field>
+  <field
+      var='muc#roomconfig_publicroom'
+      type='boolean'
+      label='Make room public searchable'/>
+  <field
+      var='public_list'
+      type='boolean'
+      label='Make participants list public'/>
+  <field
+      var='muc#roomconfig_roomadmins'
+      type='jid-multi'
+      label='Full List of Room Admins'/>
+  <field
+      var='muc#roomconfig_roomdesc'
+      type='text-single'
+      label='Room description'/>
+  <field
+      var='muc#roomconfig_roomname'
+      type='text-single'
+      label='Room title'/>
+  <field
+      var='muc#roomconfig_roomowners'
+      type='jid-multi'
+      label='Full List of Room Owners'/>
+  <field
+      var='muc#roomconfig_roomsecret'
+      type='text-private'
+      label='Password'/>
+  <field
+      var='muc#roomconfig_whois'
+      type='list-single'
+      label='Present real Jabber IDs to'>
+    <option label='moderators only'>
+      <value>moderators</value>
+    </option>
+    <option label='anyone'>
+      <value>anyone</value>
+    </option>
+  </field>
+  <field
+      var='mam'
+      type='boolean'
+      label='Enable message archiving'/>
+</form_type>
+
+<!--
+Local Variables:
+mode: xml
+End:
+-->
diff --git a/specs/muc_roominfo.cfg b/specs/muc_roominfo.cfg
new file mode 100644 (file)
index 0000000..98a985e
--- /dev/null
@@ -0,0 +1,11 @@
+[{prefix, <<"muc#roominfo_">>},
+ {prefix, <<"muc#">>},
+ {decode, [{<<"muc#maxhistoryfetch">>,
+           {dec_int, [0, infinity]}},
+          {<<"muc#roominfo_occupants">>,
+           {dec_int, [0, infinity]}}]}].
+
+%% Local Variables:
+%% mode: erlang
+%% End:
+%% vim: set filetype=erlang tabstop=8:
diff --git a/specs/muc_roominfo.xdata b/specs/muc_roominfo.xdata
new file mode 100644 (file)
index 0000000..70f6963
--- /dev/null
@@ -0,0 +1,55 @@
+<form_type>
+  <name>http://jabber.org/protocol/muc#roominfo</name>
+  <doc>XEP-0045</doc>
+  <desc>
+    Forms enabling the communication of extended service discovery
+    information about a Multi-User Chat (MUC) room.
+  </desc>
+  <field
+      var='muc#maxhistoryfetch'
+      type='text-single'
+      label='Maximum Number of History Messages Returned by Room'/>
+  <field
+      var='muc#roominfo_contactjid'
+      type='jid-multi'
+      label='Contact Addresses (normally, room owner or owners)'/>
+  <field
+      var='muc#roominfo_description'
+      type='text-single'
+      label='Room description'/>
+  <field
+      var='muc#roominfo_lang'
+      type='text-single'
+      label='Natural Language for Room Discussions'/>
+  <field
+      var='muc#roominfo_ldapgroup'
+      type='text-single'
+      label='An associated LDAP group that defines 
+             room membership; this should be an LDAP 
+             Distinguished Name according to an 
+             implementation-specific or 
+             deployment-specific definition of a 
+             group.'/>
+  <field
+      var='muc#roominfo_logs'
+      type='text-single'
+      label='URL for Archived Discussion Logs'/>
+  <field
+      var='muc#roominfo_occupants'
+      type='text-single'
+      label='Number of occupants'/>
+  <field
+      var='muc#roominfo_subject'
+      type='text-single'
+      label='Current Discussion Topic'/>
+  <field
+      var='muc#roominfo_subjectmod'
+      type='boolean'
+      label='The room subject can be modified by participants'/>
+</form_type>
+
+<!--
+Local Variables:
+mode: xml
+End:
+-->
diff --git a/specs/pubsub_get_pending.cfg b/specs/pubsub_get_pending.cfg
new file mode 100644 (file)
index 0000000..4ae57ac
--- /dev/null
@@ -0,0 +1,7 @@
+[{prefix, <<"pubsub#">>},
+ {required, [<<"pubsub#node">>]}].
+
+%% Local Variables:
+%% mode: erlang
+%% End:
+%% vim: set filetype=erlang tabstop=8:
diff --git a/specs/pubsub_get_pending.xdata b/specs/pubsub_get_pending.xdata
new file mode 100644 (file)
index 0000000..1a6f552
--- /dev/null
@@ -0,0 +1,15 @@
+<form_type>
+  <name>http://jabber.org/protocol/pubsub#subscribe_authorization</name>
+  <doc>XEP-0060</doc>
+  <desc>Forms enabling authorization of subscriptions to pubsub nodes</desc> 
+  <field
+      var='pubsub#node'
+      type='list-single'
+      label='The NodeID of the relevant node'/>  
+</form_type>
+
+<!--
+Local Variables:
+mode: xml
+End:
+-->
diff --git a/specs/pubsub_node_config.cfg b/specs/pubsub_node_config.cfg
new file mode 100644 (file)
index 0000000..f3bf72d
--- /dev/null
@@ -0,0 +1,8 @@
+[{prefix, <<"pubsub#">>},
+ {decode, [{<<"pubsub#max_items">>, {dec_int, [0,infinity]}},
+          {<<"pubsub#max_payload_size">>, {dec_int, [0,infinity]}}]}].
+
+%% Local Variables:
+%% mode: erlang
+%% End:
+%% vim: set filetype=erlang tabstop=8:
diff --git a/specs/pubsub_node_config.xdata b/specs/pubsub_node_config.xdata
new file mode 100644 (file)
index 0000000..1691808
--- /dev/null
@@ -0,0 +1,189 @@
+<form_type>
+  <name>http://jabber.org/protocol/pubsub#node_config</name>
+  <doc>XEP-0060</doc>
+  <desc>Forms enabling configuration of pubsub nodes</desc>
+  <field var='pubsub#access_model'
+         type='list-single'
+         label='Specify the access model'>
+    <option label='Subscription requests must be approved and only subscribers may retrieve items'>
+      <value>authorize</value>
+    </option>
+    <option label='Anyone may subscribe and retrieve items'>
+      <value>open</value>
+    </option>
+    <option label='Anyone with a presence subscription of both or from may subscribe and retrieve items'>
+      <value>presence</value>
+    </option>
+    <option label='Anyone in the specified roster group(s) may subscribe and retrieve items'>
+      <value>roster</value>
+    </option>
+    <option label='Only those on a whitelist may subscribe and retrieve items'>
+      <value>whitelist</value>
+    </option>
+  </field>
+  <field var='pubsub#body_xslt'
+         type='text-single'
+         label='The URL of an XSL transformation which can be
+                applied to payloads in order to generate an
+                appropriate message body element.'/>
+  <field var='pubsub#children_association_policy'
+         type='list-single'
+         label='Who may associate leaf nodes with a collection'>
+    <option label='Anyone may associate leaf nodes with the collection'>
+      <value>all</value>
+    </option>
+    <option label='Only collection node owners may associate leaf nodes with the collection'>
+      <value>owners</value>
+    </option>
+    <option label='Only those on a whitelist may associate leaf nodes with the collection'>
+      <value>whitelist</value>
+    </option>
+  </field>
+  <field var='pubsub#children_association_whitelist'
+         type='jid-multi'
+         label='The list of JIDs that may associate leaf nodes with a collection'/>
+  <field var='pubsub#children'
+         type='text-multi'
+         label='The child nodes (leaf or collection) associated with a collection'/>
+  <field var='pubsub#children_max'
+         type='text-single'
+         label='The maximum number of child nodes that can be associated with a collection'/>
+  <field var='pubsub#collection'
+         type='text-multi'
+         label='The collections with which a node is affiliated'/>
+  <field var='pubsub#contact'
+         type='jid-multi'
+         label='The JIDs of those to contact with questions'/>
+  <field var='pubsub#dataform_xslt'
+         type='text-single'
+         label='The URL of an XSL transformation which can be
+                applied to the payload format in order to generate
+                a valid Data Forms result that the client could
+                display using a generic Data Forms rendering
+                engine'/>
+  <field var='pubsub#deliver_notifications' type='boolean'
+         label='Deliver event notifications'>
+    <value>true</value>
+  </field>
+  <field var='pubsub#deliver_payloads'
+         type='boolean'
+         label='Deliver payloads with event notifications'/>
+  <field var='pubsub#description'
+         type='text-single'
+         label='A description of the node'/>
+  <field var='pubsub#item_expire'
+         type='text-single'
+         label='Number of seconds after which to automatically purge items'/>
+  <field var='pubsub#itemreply'
+         type='list-single'
+         label='Whether owners or publisher should receive replies to items'>
+    <option label='Statically specify a replyto of the node owner(s)'>
+      <value>owner</value>
+    </option>
+    <option label='Dynamically specify a replyto of the item publisher'>
+      <value>publisher</value>
+    </option>
+    <option>
+      <value>none</value>
+    </option>
+  </field>
+  <field var='pubsub#language'
+         type='list-single'
+         label='The default language of the node'/>
+  <field var='pubsub#max_items'
+         type='text-single'
+         label='Max # of items to persist'>
+  </field>
+  <field var='pubsub#max_payload_size'
+         type='text-single'
+         label='Max payload size in bytes'/>
+  <field var='pubsub#node_type'
+         type='list-single'
+         label='Whether the node is a leaf (default) or a collection'>
+    <option label='The node is a leaf node (default)'>
+      <value>leaf</value>
+    </option>
+    <option label='The node is a collection node'>
+      <value>collection</value>
+    </option>
+  </field>
+  <field var='pubsub#notification_type' type='list-single'
+         label='Specify the event message type'>
+    <option label='Messages of type normal'>
+      <value>normal</value>
+    </option>
+    <option label='Messages of type headline'>
+      <value>headline</value>
+    </option>
+  </field>
+  <field var='pubsub#notify_config'
+         type='boolean'
+        label='Notify subscribers when the node configuration changes'/>
+  <field var='pubsub#notify_delete'
+         type='boolean'
+         label='Notify subscribers when the node is deleted'/>
+  <field var='pubsub#notify_retract'
+         type='boolean'
+         label='Notify subscribers when items are removed from the node'/>
+  <field var='pubsub#notify_sub'
+         type='boolean'
+         label='Whether to notify owners about new subscribers and unsubscribes'/>
+  <field var='pubsub#persist_items'
+         type='boolean'
+         label='Persist items to storage'/>
+  <field var='pubsub#presence_based_delivery'
+         type='boolean'
+         label='Only deliver notifications to available users'/>
+  <field var='pubsub#publish_model'
+         type='list-single'
+         label='Specify the publisher model'>
+    <option label='Only publishers may publish'>
+      <value>publishers</value>
+    </option>
+    <option label='Subscribers may publish'>
+      <value>subscribers</value>
+    </option>
+    <option label='Anyone may publish'>
+      <value>open</value>
+    </option>
+  </field>
+  <field var='pubsub#purge_offline'
+         type='boolean'
+         label='Purge all items when the relevant publisher goes offline'/>
+  <field var='pubsub#roster_groups_allowed'
+         type='list-multi'
+         label='Roster groups allowed to subscribe'/>
+  <field var='pubsub#send_last_published_item'
+         type='list-single'
+         label='When to send the last published item'>
+    <option label='Never'>
+      <value>never</value>
+    </option>
+    <option label='When a new subscription is processed'>
+      <value>on_sub</value>
+    </option>
+    <option label='When a new subscription is processed and whenever a subscriber comes online'>
+      <value>on_sub_and_presence</value>
+    </option>
+  </field>
+  <field var='pubsub#tempsub'
+         type='boolean'
+         label='Whether to make all subscriptions temporary, based on subscriber presence'/>
+  <field var='pubsub#subscribe' type='boolean'
+         label='Whether to allow subscriptions'>
+    <value>1</value>
+  </field>
+  <field var='pubsub#title'
+         type='text-single'
+         label='A friendly name for the node'/>
+  <field var='pubsub#type'
+         type='text-single'
+         label='The type of node data, usually specified by
+                the namespace of the payload (if any)'/>
+</form_type>
+
+<!--
+Local Variables:
+mode: xml
+End:
+-->
diff --git a/specs/pubsub_publish_options.cfg b/specs/pubsub_publish_options.cfg
new file mode 100644 (file)
index 0000000..2a853e8
--- /dev/null
@@ -0,0 +1,6 @@
+[{prefix, <<"pubsub#">>}].
+
+%% Local Variables:
+%% mode: erlang
+%% End:
+%% vim: set filetype=erlang tabstop=8:
diff --git a/specs/pubsub_publish_options.xdata b/specs/pubsub_publish_options.xdata
new file mode 100644 (file)
index 0000000..26cac6a
--- /dev/null
@@ -0,0 +1,34 @@
+<form_type>
+  <name>http://jabber.org/protocol/pubsub#publish-options</name>
+  <doc>XEP-0060</doc>
+  <desc>
+    Forms enabling publication with options; each field must specify whether it
+    defines METADATA to be attached to the item, a per-item OVERRIDE of the node
+    configuration, or a PRECONDITION to be checked against the node configuration.
+  </desc>
+  <field var='pubsub#access_model'
+         type='list-single'
+         label='Specify the access model'>
+    <option label='Access model of authorize'>
+      <value>authorize</value>
+    </option>
+    <option label='Access model of open'>
+      <value>open</value>
+    </option>
+    <option label='Access model of presence'>
+      <value>presence</value>
+    </option>
+    <option label='Access model of roster'>
+      <value>roster</value>
+    </option>
+    <option label='Access model of whitelist'>
+      <value>whitelist</value>
+    </option>
+  </field>
+</form_type>
+
+<!--
+Local Variables:
+mode: xml
+End:
+-->
diff --git a/specs/pubsub_subscribe_authorization.cfg b/specs/pubsub_subscribe_authorization.cfg
new file mode 100644 (file)
index 0000000..1f38fdb
--- /dev/null
@@ -0,0 +1,7 @@
+[{prefix, <<"pubsub#">>},
+ {required, [<<"pubsub#node">>, <<"pubsub#subscriber_jid">>, <<"pubsub#allow">>]}].
+
+%% Local Variables:
+%% mode: erlang
+%% End:
+%% vim: set filetype=erlang tabstop=8:
diff --git a/specs/pubsub_subscribe_authorization.xdata b/specs/pubsub_subscribe_authorization.xdata
new file mode 100644 (file)
index 0000000..250d875
--- /dev/null
@@ -0,0 +1,27 @@
+<form_type>
+  <name>http://jabber.org/protocol/pubsub#subscribe_authorization</name>
+  <doc>XEP-0060</doc>
+  <desc>Forms enabling authorization of subscriptions to pubsub nodes</desc>
+  <field
+      var='pubsub#allow'
+      type='boolean'
+      label='Allow this Jabber ID to subscribe to this pubsub node?'/>
+  <field
+      var='pubsub#node'
+      type='text-single'
+      label='Node ID'/>
+  <field
+      var='pubsub#subscriber_jid'
+      type='jid-single'
+      label='Subscriber Address'/>
+  <field
+      var='pubsub#subid'
+      type='text-single'
+      label='The subscription identifier associated with the subscription request'/>
+</form_type>
+
+<!--
+Local Variables:
+mode: xml
+End:
+-->
diff --git a/specs/pubsub_subscribe_options.cfg b/specs/pubsub_subscribe_options.cfg
new file mode 100644 (file)
index 0000000..e4ac06a
--- /dev/null
@@ -0,0 +1 @@
+[{prefix, <<"pubsub#">>}].
diff --git a/specs/pubsub_subscribe_options.xdata b/specs/pubsub_subscribe_options.xdata
new file mode 100644 (file)
index 0000000..59b1346
--- /dev/null
@@ -0,0 +1,70 @@
+<form_type>
+  <name>http://jabber.org/protocol/pubsub#subscribe_options</name>
+  <doc>XEP-0060</doc>
+  <desc>Forms enabling configuration of subscription options for pubsub nodes</desc>
+  <field
+      var='pubsub#deliver'
+      type='boolean'
+      label='Whether an entity wants to receive
+             or disable notifications'/>
+  <field
+      var='pubsub#digest'
+      type='boolean'
+      label='Whether an entity wants to receive digests
+             (aggregations) of notifications or all
+             notifications individually'/>
+  <field var='pubsub#digest_frequency'
+         type='text-single'
+         label='The minimum number of milliseconds between
+                sending any two notification digests'/>
+  <field
+      var='pubsub#expire'
+      type='text-single'
+      label='The DateTime at which a leased subscription
+             will end or has ended'/>
+  <field
+      var='pubsub#include_body'
+      type='boolean'
+      label='Whether an entity wants to receive an XMPP
+             message body in addition to the payload
+             format'/>
+  <field
+      var='pubsub#show-values'
+      type='list-multi'
+      label='The presence states for which an entity
+             wants to receive notifications'>
+    <option label='XMPP Show Value of Away'>
+      <value>away</value>
+    </option>
+    <option label='XMPP Show Value of Chat'>
+      <value>chat</value>
+    </option>
+    <option label='XMPP Show Value of DND (Do Not Disturb)'>
+      <value>dnd</value>
+    </option>
+    <option label='Mere Availability in XMPP (No Show Value)'>
+      <value>online</value>
+    </option>
+    <option label='XMPP Show Value of XA (Extended Away)'>
+      <value>xa</value>
+    </option>
+  </field>
+  <field var='pubsub#subscription_type'
+         type='list-single'>
+    <option label='Receive notification of new items only'>
+      <value>items</value>
+    </option>
+    <option label='Receive notification of new nodes only'>
+      <value>nodes</value>
+    </option>
+  </field>
+  <field var='pubsub#subscription_depth'
+         type='list-single'>
+    <option label='Receive notification from direct child nodes only'>
+      <value>1</value>
+    </option>
+    <option label='Receive notification from all descendent nodes'>
+      <value>all</value>
+    </option>
+  </field>
+</form_type>
similarity index 99%
rename from tools/xmpp_codec.spec
rename to specs/xmpp_codec.spec
index 1f9b50066137d820c9880aeb5d0e565b687b7fe3..81f674bf3fc5b99fca5c2796b64a461a850451da 100644 (file)
                         default = false,
                         min = 0, max = 1},
                    #ref{name = xdata_field_desc,
+                       default = <<"">>,
                         label = '$desc',
                         min = 0, max = 1},
                    #ref{name = xdata_field_value,
 -record(ps_affiliation, {xmlns = <<>> :: binary(),
                         node = <<>> :: binary(),
                         type :: member | none | outcast |
-                                owner | publisher | 'publish-only',
+                                owner | publisher | publish_only,
                         jid :: jid:jid()}).
 -type ps_affiliation() :: #ps_affiliation{}.
 
                     #attr{name = <<"affiliation">>,
                           label = '$type',
                           required = true,
-                          dec = {dec_enum, [[member, none, outcast, owner,
-                                             publisher, 'publish-only']]},
-                          enc = {enc_enum, []}}]}).
+                          dec = {dec_ps_aff, []},
+                          enc = {enc_ps_aff, []}}]}).
 
 -xml(pubsub_owner_affiliation,
      #elem{name = <<"affiliation">>,
                     #attr{name = <<"affiliation">>,
                           label = '$type',
                           required = true,
-                          dec = {dec_enum, [[member, none, outcast, owner,
-                                             publisher, 'publish-only']]},
-                          enc = {enc_enum, []}}]}).
+                         dec = {dec_ps_aff, []},
+                          enc = {enc_ps_aff, []}}]}).
 
 -xml(pubsub_event_configuration,
      #elem{name = <<"configuration">>,
@@ -3501,6 +3500,22 @@ dec_version(S) ->
 enc_version({Maj, Min}) ->
     <<(integer_to_binary(Maj))/binary, $., (integer_to_binary(Min))/binary>>.
 
+-spec dec_ps_aff(_) -> member | none | outcast |
+                      owner | publisher | publish_only.
+dec_ps_aff(<<"member">>) -> member;
+dec_ps_aff(<<"none">>) -> none;
+dec_ps_aff(<<"outcast">>) -> outcast;
+dec_ps_aff(<<"owner">>) -> owner;
+dec_ps_aff(<<"publisher">>) -> publisher;
+dec_ps_aff(<<"publish-only">>) -> publish_only.
+
+enc_ps_aff(member) -> <<"member">>;
+enc_ps_aff(none) -> <<"none">>;
+enc_ps_aff(outcast) -> <<"outcast">>;
+enc_ps_aff(owner) -> <<"owner">>;
+enc_ps_aff(publisher) -> <<"publisher">>;
+enc_ps_aff(publish_only) -> <<"publish-only">>.
+
 %% Local Variables:
 %% mode: erlang
 %% End:
index 9650f3773e71d2c86d11017052cf22608c85d46f..a122eda8ea15a1b90ba9ea98a1ee4abff69e2424 100644 (file)
@@ -73,6 +73,7 @@ captcha_text(Lang) ->
 mk_ocr_field(Lang, CID, Type) ->
     URI = #media_uri{type = Type, uri = <<"cid:", CID/binary>>},
     #xdata_field{var = <<"ocr">>,
+                type = 'text-single',
                 label = captcha_text(Lang),
                 required = true,
                 sub_els = [#media{uri = [URI]}]}.
diff --git a/src/flex_offline.erl b/src/flex_offline.erl
new file mode 100644 (file)
index 0000000..090ab3d
--- /dev/null
@@ -0,0 +1,128 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: flex_offline.xdata
+%% Form type: http://jabber.org/protocol/offline
+%% Document: XEP-0013
+
+-module(flex_offline).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("flex_offline.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+dec_int(Val, Min, Max) ->
+    case list_to_integer(binary_to_list(Val)) of
+      Int when Int =< Max, Min == infinity -> Int;
+      Int when Int =< Max, Int >= Min -> Int
+    end.
+
+enc_int(Int) -> integer_to_binary(Int).
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, []);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/offline">>]} ->
+         decode(Fs, Acc, []);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/offline">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {number_of_messages, Val} ->
+               [encode_number_of_messages(Val, Translate)];
+           {number_of_messages, _, _} ->
+               erlang:error({badarg, Opt});
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/offline">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"number_of_messages">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_int(Value, 0, infinity) of
+      Result ->
+         decode(Fs, [{number_of_messages, Result} | Acc],
+                lists:delete(<<"number_of_messages">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"number_of_messages">>,
+                        <<"http://jabber.org/protocol/offline">>}})
+    end;
+decode([#xdata_field{var = <<"number_of_messages">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"number_of_messages">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"number_of_messages">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"number_of_messages">>,
+                  <<"http://jabber.org/protocol/offline">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/offline">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/offline">>}});
+decode([], Acc, []) -> Acc.
+
+encode_number_of_messages(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_int(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"number_of_messages">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Number of Offline Messages">>)}.
index 4d9bdd61f6099e79c2bc9472c1321d51a0daaf79..65a745d2d9bc03122eaf443a034606b94519b709 100644 (file)
@@ -488,14 +488,11 @@ concat_identities(#disco_info{identities = Identities}) ->
 -spec concat_info(disco_info()) -> iolist().
 concat_info(#disco_info{xdata = Xs}) ->
     lists:sort(
-      [concat_xdata_fields(Fs) || #xdata{type = result, fields = Fs} <- Xs]).
-
--spec concat_xdata_fields([xdata_field()]) -> iolist().
-concat_xdata_fields(Fields) ->
-    Form = case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var, Fields) of
-              #xdata_field{values = Values} -> Values;
-              false -> []
-          end,
+      [concat_xdata_fields(X) || #xdata{type = result} = X <- Xs]).
+
+-spec concat_xdata_fields(xdata()) -> iolist().
+concat_xdata_fields(#xdata{fields = Fields} = X) ->
+    Form = xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
     Res = [[Var, $<, lists:sort([[Val, $<] || Val <- Values])]
           || #xdata_field{var = Var, values = Values} <- Fields,
              is_binary(Var), Var /= <<"FORM_TYPE">>],
index 8569ee020032bfd50654b0552fcc2c6cbb5ee6ae..1daae5aa20b988aafc31971df205bb3649580d32 100644 (file)
@@ -37,7 +37,7 @@
         process_iq_v0_2/1, process_iq_v0_3/1, disco_sm_features/5,
         remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2,
         muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
-        get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]).
+        get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/3]).
 
 -include("xmpp.hrl").
 -include("logger.hrl").
@@ -191,39 +191,17 @@ remove_room(LServer, Name, Host) ->
     Mod:remove_room(LServer, LName, LHost),
     ok.
 
--spec get_room_config([xdata_field()], mod_muc_room:state(), jid(), binary())
-      -> [xdata_field()].                           
-get_room_config(XFields, RoomState, _From, Lang) ->
+-spec get_room_config([muc_roomconfig:property()], mod_muc_room:state(),
+                     jid(), binary()) -> [muc_roomconfig:property()].
+get_room_config(Fields, RoomState, _From, _Lang) ->
     Config = RoomState#state.config,
-    Label = <<"Enable message archiving">>,
-    Var = <<"muc#roomconfig_mam">>,
-    Val = case Config#config.mam of
-             true -> <<"1">>;
-             _ -> <<"0">>
-         end,
-    XField = #xdata_field{type = boolean,
-                         label = translate:translate(Lang, Label),
-                         var = Var,
-                         values = [Val]},
-    XFields ++ [XField].
+    Fields ++ [{mam, Config#config.mam}].
 
--spec set_room_option({pos_integer(), _}, binary(), [binary()], binary())
+-spec set_room_option({pos_integer(), _}, muc_roomconfig:property(), binary())
       -> {pos_integer(), _}.
-set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
-    try
-       Val = case Vals of
-                 [<<"0">>|_] -> false;
-                 [<<"false">>|_] -> false;
-                 [<<"1">>|_] -> true;
-                 [<<"true">>|_] -> true
-             end,
-       {#config.mam, Val}
-    catch _:{case_clause, _} ->
-           Txt = <<"Value of '~s' should be boolean">>,
-           ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-           {error, xmpp:err_bad_request(ErrTxt, Lang)}
-    end;
-set_room_option(Acc, _Opt, _Vals, _Lang) ->
+set_room_option(_Acc, {mam, Val}, _Lang) ->
+    {#config.mam, Val};
+set_room_option(Acc, _Property, _Lang) ->
     Acc.
 
 -spec user_receive_packet(stanza(), ejabberd_c2s:state(), jid(), jid(), jid()) -> stanza().
index 66604394b911a5848c06a976d40419a521f62492..b189508a8f794cca062b62f3b52615a709b83f39 100644 (file)
@@ -668,19 +668,18 @@ get_nick(ServerHost, Host, From) ->
     Mod:get_nick(LServer, Host, From).
 
 iq_get_register_info(ServerHost, Host, From, Lang) ->
-    {Nick, NickVals, Registered} = case get_nick(ServerHost, Host, From) of
-                                      error -> {<<"">>, [], false};
-                                      N -> {N, [N], true}
-                                  end,
+    {Nick, Registered} = case get_nick(ServerHost, Host, From) of
+                            error -> {<<"">>, false};
+                            N -> {N, true}
+                        end,
     Title = <<(translate:translate(
                 Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>,
     Inst = translate:translate(Lang, <<"Enter nickname you want to register">>),
-    Field = #xdata_field{type = 'text-single',
-                        label = translate:translate(Lang, <<"Nickname">>),
-                        var = <<"nick">>,
-                        values = NickVals},
+    Fields = muc_register:encode(
+              [{roomnick, Nick}],
+              fun(T) -> translate:translate(Lang, T) end),
     X = #xdata{type = form, title = Title,
-              instructions = [Inst], fields = [Field]},
+              instructions = [Inst], fields = Fields},
     #register{nick = Nick,
              registered = Registered,
              instructions = 
@@ -717,12 +716,13 @@ process_iq_register_set(ServerHost, Host, From,
                        #register{nick = Nick, xdata = XData}, Lang) ->
     case XData of
        #xdata{type = submit, fields = Fs} ->
-           case lists:keyfind(<<"nick">>, #xdata_field.var, Fs) of
-               #xdata_field{values = [N]} ->
-                   iq_set_register_info(ServerHost, Host, From, N, Lang);
-               _ ->
-                   ErrText = <<"You must fill in field \"Nickname\" in the form">>,
-                   {error, xmpp:err_not_acceptable(ErrText, Lang)}
+           try
+               Options = muc_register:decode(Fs),
+               N = proplists:get_value(roomnick, Options),
+               iq_set_register_info(ServerHost, Host, From, N, Lang)
+           catch _:{muc_register, Why} ->
+                   ErrText = muc_register:format_error(Why),
+                   {error, xmpp:err_bad_request(ErrText, Lang)}
            end;
        #xdata{} ->
            Txt = <<"Incorrect data form">>,
index 339b85ecb8c8685ea551d6c8b2e049cdccfa84d0..52401f835d3d5d67bffebd402cf4b4c1c8d5ebde 100644 (file)
@@ -754,100 +754,131 @@ process_groupchat_message(From, #message{lang = Lang} = Packet, StateData) ->
 
 -spec process_normal_message(jid(), message(), state()) -> state().
 process_normal_message(From, #message{lang = Lang} = Pkt, StateData) ->
-    IsInvitation = is_invitation(Pkt),
-    IsVoiceRequest = is_voice_request(Pkt) and
-       is_visitor(From, StateData),
-    IsVoiceApprovement = is_voice_approvement(Pkt) and
-       not is_visitor(From, StateData),
-    if IsInvitation ->
-           case check_invitation(From, Pkt, StateData) of
-               {error, Error} ->
-                   ejabberd_router:route_error(StateData#state.jid, From, Pkt, Error),
-                   StateData;
-               IJID ->
-                   Config = StateData#state.config,
-                   case Config#config.members_only of
-                       true ->
-                           case get_affiliation(IJID, StateData) of
-                               none ->
-                                   NSD = set_affiliation(IJID, member, StateData),
-                                   send_affiliation(IJID, member, StateData),
-                                   store_room(NSD),
-                                   NSD;
-                               _ ->
-                                   StateData
-                           end;
-                       false ->
-                           StateData
-                   end
-           end;
-       IsVoiceRequest ->
-           case (StateData#state.config)#config.allow_voice_requests of
+    Action = lists:foldl(
+              fun(_, {error, _} = Err) ->
+                      Err;
+                 (#muc_user{invites = [#muc_invite{to = undefined}]}, _) ->
+                      Txt = <<"No 'to' attribute found">>,
+                      {error, xmpp:err_bad_request(Txt, Lang)};
+                 (#muc_user{invites = [I]}, _) ->
+                      {ok, I};
+                 (#muc_user{invites = [_|_]}, _) ->
+                      Txt = <<"Multiple <invite/> elements are not allowed">>,
+                      {error, xmpp:err_resource_constraint(Txt, Lang)};
+                 (#xdata{type = submit, fields = Fs}, _) ->
+                      try {ok, muc_request:decode(Fs)}
+                      catch _:{muc_request, Why} ->
+                              Txt = muc_request:format_error(Why),
+                              {error, xmpp:err_bad_request(Txt, Lang)}
+                      end;
+                 (_, Acc) ->
+                      Acc
+              end, ok, xmpp:get_els(Pkt)),
+    case Action of
+       {ok, #muc_invite{} = Invitation} ->
+           process_invitation(From, Pkt, Invitation, StateData);
+       {ok, [{role, participant}]} ->
+           process_voice_request(From, Pkt, StateData);
+       {ok, VoiceApproval} ->
+           process_voice_approval(From, Pkt, VoiceApproval, StateData);
+       {error, Err} ->
+           ejabberd_router:route_error(StateData#state.jid, From, Pkt, Err),
+           StateData;
+       ok ->
+           StateData
+    end.
+
+-spec process_invitation(jid(), message(), muc_invite(), state()) -> state().
+process_invitation(From, Pkt, Invitation, StateData) ->
+    Lang = xmpp:get_lang(Pkt),
+    case check_invitation(From, Invitation, Lang, StateData) of
+       {error, Error} ->
+           ejabberd_router:route_error(StateData#state.jid, From, Pkt, Error),
+           StateData;
+       IJID ->
+           Config = StateData#state.config,
+           case Config#config.members_only of
                true ->
-                   MinInterval = (StateData#state.config)#config.voice_request_min_interval,
-                   BareFrom = jid:remove_resource(jid:tolower(From)),
-                   NowPriority = -p1_time_compat:system_time(micro_seconds),
-                   CleanPriority = NowPriority + MinInterval * 1000000,
-                   Times = clean_treap(StateData#state.last_voice_request_time,
-                                       CleanPriority),
-                   case treap:lookup(BareFrom, Times) of
-                       error ->
-                           Times1 = treap:insert(BareFrom,
-                                                 NowPriority,
-                                                 true, Times),
-                           NSD = StateData#state{last_voice_request_time = Times1},
-                           send_voice_request(From, Lang, NSD),
+                   case get_affiliation(IJID, StateData) of
+                       none ->
+                           NSD = set_affiliation(IJID, member, StateData),
+                           send_affiliation(IJID, member, StateData),
+                           store_room(NSD),
                            NSD;
-                       {ok, _, _} ->
-                           ErrText = <<"Please, wait for a while before sending "
-                                       "new voice request">>,
-                           Err = xmpp:err_not_acceptable(ErrText, Lang),
-                           ejabberd_router:route_error(
-                             StateData#state.jid, From, Pkt, Err),
-                           StateData#state{last_voice_request_time = Times}
+                       _ ->
+                           StateData
                    end;
                false ->
-                   ErrText = <<"Voice requests are disabled in this conference">>,
-                   Err = xmpp:err_forbidden(ErrText, Lang),
+                   StateData
+           end
+    end.
+
+-spec process_voice_request(jid(), message(), state()) -> state().
+process_voice_request(From, Pkt, StateData) ->
+    Lang = xmpp:get_lang(Pkt),
+    case (StateData#state.config)#config.allow_voice_requests of
+       true ->
+           MinInterval = (StateData#state.config)#config.voice_request_min_interval,
+           BareFrom = jid:remove_resource(jid:tolower(From)),
+           NowPriority = -p1_time_compat:system_time(micro_seconds),
+           CleanPriority = NowPriority + MinInterval * 1000000,
+           Times = clean_treap(StateData#state.last_voice_request_time,
+                               CleanPriority),
+           case treap:lookup(BareFrom, Times) of
+               error ->
+                   Times1 = treap:insert(BareFrom,
+                                         NowPriority,
+                                         true, Times),
+                   NSD = StateData#state{last_voice_request_time = Times1},
+                   send_voice_request(From, Lang, NSD),
+                   NSD;
+               {ok, _, _} ->
+                   ErrText = <<"Please, wait for a while before sending "
+                               "new voice request">>,
+                   Err = xmpp:err_not_acceptable(ErrText, Lang),
                    ejabberd_router:route_error(
                      StateData#state.jid, From, Pkt, Err),
-                   StateData
+                   StateData#state{last_voice_request_time = Times}
            end;
-       IsVoiceApprovement ->
-           case is_moderator(From, StateData) of
-               true ->
-                   case extract_jid_from_voice_approvement(Pkt) of
-                       error ->
-                           ErrText = <<"Failed to extract JID from your voice "
-                                       "request approval">>,
-                           Err = xmpp:err_bad_request(ErrText, Lang),
-                           ejabberd_router:route_error(
-                             StateData#state.jid, From, Pkt, Err),
-                           StateData;
-                       TargetJid ->
-                           case is_visitor(TargetJid, StateData) of
-                               true ->
-                                   Reason = <<>>,
-                                   NSD = set_role(TargetJid,
-                                                  participant,
-                                                  StateData),
-                                   catch send_new_presence(TargetJid,
-                                                           Reason,
-                                                           NSD,
-                                                           StateData),
-                                   NSD;
-                               _ ->
-                                   StateData
-                           end
+       false ->
+           ErrText = <<"Voice requests are disabled in this conference">>,
+           Err = xmpp:err_forbidden(ErrText, Lang),
+           ejabberd_router:route_error(
+             StateData#state.jid, From, Pkt, Err),
+           StateData
+    end.
+
+-spec process_voice_approval(jid(), message(), [muc_request:property()], state()) -> state().
+process_voice_approval(From, Pkt, VoiceApproval, StateData) ->
+    Lang = xmpp:get_lang(Pkt),
+    case is_moderator(From, StateData) of
+       true ->
+           case lists:keyfind(jid, 1, VoiceApproval) of
+               {_, TargetJid} ->
+                   Allow = proplists:get_bool(request_allow, VoiceApproval),
+                   case is_visitor(TargetJid, StateData) of
+                       true when Allow ->
+                           Reason = <<>>,
+                           NSD = set_role(TargetJid, participant, StateData),
+                           catch send_new_presence(
+                                   TargetJid, Reason, NSD, StateData),
+                           NSD;
+                       _ ->
+                           StateData
                    end;
-               _ ->
-                   ErrText = <<"Only moderators can approve voice requests">>,
-                   Err = xmpp:err_not_allowed(ErrText, Lang),
+               false ->
+                   ErrText = <<"Failed to extract JID from your voice "
+                               "request approval">>,
+                   Err = xmpp:err_bad_request(ErrText, Lang),
                    ejabberd_router:route_error(
                      StateData#state.jid, From, Pkt, Err),
                    StateData
            end;
-       true ->
+       false ->
+           ErrText = <<"Only moderators can approve voice requests">>,
+           Err = xmpp:err_not_allowed(ErrText, Lang),
+           ejabberd_router:route_error(
+             StateData#state.jid, From, Pkt, Err),
            StateData
     end.
 
@@ -3090,34 +3121,6 @@ is_password_settings_correct(XData, StateData) ->
       _ -> true
     end.
 
--define(XFIELD(Type, Label, Var, Vals),
-       #xdata_field{type = Type,
-                    label = translate:translate(Lang, Label),
-                    var = Var,
-                    values = Vals}).
-
--define(BOOLXFIELD(Label, Var, Val),
-       ?XFIELD(boolean, Label, Var,
-               case Val of
-                 true -> [<<"1">>];
-                 _ -> [<<"0">>]
-               end)).
-
--define(STRINGXFIELD(Label, Var, Val),
-       ?XFIELD('text-single', Label, Var, [Val])).
-
--define(PRIVATEXFIELD(Label, Var, Val),
-       ?XFIELD('text-private', Label, Var, [Val])).
-
--define(JIDMULTIXFIELD(Label, Var, JIDList),
-       ?XFIELD('jid-multi', Label, Var,
-               [jid:to_string(JID) || JID <- JIDList])).
-
--spec make_options([{binary(), binary()}], binary()) -> [xdata_option()].
-make_options(Options, Lang) ->
-    [#xdata_option{label = translate:translate(Lang, Label),
-                  value = Value} || {Label, Value} <- Options].
-
 -spec get_default_room_maxusers(state()) -> non_neg_integer().
 get_default_room_maxusers(RoomState) ->
     DefRoomOpts =
@@ -3138,424 +3141,156 @@ get_config(Lang, StateData, From) ->
     {MaxUsersRoomInteger, MaxUsersRoomString} =
        case get_max_users(StateData) of
            N when is_integer(N) ->
-               {N, integer_to_binary(N)};
-           _ -> {0, <<"none">>}
+               {N, N};
+           _ -> {0, none}
        end,
     Title = iolist_to_binary(
              io_lib:format(
                translate:translate(Lang, <<"Configuration of room ~s">>),
                [jid:to_string(StateData#state.jid)])),
-    Fs = [#xdata_field{type = hidden,
-                      var = <<"FORM_TYPE">>,
-                      values = [<<"http://jabber.org/protocol/muc#roomconfig">>]},
-         ?STRINGXFIELD(<<"Room title">>,
-                       <<"muc#roomconfig_roomname">>, (Config#config.title)),
-         ?STRINGXFIELD(<<"Room description">>,
-                       <<"muc#roomconfig_roomdesc">>,
-                       (Config#config.description))] ++
+    Fs = [{roomname, Config#config.title},
+         {roomdesc, Config#config.description}] ++
        case acl:match_rule(StateData#state.server_host, AccessPersistent, From) of
-           allow ->
-               [?BOOLXFIELD(<<"Make room persistent">>,
-                            <<"muc#roomconfig_persistentroom">>,
-                            (Config#config.persistent))];
+           allow -> [{persistentroom, Config#config.persistent}];
            deny -> []
        end ++
-       [?BOOLXFIELD(<<"Make room public searchable">>,
-                    <<"muc#roomconfig_publicroom">>,
-                    (Config#config.public)),
-        ?BOOLXFIELD(<<"Make participants list public">>,
-                    <<"public_list">>, (Config#config.public_list)),
-        ?BOOLXFIELD(<<"Make room password protected">>,
-                    <<"muc#roomconfig_passwordprotectedroom">>,
-                    (Config#config.password_protected)),
-        ?PRIVATEXFIELD(<<"Password">>,
-                       <<"muc#roomconfig_roomsecret">>,
-                       case Config#config.password_protected of
-                           true -> Config#config.password;
-                           false -> <<"">>
-                       end),
-        #xdata_field{type = 'list-single',
-                     label = translate:translate(
-                               Lang, <<"Maximum Number of Occupants">>),
-                     var = <<"muc#roomconfig_maxusers">>,
-                     values = [MaxUsersRoomString],
-                     options =
-                         if is_integer(ServiceMaxUsers) -> [];
-                            true -> make_options(
-                                      [{<<"No limit">>, <<"none">>}],
-                                      Lang)
-                         end ++
-                         make_options(
-                           [{integer_to_binary(N), integer_to_binary(N)}
-                            || N <- lists:usort([ServiceMaxUsers,
-                                                 DefaultRoomMaxUsers,
-                                                 MaxUsersRoomInteger
-                                                 | ?MAX_USERS_DEFAULT_LIST]),
-                               N =< ServiceMaxUsers],
-                           Lang)},
-        #xdata_field{type = 'list-single',
-                     label = translate:translate(
-                               Lang, <<"Present real Jabber IDs to">>),
-                     var = <<"muc#roomconfig_whois">>,
-                     values = [if Config#config.anonymous -> <<"moderators">>;
-                                  true -> <<"anyone">>
-                               end],
-                     options = make_options(
-                                 [{<<"moderators only">>, <<"moderators">>},
-                                  {<<"anyone">>, <<"anyone">>}],
-                                 Lang)},
-        #xdata_field{type = 'list-multi',
-                     label = translate:translate(
-                               Lang,
-                               <<"Roles for which Presence is Broadcasted">>),
-                     var = <<"muc#roomconfig_presencebroadcast">>,
-                     values = [atom_to_binary(Role, utf8)
-                               || Role <- Config#config.presence_broadcast],
-                     options = make_options(
-                                 [{<<"Moderator">>, <<"moderator">>},
-                                  {<<"Participant">>, <<"participant">>},
-                                  {<<"Visitor">>, <<"visitor">>}],
-                                 Lang)},
-        ?BOOLXFIELD(<<"Make room members-only">>,
-                    <<"muc#roomconfig_membersonly">>,
-                    (Config#config.members_only)),
-        ?BOOLXFIELD(<<"Make room moderated">>,
-                    <<"muc#roomconfig_moderatedroom">>,
-                    (Config#config.moderated)),
-        ?BOOLXFIELD(<<"Default users as participants">>,
-                    <<"members_by_default">>,
-                    (Config#config.members_by_default)),
-        ?BOOLXFIELD(<<"Allow users to change the subject">>,
-                    <<"muc#roomconfig_changesubject">>,
-                    (Config#config.allow_change_subj)),
-        ?BOOLXFIELD(<<"Allow users to send private messages">>,
-                    <<"allow_private_messages">>,
-                    (Config#config.allow_private_messages)),
-        #xdata_field{type = 'list-single',
-                     label = translate:translate(
-                               Lang,
-                               <<"Allow visitors to send private messages to">>),
-                     var = <<"allow_private_messages_from_visitors">>,
-                     values = [case Config#config.allow_private_messages_from_visitors of
-                                   anyone -> <<"anyone">>;
-                                   moderators -> <<"moderators">>;
-                                   nobody -> <<"nobody">>
-                               end],
-                     options = make_options(
-                                 [{<<"nobody">>, <<"nobody">>},
-                                  {<<"moderators only">>, <<"moderators">>},
-                                  {<<"anyone">>, <<"anyone">>}],
-                                 Lang)},
-        ?BOOLXFIELD(<<"Allow users to query other users">>,
-                    <<"allow_query_users">>,
-                    (Config#config.allow_query_users)),
-        ?BOOLXFIELD(<<"Allow users to send invites">>,
-                    <<"muc#roomconfig_allowinvites">>,
-                    (Config#config.allow_user_invites)),
-        ?BOOLXFIELD(<<"Allow visitors to send status text in "
-                      "presence updates">>,
-                    <<"muc#roomconfig_allowvisitorstatus">>,
-                    (Config#config.allow_visitor_status)),
-        ?BOOLXFIELD(<<"Allow visitors to change nickname">>,
-                    <<"muc#roomconfig_allowvisitornickchange">>,
-                    (Config#config.allow_visitor_nickchange)),
-        ?BOOLXFIELD(<<"Allow visitors to send voice requests">>,
-                    <<"muc#roomconfig_allowvoicerequests">>,
-                    (Config#config.allow_voice_requests)),
-        ?BOOLXFIELD(<<"Allow subscription">>,
-                    <<"muc#roomconfig_allow_subscription">>,
-                    (Config#config.allow_subscription)),
-        ?STRINGXFIELD(<<"Minimum interval between voice requests "
-                        "(in seconds)">>,
-                      <<"muc#roomconfig_voicerequestmininterval">>,
-                      integer_to_binary(Config#config.voice_request_min_interval))]
+       [{publicroom, Config#config.public},
+        {public_list, Config#config.public_list},
+        {passwordprotectedroom, Config#config.password_protected},
+        {roomsecret, case Config#config.password_protected of
+                         true -> Config#config.password;
+                         false -> <<"">>
+                     end},
+        {maxusers, MaxUsersRoomString,
+         [if is_integer(ServiceMaxUsers) -> [];
+             true -> [{<<"No limit">>, <<"none">>}]
+          end] ++ [{integer_to_binary(N), N}
+                   || N <- lists:usort([ServiceMaxUsers,
+                                        DefaultRoomMaxUsers,
+                                        MaxUsersRoomInteger
+                                        | ?MAX_USERS_DEFAULT_LIST]),
+                      N =< ServiceMaxUsers]},
+        {whois, if Config#config.anonymous -> moderators;
+                   true -> anyone
+                end},
+        {presencebroadcast, Config#config.presence_broadcast},
+        {membersonly, Config#config.members_only},
+        {moderatedroom, Config#config.moderated},
+        {members_by_default, Config#config.members_by_default},
+        {changesubject, Config#config.allow_change_subj},
+        {allow_private_messages, Config#config.allow_private_messages},
+        {allow_private_messages_from_visitors,
+         Config#config.allow_private_messages_from_visitors},
+        {allow_query_users, Config#config.allow_query_users},
+        {allowinvites, Config#config.allow_user_invites},
+        {allow_visitor_status, Config#config.allow_visitor_status},
+        {allow_visitor_nickchange, Config#config.allow_visitor_nickchange},
+        {allow_voice_requests, Config#config.allow_voice_requests},
+        {allow_subscription, Config#config.allow_subscription},
+        {voice_request_min_interval, Config#config.voice_request_min_interval}]
        ++
        case ejabberd_captcha:is_feature_available() of
-           true ->
-               [?BOOLXFIELD(<<"Make room CAPTCHA protected">>,
-                            <<"captcha_protected">>,
-                            (Config#config.captcha_protected))];
+           true -> [{captcha_protected, Config#config.captcha_protected}];
            false -> []
        end ++
-       [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>,
-                        <<"muc#roomconfig_captcha_whitelist">>,
-                        ((?SETS):to_list(Config#config.captcha_whitelist)))]
+       [{captcha_whitelist,
+         lists:map(fun jid:make/1, ?SETS:to_list(Config#config.captcha_whitelist))}]
        ++
        case mod_muc_log:check_access_log(StateData#state.server_host, From) of
-           allow ->
-               [?BOOLXFIELD(<<"Enable logging">>,
-                            <<"muc#roomconfig_enablelogging">>,
-                            (Config#config.logging))];
+           allow -> [{enablelogging, Config#config.logging}];
            deny -> []
        end,
     Fields = ejabberd_hooks:run_fold(get_room_config,
                                     StateData#state.server_host,
                                     Fs,
                                     [StateData, From, Lang]),
-    #xdata{type = form, title = Title, fields = Fields}.
+    #xdata{type = form, title = Title,
+          fields = muc_roomconfig:encode(
+                     Fields, fun(T) -> translate:translate(Lang, T) end)}.
 
 -spec set_config(xdata(), state(), binary()) -> {error, stanza_error()} |
                                                {result, undefined, state()}.
 set_config(#xdata{fields = Fields}, StateData, Lang) ->
-    Options = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- Fields],
-    case set_xoption(Options, StateData#state.config,
-                    StateData#state.server_host, Lang) of
-       #config{} = Config ->
-           Res = change_config(Config, StateData),
-           {result, _, NSD} = Res,
-           Type = case {(StateData#state.config)#config.logging,
-                        Config#config.logging}
-                  of
-                      {true, false} -> roomconfig_change_disabledlogging;
-                      {false, true} -> roomconfig_change_enabledlogging;
-                      {_, _} -> roomconfig_change
-                  end,
-           Users = [{U#user.jid, U#user.nick, U#user.role}
-                    || {_, U} <- (?DICT):to_list(StateData#state.users)],
-           add_to_log(Type, Users, NSD),
-           Res;
-       Err -> Err
+    try
+       Options = muc_roomconfig:decode(Fields),
+       #config{} = Config = set_config(Options, StateData#state.config,
+                                       StateData#state.server_host, Lang),
+       {result, _, NSD} = Res = change_config(Config, StateData),
+       Type = case {(StateData#state.config)#config.logging,
+                    Config#config.logging}
+              of
+                  {true, false} -> roomconfig_change_disabledlogging;
+                  {false, true} -> roomconfig_change_enabledlogging;
+                  {_, _} -> roomconfig_change
+              end,
+       Users = [{U#user.jid, U#user.nick, U#user.role}
+                || {_, U} <- (?DICT):to_list(StateData#state.users)],
+       add_to_log(Type, Users, NSD),
+       Res
+    catch _:{muc_roomconfig, Why} ->
+           Txt = muc_roomconfig:format_error(Why),
+           {error, xmpp:err_bad_request(Txt, Lang)};
+         _:{badmatch, {error, #stanza_error{}} = Err} ->
+           Err
     end.
 
 get_config_opt_name(Pos) ->
     Fs = [config|record_info(fields, config)],
     lists:nth(Pos, Fs).
 
--define(SET_BOOL_XOPT(Opt, Val),
-       case Val of
-         <<"0">> ->
-             set_xoption(Opts, setelement(Opt, Config, false), ServerHost, Lang);
-         <<"false">> ->
-             set_xoption(Opts, setelement(Opt, Config, false), ServerHost, Lang);
-         <<"1">> -> set_xoption(Opts, setelement(Opt, Config, true), ServerHost, Lang);
-         <<"true">> ->
-             set_xoption(Opts, setelement(Opt, Config, true), ServerHost, Lang);
-         _ ->
-             Txt = <<"Value of '~s' should be boolean">>,
-             OptName = get_config_opt_name(Opt),
-             ErrTxt = iolist_to_binary(io_lib:format(Txt, [OptName])),
-             {error, xmpp:err_bad_request(ErrTxt, Lang)}
-       end).
-
--define(SET_NAT_XOPT(Opt, Val),
-       case catch binary_to_integer(Val) of
-         I when is_integer(I), I > 0 ->
-             set_xoption(Opts, setelement(Opt, Config, I), ServerHost, Lang);
-         _ ->
-             Txt = <<"Value of '~s' should be integer">>,
-             OptName = get_config_opt_name(Opt),
-             ErrTxt = iolist_to_binary(io_lib:format(Txt, [OptName])),
-             {error, xmpp:err_bad_request(ErrTxt, Lang)}
-       end).
-
--define(SET_STRING_XOPT(Opt, Vals),
-       try 
-           V = case Vals of
-                   [] -> <<"">>;
-                   [Val] -> Val;
-                   _ when is_atom(Vals) -> Vals
-               end,
-           set_xoption(Opts, setelement(Opt, Config, V), ServerHost, Lang)
-       catch _:_ ->
-               Txt = <<"Incorrect value of option '~s'">>,
-               OptName = get_config_opt_name(Opt),
-               ErrTxt = iolist_to_binary(io_lib:format(Txt, [OptName])),
-               {error, xmpp:err_bad_request(ErrTxt, Lang)}
-       end).
-
--define(SET_JIDMULTI_XOPT(Opt, Vals),
-       begin
-         Set = lists:foldl(fun ({U, S, R}, Set1) ->
-                                   (?SETS):add_element({U, S, R}, Set1);
-                               (#jid{luser = U, lserver = S, lresource = R},
-                                Set1) ->
-                                   (?SETS):add_element({U, S, R}, Set1);
-                               (_, Set1) -> Set1
-                           end,
-                           (?SETS):empty(), Vals),
-         set_xoption(Opts, setelement(Opt, Config, Set), ServerHost, Lang)
-       end).
-
--spec set_xoption([{binary(), [binary()]}], #config{},
+-spec set_config([muc_roomconfig:property()], #config{},
                  binary(), binary()) -> #config{} | {error, stanza_error()}.
-set_xoption([], Config, _ServerHost, _Lang) -> Config;
-set_xoption([{<<"muc#roomconfig_roomname">>, Vals}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_STRING_XOPT(#config.title, Vals);
-set_xoption([{<<"muc#roomconfig_roomdesc">>, Vals}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_STRING_XOPT(#config.description, Vals);
-set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.allow_change_subj, Val);
-set_xoption([{<<"allow_query_users">>, [Val]} | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.allow_query_users, Val);
-set_xoption([{<<"allow_private_messages">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.allow_private_messages, Val);
-set_xoption([{<<"allow_private_messages_from_visitors">>,
-             [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    case Val of
-      <<"anyone">> ->
-         ?SET_STRING_XOPT(#config.allow_private_messages_from_visitors,
-                          anyone);
-      <<"moderators">> ->
-         ?SET_STRING_XOPT(#config.allow_private_messages_from_visitors,
-                          moderators);
-      <<"nobody">> ->
-         ?SET_STRING_XOPT(#config.allow_private_messages_from_visitors,
-                          nobody);
-      _ ->
-         Txt = <<"Value of 'allow_private_messages_from_visitors' "
-                 "should be anyone|moderators|nobody">>,
-         {error, xmpp:err_bad_request(Txt, Lang)}
-    end;
-set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>,
-             [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.allow_visitor_status, Val);
-set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>,
-             [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.allow_visitor_nickchange, Val);
-set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.public, Val);
-set_xoption([{<<"public_list">>, [Val]} | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.public_list, Val);
-set_xoption([{<<"muc#roomconfig_persistentroom">>,
-             [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.persistent, Val);
-set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.moderated, Val);
-set_xoption([{<<"members_by_default">>, [Val]} | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.members_by_default, Val);
-set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.members_only, Val);
-set_xoption([{<<"captcha_protected">>, [Val]} | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.captcha_protected, Val);
-set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.allow_user_invites, Val);
-set_xoption([{<<"muc#roomconfig_allow_subscription">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.allow_subscription, Val);
-set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>,
-             [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.password_protected, Val);
-set_xoption([{<<"muc#roomconfig_roomsecret">>, Vals}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_STRING_XOPT(#config.password, Vals);
-set_xoption([{<<"anonymous">>, [Val]} | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.anonymous, Val);
-set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts],
-           Config, ServerHost, Lang) ->
-    Roles =
-        lists:foldl(
-          fun(_S, error) -> error;
-             (S, {M, P, V}) ->
-                  case S of
-                      <<"moderator">> -> {true, P, V};
-                      <<"participant">> -> {M, true, V};
-                      <<"visitor">> -> {M, P, true};
-                      _ -> error
-                  end
-          end, {false, false, false}, Vals),
-    case Roles of
-        error ->
-           Txt = <<"Value of 'muc#roomconfig_presencebroadcast' should "
-                   "be moderator|participant|visitor">>,
-           {error, xmpp:err_bad_request(Txt, Lang)};
-        {M, P, V} ->
-            Res =
-                if M -> [moderator]; true -> [] end ++
-                if P -> [participant]; true -> [] end ++
-                if V -> [visitor]; true -> [] end,
-            set_xoption(Opts, Config#config{presence_broadcast = Res},
-                       ServerHost, Lang)
-    end;
-set_xoption([{<<"muc#roomconfig_allowvoicerequests">>,
-             [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.allow_voice_requests, Val);
-set_xoption([{<<"muc#roomconfig_voicerequestmininterval">>,
-             [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_NAT_XOPT(#config.voice_request_min_interval, Val);
-set_xoption([{<<"muc#roomconfig_whois">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    case Val of
-      <<"moderators">> ->
-         ?SET_BOOL_XOPT(#config.anonymous,
-                        (iolist_to_binary(integer_to_list(1))));
-      <<"anyone">> ->
-         ?SET_BOOL_XOPT(#config.anonymous,
-                        (iolist_to_binary(integer_to_list(0))));
-      _ ->
-         Txt = <<"Value of 'muc#roomconfig_whois' should be "
-                 "moderators|anyone">>,
-         {error, xmpp:err_bad_request(Txt, Lang)}
-    end;
-set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    case Val of
-      <<"none">> -> ?SET_STRING_XOPT(#config.max_users, none);
-      _ -> ?SET_NAT_XOPT(#config.max_users, Val)
-    end;
-set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    ?SET_BOOL_XOPT(#config.logging, Val);
-set_xoption([{<<"muc#roomconfig_captcha_whitelist">>,
-             Vals}
-            | Opts],
-           Config, ServerHost, Lang) ->
-    JIDs = [jid:from_string(Val) || Val <- Vals],
-    ?SET_JIDMULTI_XOPT(#config.captcha_whitelist, JIDs);
-set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config, ServerHost, Lang) ->
-    set_xoption(Opts, Config, ServerHost, Lang);
-set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) ->
-    Txt = <<"Unknown option '~s'">>,
-    ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-    Err = {error, xmpp:err_bad_request(ErrTxt, Lang)},
-    case ejabberd_hooks:run_fold(set_room_option,
-                                ServerHost,
-                                Err,
-                                [Opt, Vals, Lang]) of
-       {error, Reason} ->
-           {error, Reason};
-       {Pos, Val} ->
-           set_xoption(Opts, setelement(Pos, Config, Val), ServerHost, Lang)
-    end.
+set_config(Opts, Config, ServerHost, Lang) ->
+    lists:foldl(
+      fun(_, {error, _} = Err) -> Err;
+        ({roomname, Title}, C) -> C#config{title = Title};
+        ({roomdesc, Desc}, C) -> C#config{description = Desc};
+        ({changesubject, V}, C) -> C#config{allow_change_subj = V};
+        ({allow_query_users, V}, C) -> C#config{allow_query_users = V};
+        ({allow_private_messages, V}, C) ->
+             C#config{allow_private_messages = V};
+        ({allow_private_messages_from_visitors, V}, C) ->
+             C#config{allow_private_messages_from_visitors = V};
+        ({allow_visitor_status, V}, C) -> C#config{allow_visitor_status = V};
+        ({allow_visitor_nickchange, V}, C) ->
+             C#config{allow_visitor_nickchange = V};
+        ({publicroom, V}, C) -> C#config{public = V};
+        ({public_list, V}, C) -> C#config{public_list = V};
+        ({persistentroom, V}, C) -> C#config{persistent = V};
+        ({moderatedroom, V}, C) -> C#config{moderated = V};
+        ({members_by_default, V}, C) -> C#config{members_by_default = V};
+        ({membersonly, V}, C) -> C#config{members_only = V};
+        ({captcha_protected, V}, C) -> C#config{captcha_protected = V};
+        ({allowinvites, V}, C) -> C#config{allow_user_invites = V};
+        ({allow_subscription, V}, C) -> C#config{allow_subscription = V};
+        ({passwordprotectedroom, V}, C) -> C#config{password_protected = V};
+        ({roomsecret, V}, C) -> C#config{password = V};
+        ({anonymous, V}, C) -> C#config{anonymous = V};
+        ({presencebroadcast, V}, C) -> C#config{presence_broadcast = V};
+        ({allow_voice_requests, V}, C) -> C#config{allow_voice_requests = V};
+        ({voice_request_min_interval, V}, C) ->
+             C#config{voice_request_min_interval = V};
+        ({whois, moderators}, C) -> C#config{anonymous = true};
+        ({whois, anyone}, C) -> C#config{anonymous = false};
+        ({maxusers, V}, C) -> C#config{max_users = V};
+        ({enablelogging, V}, C) -> C#config{logging = V};
+        ({captcha_whitelist, Js}, C) ->
+             LJIDs = [jid:tolower(J) || J <- Js],
+             C#config{captcha_whitelist = ?SETS:from_list(LJIDs)};
+        ({O, V} = Opt, C) ->
+             case ejabberd_hooks:run_fold(set_room_option,
+                                          ServerHost,
+                                          {0, undefined},
+                                          [Opt, Lang]) of
+                 {0, undefined} ->
+                     ?ERROR_MSG("set_room_option hook failed for "
+                                "option '~s' with value ~p", [O, V]),
+                     Txt = <<"Failed to process option '", O/binary, "'">>,
+                     {error, xmpp:err_internal_server_error(Txt, Lang)};
+                 {Pos, Val} ->
+                     setelement(Pos, C, Val)
+             end
+      end, Config, Opts).
 
 -spec change_config(#config{}, state()) -> {result, undefined, state()}.
 change_config(Config, StateData) ->
@@ -3872,33 +3607,13 @@ process_iq_disco_info(_From, #iq{type = get, lang = Lang}, StateData) ->
                                                 name = get_title(StateData)}],
                         features = Feats}}.
 
--spec mk_rfieldt('boolean' | 'fixed' | 'hidden' |
-                'jid-multi' | 'jid-single' | 'list-multi' |
-                'list-single' | 'text-multi' | 'text-private' |
-                'text-single', binary(), binary()) -> xdata_field().
-mk_rfieldt(Type, Var, Val) ->
-    #xdata_field{type = Type, var = Var, values = [Val]}.
-
--spec mk_rfield(binary(), binary(), binary(), binary()) -> xdata_field().
-mk_rfield(Label, Var, Val, Lang) ->
-    #xdata_field{type = 'text-single',
-                label = translate:translate(Lang, Label),
-                var = Var,
-                values = [Val]}.
-
 -spec iq_disco_info_extras(binary(), state()) -> xdata().
 iq_disco_info_extras(Lang, StateData) ->
-    Len = (?DICT):size(StateData#state.users),
-    RoomDescription = (StateData#state.config)#config.description,
+    Fs = [{description, (StateData#state.config)#config.description},
+         {occupants, ?DICT:size(StateData#state.users)}],
     #xdata{type = result,
-          fields = [mk_rfieldt(hidden, <<"FORM_TYPE">>,
-                               "http://jabber.org/protocol/muc#roominfo"),
-                    mk_rfield(<<"Room description">>,
-                              <<"muc#roominfo_description">>,
-                              RoomDescription, Lang),
-                    mk_rfield(<<"Number of occupants">>,
-                              <<"muc#roominfo_occupants">>,
-                              integer_to_binary(Len), Lang)]}.
+          fields = muc_roominfo:encode(
+                     Fs, fun(T) -> translate:translate(Lang, T) end)}.
 
 -spec process_iq_disco_items(jid(), iq(), state()) ->
                                    {error, stanza_error()} | {result, disco_items()}.
@@ -4105,39 +3820,16 @@ get_mucroom_disco_items(StateData) ->
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Voice request support
 
--spec is_voice_request(message()) -> boolean().
-is_voice_request(Packet) ->
-    Els = xmpp:get_els(Packet),
-    lists:any(
-      fun(#xdata{} = X) ->
-             case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
-                   xmpp_util:get_xdata_values(<<"muc#role">>, X)} of
-                 {[<<"http://jabber.org/protocol/muc#request">>],
-                  [<<"participant">>]} ->
-                     true;
-                 _ ->
-                     false
-             end;
-        (_) ->
-             false
-      end, Els).
-
 -spec prepare_request_form(jid(), binary(), binary()) -> message().
 prepare_request_form(Requester, Nick, Lang) ->
     Title = translate:translate(Lang, <<"Voice request">>),
     Instruction = translate:translate(
                    Lang, <<"Either approve or decline the voice request.">>),
-    Fs = [#xdata_field{var = <<"FORM_TYPE">>,
-                      type = hidden,
-                      values = [<<"http://jabber.org/protocol/muc#request">>]},
-         #xdata_field{var = <<"muc#role">>,
-                      type = hidden,
-                      values = [<<"participant">>]},
-         ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>,
-                       jid:to_string(Requester)),
-         ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, Nick),
-         ?BOOLXFIELD(<<"Grant voice to this person?">>,
-                     <<"muc#request_allow">>, false)],
+    Fs = muc_request:encode([{role, participant},
+                            {jid, Requester},
+                            {roomnick, Nick},
+                            {request_allow, false}],
+                           fun(T) -> translate:translate(Lang, T) end),
     #message{type = normal,
             sub_els = [#xdata{type = form,
                               title = Title,
@@ -4155,59 +3847,11 @@ send_voice_request(From, Lang, StateData) ->
                  end,
                  Moderators).
 
--spec is_voice_approvement(message()) -> boolean().
-is_voice_approvement(Packet) ->
-    Els = xmpp:get_els(Packet),
-    lists:any(
-      fun(#xdata{} = X) ->
-             case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
-                   xmpp_util:get_xdata_values(<<"muc#role">>, X),
-                   xmpp_util:get_xdata_values(<<"muc#request_allow">>, X)} of
-                 {[<<"http://jabber.org/protocol/muc#request">>],
-                  [<<"participant">>], [Flag]} when Flag == <<"true">>;
-                                                    Flag == <<"1">> ->
-                     true;
-                 _ ->
-                     false
-             end;
-        (_) ->
-             false
-      end, Els).
-
--spec extract_jid_from_voice_approvement(message()) -> jid() | error.
-extract_jid_from_voice_approvement(Packet) ->
-    Els = xmpp:get_els(Packet),
-    lists:foldl(
-      fun(#xdata{} = X, error) ->
-             case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
-                   xmpp_util:get_xdata_values(<<"muc#role">>, X),
-                   xmpp_util:get_xdata_values(<<"muc#request_allow">>, X),
-                   xmpp_util:get_xdata_values(<<"muc#jid">>, X)} of
-                 {[<<"http://jabber.org/protocol/muc#request">>],
-                  [<<"participant">>], [Flag], [J]} when Flag == <<"true">>;
-                                                         Flag == <<"1">> ->
-                     jid:from_string(J);
-                 _ ->
-                     error
-             end;
-        (_, Acc) ->
-             Acc
-      end, error, Els).
-
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Invitation support
 
--spec is_invitation(message()) -> boolean().
-is_invitation(Packet) ->
-    Els = xmpp:get_els(Packet),
-    lists:any(
-      fun(#muc_user{invites = [_|_]}) -> true;
-        (_) -> false
-      end, Els).
-
--spec check_invitation(jid(), message(), state()) -> {error, stanza_error()} | jid().
-check_invitation(From, Packet, StateData) ->
-    Lang = xmpp:get_lang(Packet),
+-spec check_invitation(jid(), muc_invite(), binary(), state()) -> {error, stanza_error()} | jid().
+check_invitation(From, Invitation, Lang, StateData) ->
     FAffiliation = get_affiliation(From, StateData),
     CanInvite = (StateData#state.config)#config.allow_user_invites
        orelse
@@ -4217,57 +3861,46 @@ check_invitation(From, Packet, StateData) ->
            Txt = <<"Invitations are not allowed in this conference">>,
            {error, xmpp:err_not_allowed(Txt, Lang)};
        true ->
-           case xmpp:get_subtag(Packet, #muc_user{}) of
-               #muc_user{invites = [#muc_invite{to = undefined}]} ->
-                   Txt = <<"No 'to' attribute found">>,
-                   {error, xmpp:err_bad_request(Txt, Lang)};
-               #muc_user{invites = [#muc_invite{to = JID, reason = Reason} = I]} ->
-                   Invite = I#muc_invite{to = undefined, from = From},
-                   Password = case (StateData#state.config)#config.password_protected of
-                                  true ->
-                                      (StateData#state.config)#config.password;
-                                  false ->
-                                      undefined
-                              end,
-                   XUser = #muc_user{password = Password, invites = [Invite]},
-                   XConference = #x_conference{jid = jid:make(StateData#state.room,
-                                                              StateData#state.host),
-                                               reason = Reason},
-                   Body = iolist_to_binary(
-                            [io_lib:format(
-                               translate:translate(
-                                 Lang,
-                                 <<"~s invites you to the room ~s">>),
-                               [jid:to_string(From),
-                                jid:to_string({StateData#state.room,
-                                               StateData#state.host,
-                                               <<"">>})]),
-                             case (StateData#state.config)#config.password_protected of
-                                 true ->
-                                     <<", ",
-                                       (translate:translate(
-                                          Lang, <<"the password is">>))/binary,
-                                       " '",
-                                       ((StateData#state.config)#config.password)/binary,
-                                       "'">>;
-                                 _ -> <<"">>
-                             end,
-                             case Reason of
-                                 <<"">> -> <<"">>;
-                                 _ -> <<" (", Reason/binary, ") ">>
-                             end]),
-                   Msg = #message{type = normal,
-                                  body = xmpp:mk_text(Body),
-                                  sub_els = [XUser, XConference]},
-                   ejabberd_router:route(StateData#state.jid, JID, Msg),
-                   JID;
-               #muc_user{invites = [_|_]} ->
-                   Txt = <<"Multiple <invite/> elements are not allowed">>,
-                   {error, xmpp:err_forbidden(Txt, Lang)};
-               _ ->
-                   Txt = <<"No <invite/> element found">>,
-                   {error, xmpp:err_bad_request(Txt, Lang)}
-           end
+           #muc_invite{to = JID, reason = Reason} = Invitation,
+           Invite = Invitation#muc_invite{to = undefined, from = From},
+           Password = case (StateData#state.config)#config.password_protected of
+                          true ->
+                              (StateData#state.config)#config.password;
+                          false ->
+                              undefined
+                      end,
+           XUser = #muc_user{password = Password, invites = [Invite]},
+           XConference = #x_conference{jid = jid:make(StateData#state.room,
+                                                      StateData#state.host),
+                                       reason = Reason},
+           Body = iolist_to_binary(
+                    [io_lib:format(
+                       translate:translate(
+                         Lang,
+                         <<"~s invites you to the room ~s">>),
+                       [jid:to_string(From),
+                        jid:to_string({StateData#state.room,
+                                       StateData#state.host,
+                                       <<"">>})]),
+                     case (StateData#state.config)#config.password_protected of
+                         true ->
+                             <<", ",
+                               (translate:translate(
+                                  Lang, <<"the password is">>))/binary,
+                               " '",
+                               ((StateData#state.config)#config.password)/binary,
+                               "'">>;
+                         _ -> <<"">>
+                     end,
+                     case Reason of
+                         <<"">> -> <<"">>;
+                         _ -> <<" (", Reason/binary, ") ">>
+                     end]),
+           Msg = #message{type = normal,
+                          body = xmpp:mk_text(Body),
+                          sub_els = [XUser, XConference]},
+           ejabberd_router:route(StateData#state.jid, JID, Msg),
+           JID
     end.
 
 %% Handle a message sent to the room by a non-participant.
index 258c97d4f34d46fb52307ae0f737eacafa135217..240650234084b48b2915d9cd523b492c3258c4bb 100644 (file)
@@ -300,8 +300,7 @@ get_sm_items(Acc, _From, _To, _Node, _Lang) ->
 -spec get_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
              ([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
 get_info(_Acc, #jid{luser = U, lserver = S, lresource = R},
-        #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, _Lang) ->
-    N = integer_to_binary(count_offline_messages(U, S)),
+        #jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, Lang) ->
     case ejabberd_sm:get_session_pid(U, S, R) of
        Pid when is_pid(Pid) ->
            Pid ! dont_ask_offline;
@@ -309,11 +308,9 @@ get_info(_Acc, #jid{luser = U, lserver = S, lresource = R},
            ok
     end,
     [#xdata{type = result,
-           fields = [#xdata_field{var = <<"FORM_TYPE">>,
-                                  type = hidden,
-                                  values = [?NS_FLEX_OFFLINE]},
-                     #xdata_field{var = <<"number_of_messages">>,
-                                  values = [N]}]}];
+           fields = flex_offline:encode(
+                      [{number_of_messages, count_offline_messages(U, S)}],
+                      fun(T) -> translate:translate(Lang, T) end)}];
 get_info(Acc, _From, _To, _Node, _Lang) ->
     Acc.
 
index b55115c9a0fa249b449068ed498af324b762d27a..e3fe64c167606fc2eb3aa8e7cd3c89e9961dfde7 100644 (file)
@@ -1028,8 +1028,11 @@ do_route(Host, From, To, Packet) ->
                    case find_authorization_response(Packet) of
                        undefined ->
                            ok;
-                       XData ->
-                           handle_authorization_response(Host, From, To, Packet, XData)
+                       {error, Err} ->
+                           ejabberd_router:route_error(To, From, Packet, Err);
+                       AuthResponse ->
+                           handle_authorization_response(
+                             Host, From, To, Packet, AuthResponse)
                    end;
                _ ->
                    Err = xmpp:err_service_unavailable(),
@@ -1200,19 +1203,28 @@ iq_pubsub(Host, Access, #iq{from = From, type = IQType, lang = Lang,
            ServerHost = serverhost(Host),
            Plugins = config(ServerHost, plugins),
            Config = case Configure of
-                        {_, XData} -> get_xdata_fields(XData);
+                        {_, XData} -> decode_node_config(XData, Host, Lang);
                         undefined -> []
                     end,
            Type = hd(Plugins),
-           create_node(Host, ServerHost, Node, From, Type, Access, Config);
+           case Config of
+               {error, _} = Err ->
+                   Err;
+               _ ->
+                   create_node(Host, ServerHost, Node, From, Type, Access, Config)
+           end;
        {set, #pubsub{publish = #ps_publish{node = Node, items = Items},
                      publish_options = XData, _ = undefined}} ->
            ServerHost = serverhost(Host),
            case Items of
                [#ps_item{id = ItemId, xml_els = Payload}] ->
-                   PubOpts = get_xdata_fields(XData),
-                   publish_item(Host, ServerHost, Node, From, ItemId,
-                                Payload, PubOpts, Access);
+                   case decode_publish_options(XData, Lang) of
+                       {error, _} = Err ->
+                           Err;
+                       PubOpts ->
+                           publish_item(Host, ServerHost, Node, From, ItemId,
+                                        Payload, PubOpts, Access)
+                   end;
                [] ->
                    {error, extended_error(xmpp:err_bad_request(), err_item_required())};
                _ ->
@@ -1236,10 +1248,17 @@ iq_pubsub(Host, Access, #iq{from = From, type = IQType, lang = Lang,
        {set, #pubsub{subscribe = #ps_subscribe{node = Node, jid = JID},
                      options = Options, _ = undefined}} ->
            Config = case Options of
-                        #ps_options{xdata = XData} -> get_xdata_fields(XData);
-                        _ -> []
+                        #ps_options{xdata = XData} ->
+                            decode_subscribe_options(XData, Lang);
+                        _ ->
+                            []
                     end,
-           subscribe_node(Host, Node, From, JID, Config);
+           case Config of
+               {error, _} = Err ->
+                   Err;
+               _ ->
+                   subscribe_node(Host, Node, From, JID, Config)
+           end;
        {set, #pubsub{unsubscribe = #ps_unsubscribe{node = Node, jid = JID, subid = SubId},
                      _ = undefined}} ->
            unsubscribe_node(Host, Node, From, JID, SubId);
@@ -1262,7 +1281,12 @@ iq_pubsub(Host, Access, #iq{from = From, type = IQType, lang = Lang,
        {set, #pubsub{options = #ps_options{node = Node, subid = SubId,
                                            jid = JID, xdata = XData},
                      _ = undefined}} ->
-           set_options(Host, Node, JID, SubId, get_xdata_fields(XData));
+           case decode_subscribe_options(XData, Lang) of
+               {error, _} = Err ->
+                   Err;
+               Config ->
+                   set_options(Host, Node, JID, SubId, Config)
+           end;
        {set, #pubsub{}} ->
            {error, xmpp:err_bad_request()};
        _ ->
@@ -1284,8 +1308,12 @@ iq_pubsub_owner(Host, #iq{type = IQType, from = From,
                #xdata{type = cancel} ->
                    {result, #pubsub_owner{}};
                #xdata{type = submit} ->
-                   Config = get_xdata_fields(XData),
-                   set_configure(Host, Node, From, Config, Lang);
+                   case decode_node_config(XData, Host, Lang) of
+                       {error, _} = Err ->
+                           Err;
+                       Config ->
+                           set_configure(Host, Node, From, Config, Lang)
+                   end;
                #xdata{} ->
                    {error, xmpp:err_bad_request(<<"Incorrect data form">>, Lang)}
            end;
@@ -1318,19 +1346,20 @@ adhoc_request(Host, _ServerHost, Owner,
     send_pending_node_form(Host, Owner, Lang, Plugins);
 adhoc_request(Host, _ServerHost, Owner,
              #adhoc_command{node = ?NS_PUBSUB_GET_PENDING, lang = Lang,
-                            action = execute, xdata = #xdata{} = XData},
+                            action = execute, xdata = #xdata{} = XData} = Request,
              _Access, _Plugins) ->
-    Config = get_xdata_fields(XData),
-    case set_xoption(Host, Config, []) of
-       XForm when is_list(XForm) ->
-           case lists:keysearch(node, 1, XForm) of
-               {value, {_, Node}} ->
-                   send_pending_auth_events(Host, Node, Owner, Lang);
-               false ->
-                   {error, extended_error(xmpp:err_bad_request(), err_invalid_payload())}
-           end;
-       Err ->
-           Err
+    case decode_get_pending(XData, Lang) of
+       {error, _} = Err ->
+           Err;
+       Config ->
+           Node = proplists:get_value(node, Config),
+           case send_pending_auth_events(Host, Node, Owner, Lang) of
+               ok ->
+                   xmpp_util:make_adhoc_response(
+                     Request, #adhoc_command{action = completed});
+               Err ->
+                   Err
+           end
     end;
 adhoc_request(_Host, _ServerHost, _Owner,
              #adhoc_command{action = cancel}, _Access, _Plugins) ->
@@ -1353,12 +1382,9 @@ send_pending_node_form(Host, Owner, _Lang, Plugins) ->
        Ps ->
            case get_pending_nodes(Host, Owner, Ps) of
                {ok, Nodes} ->
-                   XOpts = [#xdata_option{value = Node} || Node <- Nodes],
                    XForm = #xdata{type = form,
-                                  fields = [#xdata_field{
-                                               type = 'list-single',
-                                               var = <<"pubsub#node">>,
-                                               options = lists:usort(XOpts)}]},
+                                  fields = pubsub_get_pending:encode(
+                                             [{node, Nodes}])},
                    #adhoc_command{status = executing, action = execute,
                                   xdata = XForm};
                Err ->
@@ -1423,24 +1449,11 @@ send_authorization_request(#pubsub_node{nodeid = {Host, Node},
                           Subscriber) ->
     %% TODO: pass lang to this function
     Lang = <<"en">>,
-    Fs = [#xdata_field{var = <<"FORM_TYPE">>,
-                      type = hidden,
-                      values = [?NS_PUBSUB_SUB_AUTH]},
-         #xdata_field{var = <<"pubsub#node">>,
-                      type = 'text-single',
-                      label = translate:translate(Lang, <<"Node ID">>),
-                      values = [Node]},
-         #xdata_field{var = <<"pubsub#subscriber_jid">>,
-                      type = 'jid-single',
-                      label = translate:translate(Lang, <<"Subscriber Address">>),
-                      values = [jid:to_string(Subscriber)]},
-         #xdata_field{var = <<"pubsub#allow">>,
-                      type = boolean,
-                      label = translate:translate(
-                                Lang,
-                                <<"Allow this Jabber ID to subscribe to "
-                                  "this pubsub node?">>),
-                      values = [<<"false">>]}],
+    Fs = pubsub_subscribe_authorization:encode(
+          [{node, Node},
+           {subscriber_jid, Subscriber},
+           {allow, false}],
+          fun(T) -> translate:translate(Lang, T) end),
     X = #xdata{type = form,
               title = translate:translate(
                         Lang, <<"PubSub subscriber request">>),
@@ -1455,15 +1468,24 @@ send_authorization_request(#pubsub_node{nodeid = {Host, Node},
              ejabberd_router:route(service_jid(Host), jid:make(Owner), Stanza)
       end, node_owners_action(Host, Type, Nidx, O)).
 
--spec find_authorization_response(message()) -> undefined | xdata().
+-spec find_authorization_response(message()) -> undefined |
+                                               pubsub_subscribe_authorization:result() |
+                                               {error, stanza_error()}.
 find_authorization_response(Packet) ->
     case xmpp:get_subtag(Packet, #xdata{}) of
-       #xdata{type = submit} = X ->
-           case xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X) of
-               [?NS_PUBSUB_SUB_AUTH] -> X;
-               _ -> undefined
+       #xdata{type = cancel} ->
+           undefined;
+       #xdata{type = submit, fields = Fs} ->
+           try pubsub_subscribe_authorization:decode(Fs) of
+               Result -> Result
+           catch _:{pubsub_subscribe_authorization, Why} ->
+                   Lang = xmpp:get_lang(Packet),
+                   Txt = pubsub_subscribe_authorization:format_error(Why),
+                   {error, xmpp:err_bad_request(Txt, Lang)}
            end;
-       _ ->
+       #xdata{} ->
+           {error, xmpp:err_bad_request()};
+       false ->
            undefined
     end.
 
@@ -1477,43 +1499,33 @@ send_authorization_approval(Host, JID, SNode, Subscription) ->
     Stanza = #message{sub_els = [Event]},
     ejabberd_router:route(service_jid(Host), JID, Stanza).
 
--spec handle_authorization_response(binary(), jid(), jid(), message(), xdata()) -> ok.
-handle_authorization_response(Host, From, To, Packet, X) ->
+-spec handle_authorization_response(binary(), jid(), jid(), message(),
+                                   pubsub_subscribe_authorization:result()) -> ok.
+handle_authorization_response(Host, From, To, Packet, Response) ->
+    Node = proplists:get_value(node, Response),
+    Subscriber = proplists:get_value(subscriber_jid, Response),
+    Allow = proplists:get_value(allow, Response),
     Lang = xmpp:get_lang(Packet),
-    case {xmpp_util:get_xdata_values(<<"pubsub#node">>, X),
-         xmpp_util:get_xdata_values(<<"pubsub#subscriber_jid">>, X),
-         xmpp_util:get_xdata_values(<<"pubsub#allow">>, X)} of
-       {[Node], [SSubscriber], [SAllow]} ->
-           FromLJID = jid:tolower(jid:remove_resource(From)),
-           Subscriber = jid:from_string(SSubscriber),
-           Allow = case SAllow of
-                       <<"1">> -> true;
-                       <<"true">> -> true;
-                       _ -> false
-                   end,
-           Action =
-               fun(#pubsub_node{type = Type, id = Nidx, owners = O}) ->
-                       Owners = node_owners_call(Host, Type, Nidx, O),
-                       case lists:member(FromLJID, Owners) of
-                           true ->
-                               {result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]),
-                               update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs);
-                           false ->
-                               {error, xmpp:err_forbidden(<<"Owner privileges required">>, Lang)}
-                       end
-               end,
-           case transaction(Host, Node, Action, sync_dirty) of
-               {error, Error} ->
-                   ejabberd_router:route_error(To, From, Packet, Error);
-               {result, {_, _NewSubscription}} ->
-                   %% XXX: notify about subscription state change, section 12.11
-                   ok;
-               _ ->
-                   Err = xmpp:err_internal_server_error(),
-                   ejabberd_router:route_error(To, From, Packet, Err)
-           end;
+    FromLJID = jid:tolower(jid:remove_resource(From)),
+    Action =
+       fun(#pubsub_node{type = Type, id = Nidx, owners = O}) ->
+               Owners = node_owners_call(Host, Type, Nidx, O),
+               case lists:member(FromLJID, Owners) of
+                   true ->
+                       {result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]),
+                       update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs);
+                   false ->
+                       {error, xmpp:err_forbidden(<<"Owner privileges required">>, Lang)}
+               end
+       end,
+    case transaction(Host, Node, Action, sync_dirty) of
+       {error, Error} ->
+           ejabberd_router:route_error(To, From, Packet, Error);
+       {result, {_, _NewSubscription}} ->
+           %% XXX: notify about subscription state change, section 12.11
+           ok;
        _ ->
-           Err = xmpp:err_not_acceptable(<<"Incorrect data form">>, Lang),
+           Err = xmpp:err_internal_server_error(),
            ejabberd_router:route_error(To, From, Packet, Err)
     end.
 
@@ -1539,45 +1551,6 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
            {error, xmpp:err_unexpected_request(Txt, ?MYLANG)}
     end.
 
--define(XFIELD(Type, Label, Var, Val),
-       #xdata_field{type = Type,
-                    label = translate:translate(Lang, Label),
-                    var = Var,
-                    values = [Val]}).
-
--define(BOOLXFIELD(Label, Var, Val),
-       ?XFIELD(boolean, Label, Var,
-               case Val of
-                   true -> <<"1">>;
-                   _ -> <<"0">>
-               end)).
-
--define(STRINGXFIELD(Label, Var, Val),
-       ?XFIELD('text-single', Label, Var, Val)).
-
--define(STRINGMXFIELD(Label, Var, Vals),
-       #xdata_field{type = 'text-multi',
-                    label = translate:translate(Lang, Label),
-                    var = Var,
-                    values = Vals}).
-
--define(XFIELDOPT(Type, Label, Var, Val, Opts),
-       #xdata_field{type = Type,
-                    label = translate:translate(Lang, Label),
-                    var = Var,
-                    options = [#xdata_option{value = Opt} || Opt <- Opts],
-                    values = [Val]}).
-
--define(LISTXFIELD(Label, Var, Val, Opts),
-       ?XFIELDOPT('list-single', Label, Var, Val, Opts)).
-
--define(LISTMXFIELD(Label, Var, Vals, Opts),
-       #xdata_field{type = 'list-multi',
-                    label = translate:translate(Lang, Label),
-                    var = Var,
-                    options = [#xdata_option{value = Opt} || Opt <- Opts],
-                    values = Vals}).
-
 %% @doc <p>Create new pubsub nodes</p>
 %%<p>In addition to method-specific error conditions, there are several general reasons why the node creation request might fail:</p>
 %%<ul>
@@ -1617,70 +1590,66 @@ create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
     end;
 create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
     Type = select_type(ServerHost, Host, Node, GivenType),
-    case set_xoption(Host, Configuration, node_options(Host, Type)) of
-       NodeOptions when is_list(NodeOptions) ->
-           CreateNode =
-               fun() ->
-                       Parent = case node_call(Host, Type, node_to_path, [Node]) of
-                                    {result, [Node]} ->
-                                        <<>>;
-                                    {result, Path} ->
-                                        element(2, node_call(Host, Type, path_to_node,
-                                                             [lists:sublist(Path, length(Path)-1)]))
-                                end,
-                       Parents = case Parent of
-                                     <<>> -> [];
-                                     _ -> [Parent]
-                                 end,
-                       case node_call(Host, Type, create_node_permission,
-                                      [Host, ServerHost, Node, Parent, Owner, Access]) of
-                           {result, true} ->
-                               case tree_call(Host, create_node,
-                                              [Host, Node, Type, Owner, NodeOptions, Parents])
-                               of
-                                   {ok, Nidx} ->
-                                       SubsByDepth = get_node_subs_by_depth(Host, Node, Owner),
-                                       case node_call(Host, Type, create_node, [Nidx, Owner]) of
-                                           {result, Result} -> {result, {Nidx, SubsByDepth, Result}};
-                                           Error -> Error
-                                       end;
-                                   {error, {virtual, Nidx}} ->
-                                       case node_call(Host, Type, create_node, [Nidx, Owner]) of
-                                           {result, Result} -> {result, {Nidx, [], Result}};
-                                           Error -> Error
-                                       end;
-                                   Error ->
-                                       Error
+    NodeOptions = merge_config(Configuration, node_options(Host, Type)),
+    CreateNode =
+       fun() ->
+               Parent = case node_call(Host, Type, node_to_path, [Node]) of
+                            {result, [Node]} ->
+                                <<>>;
+                            {result, Path} ->
+                                element(2, node_call(Host, Type, path_to_node,
+                                                     [lists:sublist(Path, length(Path)-1)]))
+                        end,
+               Parents = case Parent of
+                             <<>> -> [];
+                             _ -> [Parent]
+                         end,
+               case node_call(Host, Type, create_node_permission,
+                              [Host, ServerHost, Node, Parent, Owner, Access]) of
+                   {result, true} ->
+                       case tree_call(Host, create_node,
+                                      [Host, Node, Type, Owner, NodeOptions, Parents])
+                       of
+                           {ok, Nidx} ->
+                               SubsByDepth = get_node_subs_by_depth(Host, Node, Owner),
+                               case node_call(Host, Type, create_node, [Nidx, Owner]) of
+                                   {result, Result} -> {result, {Nidx, SubsByDepth, Result}};
+                                   Error -> Error
                                end;
-                           _ ->
-                               Txt = <<"You're not allowed to create nodes">>,
-                               {error, xmpp:err_forbidden(Txt, ?MYLANG)}
-                       end
-               end,
-           Reply = #pubsub{create = Node},
-           case transaction(Host, CreateNode, transaction) of
-               {result, {Nidx, SubsByDepth, {Result, broadcast}}} ->
-                   broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth),
-                   ejabberd_hooks:run(pubsub_create_node, ServerHost,
-                       [ServerHost, Host, Node, Nidx, NodeOptions]),
-                   case Result of
-                       default -> {result, Reply};
-                       _ -> {result, Result}
-                   end;
-               {result, {Nidx, _SubsByDepth, Result}} ->
-                   ejabberd_hooks:run(pubsub_create_node, ServerHost,
-                       [ServerHost, Host, Node, Nidx, NodeOptions]),
-                   case Result of
-                       default -> {result, Reply};
-                       _ -> {result, Result}
-                   end;
-               Error ->
-                   %% in case we change transaction to sync_dirty...
-                   %%  node_call(Host, Type, delete_node, [Host, Node]),
-                   %%  tree_call(Host, delete_node, [Host, Node]),
-                   Error
+                           {error, {virtual, Nidx}} ->
+                               case node_call(Host, Type, create_node, [Nidx, Owner]) of
+                                   {result, Result} -> {result, {Nidx, [], Result}};
+                                   Error -> Error
+                               end;
+                           Error ->
+                               Error
+                       end;
+                   _ ->
+                       Txt = <<"You're not allowed to create nodes">>,
+                       {error, xmpp:err_forbidden(Txt, ?MYLANG)}
+               end
+       end,
+    Reply = #pubsub{create = Node},
+    case transaction(Host, CreateNode, transaction) of
+       {result, {Nidx, SubsByDepth, {Result, broadcast}}} ->
+           broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth),
+           ejabberd_hooks:run(pubsub_create_node, ServerHost,
+                              [ServerHost, Host, Node, Nidx, NodeOptions]),
+           case Result of
+               default -> {result, Reply};
+               _ -> {result, Result}
+           end;
+       {result, {Nidx, _SubsByDepth, Result}} ->
+           ejabberd_hooks:run(pubsub_create_node, ServerHost,
+                              [ServerHost, Host, Node, Nidx, NodeOptions]),
+           case Result of
+               default -> {result, Reply};
+               _ -> {result, Result}
            end;
        Error ->
+           %% in case we change transaction to sync_dirty...
+           %%  node_call(Host, Type, delete_node, [Host, Node]),
+           %%  tree_call(Host, delete_node, [Host, Node]),
            Error
     end.
 
@@ -2636,7 +2605,7 @@ set_subscriptions(Host, Node, From, Entities) ->
     Owner = jid:tolower(jid:remove_resource(From)),
     Notify = fun(#ps_subscription{jid = JID, type = Sub}) ->
                     Stanza = #message{
-                                sub_els = [#pubsub{
+                                sub_els = [#ps_event{
                                               subscription = #ps_subscription{
                                                                 jid = JID,
                                                                 type = Sub,
@@ -3266,83 +3235,17 @@ max_items(Host, Options) ->
            end
     end.
 
--define(BOOL_CONFIG_FIELD(Label, Var),
-    ?BOOLXFIELD(Label,
-       <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
-       (get_option(Options, Var)))).
-
--define(STRING_CONFIG_FIELD(Label, Var),
-    ?STRINGXFIELD(Label,
-       <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
-       (get_option(Options, Var, <<>>)))).
-
--define(INTEGER_CONFIG_FIELD(Label, Var),
-    ?STRINGXFIELD(Label,
-       <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
-       (integer_to_binary(get_option(Options, Var))))).
-
--define(JLIST_CONFIG_FIELD(Label, Var, Opts),
-    ?LISTXFIELD(Label,
-       <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
-       (jid:to_string(get_option(Options, Var))),
-       [jid:to_string(O) || O <- Opts])).
-
--define(ALIST_CONFIG_FIELD(Label, Var, Opts),
-       ?LISTXFIELD(Label,
-                   <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
-                   (atom_to_binary(get_option(Options, Var), latin1)),
-                   [atom_to_binary(O, latin1) || O <- Opts])).
-
--define(LISTM_CONFIG_FIELD(Label, Var, Opts),
-    ?LISTMXFIELD(Label,
-       <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
-       (get_option(Options, Var)), Opts)).
-
--define(NLIST_CONFIG_FIELD(Label, Var),
-    ?STRINGMXFIELD(Label,
-       <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
-       get_option(Options, Var, []))).
-
+-spec get_configure_xfields(_, pubsub_node_config:result(),
+                           binary(), [binary()]) -> [xdata_field()].
 get_configure_xfields(_Type, Options, Lang, Groups) ->
-    [?XFIELD(hidden, <<>>, <<"FORM_TYPE">>, ?NS_PUBSUB_NODE_CONFIG),
-     ?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>,
-                       deliver_payloads),
-     ?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>,
-                       deliver_notifications),
-     ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuration changes">>,
-                       notify_config),
-     ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is deleted">>,
-                       notify_delete),
-     ?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed from the node">>,
-                       notify_retract),
-     ?BOOL_CONFIG_FIELD(<<"Persist items to storage">>,
-                       persist_items),
-     ?STRING_CONFIG_FIELD(<<"A friendly name for the node">>,
-                         title),
-     ?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>,
-                          max_items),
-     ?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>,
-                       subscribe),
-     ?ALIST_CONFIG_FIELD(<<"Specify the access model">>,
-                        access_model, [open, authorize, presence, roster, whitelist]),
-     ?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>,
-                        roster_groups_allowed, Groups),
-     ?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>,
-                        publish_model, [publishers, subscribers, open]),
-     ?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher goes offline">>,
-                       purge_offline),
-     ?ALIST_CONFIG_FIELD(<<"Specify the event message type">>,
-                        notification_type, [headline, normal]),
-     ?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>,
-                          max_payload_size),
-     ?ALIST_CONFIG_FIELD(<<"When to send the last published item">>,
-                        send_last_published_item, [never, on_sub, on_sub_and_presence]),
-     ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available users">>,
-                       presence_based_delivery),
-     ?NLIST_CONFIG_FIELD(<<"The collections with which a node is affiliated">>,
-                        collection),
-     ?ALIST_CONFIG_FIELD(<<"Whether owners or publisher should receive replies to items">>,
-                        itemreply, [none, owner, publisher])].
+    pubsub_node_config:encode(
+      lists:map(
+       fun({roster_groups_allowed, Value}) ->
+               {roster_groups_allowed, Value, Groups};
+          (Opt) ->
+               Opt
+       end, Options),
+      fun(Txt) -> translate:translate(Lang, Txt) end).
 
 %%<p>There are several reasons why the node configuration request might fail:</p>
 %%<ul>
@@ -3365,18 +3268,13 @@ set_configure(Host, Node, From, Config, Lang) ->
                                      [] -> node_options(Host, Type);
                                      _ -> Options
                                  end,
-                       case set_xoption(Host, Config, OldOpts) of
-                           NewOpts when is_list(NewOpts) ->
-                               case tree_call(Host,
-                                              set_node,
-                                              [N#pubsub_node{options = NewOpts}])
-                               of
-                                   {result, Nidx} -> {result, ok};
-                                   ok -> {result, ok};
-                                   Err -> Err
-                               end;
-                           Error ->
-                               Error
+                       NewOpts = merge_config(Config, OldOpts),
+                       case tree_call(Host,
+                                      set_node,
+                                      [N#pubsub_node{options = NewOpts}]) of
+                           {result, Nidx} -> {result, ok};
+                           ok -> {result, ok};
+                           Err -> Err
                        end;
                    _ ->
                        {error, xmpp:err_forbidden(
@@ -3394,119 +3292,82 @@ set_configure(Host, Node, From, Config, Lang) ->
            Other
     end.
 
--spec add_opt(atom(), any(), [{atom(), any()}]) -> [{atom(), any()}].
-add_opt(Key, Value, Opts) ->
-    lists:keystore(Key, 1, Opts, {Key, Value}).
+-spec merge_config([proplists:property()], [proplists:property()]) -> [proplists:property()].
+merge_config(Config1, Config2) ->
+    lists:foldl(
+      fun({Opt, Val}, Acc) ->
+             lists:keystore(Opt, 1, Acc, {Opt, Val})
+      end, Config2, Config1).
 
--define(SET_BOOL_XOPT(Opt, Val),
-    BoolVal = case Val of
-       <<"0">> -> false;
-       <<"1">> -> true;
-       <<"false">> -> false;
-       <<"true">> -> true;
-       _ -> error
-    end,
-    case BoolVal of
-       error ->
-           Txt = <<"Value of '~s' should be boolean">>,
-           ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-           {error, xmpp:err_not_acceptable(ErrTxt, ?MYLANG)};
-       _ -> set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts))
-    end).
-
--define(SET_STRING_XOPT(Opt, Val),
-    set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
-
--define(SET_INTEGER_XOPT(Opt, Val, Min, Max),
-       case catch binary_to_integer(Val) of
-           IVal when is_integer(IVal), IVal >= Min ->
-               if (Max =:= undefined) orelse (IVal =< Max) ->
-                       set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
-                  true ->
-                       Txt = <<"Incorrect value of '~s'">>,
-                       ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-                       {error, xmpp:err_not_acceptable(ErrTxt, ?MYLANG)}
-               end;
-           _ ->
-               Txt = <<"Value of '~s' should be integer">>,
-               ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-               {error, xmpp:err_not_acceptable(ErrTxt, ?MYLANG)}
-       end).
+-spec decode_node_config(undefined | xdata(), binary(), binary()) ->
+                               pubsub_node_config:result() |
+                               {error, stanza_error()}.
+decode_node_config(undefined, _, _) ->
+    [];
+decode_node_config(#xdata{fields = Fs}, Host, Lang) ->
+    try
+       Config = pubsub_node_config:decode(Fs),
+       Max = get_max_items_node(Host),
+       case {check_opt_range(max_items, Config, Max),
+             check_opt_range(max_payload_size, Config, ?MAX_PAYLOAD_SIZE)} of
+           {true, true} ->
+               Config;
+           {true, false} ->
+               erlang:error(
+                 {pubsub_node_config,
+                  {bad_var_value, <<"pubsub#max_payload_size">>,
+                   ?NS_PUBSUB_NODE_CONFIG}});
+           {false, _} ->
+               erlang:error(
+                 {pubsub_node_config,
+                  {bad_var_value, <<"pubsub#max_items">>,
+                   ?NS_PUBSUB_NODE_CONFIG}})
+       end
+    catch _:{pubsub_node_config, Why} ->
+           Txt = pubsub_node_config:format_error(Why),
+           {error, xmpp:err_resource_constraint(Txt, Lang)}
+    end.
+
+-spec decode_subscribe_options(undefined | xdata(), binary()) ->
+                                     pubsub_subscribe_options:result() |
+                                     {error, stanza_error()}.
+decode_subscribe_options(undefined, _) ->
+    [];
+decode_subscribe_options(#xdata{fields = Fs}, Lang) ->
+    try pubsub_subscribe_options:decode(Fs)
+    catch _:{pubsub_subscribe_options, Why} ->
+           Txt = pubsub_subscribe_options:format_error(Why),
+           {error, xmpp:err_resource_constraint(Txt, Lang)}
+    end.
 
--define(SET_ALIST_XOPT(Opt, Val, Vals),
-    case lists:member(Val, [atom_to_binary(V, latin1) || V <- Vals]) of
-       true ->
-           set_xoption(Host, Opts, add_opt(Opt, jlib:binary_to_atom(Val), NewOpts));
-       false ->
-           Txt = <<"Incorrect value of '~s'">>,
-           ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-           {error, xmpp:err_not_acceptable(ErrTxt, ?MYLANG)}
-    end).
-
--define(SET_LIST_XOPT(Opt, Val),
-       set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
-
--spec set_xoption(host(), [{binary(), [binary()]}], [{atom(), any()}]) -> [{atom(), any()}].
-set_xoption(_Host, [], NewOpts) -> NewOpts;
-set_xoption(Host, [{<<"FORM_TYPE">>, _} | Opts], NewOpts) ->
-    set_xoption(Host, Opts, NewOpts);
-set_xoption(Host, [{<<"pubsub#roster_groups_allowed">>, Value} | Opts], NewOpts) ->
-    ?SET_LIST_XOPT(roster_groups_allowed, Value);
-set_xoption(Host, [{<<"pubsub#deliver_payloads">>, [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(deliver_payloads, Val);
-set_xoption(Host, [{<<"pubsub#deliver_notifications">>, [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(deliver_notifications, Val);
-set_xoption(Host, [{<<"pubsub#notify_config">>, [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(notify_config, Val);
-set_xoption(Host, [{<<"pubsub#notify_delete">>, [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(notify_delete, Val);
-set_xoption(Host, [{<<"pubsub#notify_retract">>, [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(notify_retract, Val);
-set_xoption(Host, [{<<"pubsub#persist_items">>, [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(persist_items, Val);
-set_xoption(Host, [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) ->
-    MaxItems = get_max_items_node(Host),
-    ?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems);
-set_xoption(Host, [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(subscribe, Val);
-set_xoption(Host, [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) ->
-    ?SET_ALIST_XOPT(access_model, Val, [open, authorize, presence, roster, whitelist]);
-set_xoption(Host, [{<<"pubsub#publish_model">>, [Val]} | Opts], NewOpts) ->
-    ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]);
-set_xoption(Host, [{<<"pubsub#notification_type">>, [Val]} | Opts], NewOpts) ->
-    ?SET_ALIST_XOPT(notification_type, Val, [headline, normal]);
-set_xoption(Host, [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) ->
-    ?SET_ALIST_XOPT(node_type, Val, [leaf, collection]);
-set_xoption(Host, [{<<"pubsub#max_payload_size">>, [Val]} | Opts], NewOpts) ->
-    ?SET_INTEGER_XOPT(max_payload_size, Val, 0, (?MAX_PAYLOAD_SIZE));
-set_xoption(Host, [{<<"pubsub#send_last_published_item">>, [Val]} | Opts], NewOpts) ->
-    ?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]);
-set_xoption(Host, [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(presence_based_delivery, Val);
-set_xoption(Host, [{<<"pubsub#purge_offline">>, [Val]} | Opts], NewOpts) ->
-    ?SET_BOOL_XOPT(purge_offline, Val);
-set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts], NewOpts) ->
-    ?SET_STRING_XOPT(title, Value);
-set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts], NewOpts) ->
-    ?SET_STRING_XOPT(type, Value);
-set_xoption(Host, [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) ->
-    ?SET_STRING_XOPT(body_xslt, Value);
-set_xoption(Host, [{<<"pubsub#collection">>, Value} | Opts], NewOpts) ->
-    %    NewValue = [string_to_node(V) || V <- Value],
-    ?SET_LIST_XOPT(collection, Value);
-set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], NewOpts) ->
-    %    NewValue = string_to_node(Value),
-    ?SET_LIST_XOPT(node, Value);
-set_xoption(Host, [{<<"pubsub#itemreply">>, [Val]} | Opts], NewOpts) ->
-    ?SET_ALIST_XOPT(itemreply, Val, [none, owner, publisher]);
-set_xoption(Host, [_ | Opts], NewOpts) ->
-    set_xoption(Host, Opts, NewOpts).
-
--spec get_xdata_fields(undefined | xdata()) -> [{binary(), [binary()]}].
-get_xdata_fields(undefined) ->
+-spec decode_publish_options(undefined | xdata(), binary()) ->
+                                   pubsub_publish_options:result() |
+                                   {error, stanza_error()}.
+decode_publish_options(undefined, _) ->
     [];
-get_xdata_fields(#xdata{fields = Fs}) ->
-    [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- Fs].
+decode_publish_options(#xdata{fields = Fs}, Lang) ->
+    try pubsub_publish_options:decode(Fs)
+    catch _:{pubsub_publish_options, Why} ->
+           Txt = pubsub_publish_options:format_error(Why),
+           {error, xmpp:err_resource_constraint(Txt, Lang)}
+    end.
+
+-spec decode_get_pending(xdata(), binary()) ->
+                               pubsub_get_pending:result() |
+                               {error, stanza_error()}.
+decode_get_pending(#xdata{fields = Fs}, Lang) ->
+    try pubsub_get_pending:decode(Fs)
+    catch _:{pubsub_get_pending, Why} ->
+           Txt = pubsub_get_pending:format_error(Why),
+           {error, xmpp:err_resource_constraint(Txt, Lang)}
+    end;
+decode_get_pending(undefined, Lang) ->
+    {error, xmpp:err_bad_request(<<"No data form found">>, Lang)}.
+
+-spec check_opt_range(atom(), [proplists:property()], non_neg_integer()) -> boolean().
+check_opt_range(Opt, Opts, Max) ->
+    Val = proplists:get_value(Opt, Opts, Max),
+    Val =< Max.
 
 -spec get_max_items_node(host()) -> undefined | non_neg_integer().
 get_max_items_node(Host) ->
diff --git a/src/muc_register.erl b/src/muc_register.erl
new file mode 100644 (file)
index 0000000..cddce2b
--- /dev/null
@@ -0,0 +1,364 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: muc_register.xdata
+%% Form type: http://jabber.org/protocol/muc#register
+%% Document: XEP-0045
+
+-module(muc_register).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("muc_register.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+dec_bool(<<"1">>) -> true;
+dec_bool(<<"0">>) -> false;
+dec_bool(<<"true">>) -> true;
+dec_bool(<<"false">>) -> false.
+
+enc_bool(true) -> <<"1">>;
+enc_bool(false) -> <<"0">>.
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, [<<"muc#register_roomnick">>]);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/muc#register">>]} ->
+         decode(Fs, Acc, [<<"muc#register_roomnick">>]);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/muc#register">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {allow, Val} -> [encode_allow(Val, Translate)];
+           {allow, _, _} -> erlang:error({badarg, Opt});
+           {email, Val} -> [encode_email(Val, Translate)];
+           {email, _, _} -> erlang:error({badarg, Opt});
+           {faqentry, Val} -> [encode_faqentry(Val, Translate)];
+           {faqentry, _, _} -> erlang:error({badarg, Opt});
+           {first, Val} -> [encode_first(Val, Translate)];
+           {first, _, _} -> erlang:error({badarg, Opt});
+           {last, Val} -> [encode_last(Val, Translate)];
+           {last, _, _} -> erlang:error({badarg, Opt});
+           {roomnick, Val} -> [encode_roomnick(Val, Translate)];
+           {roomnick, _, _} -> erlang:error({badarg, Opt});
+           {url, Val} -> [encode_url(Val, Translate)];
+           {url, _, _} -> erlang:error({badarg, Opt});
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/muc#register">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"muc#register_allow">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{allow, Result} | Acc],
+                lists:delete(<<"muc#register_allow">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#register_allow">>,
+                        <<"http://jabber.org/protocol/muc#register">>}})
+    end;
+decode([#xdata_field{var = <<"muc#register_allow">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#register_allow">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#register_allow">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#register_allow">>,
+                  <<"http://jabber.org/protocol/muc#register">>}});
+decode([#xdata_field{var = <<"muc#register_email">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{email, Result} | Acc],
+                lists:delete(<<"muc#register_email">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#register_email">>,
+                        <<"http://jabber.org/protocol/muc#register">>}})
+    end;
+decode([#xdata_field{var = <<"muc#register_email">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#register_email">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#register_email">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#register_email">>,
+                  <<"http://jabber.org/protocol/muc#register">>}});
+decode([#xdata_field{var = <<"muc#register_faqentry">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [Value || Value <- Values] of
+      Result ->
+         decode(Fs, [{faqentry, Result} | Acc],
+                lists:delete(<<"muc#register_faqentry">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#register_faqentry">>,
+                        <<"http://jabber.org/protocol/muc#register">>}})
+    end;
+decode([#xdata_field{var = <<"muc#register_first">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{first, Result} | Acc],
+                lists:delete(<<"muc#register_first">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#register_first">>,
+                        <<"http://jabber.org/protocol/muc#register">>}})
+    end;
+decode([#xdata_field{var = <<"muc#register_first">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#register_first">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#register_first">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#register_first">>,
+                  <<"http://jabber.org/protocol/muc#register">>}});
+decode([#xdata_field{var = <<"muc#register_last">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{last, Result} | Acc],
+                lists:delete(<<"muc#register_last">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#register_last">>,
+                        <<"http://jabber.org/protocol/muc#register">>}})
+    end;
+decode([#xdata_field{var = <<"muc#register_last">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#register_last">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#register_last">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#register_last">>,
+                  <<"http://jabber.org/protocol/muc#register">>}});
+decode([#xdata_field{var = <<"muc#register_roomnick">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{roomnick, Result} | Acc],
+                lists:delete(<<"muc#register_roomnick">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#register_roomnick">>,
+                        <<"http://jabber.org/protocol/muc#register">>}})
+    end;
+decode([#xdata_field{var = <<"muc#register_roomnick">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#register_roomnick">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#register_roomnick">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#register_roomnick">>,
+                  <<"http://jabber.org/protocol/muc#register">>}});
+decode([#xdata_field{var = <<"muc#register_url">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{url, Result} | Acc],
+                lists:delete(<<"muc#register_url">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#register_url">>,
+                        <<"http://jabber.org/protocol/muc#register">>}})
+    end;
+decode([#xdata_field{var = <<"muc#register_url">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#register_url">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#register_url">>} | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#register_url">>,
+                  <<"http://jabber.org/protocol/muc#register">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/muc#register">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/muc#register">>}});
+decode([], Acc, []) -> Acc.
+
+encode_allow(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#register_allow">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Allow this person to register with the "
+                                "room?">>)}.
+
+encode_email(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#register_email">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Email Address">>)}.
+
+encode_faqentry(Value, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#register_faqentry">>,
+                values = Values, required = false, type = 'text-multi',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"FAQ Entry">>)}.
+
+encode_first(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#register_first">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Given Name">>)}.
+
+encode_last(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#register_last">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Family Name">>)}.
+
+encode_roomnick(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#register_roomnick">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Nickname">>)}.
+
+encode_url(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#register_url">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"A Web Page">>)}.
diff --git a/src/muc_request.erl b/src/muc_request.erl
new file mode 100644 (file)
index 0000000..4c7becd
--- /dev/null
@@ -0,0 +1,269 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: muc_request.xdata
+%% Form type: http://jabber.org/protocol/muc#request
+%% Document: XEP-0045
+
+-module(muc_request).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("muc_request.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+dec_enum(Val, Enums) ->
+    AtomVal = erlang:binary_to_existing_atom(Val, utf8),
+    case lists:member(AtomVal, Enums) of
+      true -> AtomVal
+    end.
+
+enc_enum(Atom) -> erlang:atom_to_binary(Atom, utf8).
+
+dec_bool(<<"1">>) -> true;
+dec_bool(<<"0">>) -> false;
+dec_bool(<<"true">>) -> true;
+dec_bool(<<"false">>) -> false.
+
+enc_bool(true) -> <<"1">>;
+enc_bool(false) -> <<"0">>.
+
+enc_jid(J) -> jid:to_string(J).
+
+dec_jid(Val) ->
+    case jid:from_string(Val) of
+      error -> erlang:error(badarg);
+      J -> J
+    end.
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, [<<"muc#role">>]);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/muc#request">>]} ->
+         decode(Fs, Acc, [<<"muc#role">>]);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/muc#request">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {role, Val} -> [encode_role(Val, default, Translate)];
+           {role, Val, Opts} ->
+               [encode_role(Val, Opts, Translate)];
+           {jid, Val} -> [encode_jid(Val, Translate)];
+           {jid, _, _} -> erlang:error({badarg, Opt});
+           {roomnick, Val} -> [encode_roomnick(Val, Translate)];
+           {roomnick, _, _} -> erlang:error({badarg, Opt});
+           {request_allow, Val} ->
+               [encode_request_allow(Val, Translate)];
+           {request_allow, _, _} -> erlang:error({badarg, Opt});
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/muc#request">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"muc#role">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, [participant]) of
+      Result ->
+         decode(Fs, [{role, Result} | Acc],
+                lists:delete(<<"muc#role">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#role">>,
+                        <<"http://jabber.org/protocol/muc#request">>}})
+    end;
+decode([#xdata_field{var = <<"muc#role">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#role">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#role">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#role">>,
+                  <<"http://jabber.org/protocol/muc#request">>}});
+decode([#xdata_field{var = <<"muc#jid">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_jid(Value) of
+      Result ->
+         decode(Fs, [{jid, Result} | Acc],
+                lists:delete(<<"muc#jid">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#jid">>,
+                        <<"http://jabber.org/protocol/muc#request">>}})
+    end;
+decode([#xdata_field{var = <<"muc#jid">>, values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#jid">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#jid">>} | _], _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#jid">>,
+                  <<"http://jabber.org/protocol/muc#request">>}});
+decode([#xdata_field{var = <<"muc#roomnick">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{roomnick, Result} | Acc],
+                lists:delete(<<"muc#roomnick">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomnick">>,
+                        <<"http://jabber.org/protocol/muc#request">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roomnick">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#roomnick">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roomnick">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomnick">>,
+                  <<"http://jabber.org/protocol/muc#request">>}});
+decode([#xdata_field{var = <<"muc#request_allow">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{request_allow, Result} | Acc],
+                lists:delete(<<"muc#request_allow">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#request_allow">>,
+                        <<"http://jabber.org/protocol/muc#request">>}})
+    end;
+decode([#xdata_field{var = <<"muc#request_allow">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#request_allow">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#request_allow">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#request_allow">>,
+                  <<"http://jabber.org/protocol/muc#request">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/muc#request">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/muc#request">>}});
+decode([], Acc, []) -> Acc.
+
+encode_role(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label = Translate(<<"Participant">>),
+                                value = <<"participant">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"muc#role">>, values = Values,
+                required = false, type = 'list-single', options = Opts,
+                desc = <<>>, label = Translate(<<"Requested role">>)}.
+
+encode_jid(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_jid(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#jid">>, values = Values,
+                required = false, type = 'jid-single', options = Opts,
+                desc = <<>>, label = Translate(<<"User JID">>)}.
+
+encode_roomnick(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomnick">>, values = Values,
+                required = false, type = 'text-single', options = Opts,
+                desc = <<>>, label = Translate(<<"Nickname">>)}.
+
+encode_request_allow(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#request_allow">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Grant voice to this person?">>)}.
diff --git a/src/muc_roomconfig.erl b/src/muc_roomconfig.erl
new file mode 100644 (file)
index 0000000..73ceb64
--- /dev/null
@@ -0,0 +1,1675 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: muc_roomconfig.xdata
+%% Form type: http://jabber.org/protocol/muc#roomconfig
+%% Document: XEP-0045
+
+-module(muc_roomconfig).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("muc_roomconfig.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+dec_int(Val, Min, Max) ->
+    case list_to_integer(binary_to_list(Val)) of
+      Int when Int =< Max, Min == infinity -> Int;
+      Int when Int =< Max, Int >= Min -> Int
+    end.
+
+enc_int(Int) -> integer_to_binary(Int).
+
+dec_enum(Val, Enums) ->
+    AtomVal = erlang:binary_to_existing_atom(Val, utf8),
+    case lists:member(AtomVal, Enums) of
+      true -> AtomVal
+    end.
+
+enc_enum(Atom) -> erlang:atom_to_binary(Atom, utf8).
+
+dec_enum_int(Val, Enums, Min, Max) ->
+    try dec_int(Val, Min, Max) catch
+      _:_ -> dec_enum(Val, Enums)
+    end.
+
+enc_enum_int(Int) when is_integer(Int) -> enc_int(Int);
+enc_enum_int(Atom) -> enc_enum(Atom).
+
+dec_bool(<<"1">>) -> true;
+dec_bool(<<"0">>) -> false;
+dec_bool(<<"true">>) -> true;
+dec_bool(<<"false">>) -> false.
+
+enc_bool(true) -> <<"1">>;
+enc_bool(false) -> <<"0">>.
+
+enc_jid(J) -> jid:to_string(J).
+
+dec_jid(Val) ->
+    case jid:from_string(Val) of
+      error -> erlang:error(badarg);
+      J -> J
+    end.
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, []);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/muc#roomconfig">>]} ->
+         decode(Fs, Acc, []);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {maxhistoryfetch, Val} ->
+               [encode_maxhistoryfetch(Val, Translate)];
+           {maxhistoryfetch, _, _} -> erlang:error({badarg, Opt});
+           {allowpm, Val} ->
+               [encode_allowpm(Val, default, Translate)];
+           {allowpm, Val, Opts} ->
+               [encode_allowpm(Val, Opts, Translate)];
+           {allow_private_messages, Val} ->
+               [encode_allow_private_messages(Val, Translate)];
+           {allow_private_messages, _, _} ->
+               erlang:error({badarg, Opt});
+           {allow_private_messages_from_visitors, Val} ->
+               [encode_allow_private_messages_from_visitors(Val,
+                                                            default,
+                                                            Translate)];
+           {allow_private_messages_from_visitors, Val, Opts} ->
+               [encode_allow_private_messages_from_visitors(Val, Opts,
+                                                            Translate)];
+           {allow_visitor_status, Val} ->
+               [encode_allow_visitor_status(Val, Translate)];
+           {allow_visitor_status, _, _} ->
+               erlang:error({badarg, Opt});
+           {allow_visitor_nickchange, Val} ->
+               [encode_allow_visitor_nickchange(Val, Translate)];
+           {allow_visitor_nickchange, _, _} ->
+               erlang:error({badarg, Opt});
+           {allow_voice_requests, Val} ->
+               [encode_allow_voice_requests(Val, Translate)];
+           {allow_voice_requests, _, _} ->
+               erlang:error({badarg, Opt});
+           {allow_subscription, Val} ->
+               [encode_allow_subscription(Val, Translate)];
+           {allow_subscription, _, _} ->
+               erlang:error({badarg, Opt});
+           {voice_request_min_interval, Val} ->
+               [encode_voice_request_min_interval(Val, Translate)];
+           {voice_request_min_interval, _, _} ->
+               erlang:error({badarg, Opt});
+           {captcha_protected, Val} ->
+               [encode_captcha_protected(Val, Translate)];
+           {captcha_protected, _, _} ->
+               erlang:error({badarg, Opt});
+           {captcha_whitelist, Val} ->
+               [encode_captcha_whitelist(Val, Translate)];
+           {captcha_whitelist, _, _} ->
+               erlang:error({badarg, Opt});
+           {allow_query_users, Val} ->
+               [encode_allow_query_users(Val, Translate)];
+           {allow_query_users, _, _} ->
+               erlang:error({badarg, Opt});
+           {allowinvites, Val} ->
+               [encode_allowinvites(Val, Translate)];
+           {allowinvites, _, _} -> erlang:error({badarg, Opt});
+           {changesubject, Val} ->
+               [encode_changesubject(Val, Translate)];
+           {changesubject, _, _} -> erlang:error({badarg, Opt});
+           {enablelogging, Val} ->
+               [encode_enablelogging(Val, Translate)];
+           {enablelogging, _, _} -> erlang:error({badarg, Opt});
+           {getmemberlist, Val} ->
+               [encode_getmemberlist(Val, default, Translate)];
+           {getmemberlist, Val, Opts} ->
+               [encode_getmemberlist(Val, Opts, Translate)];
+           {lang, Val} -> [encode_lang(Val, Translate)];
+           {lang, _, _} -> erlang:error({badarg, Opt});
+           {pubsub, Val} -> [encode_pubsub(Val, Translate)];
+           {pubsub, _, _} -> erlang:error({badarg, Opt});
+           {maxusers, Val} ->
+               [encode_maxusers(Val, default, Translate)];
+           {maxusers, Val, Opts} ->
+               [encode_maxusers(Val, Opts, Translate)];
+           {membersonly, Val} ->
+               [encode_membersonly(Val, Translate)];
+           {membersonly, _, _} -> erlang:error({badarg, Opt});
+           {moderatedroom, Val} ->
+               [encode_moderatedroom(Val, Translate)];
+           {moderatedroom, _, _} -> erlang:error({badarg, Opt});
+           {members_by_default, Val} ->
+               [encode_members_by_default(Val, Translate)];
+           {members_by_default, _, _} ->
+               erlang:error({badarg, Opt});
+           {passwordprotectedroom, Val} ->
+               [encode_passwordprotectedroom(Val, Translate)];
+           {passwordprotectedroom, _, _} ->
+               erlang:error({badarg, Opt});
+           {persistentroom, Val} ->
+               [encode_persistentroom(Val, Translate)];
+           {persistentroom, _, _} -> erlang:error({badarg, Opt});
+           {presencebroadcast, Val} ->
+               [encode_presencebroadcast(Val, default, Translate)];
+           {presencebroadcast, Val, Opts} ->
+               [encode_presencebroadcast(Val, Opts, Translate)];
+           {publicroom, Val} ->
+               [encode_publicroom(Val, Translate)];
+           {publicroom, _, _} -> erlang:error({badarg, Opt});
+           {public_list, Val} ->
+               [encode_public_list(Val, Translate)];
+           {public_list, _, _} -> erlang:error({badarg, Opt});
+           {roomadmins, Val} ->
+               [encode_roomadmins(Val, Translate)];
+           {roomadmins, _, _} -> erlang:error({badarg, Opt});
+           {roomdesc, Val} -> [encode_roomdesc(Val, Translate)];
+           {roomdesc, _, _} -> erlang:error({badarg, Opt});
+           {roomname, Val} -> [encode_roomname(Val, Translate)];
+           {roomname, _, _} -> erlang:error({badarg, Opt});
+           {roomowners, Val} ->
+               [encode_roomowners(Val, Translate)];
+           {roomowners, _, _} -> erlang:error({badarg, Opt});
+           {roomsecret, Val} ->
+               [encode_roomsecret(Val, Translate)];
+           {roomsecret, _, _} -> erlang:error({badarg, Opt});
+           {whois, Val} -> [encode_whois(Val, default, Translate)];
+           {whois, Val, Opts} ->
+               [encode_whois(Val, Opts, Translate)];
+           {mam, Val} -> [encode_mam(Val, Translate)];
+           {mam, _, _} -> erlang:error({badarg, Opt});
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/muc#roomconfig">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"muc#maxhistoryfetch">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{maxhistoryfetch, Result} | Acc],
+                lists:delete(<<"muc#maxhistoryfetch">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#maxhistoryfetch">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"muc#maxhistoryfetch">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#maxhistoryfetch">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#maxhistoryfetch">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#maxhistoryfetch">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"muc#roomconfig_allowpm">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{allowpm, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_allowpm">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_allowpm">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roomconfig_allowpm">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_allowpm">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roomconfig_allowpm">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_allowpm">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"allow_private_messages">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{allow_private_messages, Result} | Acc],
+                lists:delete(<<"allow_private_messages">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"allow_private_messages">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"allow_private_messages">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"allow_private_messages">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"allow_private_messages">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"allow_private_messages">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"allow_private_messages_from_visitors">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, [nobody, moderators, anyone]) of
+      Result ->
+         decode(Fs,
+                [{allow_private_messages_from_visitors, Result} | Acc],
+                lists:delete(<<"allow_private_messages_from_visitors">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value,
+                        <<"allow_private_messages_from_visitors">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"allow_private_messages_from_visitors">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"allow_private_messages_from_visitors">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"allow_private_messages_from_visitors">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values,
+                  <<"allow_private_messages_from_visitors">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"allow_visitor_status">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{allow_visitor_status, Result} | Acc],
+                lists:delete(<<"allow_visitor_status">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"allow_visitor_status">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"allow_visitor_status">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"allow_visitor_status">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"allow_visitor_status">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"allow_visitor_status">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"allow_visitor_nickchange">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{allow_visitor_nickchange, Result} | Acc],
+                lists:delete(<<"allow_visitor_nickchange">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"allow_visitor_nickchange">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"allow_visitor_nickchange">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"allow_visitor_nickchange">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"allow_visitor_nickchange">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"allow_visitor_nickchange">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"allow_voice_requests">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{allow_voice_requests, Result} | Acc],
+                lists:delete(<<"allow_voice_requests">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"allow_voice_requests">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"allow_voice_requests">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"allow_voice_requests">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"allow_voice_requests">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"allow_voice_requests">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"allow_subscription">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{allow_subscription, Result} | Acc],
+                lists:delete(<<"allow_subscription">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"allow_subscription">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"allow_subscription">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"allow_subscription">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"allow_subscription">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"allow_subscription">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"voice_request_min_interval">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_int(Value, 0, infinity) of
+      Result ->
+         decode(Fs, [{voice_request_min_interval, Result} | Acc],
+                lists:delete(<<"voice_request_min_interval">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"voice_request_min_interval">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"voice_request_min_interval">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"voice_request_min_interval">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"voice_request_min_interval">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"voice_request_min_interval">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"captcha_protected">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{captcha_protected, Result} | Acc],
+                lists:delete(<<"captcha_protected">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"captcha_protected">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"captcha_protected">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"captcha_protected">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"captcha_protected">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"captcha_protected">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"captcha_whitelist">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [dec_jid(Value) || Value <- Values] of
+      Result ->
+         decode(Fs, [{captcha_whitelist, Result} | Acc],
+                lists:delete(<<"captcha_whitelist">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"captcha_whitelist">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"allow_query_users">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{allow_query_users, Result} | Acc],
+                lists:delete(<<"allow_query_users">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"allow_query_users">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"allow_query_users">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"allow_query_users">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"allow_query_users">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"allow_query_users">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_allowinvites">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{allowinvites, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_allowinvites">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_allowinvites">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_allowinvites">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_allowinvites">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_allowinvites">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_allowinvites">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_changesubject">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{changesubject, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_changesubject">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_changesubject">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_changesubject">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_changesubject">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_changesubject">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_changesubject">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_enablelogging">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{enablelogging, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_enablelogging">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_enablelogging">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_enablelogging">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_enablelogging">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_enablelogging">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_enablelogging">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_getmemberlist">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [Value || Value <- Values] of
+      Result ->
+         decode(Fs, [{getmemberlist, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_getmemberlist">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_getmemberlist">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roomconfig_lang">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{lang, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_lang">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_lang">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roomconfig_lang">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#roomconfig_lang">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roomconfig_lang">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_lang">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"muc#roomconfig_pubsub">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{pubsub, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_pubsub">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_pubsub">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roomconfig_pubsub">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#roomconfig_pubsub">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roomconfig_pubsub">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_pubsub">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_maxusers">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum_int(Value, [none], 0, infinity) of
+      Result ->
+         decode(Fs, [{maxusers, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_maxusers">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_maxusers">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_maxusers">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_maxusers">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_maxusers">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_maxusers">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_membersonly">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{membersonly, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_membersonly">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_membersonly">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_membersonly">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_membersonly">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_membersonly">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_membersonly">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_moderatedroom">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{moderatedroom, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_moderatedroom">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_moderatedroom">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_moderatedroom">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_moderatedroom">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_moderatedroom">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_moderatedroom">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"members_by_default">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{members_by_default, Result} | Acc],
+                lists:delete(<<"members_by_default">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"members_by_default">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"members_by_default">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"members_by_default">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"members_by_default">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"members_by_default">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_passwordprotectedroom">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{passwordprotectedroom, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_passwordprotectedroom">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value,
+                        <<"muc#roomconfig_passwordprotectedroom">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_passwordprotectedroom">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_passwordprotectedroom">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_passwordprotectedroom">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values,
+                  <<"muc#roomconfig_passwordprotectedroom">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_persistentroom">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{persistentroom, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_persistentroom">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_persistentroom">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_persistentroom">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_persistentroom">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_persistentroom">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_persistentroom">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_presencebroadcast">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [dec_enum(Value, [moderator, participant, visitor])
+        || Value <- Values]
+    of
+      Result ->
+         decode(Fs, [{presencebroadcast, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_presencebroadcast">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_presencebroadcast">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_publicroom">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{publicroom, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_publicroom">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_publicroom">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_publicroom">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_publicroom">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_publicroom">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_publicroom">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"public_list">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{public_list, Result} | Acc],
+                lists:delete(<<"public_list">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"public_list">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"public_list">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"public_list">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"public_list">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"public_list">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomadmins">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [dec_jid(Value) || Value <- Values] of
+      Result ->
+         decode(Fs, [{roomadmins, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_roomadmins">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_roomadmins">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomdesc">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{roomdesc, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_roomdesc">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_roomdesc">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomdesc">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_roomdesc">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomdesc">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_roomdesc">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomname">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{roomname, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_roomname">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_roomname">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomname">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_roomname">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomname">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_roomname">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomowners">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [dec_jid(Value) || Value <- Values] of
+      Result ->
+         decode(Fs, [{roomowners, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_roomowners">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_roomowners">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomsecret">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{roomsecret, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_roomsecret">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_roomsecret">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomsecret">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roomconfig_roomsecret">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roomconfig_roomsecret">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_roomsecret">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"muc#roomconfig_whois">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, [moderators, anyone]) of
+      Result ->
+         decode(Fs, [{whois, Result} | Acc],
+                lists:delete(<<"muc#roomconfig_whois">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roomconfig_whois">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roomconfig_whois">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#roomconfig_whois">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roomconfig_whois">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roomconfig_whois">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = <<"mam">>, values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{mam, Result} | Acc],
+                lists:delete(<<"mam">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"mam">>,
+                        <<"http://jabber.org/protocol/muc#roomconfig">>}})
+    end;
+decode([#xdata_field{var = <<"mam">>, values = []} = F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"mam">>, values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"mam">>} | _], _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"mam">>,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/muc#roomconfig">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/muc#roomconfig">>}});
+decode([], Acc, []) -> Acc.
+
+encode_maxhistoryfetch(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#maxhistoryfetch">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Maximum Number of History Messages Returned "
+                                "by Room">>)}.
+
+encode_allowpm(Value, Options, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = if Options == default -> [];
+             true ->
+                 [#xdata_option{label = Translate(L), value = V}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"muc#roomconfig_allowpm">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Roles that May Send Private Messages">>)}.
+
+encode_allow_private_messages(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"allow_private_messages">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Allow users to send private messages">>)}.
+
+encode_allow_private_messages_from_visitors(Value,
+                                           Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label = Translate(<<"nobody">>),
+                                value = <<"nobody">>},
+                  #xdata_option{label = Translate(<<"moderators only">>),
+                                value = <<"moderators">>},
+                  #xdata_option{label = Translate(<<"anyone">>),
+                                value = <<"anyone">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var =
+                    <<"allow_private_messages_from_visitors">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Allow visitors to send private messages to">>)}.
+
+encode_allow_visitor_status(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"allow_visitor_status">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Allow visitors to send status text in "
+                                "presence updates">>)}.
+
+encode_allow_visitor_nickchange(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"allow_visitor_nickchange">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Allow visitors to change nickname">>)}.
+
+encode_allow_voice_requests(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"allow_voice_requests">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Allow visitors to send voice requests">>)}.
+
+encode_allow_subscription(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"allow_subscription">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Allow subscription">>)}.
+
+encode_voice_request_min_interval(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_int(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"voice_request_min_interval">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Minimum interval between voice requests "
+                                "(in seconds)">>)}.
+
+encode_captcha_protected(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"captcha_protected">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Make room CAPTCHA protected">>)}.
+
+encode_captcha_whitelist(Value, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [enc_jid(V) || V <- Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"captcha_whitelist">>,
+                values = Values, required = false, type = 'jid-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Exclude Jabber IDs from CAPTCHA challenge">>)}.
+
+encode_allow_query_users(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"allow_query_users">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Allow users to query other users">>)}.
+
+encode_allowinvites(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_allowinvites">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Allow users to send invites">>)}.
+
+encode_changesubject(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_changesubject">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Allow users to change the subject">>)}.
+
+encode_enablelogging(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_enablelogging">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Enable logging">>)}.
+
+encode_getmemberlist(Value, Options, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [Value]
+            end,
+    Opts = if Options == default -> [];
+             true ->
+                 [#xdata_option{label = Translate(L), value = V}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"muc#roomconfig_getmemberlist">>,
+                values = Values, required = false, type = 'list-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Roles and Affiliations that May Retrieve "
+                                "Member List">>)}.
+
+encode_lang(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_lang">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Natural Language for Room Discussions">>)}.
+
+encode_pubsub(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_pubsub">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"XMPP URI of Associated Publish-Subscribe "
+                                "Node">>)}.
+
+encode_maxusers(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum_int(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label = Translate(<<"No limit">>),
+                                value = <<"none">>},
+                  #xdata_option{value = <<"5">>},
+                  #xdata_option{value = <<"10">>},
+                  #xdata_option{value = <<"20">>},
+                  #xdata_option{value = <<"30">>},
+                  #xdata_option{value = <<"50">>},
+                  #xdata_option{value = <<"100">>},
+                  #xdata_option{value = <<"200">>},
+                  #xdata_option{value = <<"500">>},
+                  #xdata_option{value = <<"1000">>},
+                  #xdata_option{value = <<"2000">>},
+                  #xdata_option{value = <<"5000">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum_int(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"muc#roomconfig_maxusers">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Maximum Number of Occupants">>)}.
+
+encode_membersonly(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_membersonly">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Make room members-only">>)}.
+
+encode_moderatedroom(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_moderatedroom">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Make room moderated">>)}.
+
+encode_members_by_default(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"members_by_default">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Default users as participants">>)}.
+
+encode_passwordprotectedroom(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var =
+                    <<"muc#roomconfig_passwordprotectedroom">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Make room password protected">>)}.
+
+encode_persistentroom(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_persistentroom">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Make room persistent">>)}.
+
+encode_presencebroadcast(Value, Options, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [enc_enum(V) || V <- Value]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label = Translate(<<"Moderator">>),
+                                value = <<"moderator">>},
+                  #xdata_option{label = Translate(<<"Participant">>),
+                                value = <<"participant">>},
+                  #xdata_option{label = Translate(<<"Visitor">>),
+                                value = <<"visitor">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var =
+                    <<"muc#roomconfig_presencebroadcast">>,
+                values = Values, required = false, type = 'list-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Roles for which Presence is Broadcasted">>)}.
+
+encode_publicroom(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_publicroom">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Make room public searchable">>)}.
+
+encode_public_list(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"public_list">>, values = Values,
+                required = false, type = boolean, options = Opts,
+                desc = <<>>,
+                label = Translate(<<"Make participants list public">>)}.
+
+encode_roomadmins(Value, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [enc_jid(V) || V <- Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_roomadmins">>,
+                values = Values, required = false, type = 'jid-multi',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Full List of Room Admins">>)}.
+
+encode_roomdesc(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_roomdesc">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Room description">>)}.
+
+encode_roomname(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_roomname">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Room title">>)}.
+
+encode_roomowners(Value, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [enc_jid(V) || V <- Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_roomowners">>,
+                values = Values, required = false, type = 'jid-multi',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Full List of Room Owners">>)}.
+
+encode_roomsecret(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roomconfig_roomsecret">>,
+                values = Values, required = false,
+                type = 'text-private', options = Opts, desc = <<>>,
+                label = Translate(<<"Password">>)}.
+
+encode_whois(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label = Translate(<<"moderators only">>),
+                                value = <<"moderators">>},
+                  #xdata_option{label = Translate(<<"anyone">>),
+                                value = <<"anyone">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"muc#roomconfig_whois">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Present real Jabber IDs to">>)}.
+
+encode_mam(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"mam">>, values = Values,
+                required = false, type = boolean, options = Opts,
+                desc = <<>>,
+                label = Translate(<<"Enable message archiving">>)}.
diff --git a/src/muc_roominfo.erl b/src/muc_roominfo.erl
new file mode 100644 (file)
index 0000000..809dcef
--- /dev/null
@@ -0,0 +1,491 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: muc_roominfo.xdata
+%% Form type: http://jabber.org/protocol/muc#roominfo
+%% Document: XEP-0045
+
+-module(muc_roominfo).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("muc_roominfo.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+dec_int(Val, Min, Max) ->
+    case list_to_integer(binary_to_list(Val)) of
+      Int when Int =< Max, Min == infinity -> Int;
+      Int when Int =< Max, Int >= Min -> Int
+    end.
+
+enc_int(Int) -> integer_to_binary(Int).
+
+dec_bool(<<"1">>) -> true;
+dec_bool(<<"0">>) -> false;
+dec_bool(<<"true">>) -> true;
+dec_bool(<<"false">>) -> false.
+
+enc_bool(true) -> <<"1">>;
+enc_bool(false) -> <<"0">>.
+
+enc_jid(J) -> jid:to_string(J).
+
+dec_jid(Val) ->
+    case jid:from_string(Val) of
+      error -> erlang:error(badarg);
+      J -> J
+    end.
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, []);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/muc#roominfo">>]} ->
+         decode(Fs, Acc, []);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {maxhistoryfetch, Val} ->
+               [encode_maxhistoryfetch(Val, Translate)];
+           {maxhistoryfetch, _, _} -> erlang:error({badarg, Opt});
+           {contactjid, Val} ->
+               [encode_contactjid(Val, Translate)];
+           {contactjid, _, _} -> erlang:error({badarg, Opt});
+           {description, Val} ->
+               [encode_description(Val, Translate)];
+           {description, _, _} -> erlang:error({badarg, Opt});
+           {lang, Val} -> [encode_lang(Val, Translate)];
+           {lang, _, _} -> erlang:error({badarg, Opt});
+           {ldapgroup, Val} -> [encode_ldapgroup(Val, Translate)];
+           {ldapgroup, _, _} -> erlang:error({badarg, Opt});
+           {logs, Val} -> [encode_logs(Val, Translate)];
+           {logs, _, _} -> erlang:error({badarg, Opt});
+           {occupants, Val} -> [encode_occupants(Val, Translate)];
+           {occupants, _, _} -> erlang:error({badarg, Opt});
+           {subject, Val} -> [encode_subject(Val, Translate)];
+           {subject, _, _} -> erlang:error({badarg, Opt});
+           {subjectmod, Val} ->
+               [encode_subjectmod(Val, Translate)];
+           {subjectmod, _, _} -> erlang:error({badarg, Opt});
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/muc#roominfo">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"muc#maxhistoryfetch">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_int(Value, 0, infinity) of
+      Result ->
+         decode(Fs, [{maxhistoryfetch, Result} | Acc],
+                lists:delete(<<"muc#maxhistoryfetch">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#maxhistoryfetch">>,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end;
+decode([#xdata_field{var = <<"muc#maxhistoryfetch">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#maxhistoryfetch">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#maxhistoryfetch">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#maxhistoryfetch">>,
+                  <<"http://jabber.org/protocol/muc#roominfo">>}});
+decode([#xdata_field{var =
+                        <<"muc#roominfo_contactjid">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [dec_jid(Value) || Value <- Values] of
+      Result ->
+         decode(Fs, [{contactjid, Result} | Acc],
+                lists:delete(<<"muc#roominfo_contactjid">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roominfo_contactjid">>,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roominfo_description">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{description, Result} | Acc],
+                lists:delete(<<"muc#roominfo_description">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roominfo_description">>,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roominfo_description">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roominfo_description">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roominfo_description">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roominfo_description">>,
+                  <<"http://jabber.org/protocol/muc#roominfo">>}});
+decode([#xdata_field{var = <<"muc#roominfo_lang">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{lang, Result} | Acc],
+                lists:delete(<<"muc#roominfo_lang">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roominfo_lang">>,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roominfo_lang">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#roominfo_lang">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roominfo_lang">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roominfo_lang">>,
+                  <<"http://jabber.org/protocol/muc#roominfo">>}});
+decode([#xdata_field{var = <<"muc#roominfo_ldapgroup">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{ldapgroup, Result} | Acc],
+                lists:delete(<<"muc#roominfo_ldapgroup">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roominfo_ldapgroup">>,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roominfo_ldapgroup">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roominfo_ldapgroup">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roominfo_ldapgroup">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roominfo_ldapgroup">>,
+                  <<"http://jabber.org/protocol/muc#roominfo">>}});
+decode([#xdata_field{var = <<"muc#roominfo_logs">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{logs, Result} | Acc],
+                lists:delete(<<"muc#roominfo_logs">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roominfo_logs">>,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roominfo_logs">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#roominfo_logs">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roominfo_logs">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roominfo_logs">>,
+                  <<"http://jabber.org/protocol/muc#roominfo">>}});
+decode([#xdata_field{var = <<"muc#roominfo_occupants">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_int(Value, 0, infinity) of
+      Result ->
+         decode(Fs, [{occupants, Result} | Acc],
+                lists:delete(<<"muc#roominfo_occupants">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roominfo_occupants">>,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roominfo_occupants">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roominfo_occupants">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roominfo_occupants">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roominfo_occupants">>,
+                  <<"http://jabber.org/protocol/muc#roominfo">>}});
+decode([#xdata_field{var = <<"muc#roominfo_subject">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{subject, Result} | Acc],
+                lists:delete(<<"muc#roominfo_subject">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roominfo_subject">>,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end;
+decode([#xdata_field{var = <<"muc#roominfo_subject">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"muc#roominfo_subject">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"muc#roominfo_subject">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roominfo_subject">>,
+                  <<"http://jabber.org/protocol/muc#roominfo">>}});
+decode([#xdata_field{var =
+                        <<"muc#roominfo_subjectmod">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{subjectmod, Result} | Acc],
+                lists:delete(<<"muc#roominfo_subjectmod">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"muc#roominfo_subjectmod">>,
+                        <<"http://jabber.org/protocol/muc#roominfo">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"muc#roominfo_subjectmod">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"muc#roominfo_subjectmod">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"muc#roominfo_subjectmod">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"muc#roominfo_subjectmod">>,
+                  <<"http://jabber.org/protocol/muc#roominfo">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/muc#roominfo">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/muc#roominfo">>}});
+decode([], Acc, []) -> Acc.
+
+encode_maxhistoryfetch(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_int(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#maxhistoryfetch">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Maximum Number of History Messages Returned "
+                                "by Room">>)}.
+
+encode_contactjid(Value, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [enc_jid(V) || V <- Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roominfo_contactjid">>,
+                values = Values, required = false, type = 'jid-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Contact Addresses (normally, room owner "
+                                "or owners)">>)}.
+
+encode_description(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roominfo_description">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Room description">>)}.
+
+encode_lang(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roominfo_lang">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Natural Language for Room Discussions">>)}.
+
+encode_ldapgroup(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roominfo_ldapgroup">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"An associated LDAP group that defines "
+                                "room membership; this should be an LDAP "
+                                "Distinguished Name according to an implementa"
+                                "tion-specific or deployment-specific "
+                                "definition of a group.">>)}.
+
+encode_logs(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roominfo_logs">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"URL for Archived Discussion Logs">>)}.
+
+encode_occupants(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_int(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roominfo_occupants">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Number of occupants">>)}.
+
+encode_subject(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roominfo_subject">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Current Discussion Topic">>)}.
+
+encode_subjectmod(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"muc#roominfo_subjectmod">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The room subject can be modified by "
+                                "participants">>)}.
index e3170a2632bfb68c7b923e252dc309ba1c5135c3..3afa49f222c5f96fd592f56548c8f53da1f53ab5 100644 (file)
@@ -84,6 +84,7 @@ options() ->
        {max_payload_size, ?MAX_PAYLOAD_SIZE},
        {send_last_published_item, on_sub_and_presence},
        {deliver_notifications, true},
+        {title, <<>>},
        {presence_based_delivery, false},
        {itemreply, none}].
 
@@ -452,7 +453,7 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) ->
                                end,
                                {error, xmpp:err_item_not_found()}, States);
                        _ ->
-                           {error, xmpp:err_item_not_found()}
+                           {error, xmpp:err_forbidden()}
                    end
            end
     end.
index bd084333b46256595d60bd12a808fec0732fbce4..c1dfd0e81b4c332c47ce9e312f2e31977b93049c 100644 (file)
@@ -901,7 +901,7 @@ first_in_list(Pred, [H | T]) ->
 
 itemids(Nidx, {_U, _S, _R} = JID) ->
     SJID = encode_jid(JID),
-    SJIDLike = <<(ejabberd_sql:escape(encode_jid_like(JID)))/binary, "/%">>,
+    SJIDLike = <<(encode_jid_like(JID))/binary, "/%">>,
     case catch
        ejabberd_sql:sql_query_t(
           ?SQL("select @(itemid)s from pubsub_item where "
diff --git a/src/pubsub_get_pending.erl b/src/pubsub_get_pending.erl
new file mode 100644 (file)
index 0000000..1a7de6a
--- /dev/null
@@ -0,0 +1,130 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_get_pending.xdata
+%% Form type: http://jabber.org/protocol/pubsub#subscribe_authorization
+%% Document: XEP-0060
+
+-module(pubsub_get_pending).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("pubsub_get_pending.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, [<<"pubsub#node">>]);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/pubsub#subscribe_a"
+                         "uthorization">>]} ->
+         decode(Fs, Acc, [<<"pubsub#node">>]);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                          "uthorization">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {node, Val} -> [encode_node(Val, default, Translate)];
+           {node, Val, Opts} ->
+               [encode_node(Val, Opts, Translate)];
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/pubsub#subscribe_a"
+                                  "uthorization">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"pubsub#node">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{node, Result} | Acc],
+                lists:delete(<<"pubsub#node">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#node">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                          "uthorization">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#node">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#node">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#node">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#node">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                    "uthorization">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                           "uthorization">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                    "uthorization">>}});
+decode([], Acc, []) -> Acc.
+
+encode_node(Value, Options, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = if Options == default -> [];
+             true ->
+                 [#xdata_option{label = Translate(L), value = V}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#node">>, values = Values,
+                required = false, type = 'list-single', options = Opts,
+                desc = <<>>,
+                label =
+                    Translate(<<"The NodeID of the relevant node">>)}.
diff --git a/src/pubsub_node_config.erl b/src/pubsub_node_config.erl
new file mode 100644 (file)
index 0000000..47ed10b
--- /dev/null
@@ -0,0 +1,1666 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_node_config.xdata
+%% Form type: http://jabber.org/protocol/pubsub#node_config
+%% Document: XEP-0060
+
+-module(pubsub_node_config).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("pubsub_node_config.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+dec_int(Val, Min, Max) ->
+    case list_to_integer(binary_to_list(Val)) of
+      Int when Int =< Max, Min == infinity -> Int;
+      Int when Int =< Max, Int >= Min -> Int
+    end.
+
+enc_int(Int) -> integer_to_binary(Int).
+
+dec_enum(Val, Enums) ->
+    AtomVal = erlang:binary_to_existing_atom(Val, utf8),
+    case lists:member(AtomVal, Enums) of
+      true -> AtomVal
+    end.
+
+enc_enum(Atom) -> erlang:atom_to_binary(Atom, utf8).
+
+dec_bool(<<"1">>) -> true;
+dec_bool(<<"0">>) -> false;
+dec_bool(<<"true">>) -> true;
+dec_bool(<<"false">>) -> false.
+
+enc_bool(true) -> <<"1">>;
+enc_bool(false) -> <<"0">>.
+
+enc_jid(J) -> jid:to_string(J).
+
+dec_jid(Val) ->
+    case jid:from_string(Val) of
+      error -> erlang:error(badarg);
+      J -> J
+    end.
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, []);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/pubsub#node_config">>]} ->
+         decode(Fs, Acc, []);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {access_model, Val} ->
+               [encode_access_model(Val, default, Translate)];
+           {access_model, Val, Opts} ->
+               [encode_access_model(Val, Opts, Translate)];
+           {body_xslt, Val} -> [encode_body_xslt(Val, Translate)];
+           {body_xslt, _, _} -> erlang:error({badarg, Opt});
+           {children_association_policy, Val} ->
+               [encode_children_association_policy(Val, default,
+                                                   Translate)];
+           {children_association_policy, Val, Opts} ->
+               [encode_children_association_policy(Val, Opts,
+                                                   Translate)];
+           {children_association_whitelist, Val} ->
+               [encode_children_association_whitelist(Val, Translate)];
+           {children_association_whitelist, _, _} ->
+               erlang:error({badarg, Opt});
+           {children, Val} -> [encode_children(Val, Translate)];
+           {children, _, _} -> erlang:error({badarg, Opt});
+           {children_max, Val} ->
+               [encode_children_max(Val, Translate)];
+           {children_max, _, _} -> erlang:error({badarg, Opt});
+           {collection, Val} ->
+               [encode_collection(Val, Translate)];
+           {collection, _, _} -> erlang:error({badarg, Opt});
+           {contact, Val} -> [encode_contact(Val, Translate)];
+           {contact, _, _} -> erlang:error({badarg, Opt});
+           {dataform_xslt, Val} ->
+               [encode_dataform_xslt(Val, Translate)];
+           {dataform_xslt, _, _} -> erlang:error({badarg, Opt});
+           {deliver_notifications, Val} ->
+               [encode_deliver_notifications(Val, Translate)];
+           {deliver_notifications, _, _} ->
+               erlang:error({badarg, Opt});
+           {deliver_payloads, Val} ->
+               [encode_deliver_payloads(Val, Translate)];
+           {deliver_payloads, _, _} -> erlang:error({badarg, Opt});
+           {description, Val} ->
+               [encode_description(Val, Translate)];
+           {description, _, _} -> erlang:error({badarg, Opt});
+           {item_expire, Val} ->
+               [encode_item_expire(Val, Translate)];
+           {item_expire, _, _} -> erlang:error({badarg, Opt});
+           {itemreply, Val} ->
+               [encode_itemreply(Val, default, Translate)];
+           {itemreply, Val, Opts} ->
+               [encode_itemreply(Val, Opts, Translate)];
+           {language, Val} ->
+               [encode_language(Val, default, Translate)];
+           {language, Val, Opts} ->
+               [encode_language(Val, Opts, Translate)];
+           {max_items, Val} -> [encode_max_items(Val, Translate)];
+           {max_items, _, _} -> erlang:error({badarg, Opt});
+           {max_payload_size, Val} ->
+               [encode_max_payload_size(Val, Translate)];
+           {max_payload_size, _, _} -> erlang:error({badarg, Opt});
+           {node_type, Val} ->
+               [encode_node_type(Val, default, Translate)];
+           {node_type, Val, Opts} ->
+               [encode_node_type(Val, Opts, Translate)];
+           {notification_type, Val} ->
+               [encode_notification_type(Val, default, Translate)];
+           {notification_type, Val, Opts} ->
+               [encode_notification_type(Val, Opts, Translate)];
+           {notify_config, Val} ->
+               [encode_notify_config(Val, Translate)];
+           {notify_config, _, _} -> erlang:error({badarg, Opt});
+           {notify_delete, Val} ->
+               [encode_notify_delete(Val, Translate)];
+           {notify_delete, _, _} -> erlang:error({badarg, Opt});
+           {notify_retract, Val} ->
+               [encode_notify_retract(Val, Translate)];
+           {notify_retract, _, _} -> erlang:error({badarg, Opt});
+           {notify_sub, Val} ->
+               [encode_notify_sub(Val, Translate)];
+           {notify_sub, _, _} -> erlang:error({badarg, Opt});
+           {persist_items, Val} ->
+               [encode_persist_items(Val, Translate)];
+           {persist_items, _, _} -> erlang:error({badarg, Opt});
+           {presence_based_delivery, Val} ->
+               [encode_presence_based_delivery(Val, Translate)];
+           {presence_based_delivery, _, _} ->
+               erlang:error({badarg, Opt});
+           {publish_model, Val} ->
+               [encode_publish_model(Val, default, Translate)];
+           {publish_model, Val, Opts} ->
+               [encode_publish_model(Val, Opts, Translate)];
+           {purge_offline, Val} ->
+               [encode_purge_offline(Val, Translate)];
+           {purge_offline, _, _} -> erlang:error({badarg, Opt});
+           {roster_groups_allowed, Val} ->
+               [encode_roster_groups_allowed(Val, default, Translate)];
+           {roster_groups_allowed, Val, Opts} ->
+               [encode_roster_groups_allowed(Val, Opts, Translate)];
+           {send_last_published_item, Val} ->
+               [encode_send_last_published_item(Val, default,
+                                                Translate)];
+           {send_last_published_item, Val, Opts} ->
+               [encode_send_last_published_item(Val, Opts, Translate)];
+           {tempsub, Val} -> [encode_tempsub(Val, Translate)];
+           {tempsub, _, _} -> erlang:error({badarg, Opt});
+           {subscribe, Val} -> [encode_subscribe(Val, Translate)];
+           {subscribe, _, _} -> erlang:error({badarg, Opt});
+           {title, Val} -> [encode_title(Val, Translate)];
+           {title, _, _} -> erlang:error({badarg, Opt});
+           {type, Val} -> [encode_type(Val, Translate)];
+           {type, _, _} -> erlang:error({badarg, Opt});
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/pubsub#node_config">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"pubsub#access_model">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value,
+                [authorize, open, presence, roster, whitelist])
+    of
+      Result ->
+         decode(Fs, [{access_model, Result} | Acc],
+                lists:delete(<<"pubsub#access_model">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#access_model">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#access_model">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#access_model">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#access_model">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#access_model">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#body_xslt">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{body_xslt, Result} | Acc],
+                lists:delete(<<"pubsub#body_xslt">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#body_xslt">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#body_xslt">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#body_xslt">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#body_xslt">>} | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#body_xslt">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#children_association_policy">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, [all, owners, whitelist]) of
+      Result ->
+         decode(Fs,
+                [{children_association_policy, Result} | Acc],
+                lists:delete(<<"pubsub#children_association_policy">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value,
+                        <<"pubsub#children_association_policy">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#children_association_policy">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#children_association_policy">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#children_association_policy">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values,
+                  <<"pubsub#children_association_policy">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#children_association_whitelist">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [dec_jid(Value) || Value <- Values] of
+      Result ->
+         decode(Fs,
+                [{children_association_whitelist, Result} | Acc],
+                lists:delete(<<"pubsub#children_association_whitelist">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value,
+                        <<"pubsub#children_association_whitelist">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#children">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [Value || Value <- Values] of
+      Result ->
+         decode(Fs, [{children, Result} | Acc],
+                lists:delete(<<"pubsub#children">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#children">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#children_max">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{children_max, Result} | Acc],
+                lists:delete(<<"pubsub#children_max">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#children_max">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#children_max">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#children_max">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#children_max">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#children_max">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#collection">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [Value || Value <- Values] of
+      Result ->
+         decode(Fs, [{collection, Result} | Acc],
+                lists:delete(<<"pubsub#collection">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#collection">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#contact">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [dec_jid(Value) || Value <- Values] of
+      Result ->
+         decode(Fs, [{contact, Result} | Acc],
+                lists:delete(<<"pubsub#contact">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#contact">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#dataform_xslt">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{dataform_xslt, Result} | Acc],
+                lists:delete(<<"pubsub#dataform_xslt">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#dataform_xslt">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#dataform_xslt">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#dataform_xslt">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#dataform_xslt">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#dataform_xslt">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#deliver_notifications">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{deliver_notifications, Result} | Acc],
+                lists:delete(<<"pubsub#deliver_notifications">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#deliver_notifications">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#deliver_notifications">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#deliver_notifications">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#deliver_notifications">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#deliver_notifications">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#deliver_payloads">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{deliver_payloads, Result} | Acc],
+                lists:delete(<<"pubsub#deliver_payloads">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#deliver_payloads">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#deliver_payloads">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#deliver_payloads">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#deliver_payloads">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#deliver_payloads">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#description">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{description, Result} | Acc],
+                lists:delete(<<"pubsub#description">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#description">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#description">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#description">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#description">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#description">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#item_expire">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{item_expire, Result} | Acc],
+                lists:delete(<<"pubsub#item_expire">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#item_expire">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#item_expire">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#item_expire">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#item_expire">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#item_expire">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#itemreply">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, [owner, publisher, none]) of
+      Result ->
+         decode(Fs, [{itemreply, Result} | Acc],
+                lists:delete(<<"pubsub#itemreply">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#itemreply">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#itemreply">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#itemreply">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#itemreply">>} | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#itemreply">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#language">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{language, Result} | Acc],
+                lists:delete(<<"pubsub#language">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#language">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#language">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#language">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#language">>} | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#language">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#max_items">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_int(Value, 0, infinity) of
+      Result ->
+         decode(Fs, [{max_items, Result} | Acc],
+                lists:delete(<<"pubsub#max_items">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#max_items">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#max_items">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#max_items">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#max_items">>} | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#max_items">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#max_payload_size">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_int(Value, 0, infinity) of
+      Result ->
+         decode(Fs, [{max_payload_size, Result} | Acc],
+                lists:delete(<<"pubsub#max_payload_size">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#max_payload_size">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#max_payload_size">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#max_payload_size">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#max_payload_size">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#max_payload_size">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#node_type">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, [leaf, collection]) of
+      Result ->
+         decode(Fs, [{node_type, Result} | Acc],
+                lists:delete(<<"pubsub#node_type">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#node_type">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#node_type">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#node_type">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#node_type">>} | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#node_type">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#notification_type">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, [normal, headline]) of
+      Result ->
+         decode(Fs, [{notification_type, Result} | Acc],
+                lists:delete(<<"pubsub#notification_type">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#notification_type">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#notification_type">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#notification_type">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#notification_type">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#notification_type">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#notify_config">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{notify_config, Result} | Acc],
+                lists:delete(<<"pubsub#notify_config">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#notify_config">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#notify_config">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#notify_config">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#notify_config">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#notify_config">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#notify_delete">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{notify_delete, Result} | Acc],
+                lists:delete(<<"pubsub#notify_delete">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#notify_delete">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#notify_delete">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#notify_delete">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#notify_delete">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#notify_delete">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#notify_retract">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{notify_retract, Result} | Acc],
+                lists:delete(<<"pubsub#notify_retract">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#notify_retract">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#notify_retract">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#notify_retract">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#notify_retract">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#notify_retract">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#notify_sub">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{notify_sub, Result} | Acc],
+                lists:delete(<<"pubsub#notify_sub">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#notify_sub">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#notify_sub">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#notify_sub">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#notify_sub">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#notify_sub">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#persist_items">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{persist_items, Result} | Acc],
+                lists:delete(<<"pubsub#persist_items">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#persist_items">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#persist_items">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#persist_items">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#persist_items">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#persist_items">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#presence_based_delivery">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{presence_based_delivery, Result} | Acc],
+                lists:delete(<<"pubsub#presence_based_delivery">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#presence_based_delivery">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#presence_based_delivery">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#presence_based_delivery">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#presence_based_delivery">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#presence_based_delivery">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#publish_model">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, [publishers, subscribers, open]) of
+      Result ->
+         decode(Fs, [{publish_model, Result} | Acc],
+                lists:delete(<<"pubsub#publish_model">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#publish_model">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#publish_model">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#publish_model">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#publish_model">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#publish_model">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#purge_offline">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{purge_offline, Result} | Acc],
+                lists:delete(<<"pubsub#purge_offline">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#purge_offline">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#purge_offline">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#purge_offline">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#purge_offline">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#purge_offline">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#roster_groups_allowed">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [Value || Value <- Values] of
+      Result ->
+         decode(Fs, [{roster_groups_allowed, Result} | Acc],
+                lists:delete(<<"pubsub#roster_groups_allowed">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#roster_groups_allowed">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#send_last_published_item">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value,
+                [never, on_sub, on_sub_and_presence])
+    of
+      Result ->
+         decode(Fs, [{send_last_published_item, Result} | Acc],
+                lists:delete(<<"pubsub#send_last_published_item">>,
+                             Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#send_last_published_item">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#send_last_published_item">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#send_last_published_item">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#send_last_published_item">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#send_last_published_item">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#tempsub">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{tempsub, Result} | Acc],
+                lists:delete(<<"pubsub#tempsub">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#tempsub">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#tempsub">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#tempsub">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#tempsub">>} | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#tempsub">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#subscribe">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{subscribe, Result} | Acc],
+                lists:delete(<<"pubsub#subscribe">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#subscribe">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#subscribe">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#subscribe">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#subscribe">>} | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#subscribe">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#title">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{title, Result} | Acc],
+                lists:delete(<<"pubsub#title">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#title">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#title">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#title">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#title">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#title">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = <<"pubsub#type">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{type, Result} | Acc],
+                lists:delete(<<"pubsub#type">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#type">>,
+                        <<"http://jabber.org/protocol/pubsub#node_config">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#type">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#type">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#type">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#type">>,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/pubsub#node_config">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/pubsub#node_config">>}});
+decode([], Acc, []) -> Acc.
+
+encode_access_model(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"Subscription requests must be approved "
+                                                "and only subscribers may retrieve items">>),
+                                value = <<"authorize">>},
+                  #xdata_option{label =
+                                    Translate(<<"Anyone may subscribe and retrieve items">>),
+                                value = <<"open">>},
+                  #xdata_option{label =
+                                    Translate(<<"Anyone with a presence subscription "
+                                                "of both or from may subscribe and retrieve "
+                                                "items">>),
+                                value = <<"presence">>},
+                  #xdata_option{label =
+                                    Translate(<<"Anyone in the specified roster group(s) "
+                                                "may subscribe and retrieve items">>),
+                                value = <<"roster">>},
+                  #xdata_option{label =
+                                    Translate(<<"Only those on a whitelist may subscribe "
+                                                "and retrieve items">>),
+                                value = <<"whitelist">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#access_model">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Specify the access model">>)}.
+
+encode_body_xslt(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#body_xslt">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The URL of an XSL transformation which "
+                                "can be applied to payloads in order "
+                                "to generate an appropriate message body "
+                                "element.">>)}.
+
+encode_children_association_policy(Value, Options,
+                                  Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"Anyone may associate leaf nodes with "
+                                                "the collection">>),
+                                value = <<"all">>},
+                  #xdata_option{label =
+                                    Translate(<<"Only collection node owners may associate "
+                                                "leaf nodes with the collection">>),
+                                value = <<"owners">>},
+                  #xdata_option{label =
+                                    Translate(<<"Only those on a whitelist may associate "
+                                                "leaf nodes with the collection">>),
+                                value = <<"whitelist">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var =
+                    <<"pubsub#children_association_policy">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Who may associate leaf nodes with a "
+                                "collection">>)}.
+
+encode_children_association_whitelist(Value,
+                                     Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [enc_jid(V) || V <- Value]
+            end,
+    Opts = [],
+    #xdata_field{var =
+                    <<"pubsub#children_association_whitelist">>,
+                values = Values, required = false, type = 'jid-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The list of JIDs that may associate "
+                                "leaf nodes with a collection">>)}.
+
+encode_children(Value, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#children">>,
+                values = Values, required = false, type = 'text-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The child nodes (leaf or collection) "
+                                "associated with a collection">>)}.
+
+encode_children_max(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#children_max">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The maximum number of child nodes that "
+                                "can be associated with a collection">>)}.
+
+encode_collection(Value, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#collection">>,
+                values = Values, required = false, type = 'text-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The collections with which a node is "
+                                "affiliated">>)}.
+
+encode_contact(Value, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [enc_jid(V) || V <- Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#contact">>,
+                values = Values, required = false, type = 'jid-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The JIDs of those to contact with questions">>)}.
+
+encode_dataform_xslt(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#dataform_xslt">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The URL of an XSL transformation which "
+                                "can be applied to the payload format "
+                                "in order to generate a valid Data Forms "
+                                "result that the client could display "
+                                "using a generic Data Forms rendering "
+                                "engine">>)}.
+
+encode_deliver_notifications(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#deliver_notifications">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Deliver event notifications">>)}.
+
+encode_deliver_payloads(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#deliver_payloads">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Deliver payloads with event notifications">>)}.
+
+encode_description(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#description">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"A description of the node">>)}.
+
+encode_item_expire(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#item_expire">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Number of seconds after which to automaticall"
+                                "y purge items">>)}.
+
+encode_itemreply(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"Statically specify a replyto of the "
+                                                "node owner(s)">>),
+                                value = <<"owner">>},
+                  #xdata_option{label =
+                                    Translate(<<"Dynamically specify a replyto of the "
+                                                "item publisher">>),
+                                value = <<"publisher">>},
+                  #xdata_option{value = <<"none">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#itemreply">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Whether owners or publisher should receive "
+                                "replies to items">>)}.
+
+encode_language(Value, Options, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = if Options == default -> [];
+             true ->
+                 [#xdata_option{label = Translate(L), value = V}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#language">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The default language of the node">>)}.
+
+encode_max_items(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_int(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#max_items">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Max # of items to persist">>)}.
+
+encode_max_payload_size(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_int(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#max_payload_size">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Max payload size in bytes">>)}.
+
+encode_node_type(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"The node is a leaf node (default)">>),
+                                value = <<"leaf">>},
+                  #xdata_option{label =
+                                    Translate(<<"The node is a collection node">>),
+                                value = <<"collection">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#node_type">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Whether the node is a leaf (default) "
+                                "or a collection">>)}.
+
+encode_notification_type(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"Messages of type normal">>),
+                                value = <<"normal">>},
+                  #xdata_option{label =
+                                    Translate(<<"Messages of type headline">>),
+                                value = <<"headline">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#notification_type">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Specify the event message type">>)}.
+
+encode_notify_config(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#notify_config">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Notify subscribers when the node configuratio"
+                                "n changes">>)}.
+
+encode_notify_delete(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#notify_delete">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Notify subscribers when the node is "
+                                "deleted">>)}.
+
+encode_notify_retract(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#notify_retract">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Notify subscribers when items are removed "
+                                "from the node">>)}.
+
+encode_notify_sub(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#notify_sub">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Whether to notify owners about new subscriber"
+                                "s and unsubscribes">>)}.
+
+encode_persist_items(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#persist_items">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Persist items to storage">>)}.
+
+encode_presence_based_delivery(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#presence_based_delivery">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Only deliver notifications to available "
+                                "users">>)}.
+
+encode_publish_model(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"Only publishers may publish">>),
+                                value = <<"publishers">>},
+                  #xdata_option{label =
+                                    Translate(<<"Subscribers may publish">>),
+                                value = <<"subscribers">>},
+                  #xdata_option{label =
+                                    Translate(<<"Anyone may publish">>),
+                                value = <<"open">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#publish_model">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Specify the publisher model">>)}.
+
+encode_purge_offline(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#purge_offline">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Purge all items when the relevant publisher "
+                                "goes offline">>)}.
+
+encode_roster_groups_allowed(Value, Options,
+                            Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [Value]
+            end,
+    Opts = if Options == default -> [];
+             true ->
+                 [#xdata_option{label = Translate(L), value = V}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#roster_groups_allowed">>,
+                values = Values, required = false, type = 'list-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Roster groups allowed to subscribe">>)}.
+
+encode_send_last_published_item(Value, Options,
+                               Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label = Translate(<<"Never">>),
+                                value = <<"never">>},
+                  #xdata_option{label =
+                                    Translate(<<"When a new subscription is processed">>),
+                                value = <<"on_sub">>},
+                  #xdata_option{label =
+                                    Translate(<<"When a new subscription is processed "
+                                                "and whenever a subscriber comes online">>),
+                                value = <<"on_sub_and_presence">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var =
+                    <<"pubsub#send_last_published_item">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"When to send the last published item">>)}.
+
+encode_tempsub(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#tempsub">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Whether to make all subscriptions temporary, "
+                                "based on subscriber presence">>)}.
+
+encode_subscribe(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#subscribe">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Whether to allow subscriptions">>)}.
+
+encode_title(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#title">>, values = Values,
+                required = false, type = 'text-single', options = Opts,
+                desc = <<>>,
+                label = Translate(<<"A friendly name for the node">>)}.
+
+encode_type(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#type">>, values = Values,
+                required = false, type = 'text-single', options = Opts,
+                desc = <<>>,
+                label =
+                    Translate(<<"The type of node data, usually specified "
+                                "by the namespace of the payload (if "
+                                "any)">>)}.
diff --git a/src/pubsub_publish_options.erl b/src/pubsub_publish_options.erl
new file mode 100644 (file)
index 0000000..8d02290
--- /dev/null
@@ -0,0 +1,157 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_publish_options.xdata
+%% Form type: http://jabber.org/protocol/pubsub#publish-options
+%% Document: XEP-0060
+
+-module(pubsub_publish_options).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("pubsub_publish_options.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+dec_enum(Val, Enums) ->
+    AtomVal = erlang:binary_to_existing_atom(Val, utf8),
+    case lists:member(AtomVal, Enums) of
+      true -> AtomVal
+    end.
+
+enc_enum(Atom) -> erlang:atom_to_binary(Atom, utf8).
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, []);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/pubsub#publish-opt"
+                         "ions">>]} ->
+         decode(Fs, Acc, []);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/pubsub#publish-opt"
+                          "ions">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {access_model, Val} ->
+               [encode_access_model(Val, default, Translate)];
+           {access_model, Val, Opts} ->
+               [encode_access_model(Val, Opts, Translate)];
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/pubsub#publish-opt"
+                                  "ions">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"pubsub#access_model">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value,
+                [authorize, open, presence, roster, whitelist])
+    of
+      Result ->
+         decode(Fs, [{access_model, Result} | Acc],
+                lists:delete(<<"pubsub#access_model">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#access_model">>,
+                        <<"http://jabber.org/protocol/pubsub#publish-opt"
+                          "ions">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#access_model">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#access_model">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#access_model">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#access_model">>,
+                  <<"http://jabber.org/protocol/pubsub#publish-opt"
+                    "ions">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/pubsub#publish-opt"
+                           "ions">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/pubsub#publish-opt"
+                    "ions">>}});
+decode([], Acc, []) -> Acc.
+
+encode_access_model(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"Access model of authorize">>),
+                                value = <<"authorize">>},
+                  #xdata_option{label =
+                                    Translate(<<"Access model of open">>),
+                                value = <<"open">>},
+                  #xdata_option{label =
+                                    Translate(<<"Access model of presence">>),
+                                value = <<"presence">>},
+                  #xdata_option{label =
+                                    Translate(<<"Access model of roster">>),
+                                value = <<"roster">>},
+                  #xdata_option{label =
+                                    Translate(<<"Access model of whitelist">>),
+                                value = <<"whitelist">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#access_model">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Specify the access model">>)}.
diff --git a/src/pubsub_subscribe_authorization.erl b/src/pubsub_subscribe_authorization.erl
new file mode 100644 (file)
index 0000000..e019ed6
--- /dev/null
@@ -0,0 +1,279 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_subscribe_authorization.xdata
+%% Form type: http://jabber.org/protocol/pubsub#subscribe_authorization
+%% Document: XEP-0060
+
+-module(pubsub_subscribe_authorization).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("pubsub_subscribe_authorization.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+dec_bool(<<"1">>) -> true;
+dec_bool(<<"0">>) -> false;
+dec_bool(<<"true">>) -> true;
+dec_bool(<<"false">>) -> false.
+
+enc_bool(true) -> <<"1">>;
+enc_bool(false) -> <<"0">>.
+
+enc_jid(J) -> jid:to_string(J).
+
+dec_jid(Val) ->
+    case jid:from_string(Val) of
+      error -> erlang:error(badarg);
+      J -> J
+    end.
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false ->
+         decode(Fs, Acc,
+                [<<"pubsub#allow">>, <<"pubsub#node">>,
+                 <<"pubsub#subscriber_jid">>]);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/pubsub#subscribe_a"
+                         "uthorization">>]} ->
+         decode(Fs, Acc,
+                [<<"pubsub#allow">>, <<"pubsub#node">>,
+                 <<"pubsub#subscriber_jid">>]);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                          "uthorization">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {allow, Val} -> [encode_allow(Val, Translate)];
+           {allow, _, _} -> erlang:error({badarg, Opt});
+           {node, Val} -> [encode_node(Val, Translate)];
+           {node, _, _} -> erlang:error({badarg, Opt});
+           {subscriber_jid, Val} ->
+               [encode_subscriber_jid(Val, Translate)];
+           {subscriber_jid, _, _} -> erlang:error({badarg, Opt});
+           {subid, Val} -> [encode_subid(Val, Translate)];
+           {subid, _, _} -> erlang:error({badarg, Opt});
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/pubsub#subscribe_a"
+                                  "uthorization">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"pubsub#allow">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{allow, Result} | Acc],
+                lists:delete(<<"pubsub#allow">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#allow">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                          "uthorization">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#allow">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#allow">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#allow">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#allow">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                    "uthorization">>}});
+decode([#xdata_field{var = <<"pubsub#node">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{node, Result} | Acc],
+                lists:delete(<<"pubsub#node">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#node">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                          "uthorization">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#node">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#node">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#node">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#node">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                    "uthorization">>}});
+decode([#xdata_field{var = <<"pubsub#subscriber_jid">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_jid(Value) of
+      Result ->
+         decode(Fs, [{subscriber_jid, Result} | Acc],
+                lists:delete(<<"pubsub#subscriber_jid">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#subscriber_jid">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                          "uthorization">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#subscriber_jid">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#subscriber_jid">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#subscriber_jid">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#subscriber_jid">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                    "uthorization">>}});
+decode([#xdata_field{var = <<"pubsub#subid">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{subid, Result} | Acc],
+                lists:delete(<<"pubsub#subid">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#subid">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                          "uthorization">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#subid">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#subid">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#subid">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#subid">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                    "uthorization">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                           "uthorization">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_a"
+                    "uthorization">>}});
+decode([], Acc, []) -> Acc.
+
+encode_allow(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#allow">>, values = Values,
+                required = false, type = boolean, options = Opts,
+                desc = <<>>,
+                label =
+                    Translate(<<"Allow this Jabber ID to subscribe to "
+                                "this pubsub node?">>)}.
+
+encode_node(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#node">>, values = Values,
+                required = false, type = 'text-single', options = Opts,
+                desc = <<>>, label = Translate(<<"Node ID">>)}.
+
+encode_subscriber_jid(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_jid(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#subscriber_jid">>,
+                values = Values, required = false, type = 'jid-single',
+                options = Opts, desc = <<>>,
+                label = Translate(<<"Subscriber Address">>)}.
+
+encode_subid(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#subid">>, values = Values,
+                required = false, type = 'text-single', options = Opts,
+                desc = <<>>,
+                label =
+                    Translate(<<"The subscription identifier associated "
+                                "with the subscription request">>)}.
diff --git a/src/pubsub_subscribe_options.erl b/src/pubsub_subscribe_options.erl
new file mode 100644 (file)
index 0000000..446a84a
--- /dev/null
@@ -0,0 +1,508 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: pubsub_subscribe_options.xdata
+%% Form type: http://jabber.org/protocol/pubsub#subscribe_options
+%% Document: XEP-0060
+
+-module(pubsub_subscribe_options).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("pubsub_subscribe_options.hrl").
+
+-export_type([{property, 0}, {result, 0}, {form, 0}]).
+
+dec_enum(Val, Enums) ->
+    AtomVal = erlang:binary_to_existing_atom(Val, utf8),
+    case lists:member(AtomVal, Enums) of
+      true -> AtomVal
+    end.
+
+enc_enum(Atom) -> erlang:atom_to_binary(Atom, utf8).
+
+dec_bool(<<"1">>) -> true;
+dec_bool(<<"0">>) -> false;
+dec_bool(<<"true">>) -> true;
+dec_bool(<<"false">>) -> false.
+
+enc_bool(true) -> <<"1">>;
+enc_bool(false) -> <<"0">>.
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, []);
+      #xdata_field{values =
+                      [<<"http://jabber.org/protocol/pubsub#subscribe_o"
+                         "ptions">>]} ->
+         decode(Fs, Acc, []);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                          "ptions">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {deliver, Val} -> [encode_deliver(Val, Translate)];
+           {deliver, _, _} -> erlang:error({badarg, Opt});
+           {digest, Val} -> [encode_digest(Val, Translate)];
+           {digest, _, _} -> erlang:error({badarg, Opt});
+           {digest_frequency, Val} ->
+               [encode_digest_frequency(Val, Translate)];
+           {digest_frequency, _, _} -> erlang:error({badarg, Opt});
+           {expire, Val} -> [encode_expire(Val, Translate)];
+           {expire, _, _} -> erlang:error({badarg, Opt});
+           {include_body, Val} ->
+               [encode_include_body(Val, Translate)];
+           {include_body, _, _} -> erlang:error({badarg, Opt});
+           {'show-values', Val} ->
+               ['encode_show-values'(Val, default, Translate)];
+           {'show-values', Val, Opts} ->
+               ['encode_show-values'(Val, Opts, Translate)];
+           {subscription_type, Val} ->
+               [encode_subscription_type(Val, default, Translate)];
+           {subscription_type, Val, Opts} ->
+               [encode_subscription_type(Val, Opts, Translate)];
+           {subscription_depth, Val} ->
+               [encode_subscription_depth(Val, default, Translate)];
+           {subscription_depth, Val, Opts} ->
+               [encode_subscription_depth(Val, Opts, Translate)];
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden,
+                           values =
+                               [<<"http://jabber.org/protocol/pubsub#subscribe_o"
+                                  "ptions">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"pubsub#deliver">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{deliver, Result} | Acc],
+                lists:delete(<<"pubsub#deliver">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#deliver">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                          "ptions">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#deliver">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#deliver">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#deliver">>} | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#deliver">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                    "ptions">>}});
+decode([#xdata_field{var = <<"pubsub#digest">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{digest, Result} | Acc],
+                lists:delete(<<"pubsub#digest">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#digest">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                          "ptions">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#digest">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#digest">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#digest">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#digest">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                    "ptions">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#digest_frequency">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{digest_frequency, Result} | Acc],
+                lists:delete(<<"pubsub#digest_frequency">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#digest_frequency">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                          "ptions">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#digest_frequency">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#digest_frequency">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#digest_frequency">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#digest_frequency">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                    "ptions">>}});
+decode([#xdata_field{var = <<"pubsub#expire">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{expire, Result} | Acc],
+                lists:delete(<<"pubsub#expire">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#expire">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                          "ptions">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#expire">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#expire">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#expire">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#expire">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                    "ptions">>}});
+decode([#xdata_field{var = <<"pubsub#include_body">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_bool(Value) of
+      Result ->
+         decode(Fs, [{include_body, Result} | Acc],
+                lists:delete(<<"pubsub#include_body">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#include_body">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                          "ptions">>}})
+    end;
+decode([#xdata_field{var = <<"pubsub#include_body">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"pubsub#include_body">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"pubsub#include_body">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#include_body">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                    "ptions">>}});
+decode([#xdata_field{var = <<"pubsub#show-values">>,
+                    values = Values}
+       | Fs],
+       Acc, Required) ->
+    try [dec_enum(Value, [away, chat, dnd, online, xa])
+        || Value <- Values]
+    of
+      Result ->
+         decode(Fs, [{'show-values', Result} | Acc],
+                lists:delete(<<"pubsub#show-values">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#show-values">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                          "ptions">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#subscription_type">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, [items, nodes]) of
+      Result ->
+         decode(Fs, [{subscription_type, Result} | Acc],
+                lists:delete(<<"pubsub#subscription_type">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#subscription_type">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                          "ptions">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#subscription_type">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#subscription_type">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#subscription_type">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#subscription_type">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                    "ptions">>}});
+decode([#xdata_field{var =
+                        <<"pubsub#subscription_depth">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_enum(Value, ['1', all]) of
+      Result ->
+         decode(Fs, [{subscription_depth, Result} | Acc],
+                lists:delete(<<"pubsub#subscription_depth">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"pubsub#subscription_depth">>,
+                        <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                          "ptions">>}})
+    end;
+decode([#xdata_field{var =
+                        <<"pubsub#subscription_depth">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var =
+                             <<"pubsub#subscription_depth">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var =
+                        <<"pubsub#subscription_depth">>}
+       | _],
+       _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"pubsub#subscription_depth">>,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                    "ptions">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var,
+                         <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                           "ptions">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var,
+                  <<"http://jabber.org/protocol/pubsub#subscribe_o"
+                    "ptions">>}});
+decode([], Acc, []) -> Acc.
+
+encode_deliver(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#deliver">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Whether an entity wants to receive or "
+                                "disable notifications">>)}.
+
+encode_digest(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#digest">>, values = Values,
+                required = false, type = boolean, options = Opts,
+                desc = <<>>,
+                label =
+                    Translate(<<"Whether an entity wants to receive digests "
+                                "(aggregations) of notifications or all "
+                                "notifications individually">>)}.
+
+encode_digest_frequency(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#digest_frequency">>,
+                values = Values, required = false, type = 'text-single',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The minimum number of milliseconds between "
+                                "sending any two notification digests">>)}.
+
+encode_expire(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#expire">>, values = Values,
+                required = false, type = 'text-single', options = Opts,
+                desc = <<>>,
+                label =
+                    Translate(<<"The DateTime at which a leased subscription "
+                                "will end or has ended">>)}.
+
+encode_include_body(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_bool(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"pubsub#include_body">>,
+                values = Values, required = false, type = boolean,
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"Whether an entity wants to receive an "
+                                "XMPP message body in addition to the "
+                                "payload format">>)}.
+
+'encode_show-values'(Value, Options, Translate) ->
+    Values = case Value of
+              [] -> [];
+              Value -> [enc_enum(V) || V <- Value]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"XMPP Show Value of Away">>),
+                                value = <<"away">>},
+                  #xdata_option{label =
+                                    Translate(<<"XMPP Show Value of Chat">>),
+                                value = <<"chat">>},
+                  #xdata_option{label =
+                                    Translate(<<"XMPP Show Value of DND (Do Not Disturb)">>),
+                                value = <<"dnd">>},
+                  #xdata_option{label =
+                                    Translate(<<"Mere Availability in XMPP (No Show Value)">>),
+                                value = <<"online">>},
+                  #xdata_option{label =
+                                    Translate(<<"XMPP Show Value of XA (Extended Away)">>),
+                                value = <<"xa">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#show-values">>,
+                values = Values, required = false, type = 'list-multi',
+                options = Opts, desc = <<>>,
+                label =
+                    Translate(<<"The presence states for which an entity "
+                                "wants to receive notifications">>)}.
+
+encode_subscription_type(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"Receive notification of new items only">>),
+                                value = <<"items">>},
+                  #xdata_option{label =
+                                    Translate(<<"Receive notification of new nodes only">>),
+                                value = <<"nodes">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#subscription_type">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>, label = <<>>}.
+
+encode_subscription_depth(Value, Options, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_enum(Value)]
+            end,
+    Opts = if Options == default ->
+                 [#xdata_option{label =
+                                    Translate(<<"Receive notification from direct child "
+                                                "nodes only">>),
+                                value = <<"1">>},
+                  #xdata_option{label =
+                                    Translate(<<"Receive notification from all descendent "
+                                                "nodes">>),
+                                value = <<"all">>}];
+             true ->
+                 [#xdata_option{label = Translate(L),
+                                value = enc_enum(V)}
+                  || {L, V} <- Options]
+          end,
+    #xdata_field{var = <<"pubsub#subscription_depth">>,
+                values = Values, required = false, type = 'list-single',
+                options = Opts, desc = <<>>, label = <<>>}.
diff --git a/src/xdata_codec.erl b/src/xdata_codec.erl
new file mode 100644 (file)
index 0000000..223f24a
--- /dev/null
@@ -0,0 +1,648 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 27 Sep 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(xdata_codec).
+
+%% API
+-export([compile/1, compile/2]).
+-export([dec_int/1, dec_int/3, dec_enum/2, dec_bool/1, not_empty/1,
+        dec_enum_int/2, dec_enum_int/4, enc_int/1, enc_enum/1,
+        enc_bool/1, enc_enum_int/1, format_error/1, enc_jid/1, dec_jid/1]).
+-include("xmpp.hrl").
+
+-record(state, {mod_name :: atom(),
+               file_name :: string(),
+               erl = "" :: string(),
+               hrl = "" :: string(),
+               dir = "" :: string(),
+               ns = <<>> :: binary(),
+               doc = <<>> :: binary(),
+               erl_dir = "" :: string(),
+               hrl_dir = "" :: string(),
+               prefix = [] :: [binary()],
+               dec_mfas = [] :: [{binary(), mfa()}],
+               enc_mfas = [] :: [{binary(), mfa()}],
+               specs = [] :: [{binary(), string()}],
+               required = [] :: [{binary(), boolean()} | binary()],
+               defaults = [] :: [{binary(), any()}]}).
+
+-define(is_multi_type(T),
+       ((T == 'list-multi') or (T == 'jid-multi') or (T == 'text-multi'))).
+
+-define(is_list_type(T),
+       ((T == 'list-single') or (T == 'list-multi'))).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+compile(Path) ->
+    compile(Path, []).
+
+compile(Path, Opts) ->
+    case filelib:is_dir(Path) of
+       true ->
+           filelib:fold_files(
+             Path, ".*.xdata", false,
+             fun(File, ok) ->
+                     compile_file(File, Opts);
+                (_, Err) ->
+                     Err
+             end, ok);
+       false ->
+           compile_file(Path, Opts)
+    end.
+
+compile_file(Path, Opts) ->
+    try
+       ok = application:ensure_started(fast_xml),
+       DirName = filename:dirname(Path),
+       FileName = filename:basename(Path),
+       RootName = filename:rootname(FileName),
+       ConfigPath = filename:join(DirName, RootName) ++ ".cfg",
+       ModName = list_to_atom(RootName),
+       {ok, Data} = file:read_file(Path),
+       Config = case file:consult(ConfigPath) of
+                    {ok, Terms} -> lists:flatten(Terms);
+                    {error, enoent} -> []
+                end,
+       State = #state{mod_name = ModName,
+                      file_name = FileName,
+                      erl = filename:rootname(FileName) ++ ".erl",
+                      hrl = filename:rootname(FileName) ++ ".hrl",
+                      dir = DirName,
+                      prefix = proplists:get_all_values(prefix, Config),
+                      erl_dir = proplists:get_value(erl_dir, Opts, DirName),
+                      hrl_dir = proplists:get_value(hrl_dir, Opts, DirName),
+                      dec_mfas = proplists:get_value(decode, Config, []),
+                      enc_mfas = proplists:get_value(encode, Config, []),
+                      specs = proplists:get_value(specs, Config, []),
+                      required = proplists:get_value(required, Config, []),
+                      defaults = proplists:get_value(defaults, Config, [])},
+       #xmlel{} = El = fxml_stream:parse_element(Data),
+       ok = compile_element(normalize(El), State),
+       io:format("Compiled ~s~n", [Path])
+    catch _:{badmatch, Err} ->
+           io:format(standard_error, "Failed to compile ~s: ~p~n",
+                     [Path, Err]),
+           Err
+    end.
+
+emit(Format) ->
+    emit(Format, []).
+
+emit(Format, Args) ->
+    put(outbuf, get(outbuf) ++ io_lib:format(Format, Args)).
+
+dec_int(Val) ->
+    dec_int(Val, infinity, infinity).
+
+dec_int(Val, Min, Max) ->
+    case list_to_integer(binary_to_list(Val)) of
+        Int when Int =< Max, Min == infinity ->
+            Int;
+        Int when Int =< Max, Int >= Min ->
+            Int
+    end.
+
+enc_int(Int) ->
+    integer_to_binary(Int).
+
+dec_enum(Val, Enums) ->
+    AtomVal = erlang:binary_to_existing_atom(Val, utf8),
+    case lists:member(AtomVal, Enums) of
+        true ->
+            AtomVal
+    end.
+
+enc_enum(Atom) ->
+    erlang:atom_to_binary(Atom, utf8).
+
+dec_enum_int(Val, Enums) ->
+    try dec_int(Val)
+    catch _:_ -> dec_enum(Val, Enums)
+    end.
+
+dec_enum_int(Val, Enums, Min, Max) ->
+    try dec_int(Val, Min, Max)
+    catch _:_ -> dec_enum(Val, Enums)
+    end.
+
+enc_enum_int(Int) when is_integer(Int) ->
+    enc_int(Int);
+enc_enum_int(Atom) ->
+    enc_enum(Atom).
+
+dec_bool(<<"1">>) -> true;
+dec_bool(<<"0">>) -> false;
+dec_bool(<<"true">>) -> true;
+dec_bool(<<"false">>) -> false.
+
+enc_bool(true) -> <<"1">>;
+enc_bool(false) -> <<"0">>.
+
+enc_jid(J) -> jid:to_string(J).
+
+dec_jid(Val) ->
+    case jid:from_string(Val) of
+       error -> erlang:error(badarg);
+       J -> J
+    end.
+
+not_empty(<<_, _/binary>> = Val) ->
+    Val.
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '", Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '", Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary, "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '", Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '", Type/binary, "'">>.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+compile_element(#xmlel{name = <<"form_type">>, children = Els} = Form,
+               #state{erl = OutErl, erl_dir = ErlDir,
+                      hrl = OutHrl, hrl_dir = HrlDir} = State0) ->
+    try
+       Name = fxml:get_subtag_cdata(Form, <<"name">>),
+       Doc = fxml:get_subtag_cdata(Form, <<"doc">>),
+       X = #xmlel{name = <<"x">>,
+                  attrs = [{<<"type">>, <<"form">>},
+                           {<<"xmlns">>, <<"jabber:x:data">>}],
+                  children = Els},
+       State = State0#state{ns = Name, doc = Doc},
+       #xdata{fields = Fs} = xmpp_codec:decode(X),
+       put(outbuf, []),
+       mk_header(State),
+       mk_aux_funs(),
+       mk_top_decoder(Fs, State),
+       mk_top_encoder(Fs, State),
+       mk_decoder(Fs, State),
+       mk_encoders(Fs, State),
+       ErlData = get(outbuf),
+       ok = file:write_file(filename:join(ErlDir, OutErl), ErlData),
+       ok = erl_tidy:file(filename:join(ErlDir, OutErl), [{backups, false}]),
+       put(outbuf, []),
+       mk_type_definitions(Fs, State),
+       HrlData = get(outbuf),
+       ok = file:write_file(filename:join(HrlDir, OutHrl), HrlData)
+    catch _:{badmatch, Err} ->
+           Err
+    end.
+
+mk_aux_funs() ->
+    case get_abstract_code_from_myself() of
+        {ok, AbsCode} ->
+            AST = lists:filter(
+                   fun(T) ->
+                           case catch erl_syntax_lib:analyze_function(T) of
+                               {format_error, 1} -> true;
+                               {dec_int, 3} -> true;
+                               {dec_int, 1} -> true;
+                               {dec_enum, 2} -> true;
+                               {dec_enum_int, 2} -> true;
+                               {dec_enum_int, 4} -> true;
+                               {enc_int, 1} -> true;
+                               {enc_enum, 1} -> true;
+                               {enc_enum_int, 1} -> true;
+                               {not_empty, 1} -> true;
+                               {dec_bool, 1} -> true;
+                               {enc_bool, 1} -> true;
+                               {enc_jid, 1} -> true;
+                               {dec_jid, 1} -> true;
+                               _ -> false
+                           end
+                   end, AbsCode),
+           emit(erl_prettypr:format(erl_syntax:form_list(AST)) ++ io_lib:nl());
+        error ->
+            erlang:error({no_abstract_code_found, ?MODULE})
+    end.
+
+get_abstract_code_from_myself() ->
+    {file, File} = code:is_loaded(?MODULE),
+    case beam_lib:chunks(File, [abstract_code]) of
+        {ok, {_, List}} ->
+            case lists:keyfind(abstract_code, 1, List) of
+                {abstract_code, {raw_abstract_v1, Abstr}} ->
+                    {ok, Abstr};
+                _ ->
+                    error
+            end;
+        _ ->
+            error
+    end.
+
+mk_comment_header(#state{file_name = Source, ns = NS, doc = Doc}) ->
+    emit("%% Created automatically by xdata generator (xdata_codec.erl)~n"
+        "%% Source: ~s~n"
+        "%% Form type: ~s~n", [Source, NS]),
+    if Doc /= <<>> -> emit("%% Document: ~s~n~n", [Doc]);
+       true -> emit("~n")
+    end.
+
+mk_header(#state{mod_name = Mod, hrl = Include} = State) ->
+    mk_comment_header(State),
+    emit("~n-module(~s).~n", [Mod]),
+    emit("-export([decode/1, decode/2, encode/1, encode/2, format_error/1]).~n"),
+    emit("-include(\"xmpp_codec.hrl\").~n"),
+    emit("-include(\"~s\").~n", [Include]),
+    emit("-export_type([property/0, result/0, form/0]).~n").
+
+mk_type_definitions(Fs, State) ->
+    mk_comment_header(State),
+    lists:foreach(
+      fun(#xdata_field{var = Var} = F) ->
+             Spec = get_typespec(F, State),
+             case is_complex_type(Spec) of
+                 true ->
+                     emit("-type '~s'() :: ~s.~n",
+                          [var_to_rec_field(Var, State), Spec]);
+                 false ->
+                     ok
+             end
+      end, Fs),
+    emit("~n-type property() :: "),
+    Fields = lists:map(
+              fun(#xdata_field{var = Var} = F) ->
+                      RecField = var_to_rec_field(Var, State),
+                      [io_lib:format("{'~s', ~s}",
+                                     [RecField, mk_typespec(F, State)])]
+              end, Fs),
+    emit(string:join(Fields, " |~n                    ") ++ ".~n"),
+    emit("-type result() :: [property()].~n~n"),
+    VarsWithSpec = lists:flatmap(
+                    fun(#xdata_field{type = T, var = Var} = F)
+                          when ?is_list_type(T) ->
+                            RecName = var_to_rec_field(Var, State),
+                            Spec0 = get_typespec(F, State),
+                            Spec = case is_complex_type(Spec0) of
+                                       true ->
+                                           io_lib:format("'~s'()", [RecName]);
+                                       false ->
+                                           Spec0
+                                   end,
+                            [{RecName, mk_typespec(F, State), Spec}];
+                       (_) ->
+                            []
+                    end, Fs),
+    case VarsWithSpec of
+       [] ->
+           emit("-type form() :: [property() | xdata_field()].~n");
+       _ ->
+           emit("-type options(T) :: [{binary(), T}].~n"),
+           emit("-type property_with_options() ::~n      "),
+           Options = [io_lib:format("{'~s', ~s, options(~s)}",
+                                    [Var, Spec1, Spec2])
+                      || {Var, Spec1, Spec2} <- VarsWithSpec],
+           emit(string:join(Options, " |~n      ") ++ ".~n"),
+           emit("-type form() :: [property() | property_with_options() | xdata_field()].~n")
+    end.
+
+mk_top_decoder(Fs, State) ->
+    Required = [Var || #xdata_field{var = Var} <- Fs, is_required(Var, State)],
+    emit("decode(Fs) -> decode(Fs, []).~n"),
+    emit("decode(Fs, Acc) ->"
+        "  case lists:keyfind(<<\"FORM_TYPE\">>, #xdata_field.var, Fs) of"
+        "    false ->"
+        "      decode(Fs, Acc, ~p);"
+        "    #xdata_field{values = [~p]} ->"
+        "      decode(Fs, Acc, ~p);"
+        "    _ ->"
+        "      erlang:error({?MODULE, {form_type_mismatch, ~p}})~n"
+        "  end.~n",
+        [Required, State#state.ns, Required, State#state.ns]).
+
+mk_top_encoder(Fs, State) ->
+    Clauses = string:join(
+               lists:map(
+                 fun(#xdata_field{var = Var, type = T}) when ?is_list_type(T) ->
+                         Field = var_to_rec_field(Var, State),
+                         io_lib:format(
+                           "{'~s', Val} -> ['encode_~s'(Val, default, Translate)];"
+                           "{'~s', Val, Opts} -> ['encode_~s'(Val, Opts, Translate)]",
+                           [Field, Field, Field, Field]);
+                    (#xdata_field{var = Var}) ->
+                         Field = var_to_rec_field(Var, State),
+                         io_lib:format(
+                           "{'~s', Val} -> ['encode_~s'(Val, Translate)];"
+                           "{'~s', _, _} -> erlang:error({badarg, Opt})",
+                           [Field, Field, Field])
+                 end, Fs) ++ ["#xdata_field{} -> [Opt]; _ -> []"],
+               ";"),
+    emit("encode(Cfg) -> encode(Cfg, fun(Text) -> Text end).~n"),
+    emit("encode(List, Translate) when is_list(List) ->"
+        "  Fs = [case Opt of ~s end || Opt <- List],"
+        "  FormType = #xdata_field{var = <<\"FORM_TYPE\">>, type = hidden,"
+        "                          values = [~p]},"
+        "  [FormType|lists:flatten(Fs)].~n",
+        [Clauses, State#state.ns]).
+
+mk_decoder([#xdata_field{var = Var, type = Type} = F|Fs], State) ->
+    ValVar = if ?is_multi_type(Type) -> "Values";
+               true -> "[Value]"
+            end,
+    DecFun = if ?is_multi_type(Type) ->
+                    ["[", mk_decoding_fun(F, State), " || Value <- Values]"];
+               true ->
+                    mk_decoding_fun(F, State)
+            end,
+    emit("decode([#xdata_field{var = ~p, values = ~s}|Fs], Acc, Required) ->"
+        "  try ~s of"
+        "    Result -> decode(Fs, [{'~s', Result}|Acc],"
+        "                     lists:delete(~p, Required))"
+        "  catch _:_ ->"
+        "    erlang:error({?MODULE, {bad_var_value, ~p, ~p}})"
+        "  end;",
+        [Var, ValVar, DecFun, var_to_rec_field(Var, State),
+         Var, Var, State#state.ns]),
+    if not ?is_multi_type(Type) ->
+           emit("decode([#xdata_field{var = ~p, values = []} = F|Fs],"
+                "       Acc, Required) ->"
+                "  decode([F#xdata_field{var = ~p, values = [<<>>]}|Fs],"
+                "         Acc, Required);",
+                [Var, Var]),
+           emit("decode([#xdata_field{var = ~p}|_], _, _) ->"
+                "  erlang:error({?MODULE, {too_many_values, ~p, ~p}});",
+                [Var, Var, State#state.ns]);
+       true ->
+           ok
+    end,
+    mk_decoder(Fs, State);
+mk_decoder([], State) ->
+    emit("decode([#xdata_field{var = Var}|Fs], Acc, Required) ->"
+        "  if Var /= <<\"FORM_TYPE\">> ->"
+        "    erlang:error({?MODULE, {unknown_var, Var, ~p}});"
+        "  true ->"
+        "    decode(Fs, Acc, Required)"
+        "  end;",
+        [State#state.ns]),
+    emit("decode([], _, [Var|_]) ->"
+        "  erlang:error({?MODULE, {missing_required_var, Var, ~p}});~n",
+        [State#state.ns]),
+    emit("decode([], Acc, []) -> Acc.~n").
+
+mk_encoders(Fs, State) ->
+    lists:foreach(
+      fun(#xdata_field{var = Var, required = IsRequired, desc = Desc,
+                      label = Label, type = Type} = F) ->
+             EncVals = mk_encoded_values(F, State),
+             EncOpts = mk_encoded_options(F, State),
+             FieldName = var_to_rec_field(Var, State),
+             DescStr = if Desc == <<>> -> "<<>>";
+                          true -> io_lib:format("Translate(~p)", [Desc])
+                       end,
+             LabelStr = if Label == <<>> -> "<<>>";
+                           true -> io_lib:format("Translate(~p)", [Label])
+                        end,
+             if ?is_list_type(Type) ->
+                     emit("'encode_~s'(Value, Options, Translate) ->", [FieldName]);
+                true ->
+                     emit("'encode_~s'(Value, Translate) ->", [FieldName])
+             end,
+             emit("  Values = ~s,"
+                  "  Opts = ~s,"
+                  "  #xdata_field{var = ~p,"
+                  "               values = Values,"
+                  "               required = ~p,"
+                  "               type = ~p,"
+                  "               options = Opts,"
+                  "               desc = ~s,"
+                  "               label = ~s}.~n",
+                  [EncVals, EncOpts, Var, IsRequired, Type, DescStr, LabelStr])
+      end, Fs).
+
+mk_encoded_values(#xdata_field{var = Var, type = Type,
+                              options = Options}, State) ->
+    EncFun =
+       case get_enc_fun(Var, Type, Options, State) of
+           {M, Fun, Args} ->
+               Mod = if M == undefined -> "";
+                        true -> io_lib:format("~s:", [M])
+                     end,
+               FArgs = [io_lib:format(", ~p", [A]) || A <- Args],
+               if ?is_multi_type(Type) ->
+                       "[" ++ io_lib:format("~s~s(V~s)", [Mod, Fun, FArgs]) ++
+                           " || V <- Value]";
+                  true ->
+                       "[" ++ io_lib:format("~s~s(Value~s)", [Mod, Fun, FArgs])
+                           ++ "]"
+               end;
+           undefined ->
+               "[Value]"
+       end,
+    Default = case get_dec_fun(Var, Type, Options, State) of
+                 _ when ?is_multi_type(Type) -> "[]";
+                 undefined -> "<<>>";
+                 _MFA -> "undefined"
+             end,
+    io_lib:format(
+      "case Value of"
+      "  ~s -> [];~n"
+      "  Value -> ~s~n"
+      "end",
+      [Default, EncFun]).
+
+mk_encoded_options(#xdata_field{var = Var, type = Type,
+                               options = Options}, State) ->
+    EncFun = case get_enc_fun(Var, Type, Options, State) of
+                {M, Fun, Args} ->
+                    Mod = if M == undefined -> "";
+                             true -> io_lib:format("~s:", [M])
+                          end,
+                    FArgs = [io_lib:format(", ~p", [A]) || A <- Args],
+                    io_lib:format("~s~s(V~s)", [Mod, Fun, FArgs]);
+                undefined ->
+                    "V"
+            end,
+    EncOpts = string:join(
+               [case L of
+                    <<>> ->
+                        io_lib:format("#xdata_option{value = ~p}", [V]);
+                    _ ->
+                        io_lib:format(
+                          "#xdata_option{label = Translate(~p), value = ~p}",
+                          [L, V])
+                end || #xdata_option{label = L, value = V} <- Options],
+               ","),
+    if ?is_list_type(Type) ->
+           io_lib:format(
+             "if Options == default ->"
+             "   [~s];"
+             "true ->"
+             "   [#xdata_option{label = Translate(L), value = ~s}"
+             "    || {L, V} <- Options]"
+             "end",
+             [EncOpts, EncFun]);
+       true ->
+           "[]"
+    end.
+
+mk_decoding_fun(#xdata_field{var = Var, type = Type,
+                            options = Options}, State) ->
+    case get_dec_fun(Var, Type, Options, State) of
+       {M, Fun, Args} ->
+           Mod = if M == undefined -> "";
+                    true -> io_lib:format("~s:", [M])
+                 end,
+           FArgs = [io_lib:format(", ~p", [A]) || A <- Args],
+           io_lib:format("~s~s(Value~s)", [Mod, Fun, FArgs]);
+       undefined ->
+           "Value"
+    end.
+
+var_to_rec_field(Var, #state{prefix = [Prefix|T]} = State) ->
+    Size = size(Prefix),
+    case Var of
+       <<(Prefix):Size/binary, Rest/binary>> ->
+           binary_to_atom(Rest, utf8);
+       _ ->
+           var_to_rec_field(Var, State#state{prefix = T})
+    end;
+var_to_rec_field(Var, #state{prefix = []}) ->
+    Var.
+
+get_dec_fun(Var, Type, Options, State) ->
+    case lists:keyfind(Var, 1, State#state.dec_mfas) of
+       false when Type == 'list-multi'; Type == 'list-single' ->
+           if Options /= [] ->
+                   Variants = [binary_to_atom(V, utf8)
+                               || #xdata_option{value = V} <- Options],
+                   {undefined, dec_enum, [Variants]};
+              true ->
+                   undefined
+           end;
+       false when Type == 'jid-multi'; Type == 'jid-single' ->
+           {undefined, dec_jid, []};
+       false when Type == boolean ->
+           {undefined, dec_bool, []};
+       false ->
+           undefined;
+       {Var, {M, F, A}} ->
+           {M, F, A};
+       {Var, {dec_bool, []}} ->
+           {undefined, dec_bool, []};
+       {Var, {not_empty, []}} ->
+           {undefined, not_empty, []};
+       {Var, {dec_enum, [Variants]}} ->
+           {undefined, dec_enum, [Variants]};
+       {Var, {dec_int, Args}} ->
+           {undefined, dec_int, Args};
+       {Var, {dec_enum_int, Args}} ->
+           {undefined, dec_enum_int, Args};
+       {Var, {dec_jid, []}} ->
+           {undefined, dec_jid, []}
+    end.
+
+get_enc_fun(Var, Type, Options, State) ->
+    case get_dec_fun(Var, Type, Options, State) of
+       {undefined, dec_enum, _} ->
+           {undefined, enc_enum, []};
+       {undefined, dec_bool, _} ->
+           {undefined, enc_bool, []};
+       {undefined, dec_int, _} ->
+           {undefined, enc_int, []};
+       {undefined, dec_enum_int, _} ->
+           {undefined, enc_enum_int, []};
+       {undefined, dec_jid, _} ->
+           {undefined, enc_jid, []};
+       _ ->
+           case lists:keyfind(Var, 1, State#state.enc_mfas) of
+               false ->
+                   undefined;
+               {Var, {M, F, A}} ->
+                   {M, F, A};
+               {Var, {enc_bool, []}} ->
+                   {undefined, enc_bool, []};
+               {Var, {dec_enum, _}} ->
+                   {undefined, enc_enum, []};
+               {Var, {enc_int, _}} ->
+                   {undefined, enc_int, []};
+               {Var, {dec_enum_int, _}} ->
+                   {undefined, enc_enum_int, []};
+               {Var, {enc_jid, _}} ->
+                   {undefined, enc_jid, []}
+           end
+    end.
+
+mk_typespec(#xdata_field{type = Type, var = Var} = Field, State) ->
+    Spec0 = get_typespec(Field, State),
+    Spec1 = case is_complex_type(Spec0) of
+               true ->
+                   io_lib:format("'~s'()", [var_to_rec_field(Var, State)]);
+               false ->
+                   Spec0
+           end,
+    if ?is_multi_type(Type) -> "[" ++ Spec1 ++ "]";
+       true -> Spec1
+    end.
+
+get_typespec(#xdata_field{var = Var, type = Type, options = Options}, State) ->
+    case lists:keyfind(Var, 1, State#state.specs) of
+       false ->
+           case get_dec_fun(Var, Type, Options, State) of
+               {undefined, dec_enum, Args} ->
+                   enum_spec(Args);
+               {undefined, dec_bool, _} ->
+                   "boolean()";
+               {undefined, dec_jid, _} ->
+                   "jid:jid()";
+               {undefined, dec_int, Args} ->
+                   int_spec(Args);
+               {undefined, dec_enum_int, [Variants|T]} ->
+                   enum_spec([Variants]) ++ " | " ++ int_spec(T);
+               _ ->
+                   "binary()"
+           end;
+       {Var, Spec} ->
+           Spec
+    end.
+
+-spec is_complex_type(string()) -> boolean().
+is_complex_type(Spec) ->
+    string:chr(Spec, $|) /= 0.
+
+int_spec([]) ->
+    "integer()";
+int_spec([From, To]) ->
+    if From /= infinity, To /= infinity ->
+           io_lib:format("~p..~p", [From, To]);
+       From > 0 ->
+           "pos_integer()";
+       From == 0 ->
+           "non_neg_integer()";
+       true ->
+           "integer()"
+    end.
+
+enum_spec([Variants]) ->
+    string:join([atom_to_list(V) || V <- Variants], " | ").
+
+is_required(Var, State) ->
+    lists:member(Var, State#state.required) orelse
+       proplists:get_bool(Var, State#state.required).
+
+normalize(#xmlel{name = Name, attrs = Attrs, children = Els}) ->
+    #xmlel{name = Name,
+          attrs = [normalize(Attr) || Attr <- Attrs],
+          children = [normalize(El) || El <- Els]};
+normalize({Key, Data}) ->
+    {Key, normalize(Data)};
+normalize(Txt) when is_binary(Txt) ->
+    case re:split(Txt, "[\\s\\r\\n\\t]+", [trim, {return, list}]) of
+       [""|T] ->
+           list_to_binary(string:join(T, " "));
+       T ->
+           list_to_binary(string:join(T, " "))
+    end.
index f8f8b205f198a86b2989cec81e6a070397d62c50..f230dc48917628ad5d4ee825885001d847e24e25 100644 (file)
@@ -6602,6 +6602,20 @@ pp(upload_slot, 3) -> [get, put, xmlns];
 pp(thumbnail, 4) -> [uri, 'media-type', width, height];
 pp(_, _) -> no.
 
+enc_ps_aff(member) -> <<"member">>;
+enc_ps_aff(none) -> <<"none">>;
+enc_ps_aff(outcast) -> <<"outcast">>;
+enc_ps_aff(owner) -> <<"owner">>;
+enc_ps_aff(publisher) -> <<"publisher">>;
+enc_ps_aff(publish_only) -> <<"publish-only">>.
+
+dec_ps_aff(<<"member">>) -> member;
+dec_ps_aff(<<"none">>) -> none;
+dec_ps_aff(<<"outcast">>) -> outcast;
+dec_ps_aff(<<"owner">>) -> owner;
+dec_ps_aff(<<"publisher">>) -> publisher;
+dec_ps_aff(<<"publish-only">>) -> publish_only.
+
 enc_version({Maj, Min}) ->
     <<(integer_to_binary(Maj))/binary, $.,
       (integer_to_binary(Min))/binary>>.
@@ -19203,10 +19217,7 @@ decode_pubsub_owner_affiliation_attr_affiliation(__TopXMLNS,
                   __TopXMLNS}});
 decode_pubsub_owner_affiliation_attr_affiliation(__TopXMLNS,
                                                 _val) ->
-    case catch dec_enum(_val,
-                       [member, none, outcast, owner, publisher,
-                        'publish-only'])
-       of
+    case catch dec_ps_aff(_val) of
       {'EXIT', _} ->
          erlang:error({xmpp_codec,
                        {bad_attr_value, <<"affiliation">>, <<"affiliation">>,
@@ -19216,7 +19227,7 @@ decode_pubsub_owner_affiliation_attr_affiliation(__TopXMLNS,
 
 encode_pubsub_owner_affiliation_attr_affiliation(_val,
                                                 _acc) ->
-    [{<<"affiliation">>, enc_enum(_val)} | _acc].
+    [{<<"affiliation">>, enc_ps_aff(_val)} | _acc].
 
 decode_pubsub_affiliation(__TopXMLNS, __IgnoreEls,
                          {xmlel, <<"affiliation">>, _attrs, _els}) ->
@@ -19290,10 +19301,7 @@ decode_pubsub_affiliation_attr_affiliation(__TopXMLNS,
                   __TopXMLNS}});
 decode_pubsub_affiliation_attr_affiliation(__TopXMLNS,
                                           _val) ->
-    case catch dec_enum(_val,
-                       [member, none, outcast, owner, publisher,
-                        'publish-only'])
-       of
+    case catch dec_ps_aff(_val) of
       {'EXIT', _} ->
          erlang:error({xmpp_codec,
                        {bad_attr_value, <<"affiliation">>, <<"affiliation">>,
@@ -19303,7 +19311,7 @@ decode_pubsub_affiliation_attr_affiliation(__TopXMLNS,
 
 encode_pubsub_affiliation_attr_affiliation(_val,
                                           _acc) ->
-    [{<<"affiliation">>, enc_enum(_val)} | _acc].
+    [{<<"affiliation">>, enc_ps_aff(_val)} | _acc].
 
 decode_pubsub_subscription(__TopXMLNS, __IgnoreEls,
                           {xmlel, <<"subscription">>, _attrs, _els}) ->
@@ -19826,7 +19834,7 @@ decode_xdata_field(__TopXMLNS, __IgnoreEls,
                   {xmlel, <<"field">>, _attrs, _els}) ->
     {Options, Values, Desc, Required, __Els} =
        decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
-                              [], [], undefined, false, []),
+                              [], [], <<>>, false, []),
     {Label, Type, Var} =
        decode_xdata_field_attrs(__TopXMLNS, _attrs, undefined,
                                 undefined, undefined),
@@ -20003,8 +20011,7 @@ encode_xdata_field({xdata_field, Label, Type, Var,
                                 [encode_xdata_field_value(Values, __TopXMLNS)
                                  | _acc]).
 
-'encode_xdata_field_$desc'(undefined, __TopXMLNS,
-                          _acc) ->
+'encode_xdata_field_$desc'(<<>>, __TopXMLNS, _acc) ->
     _acc;
 'encode_xdata_field_$desc'(Desc, __TopXMLNS, _acc) ->
     [encode_xdata_field_desc(Desc, __TopXMLNS) | _acc].
index 43178e86f699e9c320775283d6a149b31f5ea72f..102d884125d9cb2d5f9a6889fb29c9f4fd9d9cb9 100644 (file)
@@ -11,7 +11,8 @@
 %% API
 -export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1,
         is_standalone_chat_state/1, get_xdata_values/2,
-        has_xdata_var/2, make_adhoc_response/1, make_adhoc_response/2]).
+        has_xdata_var/2, make_adhoc_response/1, make_adhoc_response/2,
+        decode_timestamp/1, encode_timestamp/1]).
 
 -include("xmpp.hrl").
 
@@ -95,11 +96,66 @@ make_adhoc_response(#adhoc_command{lang = Lang, node = Node, sid = SID},
 
 -spec make_adhoc_response(adhoc_command()) -> adhoc_command().
 make_adhoc_response(#adhoc_command{sid = <<"">>} = Command) ->
-    SID = jlib:now_to_utc_string(p1_time_compat:timestamp()),
+    SID = encode_timestamp(p1_time_compat:timestamp()),
     Command#adhoc_command{sid = SID};
 make_adhoc_response(Command) ->
     Command.
 
+-spec decode_timestamp(binary()) -> erlang:timestamp().
+decode_timestamp(S) ->
+    try try_decode_timestamp(S)
+    catch _:_ -> erlang:error({bad_timestamp, S})
+    end.
+
+-spec encode_timestamp(erlang:timestamp()) -> binary().
+encode_timestamp({MegaSecs, Secs, MicroSecs}) ->
+    {{Year, Month, Day}, {Hour, Minute, Second}} =
+        calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}),
+    Fraction = if MicroSecs > 0 ->
+                      io_lib:format(".~6..0B", [MicroSecs]);
+                 true ->
+                      ""
+              end,
+    list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT"
+                                "~2..0B:~2..0B:~2..0B~sZ",
+                                [Year, Month, Day, Hour, Minute, Second,
+                                 Fraction])).
+
 %%%===================================================================
 %%% Internal functions
 %%%===================================================================
+try_decode_timestamp(<<Y:4/binary, $-, Mo:2/binary, $-, D:2/binary, $T,
+                      H:2/binary, $:, Mi:2/binary, $:, S:2/binary, T/binary>>) ->
+    Date = {to_integer(Y, 1970, 9999), to_integer(Mo, 1, 12), to_integer(D, 1, 31)},
+    Time = {to_integer(H, 0, 23), to_integer(Mi, 0, 59), to_integer(S, 0, 59)},
+    {MS, {TZH, TZM}} = try_decode_fraction(T),
+    Seconds = calendar:datetime_to_gregorian_seconds({Date, Time}) -
+       calendar:datetime_to_gregorian_seconds({{1970,1,1}, {0,0,0}}) -
+       TZH * 60 * 60 - TZM * 60,
+    {Seconds div 1000000, Seconds rem 1000000, MS};
+try_decode_timestamp(<<Y:4/binary, Mo:2/binary, D:2/binary, $T,
+                      H:2/binary, $:, Mi:2/binary, $:, S:2/binary>>) ->
+    try_decode_timestamp(<<Y:4/binary, $-, Mo:2/binary, $-, D:2/binary, $T,
+                          H:2/binary, $:, Mi:2/binary, $:, S:2/binary, $Z>>).
+
+try_decode_fraction(<<$., T/binary>>) ->
+    {match, [V]} = re:run(T, <<"^[0-9]+">>, [{capture, [0], binary}]),
+    Size = size(V),
+    <<V:Size/binary, TZD/binary>> = T,
+    {to_integer(binary:part(V, 0, min(6, Size)), 0, 999999),
+     try_decode_tzd(TZD)};
+try_decode_fraction(TZD) ->
+    {0, try_decode_tzd(TZD)}.
+
+try_decode_tzd(<<$Z>>) ->
+    {0, 0};
+try_decode_tzd(<<$-, H:2/binary, $:, M:2/binary>>) ->
+    {-1 * to_integer(H, 0, 12), to_integer(M, 0, 59)};
+try_decode_tzd(<<$+, H:2/binary, $:, M:2/binary>>) ->
+    {to_integer(H, 0, 12), to_integer(M, 0, 59)}.
+
+to_integer(S, Min, Max) ->
+    case binary_to_integer(S) of
+       I when I >= Min, I =< Max ->
+           I
+    end.
index 81e35d52a457f6346bbc3e64567fd4e6deb6bddf..b4249bbdf7455723aa737d57abbbfe2941b4ce28 100644 (file)
@@ -26,7 +26,7 @@
 -include("suite.hrl").
 
 suite() ->
-    [{timetrap, {seconds,30}}].
+    [{timetrap, {seconds,60}}].
 
 init_per_suite(Config) ->
     NewConfig = init_config(Config),
@@ -325,6 +325,36 @@ no_db_tests() ->
      {replaced, [parallel],
       [replaced_master, replaced_slave]}].
 
+pubsub_single_tests() ->
+    {pubsub_single, [sequence],
+     [test_pubsub_features,
+      test_pubsub_create,
+      test_pubsub_configure,
+      test_pubsub_delete,
+      test_pubsub_get_affiliations,
+      test_pubsub_get_subscriptions,
+      test_pubsub_create_instant,
+      test_pubsub_default,
+      test_pubsub_create_configure,
+      test_pubsub_publish,
+      test_pubsub_auto_create,
+      test_pubsub_get_items,
+      test_pubsub_delete_item,
+      test_pubsub_purge,
+      test_pubsub_subscribe,
+      test_pubsub_unsubscribe]}.
+
+pubsub_multiple_tests() ->
+    {pubsub_multiple, [sequence],
+     [{pubsub_publish, [parallel],
+       [pubsub_publish_master, pubsub_publish_slave]},
+      {pubsub_subscriptions, [parallel],
+       [pubsub_subscriptions_master, pubsub_subscriptions_slave]},
+      {pubsub_affiliations, [parallel],
+       [pubsub_affiliations_master, pubsub_affiliations_slave]},
+      {pubsub_authorize, [parallel],
+       [pubsub_authorize_master, pubsub_authorize_slave]}]}.
+
 db_tests(riak) ->
     %% No support for mod_pubsub
     [{single_user, [sequence],
@@ -340,7 +370,7 @@ db_tests(riak) ->
        blocking,
        vcard,
        test_unregister]},
-     {test_muc_register, [sequence],
+     {test_muc_register, [parallel],
       [muc_register_master, muc_register_slave]},
      {test_roster_subscribe, [parallel],
       [roster_subscribe_master,
@@ -372,9 +402,10 @@ db_tests(DB) when DB == mnesia; DB == redis ->
        privacy,
        blocking,
        vcard,
-       pubsub,
+       pubsub_single_tests(),
        test_unregister]},
-     {test_muc_register, [sequence],
+     pubsub_multiple_tests(),
+     {test_muc_register, [parallel],
       [muc_register_master, muc_register_slave]},
      {test_mix, [parallel],
       [mix_master, mix_slave]},
@@ -409,19 +440,20 @@ db_tests(_) ->
     [{single_user, [sequence],
       [test_register,
        legacy_auth_tests(),
-       auth_plain,
-       auth_md5,
-       presence_broadcast,
-       last,
-       roster_get,
-       roster_ver,
-       private,
-       privacy,
-       blocking,
-       vcard,
-       pubsub,
-       test_unregister]},
-     {test_muc_register, [sequence],
+       auth_plain,
+       auth_md5,
+       presence_broadcast,
+       last,
+       roster_get,
+       roster_ver,
+       private,
+       privacy,
+       blocking,
+       vcard,
+        pubsub_single_tests(),
+        test_unregister]},
+     pubsub_multiple_tests(),
+     {test_muc_register, [parallel],
       [muc_register_master, muc_register_slave]},
      {test_mix, [parallel],
       [mix_master, mix_slave]},
@@ -512,17 +544,17 @@ groups() ->
      {riak, [sequence], db_tests(riak)}].
 
 all() ->
-    [%%{group, ldap},
+    [{group, ldap},
      {group, no_db},
-     %% {group, mnesia},
-     %% {group, redis},
-     %% {group, mysql},
-     %% {group, pgsql},
-     %% {group, sqlite},
-     %% {group, extauth},
-     %% {group, riak},
-     %% {group, component},
-     %% {group, s2s},
+     {group, mnesia},
+     {group, redis},
+     {group, mysql},
+     {group, pgsql},
+     {group, sqlite},
+     {group, extauth},
+     {group, riak},
+     {group, component},
+     {group, s2s},
      stop_ejabberd].
 
 stop_ejabberd(Config) ->
@@ -943,16 +975,22 @@ disco(Config) ->
       end, Items),
     disconnect(Config).
 
-replaced_master(Config0) ->
-    Config = bind(Config0),
-    wait_for_slave(Config),
-    ?recv1(#stream_error{reason = conflict}),
-    ?recv1({xmlstreamend, <<"stream:stream">>}),
-    close_socket(Config).
+%% replaced_master(Config0) ->
+%%     Config = bind(Config0),
+%%     wait_for_slave(Config),
+%%     ?recv1(#stream_error{reason = conflict}),
+%%     ?recv1({xmlstreamend, <<"stream:stream">>}),
+%%     close_socket(Config).
+
+%% replaced_slave(Config0) ->
+%%     wait_for_master(Config0),
+%%     Config = bind(Config0),
+%%     disconnect(Config).
 
-replaced_slave(Config0) ->
-    wait_for_master(Config0),
-    Config = bind(Config0),
+replaced_master(Config) ->
+    disconnect(Config).
+
+replaced_slave(Config) ->
     disconnect(Config).
 
 sm(Config) ->
@@ -1226,78 +1264,663 @@ stats(Config) ->
       end, Stats),
     disconnect(Config).
 
-pubsub(Config) ->
-    Features = get_features(Config, pubsub_jid(Config)),
-    true = lists:member(?NS_PUBSUB, Features),
-    %% Publish <presence/> element within node "presence"
-    ItemID = randoms:get_string(),
-    Node = <<"presence!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>,
-    Item = #ps_item{id = ItemID,
-                        xml_els = [xmpp:encode(#presence{})]},
-    #iq{type = result,
-        sub_els = [#pubsub{publish = #ps_publish{
-                             node = Node,
-                             items = [#ps_item{id = ItemID}]}}]} =
-        send_recv(Config,
-                  #iq{type = set, to = pubsub_jid(Config),
-                      sub_els = [#pubsub{publish = #ps_publish{
-                                           node = Node,
-                                           items = [Item]}}]}),
-    %% Subscribe to node "presence"
-    I1 = send(Config,
-             #iq{type = set, to = pubsub_jid(Config),
-                 sub_els = [#pubsub{subscribe = #ps_subscribe{
-                                      node = Node,
-                                      jid = my_jid(Config)}}]}),
-    ?recv2(
-       #message{sub_els = [#ps_event{}, #delay{}]},
-       #iq{type = result, id = I1}),
-    %% Get subscriptions
-    true = lists:member(?PUBSUB("retrieve-subscriptions"), Features),
-    #iq{type = result,
-        sub_els =
-            [#pubsub{subscriptions =
-                         {<<>>, [#ps_subscription{node = Node}]}}]} =
-        send_recv(Config, #iq{type = get, to = pubsub_jid(Config),
-                              sub_els = [#pubsub{subscriptions = {<<>>, []}}]}),
-    %% Get affiliations
-    true = lists:member(?PUBSUB("retrieve-affiliations"), Features),
-    #iq{type = result,
-        sub_els = [#pubsub{
-                      affiliations =
-                          {<<>>, [#ps_affiliation{node = Node, type = owner}]}}]} =
-        send_recv(Config, #iq{type = get, to = pubsub_jid(Config),
-                              sub_els = [#pubsub{affiliations = {<<>>, []}}]}),
-    %% Fetching published items from node "presence"
-    #iq{type = result,
-        sub_els = [#pubsub{items = #ps_items{
-                             node = Node,
-                             items = [Item]}}]} =
-        send_recv(Config,
-                  #iq{type = get, to = pubsub_jid(Config),
-                      sub_els = [#pubsub{items = #ps_items{node = Node}}]}),
-    %% Deleting the item from the node
-    true = lists:member(?PUBSUB("delete-items"), Features),
-    I2 = send(Config,
-              #iq{type = set, to = pubsub_jid(Config),
-                  sub_els = [#pubsub{retract = #ps_retract{
-                                       node = Node,
-                                       items = [#ps_item{id = ItemID}]}}]}),
-    ?recv2(
-       #iq{type = result, id = I2, sub_els = []},
-       #message{sub_els = [#ps_event{
-                              items = #ps_items{
-                                        node = Node,
-                                        retract = ItemID}}]}),
-    %% Unsubscribe from node "presence"
-    #iq{type = result, sub_els = []} =
-        send_recv(Config,
-                  #iq{type = set, to = pubsub_jid(Config),
-                      sub_els = [#pubsub{unsubscribe = #ps_unsubscribe{
-                                           node = Node,
-                                           jid = my_jid(Config)}}]}),
+test_pubsub_features(Config) ->
+    PJID = pubsub_jid(Config),
+    AllFeatures = sets:from_list(get_features(Config, PJID)),
+    NeededFeatures = sets:from_list(
+                      [?NS_PUBSUB,
+                       ?PUBSUB("access-open"),
+                       ?PUBSUB("access-authorize"),
+                       ?PUBSUB("create-nodes"),
+                       ?PUBSUB("instant-nodes"),
+                       ?PUBSUB("config-node"),
+                       ?PUBSUB("retrieve-default"),
+                       ?PUBSUB("create-and-configure"),
+                       ?PUBSUB("publish"),
+                       ?PUBSUB("auto-create"),
+                       ?PUBSUB("retrieve-items"),
+                       ?PUBSUB("delete-items"),
+                       ?PUBSUB("subscribe"),
+                       ?PUBSUB("retrieve-affiliations"),
+                       ?PUBSUB("modify-affiliations"),
+                       ?PUBSUB("retrieve-subscriptions"),
+                       ?PUBSUB("manage-subscriptions"),
+                       ?PUBSUB("purge-nodes"),
+                       ?PUBSUB("delete-nodes")]),
+    true = sets:is_subset(NeededFeatures, AllFeatures),
+    disconnect(Config).
+
+test_pubsub_create(Config) ->
+    Node = ?config(pubsub_node, Config),
+    Node = create_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_create_instant(Config) ->
+    Node = create_node(Config, <<>>),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_configure(Config) ->
+    Node = ?config(pubsub_node, Config),
+    NodeTitle = ?config(pubsub_node_title, Config),
+    NodeConfig = get_node_config(Config, Node),
+    MyNodeConfig = set_opts(NodeConfig,
+                           [{title, NodeTitle}]),
+    set_node_config(Config, Node, MyNodeConfig),
+    NewNodeConfig = get_node_config(Config, Node),
+    NodeTitle = proplists:get_value(title, NewNodeConfig),
+    disconnect(Config).
+
+test_pubsub_default(Config) ->
+    get_default_node_config(Config),
+    disconnect(Config).
+
+test_pubsub_create_configure(Config) ->
+    NodeTitle = ?config(pubsub_node_title, Config),
+    DefaultNodeConfig = get_default_node_config(Config),
+    CustomNodeConfig = set_opts(DefaultNodeConfig,
+                               [{title, NodeTitle}]),
+    Node = create_node(Config, <<>>, CustomNodeConfig),
+    NodeConfig = get_node_config(Config, Node),
+    NodeTitle = proplists:get_value(title, NodeConfig),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_publish(Config) ->
+    Node = create_node(Config, <<>>),
+    publish_item(Config, Node),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_auto_create(Config) ->
+    Node = randoms:get_string(),
+    publish_item(Config, Node),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_get_items(Config) ->
+    Node = create_node(Config, <<>>),
+    ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)],
+    ItemsOut = get_items(Config, Node),
+    true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)]
+       == [I || #ps_item{id = I} <- lists:sort(ItemsOut)],
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_delete_item(Config) ->
+    Node = create_node(Config, <<>>),
+    #ps_item{id = I} = publish_item(Config, Node),
+    [#ps_item{id = I}] = get_items(Config, Node),
+    delete_item(Config, Node, I),
+    [] = get_items(Config, Node),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_subscribe(Config) ->
+    Node = create_node(Config, <<>>),
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    [#ps_subscription{node = Node}] = get_subscriptions(Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_unsubscribe(Config) ->
+    Node = create_node(Config, <<>>),
+    subscribe_node(Config, Node),
+    [#ps_subscription{node = Node}] = get_subscriptions(Config),
+    unsubscribe_node(Config, Node),
+    [] = get_subscriptions(Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_get_affiliations(Config) ->
+    Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]),
+    Affs = get_affiliations(Config),
+    Nodes = lists:sort([Node || #ps_affiliation{node = Node,
+                                               type = owner} <- Affs]),
+    [delete_node(Config, Node) || Node <- Nodes],
+    disconnect(Config).
+
+test_pubsub_get_subscriptions(Config) ->
+    Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]),
+    [subscribe_node(Config, Node) || Node <- Nodes],
+    Subs = get_subscriptions(Config),
+    Nodes = lists:sort([Node || #ps_subscription{node = Node} <- Subs]),
+    [delete_node(Config, Node) || Node <- Nodes],
+    disconnect(Config).
+
+test_pubsub_purge(Config) ->
+    Node = create_node(Config, <<>>),
+    ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)],
+    ItemsOut = get_items(Config, Node),
+    true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)]
+       == [I || #ps_item{id = I} <- lists:sort(ItemsOut)],
+    purge_node(Config, Node),
+    [] = get_items(Config, Node),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_pubsub_delete(Config) ->
+    Node = ?config(pubsub_node, Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+pubsub_publish_master(Config) ->
+    Node = create_node(Config, <<>>),
+    put_event(Config, Node),
+    wait_for_slave(Config),
+    #ps_item{id = ID} = publish_item(Config, Node),
+    #ps_item{id = ID} = get_event(Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+pubsub_publish_slave(Config) ->
+    Node = get_event(Config),
+    subscribe_node(Config, Node),
+    wait_for_master(Config),
+    #message{
+       sub_els =
+          [#ps_event{
+              items = #ps_items{node = Node,
+                                items = [Item]}}]} = recv(Config),
+    put_event(Config, Item),
+    disconnect(Config).
+
+pubsub_subscriptions_master(Config) ->
+    Peer = ?config(slave, Config),
+    Node = ?config(pubsub_node, Config),
+    Node = create_node(Config, Node),
+    [] = get_subscriptions(Config, Node),
+    wait_for_slave(Config),
+    lists:foreach(
+      fun(Type) ->
+             ok = set_subscriptions(Config, Node, [{Peer, Type}]),
+             #ps_item{} = publish_item(Config, Node),
+             case get_subscriptions(Config, Node) of
+                 [] when Type == none; Type == pending ->
+                     ok;
+                 [#ps_subscription{jid = Peer, type = Type}] ->
+                     ok
+             end
+      end, [subscribed, unconfigured, pending, none]),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+pubsub_subscriptions_slave(Config) ->
+    wait_for_master(Config),
+    MyJID = my_jid(Config),
+    Node = ?config(pubsub_node, Config),
+    lists:foreach(
+      fun(subscribed = Type) ->
+             ?recv2(#message{
+                       sub_els =
+                           [#ps_event{
+                               subscription = #ps_subscription{
+                                                 node = Node,
+                                                 jid = MyJID,
+                                                 type = Type}}]},
+                    #message{sub_els = [#ps_event{}]});
+        (Type) ->
+             ?recv1(#message{
+                       sub_els =
+                           [#ps_event{
+                               subscription = #ps_subscription{
+                                                 node = Node,
+                                                 jid = MyJID,
+                                                 type = Type}}]})
+      end, [subscribed, unconfigured, pending, none]),
+    disconnect(Config).
+
+pubsub_affiliations_master(Config) ->
+    Peer = ?config(slave, Config),
+    BarePeer = jid:remove_resource(Peer),
+    lists:foreach(
+      fun(Aff) ->
+             Node = <<(atom_to_binary(Aff, utf8))/binary,
+                      $-, (randoms:get_string())/binary>>,
+             create_node(Config, Node, default_node_config(Config)),
+             #ps_item{id = I} = publish_item(Config, Node),
+             ok = set_affiliations(Config, Node, [{Peer, Aff}]),
+             Affs = get_affiliations(Config, Node),
+             case lists:keyfind(BarePeer, #ps_affiliation.jid, Affs) of
+                 false when Aff == none ->
+                     ok;
+                 #ps_affiliation{type = Aff} ->
+                     ok
+             end,
+             put_event(Config, {Aff, Node, I}),
+             wait_for_slave(Config),
+             delete_node(Config, Node)
+      end, [outcast, none, member, publish_only, publisher, owner]),
+    put_event(Config, disconnect),
+    disconnect(Config).
+
+pubsub_affiliations_slave(Config) ->
+    pubsub_affiliations_slave(Config, get_event(Config)).
+
+pubsub_affiliations_slave(Config, {outcast, Node, ItemID}) ->
+    #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node),
+    #stanza_error{} = unsubscribe_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_items(Config, Node),
+    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
+    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    pubsub_affiliations_slave(Config, get_event(Config));
+pubsub_affiliations_slave(Config, {none, Node, ItemID}) ->
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    ok = unsubscribe_node(Config, Node),
+    %% This violates the affiliation char from section 4.1
+    [_|_] = get_items(Config, Node),
+    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
+    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    pubsub_affiliations_slave(Config, get_event(Config));
+pubsub_affiliations_slave(Config, {member, Node, ItemID}) ->
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    ok = unsubscribe_node(Config, Node),
+    [_|_] = get_items(Config, Node),
+    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
+    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    pubsub_affiliations_slave(Config, get_event(Config));
+pubsub_affiliations_slave(Config, {publish_only, Node, ItemID}) ->
+    #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node),
+    #stanza_error{} = unsubscribe_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_items(Config, Node),
+    #ps_item{id = MyItemID} = publish_item(Config, Node),
+    %% BUG: This should be fixed
+    %% ?match(ok, delete_item(Config, Node, MyItemID)),
+    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    pubsub_affiliations_slave(Config, get_event(Config));
+pubsub_affiliations_slave(Config, {publisher, Node, ItemID}) ->
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    ok = unsubscribe_node(Config, Node),
+    [_|_] = get_items(Config, Node),
+    #ps_item{id = MyItemID} = publish_item(Config, Node),
+    ok = delete_item(Config, Node, MyItemID),
+    %% BUG: this should be fixed
+    %% #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    pubsub_affiliations_slave(Config, get_event(Config));
+pubsub_affiliations_slave(Config, {owner, Node, ItemID}) ->
+    MyJID = my_jid(Config),
+    Peer = ?config(master, Config),
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    ok = unsubscribe_node(Config, Node),
+    [_|_] = get_items(Config, Node),
+    #ps_item{id = MyItemID} = publish_item(Config, Node),
+    ok = delete_item(Config, Node, MyItemID),
+    ok = delete_item(Config, Node, ItemID),
+    ok = purge_node(Config, Node),
+    [_|_] = get_node_config(Config, Node),
+    ok = set_node_config(Config, Node, default_node_config(Config)),
+    ok = set_subscriptions(Config, Node, []),
+    [] = get_subscriptions(Config, Node),
+    ok = set_affiliations(Config, Node, [{Peer, outcast}, {MyJID, owner}]),
+    [_, _] = get_affiliations(Config, Node),
+    ok = delete_node(Config, Node),
+    wait_for_master(Config),
+    pubsub_affiliations_slave(Config, get_event(Config));
+pubsub_affiliations_slave(Config, disconnect) ->
     disconnect(Config).
 
+pubsub_authorize_master(Config) ->
+    send(Config, #presence{}),
+    ?recv1(#presence{}),
+    Peer = ?config(slave, Config),
+    PJID = pubsub_jid(Config),
+    NodeConfig = set_opts(default_node_config(Config),
+                         [{access_model, authorize}]),
+    Node = ?config(pubsub_node, Config),
+    Node = create_node(Config, Node, NodeConfig),
+    wait_for_slave(Config),
+    #message{sub_els = [#xdata{fields = F1}]} = recv(Config),
+    C1 = pubsub_subscribe_authorization:decode(F1),
+    Node = proplists:get_value(node, C1),
+    Peer = proplists:get_value(subscriber_jid, C1),
+    %% Deny it at first
+    Deny = #xdata{type = submit,
+                 fields = pubsub_subscribe_authorization:encode(
+                            [{node, Node},
+                             {subscriber_jid, Peer},
+                             {allow, false}])},
+    send(Config, #message{to = PJID, sub_els = [Deny]}),
+    %% We should not have any subscriptions
+    [] = get_subscriptions(Config, Node),
+    wait_for_slave(Config),
+    #message{sub_els = [#xdata{fields = F2}]} = recv(Config),
+    C2 = pubsub_subscribe_authorization:decode(F2),
+    Node = proplists:get_value(node, C2),
+    Peer = proplists:get_value(subscriber_jid, C2),
+    %% Now we accept is as the peer is very insisting ;)
+    Approve = #xdata{type = submit,
+                    fields = pubsub_subscribe_authorization:encode(
+                               [{node, Node},
+                                {subscriber_jid, Peer},
+                                {allow, true}])},
+    send(Config, #message{to = PJID, sub_els = [Approve]}),
+    wait_for_slave(Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+pubsub_authorize_slave(Config) ->
+    Node = ?config(pubsub_node, Config),
+    MyJID = my_jid(Config),
+    wait_for_master(Config),
+    #ps_subscription{type = pending} = subscribe_node(Config, Node),
+    %% We're denied at first
+    ?recv1(#message{
+             sub_els =
+                 [#ps_event{
+                     subscription = #ps_subscription{type = none,
+                                                     jid = MyJID}}]}),
+    wait_for_master(Config),
+    #ps_subscription{type = pending} = subscribe_node(Config, Node),
+    %% Now much better!
+    ?recv1(#message{
+             sub_els =
+                 [#ps_event{
+                     subscription = #ps_subscription{type = subscribed,
+                                                     jid = MyJID}}]}),
+    wait_for_master(Config),
+    disconnect(Config).
+
+create_node(Config, Node) ->
+    create_node(Config, Node, undefined).
+
+create_node(Config, Node, Options) ->
+    PJID = pubsub_jid(Config),
+    NodeConfig = if is_list(Options) ->
+                        #xdata{type = submit,
+                               fields = pubsub_node_config:encode(Options)};
+                   true ->
+                        undefined
+                end,
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{create = Node,
+                                         configure = {<<>>, NodeConfig}}]}) of
+       #iq{type = result, sub_els = [#pubsub{create = NewNode}]} ->
+           NewNode;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+delete_node(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{delete = {Node, <<>>}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+purge_node(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{purge = Node}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_default_node_config(Config) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub_owner{default = {<<>>, undefined}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub_owner{default = {<<>>, NodeConfig}}]} ->
+           pubsub_node_config:decode(NodeConfig#xdata.fields);
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_node_config(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub_owner{configure = {Node, undefined}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub_owner{configure = {Node, NodeConfig}}]} ->
+           pubsub_node_config:decode(NodeConfig#xdata.fields);
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+set_node_config(Config, Node, Options) ->
+    PJID = pubsub_jid(Config),
+    NodeConfig = #xdata{type = submit,
+                       fields = pubsub_node_config:encode(Options)},
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{configure =
+                                                   {Node, NodeConfig}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+publish_item(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    ItemID = randoms:get_string(),
+    Item = #ps_item{id = ItemID, xml_els = [xmpp:encode(#presence{id = ItemID})]},
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{publish = #ps_publish{
+                                                      node = Node,
+                                                      items = [Item]}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub{publish = #ps_publish{
+                                           node = Node,
+                                           items = [#ps_item{id = ItemID}]}}]} ->
+           Item;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_items(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub{items = #ps_items{node = Node}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub{items = #ps_items{node = Node, items = Items}}]} ->
+           Items;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+delete_item(Config, Node, I) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{retract =
+                                             #ps_retract{
+                                                node = Node,
+                                                items = [#ps_item{id = I}]}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+subscribe_node(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    MyJID = my_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{subscribe = #ps_subscribe{
+                                                        node = Node,
+                                                        jid = MyJID}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub{
+                         subscription = #ps_subscription{
+                                           node = Node,
+                                           jid = MyJID} = Sub}]} ->
+           Sub;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+unsubscribe_node(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    MyJID = my_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{
+                                    unsubscribe = #ps_unsubscribe{
+                                                     node = Node,
+                                                     jid = MyJID}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_affiliations(Config) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub{affiliations = {<<>>, []}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub{affiliations = {<<>>, Affs}}]} ->
+           Affs;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_affiliations(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub_owner{affiliations = {Node, []}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub_owner{affiliations = {Node, Affs}}]} ->
+           Affs;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+set_affiliations(Config, Node, JTs) ->
+    PJID = pubsub_jid(Config),
+    Affs = [#ps_affiliation{jid = J, type = T} || {J, T} <- JTs],
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{affiliations =
+                                                   {Node, Affs}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_subscriptions(Config) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub{subscriptions = {<<>>, []}}]}) of
+       #iq{type = result, sub_els = [#pubsub{subscriptions = {<<>>, Subs}}]} ->
+           Subs;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_subscriptions(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub_owner{subscriptions = {Node, []}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub_owner{subscriptions = {Node, Subs}}]} ->
+           Subs;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+set_subscriptions(Config, Node, JTs) ->
+    PJID = pubsub_jid(Config),
+    Subs = [#ps_subscription{jid = J, type = T} || {J, T} <- JTs],
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{subscriptions =
+                                                   {Node, Subs}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+default_node_config(Config) ->
+    [{title, ?config(pubsub_node_title, Config)},
+     {notify_delete, false},
+     {send_last_published_item, never}].
+
 mix_master(Config) ->
     MIX = mix_jid(Config),
     Room = mix_room_jid(Config),
@@ -1627,7 +2250,7 @@ muc_mam_master(Config) ->
                               to = Room}),
     %% Find the MAM field in the config and enable it
     NewFields = lists:flatmap(
-                 fun(#xdata_field{var = <<"muc#roomconfig_mam">> = Var}) ->
+                 fun(#xdata_field{var = <<"mam">> = Var}) ->
                          [#xdata_field{var = Var, values = [<<"1">>]}];
                     (_) ->
                          []
@@ -1934,39 +2557,36 @@ muc_slave(Config) ->
     disconnect(Config).
 
 muc_register_nick(Config, MUC, PrevNick, Nick) ->
-    {Registered, PrevNickVals} = if PrevNick /= <<"">> ->
-                                        {true, [PrevNick]};
-                                   true ->
-                                        {false, []}
-                                end,
+    PrevRegistered = if PrevNick /= <<"">> -> true;
+                       true -> false
+                    end,
+    NewRegistered = if Nick /= <<"">> -> true;
+                      true -> false
+                   end,
     %% Request register form
     #iq{type = result,
-       sub_els = [#register{registered = Registered,
+       sub_els = [#register{registered = PrevRegistered,
                             xdata = #xdata{type = form,
                                            fields = FsWithoutNick}}]} =
        send_recv(Config, #iq{type = get, to = MUC,
                              sub_els = [#register{}]}),
-    %% Check if 'nick' field presents
-    #xdata_field{type = 'text-single',
-                var = <<"nick">>,
-                values = PrevNickVals} =
-       lists:keyfind(<<"nick">>, #xdata_field.var, FsWithoutNick),
-    X = #xdata{type = submit,
-              fields = [#xdata_field{var = <<"nick">>, values = [Nick]}]},
+    %% Check if previous nick is registered
+    PrevNick = proplists:get_value(
+                roomnick, muc_register:decode(FsWithoutNick)),
+    X = #xdata{type = submit, fields = muc_register:encode([{roomnick, Nick}])},
     %% Submitting form
     #iq{type = result, sub_els = []} =
        send_recv(Config, #iq{type = set, to = MUC,
                              sub_els = [#register{xdata = X}]}),
-    %% Check if the nick was registered
+    %% Check if new nick was registered
     #iq{type = result,
-       sub_els = [#register{registered = true,
+       sub_els = [#register{registered = NewRegistered,
                             xdata = #xdata{type = form,
                                            fields = FsWithNick}}]} =
        send_recv(Config, #iq{type = get, to = MUC,
                              sub_els = [#register{}]}),
-    #xdata_field{type = 'text-single', var = <<"nick">>,
-                values = [Nick]} =
-       lists:keyfind(<<"nick">>, #xdata_field.var, FsWithNick).
+    Nick = proplists:get_value(
+            roomnick, muc_register:decode(FsWithNick)).
 
 muc_register_master(Config) ->
     MUC = muc_jid(Config),
@@ -1980,17 +2600,23 @@ muc_register_master(Config) ->
     muc_register_nick(Config, MUC, <<"">>, <<"master2">>),
     %% Now register nick "master"
     muc_register_nick(Config, MUC, <<"master2">>, <<"master">>),
+    %% Wait for slave to fail trying to register nick "master"
+    wait_for_slave(Config),
+    wait_for_slave(Config),
+    %% Now register empty ("") nick, which means we're unregistering
+    muc_register_nick(Config, MUC, <<"master">>, <<"">>),
     disconnect(Config).
 
 muc_register_slave(Config) ->
     MUC = muc_jid(Config),
+    wait_for_master(Config),
     %% Trying to register occupied nick "master"
-    X = #xdata{type = submit,
-              fields = [#xdata_field{var = <<"nick">>,
-                                     values = [<<"master">>]}]},
+    Fs = muc_register:encode([{roomnick, <<"master">>}]),
+    X = #xdata{type = submit, fields = Fs},
     #iq{type = error} =
        send_recv(Config, #iq{type = set, to = MUC,
                              sub_els = [#register{xdata = X}]}),
+    wait_for_master(Config),
     disconnect(Config).
 
 announce_master(Config) ->
@@ -2741,6 +3367,12 @@ socks5_send(Sock, Data) ->
 socks5_recv(Sock, Data) ->
     {ok, Data} = gen_tcp:recv(Sock, size(Data)).
 
+set_opts(Config, Options) ->
+    lists:foldl(
+      fun({Opt, Val}, Acc) ->
+             lists:keystore(Opt, 1, Acc, {Opt, Val})
+      end, Config, Options).
+
 %%%===================================================================
 %%% SQL stuff
 %%%===================================================================
index 42c5dcfbe1e691e5b4e6ec9794a6c55e1fdf2e28..ed1cbd83d3f9da24f42e5f0d4cf6a0ca1fd55b33 100644 (file)
@@ -86,6 +86,8 @@ init_config(Config) ->
      {lang, <<"en">>},
      {base_dir, BaseDir},
      {socket, undefined},
+     {pubsub_node, <<"node!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
+     {pubsub_node_title, <<"title!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
      {resource, <<"resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
      {master_resource, <<"master_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
      {slave_resource, <<"slave_resource!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>},
index 4110da0df13eb182056a1d9f41ce0eab385d89ec..00239f8cf0413624554118e189159eec95b41232 100644 (file)
                  end
          end)()).
 
+-define(match(Pattern, Result),
+       case Result of
+           Pattern ->
+               Pattern;
+           Mismatch ->
+               suite:match_failure([Mismatch], [??Pattern])
+       end).
+
 -define(COMMON_VHOST, <<"localhost">>).
 -define(MNESIA_VHOST, <<"mnesia.localhost">>).
 -define(REDIS_VHOST, <<"redis.localhost">>).