[Asterisk-code-review] res_geolocation: eprofile, parsing, tests and more (asterisk[development/16/geolocation])

George Joseph asteriskteam at digium.com
Thu Mar 3 10:55:42 CST 2022


George Joseph has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/18127 )


Change subject: res_geolocation: eprofile, parsing, tests and more
......................................................................

res_geolocation: eprofile, parsing, tests and more

There's a lot of stuff in this commit so hold on to
your knickers.

* Added ability to embed xml and xslt documents into a module.
  It's actually quite simnple and works for gcc and clang.
  See Makefile.rules, res/Makefile and geoloc_geopriv for
  more info.

* Fixed an issue with ast_variable_list_from_string where
  quotes weren't being handled correctly.  This required
  the addition of ast_strsep_strict() to utils.c

* Renamed some things...
   pidf_lo_section was renamed to just pidf_element.
   The "effective_profile" API names were shortened to
   "eprofile".

* Added a ast_xml_find_child_element() convenience macro.

* Added pidf_to_eprofile.xslt that condenses pidf-lo xml
  documents to a simpler format that's common to both GML
  and civicAddress formats.  This cut quite a bit of custom
  parsing code.

* Effective profiles can now be created directly from a pidf
  xml document or a URI.

* Created unit tests for effective profile creation.

Change-Id: I50b66bd041b2a62ab329406f20dbaeef1fa68fc1
---
M Makefile.rules
M configs/samples/geolocation.conf.sample
M include/asterisk/config.h
M include/asterisk/res_geolocation.h
M include/asterisk/strings.h
M include/asterisk/xml.h
M main/config.c
M main/utils.c
M res/Makefile
M res/res_geolocation.c
M res/res_geolocation/geoloc_channel.c
A res/res_geolocation/geoloc_common.c
M res/res_geolocation/geoloc_config.c
M res/res_geolocation/geoloc_doc.xml
A res/res_geolocation/geoloc_eprofile.c
M res/res_geolocation/geoloc_private.h
A res/res_geolocation/pidf_lo_test.xml
A res/res_geolocation/pidf_to_eprofile.xslt
M tests/test_config.c
19 files changed, 1,206 insertions(+), 132 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/27/18127/1

diff --git a/Makefile.rules b/Makefile.rules
index 934e44a..e6b6589 100644
--- a/Makefile.rules
+++ b/Makefile.rules
@@ -204,4 +204,19 @@
 	$(ECHO_PREFIX) echo "   [LD] $^ -> $@"
 	$(CMD_PREFIX) $(CXX) -o $@ $(PTHREAD_CFLAGS) $(_ASTLDFLAGS) $^ $(CXX_LIBS) $(ASTLDFLAGS)
 
+# These CC commands just create an object file with the input file embedded in it.
+# It can be access from code as follows:
+# If your input file is named abc_def.xml...
+#
+# extern const uint8_t _binary_abc_def_xml_start[];
+# extern const uint8_t _binary_abc_def_xml_end[];
+# extern const size_t _binary_abc_def_xml_size;
+%.o: %.xml
+	$(ECHO_PREFIX) echo "   [LD] $^ -> $@"
+	$(CMD_PREFIX) $(CC) -g -nostartfiles  -nodefaultlibs -nostdlib -r -Wl,-b,binary -o $@ $^
+
+%.o: %.xslt
+	$(ECHO_PREFIX) echo "   [LD] $^ -> $@"
+	$(CMD_PREFIX) $(CC) -g -nostartfiles  -nodefaultlibs -nostdlib -r -Wl,-b,binary -o $@ $^
+
 dist-clean:: clean
diff --git a/configs/samples/geolocation.conf.sample b/configs/samples/geolocation.conf.sample
index f8909ec..7cf0bb5 100644
--- a/configs/samples/geolocation.conf.sample
+++ b/configs/samples/geolocation.conf.sample
@@ -8,9 +8,9 @@
   5. Reference
 --;
 
-;-----------------------------------------------------------------------
+;--=====================================================================
   Overview
------------------------------------------------------------------------;
+=====================================================================--;
 
 ;--
 
@@ -43,9 +43,9 @@
 being sent to an emergency response center or even call failure for
 which you are solely responsible.
 
-;-----------------------------------------------------------------------
+;--=====================================================================
   Location Object Description
------------------------------------------------------------------------;
+=====================================================================--;
 
 ;-- type -------------------------------------------
 Must be "location" to identify this configuration section as a
@@ -224,16 +224,16 @@
 ---------------------------------------------------;
 
 
-;-----------------------------------------------------------------------
+;--=====================================================================
   Profile Object Descriptions
------------------------------------------------------------------------;
+=====================================================================--;
 
 ;-- type -------------------------------------------
 Must be "profile" to identify this configuration section as a
 Geolocation Profile object.
 
 
-;-- pidf_lo_section -------------------------------
+;-- pidf_element ----------------------------------
 
 If the format is civicAddress or GML, this sets the PIDF element into
 which the location information will be placed.
@@ -323,9 +323,9 @@
 ---------------------------------------------------;
 
 
-;---------------------------------------------------
+;--=====================================================================
   Examples
----------------------------------------------------;
+=====================================================================--;
 
 ; Create a Location Object for a building
 [building_1]
@@ -392,9 +392,9 @@
 ; Geolocation: <https://some.company.com?building=bldg1&number=5678>
 
 
-;-------------------------------
------------ REFERENCE ----------
--------------------------------;
+;--=====================================================================
+              REFERENCE
+=====================================================================--;
 ;--
 
 [RFC4119]  A Presence-based GEOPRIV Location Object Format
diff --git a/include/asterisk/config.h b/include/asterisk/config.h
index afac229..a05e126 100644
--- a/include/asterisk/config.h
+++ b/include/asterisk/config.h
@@ -1015,12 +1015,15 @@
  * \param name_value_separator The string used to separate each item's name and value.
  *                             Only the first character in the string will be used.
  *                             If NULL, "=" will be used.
+ * \param quote_str            The string used to quote values.
+ *                             Only the first character in the string will be used.
+ *                             If NULL, '"' will be used.
  *
  * \retval A pointer to a list of ast_variables.
  * \retval NULL if there was an error or no variables could be parsed.
  */
 struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator,
-	const char *name_value_separator);
+	const char *name_value_separator, const char *quote_str);
 
 /*!
  * \brief Update variable value within a config
diff --git a/include/asterisk/res_geolocation.h b/include/asterisk/res_geolocation.h
index 6230cf2..7fae4bf 100644
--- a/include/asterisk/res_geolocation.h
+++ b/include/asterisk/res_geolocation.h
@@ -20,12 +20,14 @@
 #define INCLUDE_ASTERISK_RES_GEOLOCATION_H_
 
 #include "asterisk/sorcery.h"
+#include "asterisk/config.h"
+#include "asterisk/xml.h"
 
-enum ast_geoloc_pidf_section {
-	AST_PIDF_SECTION_NONE = 0,
-	AST_PIDF_SECTION_TUPLE,
-	AST_PIDF_SECTION_DEVICE,
-	AST_PIDF_SECTION_PERSON
+enum ast_geoloc_pidf_element {
+	AST_PIDF_ELEMENT_NONE = 0,
+	AST_PIDF_ELEMENT_TUPLE,
+	AST_PIDF_ELEMENT_DEVICE,
+	AST_PIDF_ELEMENT_PERSON
 };
 
 enum ast_geoloc_format {
@@ -44,6 +46,9 @@
 
 struct ast_geoloc_location {
 	SORCERY_OBJECT(details);
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(method);
+	);
 	enum ast_geoloc_format format;
 	struct ast_variable *location_vars;
 };
@@ -53,7 +58,7 @@
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(location_reference);
 	);
-	enum ast_geoloc_pidf_section pidf_section;
+	enum ast_geoloc_pidf_element pidf_element;
 	enum ast_geoloc_location_disposition location_disposition;
 	int geolocation_routing;
 	int send_location;
@@ -66,8 +71,9 @@
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(id);
 		AST_STRING_FIELD(location_reference);
+		AST_STRING_FIELD(method);
 	);
-	enum ast_geoloc_pidf_section pidf_section;
+	enum ast_geoloc_pidf_element pidf_element;
 	enum ast_geoloc_location_disposition location_disposition;
 	int geolocation_routing;
 	int send_location;
@@ -141,8 +147,72 @@
 enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(const struct ast_variable *varlist,
 	const char **result);
 
-struct ast_datastore *ast_geoloc_datastore_create(const char *profile_name);
+/*!
+ * \brief Create a geoloc datastore from a profile name
+ *
+ * \param profile_name The name of the profile to use.
+ *
+ * \return The datastore.
+ */
+struct ast_datastore *ast_geoloc_datastore_create_from_profile_name(const char *profile_name);
 
-struct ast_geoloc_effective_profile *ast_geoloc_effective_profile_create(struct ast_geoloc_profile *profile);
+/*!
+ * \brief Create a geoloc datastore from an effective profile.
+ *
+ * \param eprofile The effective profile to use.
+ *
+ * \return The datastore.
+ */
+struct ast_datastore *ast_geoloc_datastore_create_from_eprofile(
+	struct ast_geoloc_effective_profile *eprofile);
+
+/*!
+ * \brief Allocate a new, empty effective profile.
+ *
+ * \param name The profile's name
+ *
+ * \return The effective profile ao2 object.
+ */
+struct ast_geoloc_effective_profile *ast_geoloc_eprofile_alloc(const char *name);
+
+/*!
+ * \brief Allocate a new effective profile from an existing profile.
+ *
+ * \param profile The profile to use.
+ *
+ * \return The effective profile ao2 object.
+ */
+struct ast_geoloc_effective_profile *ast_geoloc_eprofile_create_from_profile(struct ast_geoloc_profile *profile);
+
+/*!
+ * \brief Allocate a new effective profile from an XML PIDF-LO document
+ *
+ * \param pidf_xmldoc       The ast_xml_doc to use.
+ * \param reference_string  An identifying string to use in error messages.
+ *
+ * \return The effective profile ao2 object.
+ */
+struct ast_geoloc_effective_profile *ast_geoloc_eprofile_create_from_pidf(
+	struct ast_xml_doc *pidf_xmldoc, const char *reference_string);
+
+/*!
+ * \brief Allocate a new effective profile from a URI.
+ *
+ * \param uri               The URI to use.
+ * \param reference_string  An identifying string to use in error messages.
+ *
+ * \return The effective profile ao2 object.
+ */
+struct ast_geoloc_effective_profile *ast_geoloc_eprofile_create_from_uri(const char *uri,
+	const char *reference_string);
+
+/*!
+ * \brief Refresh the effective profile with any changed info.
+ *
+ * \param eprofile The eprofile to refresh.
+ *
+ * \return 0 on success, any other value on error.
+ */
+int ast_geoloc_eprofile_refresh_location(struct ast_geoloc_effective_profile *eprofile);
 
 #endif /* INCLUDE_ASTERISK_RES_GEOLOCATION_H_ */
diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h
index ab46273..5c14f4a 100644
--- a/include/asterisk/strings.h
+++ b/include/asterisk/strings.h
@@ -308,6 +308,8 @@
  */
 char *ast_strsep(char **s, const char sep, uint32_t flags);
 
+char *ast_strsep_strict(char **s, const char sep, const char quote, uint32_t flags);
+
 /*!
   \brief Strip backslash for "escaped" semicolons,
 	the string to be stripped (will be modified).
diff --git a/include/asterisk/xml.h b/include/asterisk/xml.h
index 9d43560..5564051 100644
--- a/include/asterisk/xml.h
+++ b/include/asterisk/xml.h
@@ -189,6 +189,18 @@
 struct ast_xml_ns *ast_xml_find_namespace(struct ast_xml_doc *doc, struct ast_xml_node *node, const char *ns_name);
 
 /*!
+ * \brief Find a direct child element by name.
+ * \param parent_node This is the parent node to search.
+ * \param name Node name to find.
+ * \param attrname attribute name to match (if NULL it won't be matched).
+ * \param attrvalue attribute value to match (if NULL it won't be matched).
+ * \retval NULL if not found.
+ * \return The node on success.
+ */
+#define ast_xml_find_child_element(_parent_node, _name, _attrname, _attrvalue) \
+    ast_xml_find_element(ast_xml_node_get_children(_parent_node), _name, _attrname, _attrvalue)
+
+/*!
  * \brief Get the prefix of a namespace.
  * \param ns The namespace
  * \return The prefix of the namespace.
diff --git a/main/config.c b/main/config.c
index 122e7aa..07925bd 100644
--- a/main/config.c
+++ b/main/config.c
@@ -723,10 +723,11 @@
 }
 
 struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator,
-	const char *name_value_separator)
+	const char *name_value_separator, const char *quote_str)
 {
 	char item_sep;
 	char nv_sep;
+	char quote;
 	struct ast_variable *new_list = NULL;
 	struct ast_variable *new_var = NULL;
 	char *item_string;
@@ -740,11 +741,14 @@
 
 	item_sep = ast_strlen_zero(item_separator) ? ',' : item_separator[0];
 	nv_sep = ast_strlen_zero(name_value_separator) ? '=' : name_value_separator[0];
+	quote = ast_strlen_zero(quote_str) ? '"' : quote_str[0];
 	item_string = ast_strip(ast_strdupa(input));
 
-	while ((item = ast_strsep(&item_string, item_sep, AST_STRSEP_ALL))) {
-		item_name = ast_strsep(&item, nv_sep, AST_STRSEP_ALL);
-		item_value = ast_strsep(&item, nv_sep, AST_STRSEP_ALL);
+	while ((item = ast_strsep_strict(&item_string, item_sep, quote, AST_STRSEP_ALL))) {
+		item_name = ast_strsep_strict(&item, nv_sep, quote, AST_STRSEP_ALL);
+
+		item_value = ast_strsep_strict(&item, nv_sep, quote, AST_STRSEP_ALL);
+
 		new_var = ast_variable_new(item_name, item_value, "");
 		if (!new_var) {
 			ast_variables_destroy(new_list);
diff --git a/main/utils.c b/main/utils.c
index 29676fa..38c5b0c 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -1859,6 +1859,67 @@
 	return st;
 }
 
+char *ast_strsep_strict(char **iss, const char sep, const char quote, uint32_t flags)
+{
+	char *st = *iss;
+	char *is;
+	int inquote = 0;
+	int found = 0;
+	char stack[8];
+	const char qstr[] = { quote };
+
+	if (ast_strlen_zero(st)) {
+		return NULL;
+	}
+
+	memset(stack, 0, sizeof(stack));
+
+	for(is = st; *is; is++) {
+		if (*is == '\\') {
+			if (*++is != '\0') {
+				is++;
+			} else {
+				break;
+			}
+		}
+
+		if (*is == quote) {
+			if (*is == stack[inquote]) {
+				stack[inquote--] = '\0';
+			} else {
+				if (++inquote >= sizeof(stack)) {
+					return NULL;
+				}
+				stack[inquote] = *is;
+			}
+		}
+
+		if (*is == sep && !inquote) {
+			*is = '\0';
+			found = 1;
+			*iss = is + 1;
+			break;
+		}
+	}
+	if (!found) {
+		*iss = NULL;
+	}
+
+	if (flags & AST_STRSEP_STRIP) {
+		st = ast_strip_quoted(st, qstr, qstr);
+	}
+
+	if (flags & AST_STRSEP_TRIM) {
+		st = ast_strip(st);
+	}
+
+	if (flags & AST_STRSEP_UNESCAPE) {
+		ast_unescape_quoted(st);
+	}
+
+	return st;
+}
+
 char *ast_unescape_semicolon(char *s)
 {
 	char *e;
diff --git a/res/Makefile b/res/Makefile
index a6dd181..6abdd2d 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -69,9 +69,13 @@
 $(call MOD_ADD_C,res_stir_shaken,$(wildcard res_stir_shaken/*.c))
 $(call MOD_ADD_C,res_geolocation,$(wildcard res_geolocation/*.c))
 
+# These are the xml and xslt files to be embedded
+res_geolocation.so: res_geolocation/pidf_lo_test.o res_geolocation/pidf_to_eprofile.o
+
 res_parking.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
 snmp/agent.o: _ASTCFLAGS+=-fPIC
 res_snmp.o: _ASTCFLAGS+=-fPIC
 
 # Dependencies for res_ari_*.so are generated, so they're in this file
 include ari.make
+
diff --git a/res/res_geolocation.c b/res/res_geolocation.c
index 09a9a4b..9108102 100644
--- a/res/res_geolocation.c
+++ b/res/res_geolocation.c
@@ -17,6 +17,8 @@
  */
 
 /*** MODULEINFO
+	<depend>libxml2</depend>
+	<depend>libxslt</depend>
 	<support_level>core</support_level>
  ***/
 
@@ -24,21 +26,6 @@
 #include "asterisk.h"
 #include "res_geolocation/geoloc_private.h"
 
-static const char *result_names[] = {
-	"Success",
-	"Missing type",
-	"Invalid shape type",
-	"Invalid variable name",
-	"Not enough variables",
-	"Too many variables",
-	"Invalid variable value"
-};
-
-const char *ast_geoloc_validate_result_to_str(enum ast_geoloc_validate_result result)
-{
-	return result_names[result];
-}
-
 static int reload_module(void)
 {
 	int res = 0;
@@ -55,6 +42,10 @@
 	if (res) {
 		return AST_MODULE_LOAD_DECLINE;
 	}
+	res = geoloc_eprofile_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
 	res = geoloc_dialplan_reload();
 	if (res) {
 		return AST_MODULE_LOAD_DECLINE;
@@ -71,8 +62,9 @@
 {
 	int res = 0;
 
-	res += geoloc_channel_reload();
+	res += geoloc_channel_unload();
 	res += geoloc_dialplan_unload();
+	res += geoloc_eprofile_unload();
 	res += geoloc_config_unload();
 	res += geoloc_gml_unload();
 	res += geoloc_civicaddr_unload();
@@ -84,23 +76,29 @@
 {
 	int res = 0;
 
-	res = geoloc_gml_load();
-	if (res) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
 	res = geoloc_civicaddr_load();
 	if (res) {
 		unload_module();
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
+	res = geoloc_gml_load();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
 	res = geoloc_config_load();
 	if (res) {
 		unload_module();
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
+	res = geoloc_eprofile_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
 	res = geoloc_dialplan_load();
 	if (res) {
 		unload_module();
diff --git a/res/res_geolocation/geoloc_channel.c b/res/res_geolocation/geoloc_channel.c
index 719e214..aa8ad87 100644
--- a/res/res_geolocation/geoloc_channel.c
+++ b/res/res_geolocation/geoloc_channel.c
@@ -18,6 +18,7 @@
 
 #include "asterisk.h"
 #include "asterisk/datastore.h"
+#include "asterisk/res_geolocation.h"
 #include "geoloc_private.h"
 
 
@@ -36,7 +37,7 @@
 };
 
 
-struct ast_datastore *ast_geoloc_datastore_create(const char *profile_name)
+struct ast_datastore *ast_geoloc_datastore_create_from_profile_name(const char *profile_name)
 {
 	struct ast_datastore *ds = NULL;
 	struct ast_geoloc_effective_profile *eprofile = NULL;
@@ -59,7 +60,7 @@
 		return NULL;
 	}
 
-	eprofile = ast_geoloc_effective_profile_create(profile);
+	eprofile = ast_geoloc_eprofile_create_from_profile(profile);
 	ao2_ref(profile, -1);
 	if (!eprofile) {
 		ast_datastore_free(ds);
diff --git a/res/res_geolocation/geoloc_common.c b/res/res_geolocation/geoloc_common.c
new file mode 100644
index 0000000..bb24a31
--- /dev/null
+++ b/res/res_geolocation/geoloc_common.c
@@ -0,0 +1,36 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "geoloc_private.h"
+
+static const char *result_names[] = {
+	"Success",
+	"Missing type",
+	"Invalid shape type",
+	"Invalid variable name",
+	"Not enough variables",
+	"Too many variables",
+	"Invalid variable value"
+};
+
+const char *ast_geoloc_validate_result_to_str(enum ast_geoloc_validate_result result)
+{
+	return result_names[result];
+}
+
diff --git a/res/res_geolocation/geoloc_config.c b/res/res_geolocation/geoloc_config.c
index c2fa2d0..b707c89 100644
--- a/res/res_geolocation/geoloc_config.c
+++ b/res/res_geolocation/geoloc_config.c
@@ -24,7 +24,7 @@
 
 static struct ast_sorcery *geoloc_sorcery;
 
-static const char *pidf_section_names[] = {
+static const char *pidf_element_names[] = {
 	"<none>",
 	"tuple",
 	"device",
@@ -51,16 +51,22 @@
 static void geoloc_location_destructor(void *obj) {
 	struct ast_geoloc_location *location = obj;
 
+	ast_string_field_free_memory(location);
 	ast_variables_destroy(location->location_vars);
 }
 
 static void *geoloc_location_alloc(const char *name)
 {
-	return ast_sorcery_generic_alloc(sizeof(struct ast_geoloc_location), geoloc_location_destructor);
+	struct ast_geoloc_location *location = ast_sorcery_generic_alloc(sizeof(struct ast_geoloc_location), geoloc_location_destructor);
+	if (location) {
+		ast_string_field_init(location, 128);
+	}
+
+	return location;
 }
 
 
-CONFIG_ENUM(profile, pidf_section)
+CONFIG_ENUM(profile, pidf_element)
 CONFIG_ENUM(profile, location_disposition)
 CONFIG_VAR_LIST(profile, location_refinement)
 CONFIG_VAR_LIST(profile, location_variables)
@@ -86,77 +92,6 @@
 	return profile;
 }
 
-static void geoloc_effective_profile_destructor(void *obj) {
-	struct ast_geoloc_effective_profile *eprofile = obj;
-
-	ast_string_field_free_memory(eprofile);
-	ast_variables_destroy(eprofile->location_refinement);
-	ast_variables_destroy(eprofile->location_variables);
-	ast_variables_destroy(eprofile->effective_location);
-	ast_variables_destroy(eprofile->usage_rules_vars);
-}
-
-static void *geoloc_effective_profile_alloc(const char *name)
-{
-	struct ast_geoloc_effective_profile *eprofile = ao2_alloc_options(sizeof(*eprofile),
-		geoloc_effective_profile_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
-
-	ast_string_field_init(eprofile, 256);
-	ast_string_field_set(eprofile, id, name);
-
-	return eprofile;
-}
-
-struct ast_geoloc_effective_profile *ast_geoloc_effective_profile_create(struct ast_geoloc_profile *profile)
-{
-	struct ast_geoloc_effective_profile *eprofile;
-	const char *profile_id;
-	struct ast_geoloc_location *loc = NULL;
-
-	if (!profile) {
-		return NULL;
-	}
-	profile_id = ast_sorcery_object_get_id(profile);
-	eprofile = geoloc_effective_profile_alloc(profile_id);
-	if (!eprofile) {
-		return NULL;
-	}
-
-	ao2_lock(profile);
-	ast_string_field_set(eprofile, location_reference, profile->location_reference);
-	eprofile->geolocation_routing = profile->geolocation_routing;
-	eprofile->pidf_section = profile->pidf_section;
-	eprofile->location_refinement = profile->location_refinement ? ast_variables_dup(profile->location_refinement) : NULL;
-	eprofile->location_variables = profile->location_variables ? ast_variables_dup(profile->location_variables) : NULL;
-	eprofile->usage_rules_vars = profile->usage_rules_vars ? ast_variables_dup(profile->usage_rules_vars) : NULL;
-	eprofile->location_disposition = profile->location_disposition;
-	eprofile->send_location = profile->send_location;
-	ao2_unlock(profile);
-
-	if (!ast_strlen_zero(eprofile->location_reference)) {
-		loc = ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", eprofile->location_reference);
-		if (loc) {
-			struct ast_variable *var;
-			eprofile->format = loc->format;
-			eprofile->location_vars = ast_variables_dup(loc->location_vars);
-			ao2_ref(loc, -1);
-
-			eprofile->effective_location = ast_variables_dup(loc->location_vars);
-			for (var = profile->location_refinement; var; var = var->next) {
-				struct ast_variable *newvar = ast_variable_new(var->name, var->value, "");
-				if (ast_variable_list_replace(&eprofile->effective_location, newvar)) {
-					ast_variable_list_append(&eprofile->effective_location, newvar);
-				}
-			}
-		} else {
-			ast_log(LOG_ERROR, "Profile '%s' referenced location '%s' does not exist!", profile_id,
-				eprofile->location_reference);
-		}
-	}
-
-	return eprofile;
-}
-
 static int geoloc_location_apply_handler(const struct ast_sorcery *sorcery, void *obj)
 {
 	struct ast_geoloc_location *location = obj;
@@ -498,7 +433,7 @@
 		struct ast_str *refinement_str = NULL;
 		struct ast_str *variables_str = NULL;
 		struct ast_str *resolved_str = NULL;
-		struct ast_geoloc_effective_profile *eprofile = ast_geoloc_effective_profile_create(profile);
+		struct ast_geoloc_effective_profile *eprofile = ast_geoloc_eprofile_create_from_profile(profile);
 		ao2_ref(profile, -1);
 
 		if (!ast_strlen_zero(eprofile->location_reference)) {
@@ -515,7 +450,7 @@
 			"id:                            %-s\n"
 			"received_location_disposition: %-s\n"
 			"send_location:                 %-s\n"
-			"pidf_section:                  %-s\n"
+			"pidf_element:                  %-s\n"
 			"location_reference:            %-s\n"
 			"Location_format:               %-s\n"
 			"location_reference_details:    %-s\n"
@@ -525,7 +460,7 @@
 			eprofile->id,
 			disposition,
 			eprofile->send_location ? "yes" : "no",
-			pidf_section_names[eprofile->pidf_section],
+			pidf_element_names[eprofile->pidf_element],
 			S_OR(eprofile->location_reference, "<none>"),
 			format_names[eprofile->format],
 			S_COR(loc_str, ast_str_buffer(loc_str), "<none>"),
@@ -591,6 +526,9 @@
 {
 	ast_cli_unregister_multiple(geoloc_location_cli_commands, ARRAY_LEN(geoloc_location_cli_commands));
 
+	ast_sorcery_object_unregister(geoloc_sorcery, "profile");
+	ast_sorcery_object_unregister(geoloc_sorcery, "location");
+
 	if (geoloc_sorcery) {
 		ast_sorcery_unref(geoloc_sorcery);
 	}
@@ -629,8 +567,8 @@
 	}
 
 	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "type", "", OPT_NOOP_T, 0, 0);
-	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "pidf_lo_section", AST_PIDF_SECTION_NONE,
-		pidf_section_handler, pidf_section_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "pidf_element", AST_PIDF_ELEMENT_NONE,
+		pidf_element_handler, pidf_element_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "location_reference", "", OPT_STRINGFIELD_T,
 		0, STRFLDSET(struct ast_geoloc_profile, location_reference));
 	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "received_location_disposition", "discard",
diff --git a/res/res_geolocation/geoloc_doc.xml b/res/res_geolocation/geoloc_doc.xml
index 36bfa3c..8322585 100644
--- a/res/res_geolocation/geoloc_doc.xml
+++ b/res/res_geolocation/geoloc_doc.xml
@@ -57,7 +57,7 @@
 				<configOption name="type">
 					<synopsis>Must be of type 'profile'.</synopsis>
 				</configOption>
-				<configOption name="pidf_lo_section" default="device">
+				<configOption name="pidf_element" default="device">
 					<synopsis>PIDF-LO element to place this profile in</synopsis>
 					<description>
 						<enumlist>
diff --git a/res/res_geolocation/geoloc_eprofile.c b/res/res_geolocation/geoloc_eprofile.c
new file mode 100644
index 0000000..493265e
--- /dev/null
+++ b/res/res_geolocation/geoloc_eprofile.c
@@ -0,0 +1,468 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/xml.h"
+#include "geoloc_private.h"
+
+extern const uint8_t _binary_res_geolocation_pidf_to_eprofile_xslt_start[];
+extern const uint8_t _binary_res_geolocation_pidf_to_eprofile_xslt_end[];
+static size_t pidf_to_eprofile_xslt_size;
+
+extern const uint8_t _binary_res_geolocation_pidf_lo_test_xml_start[];
+extern const uint8_t _binary_res_geolocation_pidf_lo_test_xml_end[];
+static size_t pidf_lo_test_xml_size;
+
+static struct ast_xslt_doc *pidf_lo_xslt;
+
+static struct ast_sorcery *geoloc_sorcery;
+
+static void geoloc_effective_profile_destructor(void *obj) {
+	struct ast_geoloc_effective_profile *eprofile = obj;
+
+	ast_string_field_free_memory(eprofile);
+	ast_variables_destroy(eprofile->location_vars);
+	ast_variables_destroy(eprofile->location_refinement);
+	ast_variables_destroy(eprofile->location_variables);
+	ast_variables_destroy(eprofile->effective_location);
+	ast_variables_destroy(eprofile->usage_rules_vars);
+}
+
+struct ast_geoloc_effective_profile *ast_geoloc_eprofile_alloc(const char *name)
+{
+	struct ast_geoloc_effective_profile *eprofile = ao2_alloc_options(sizeof(*eprofile),
+		geoloc_effective_profile_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+	ast_string_field_init(eprofile, 256);
+	ast_string_field_set(eprofile, id, name);
+
+	return eprofile;
+}
+
+int ast_geoloc_eprofile_refresh_location(struct ast_geoloc_effective_profile *eprofile)
+{
+	struct ast_geoloc_location *loc = NULL;
+	struct ast_variable *var;
+
+	if (!ast_strlen_zero(eprofile->location_reference)) {
+		loc = ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", eprofile->location_reference);
+		if (!loc) {
+			ast_log(LOG_ERROR, "Profile '%s' referenced location '%s' does not exist!", eprofile->id,
+				eprofile->location_reference);
+			return -1;
+		}
+
+		eprofile->format = loc->format;
+		ast_variables_destroy(eprofile->location_vars);
+		eprofile->location_vars = ast_variables_dup(loc->location_vars);
+		ao2_ref(loc, -1);
+	}
+
+	ast_variables_destroy(eprofile->effective_location);
+	eprofile->effective_location = ast_variables_dup(eprofile->location_vars);
+	if (eprofile->location_refinement) {
+		for (var = eprofile->location_refinement; var; var = var->next) {
+			struct ast_variable *newvar = ast_variable_new(var->name, var->value, "");
+			if (ast_variable_list_replace(&eprofile->effective_location, newvar)) {
+				ast_variable_list_append(&eprofile->effective_location, newvar);
+			}
+		}
+	}
+
+	return 0;
+}
+
+struct ast_geoloc_effective_profile *ast_geoloc_eprofile_create_from_profile(struct ast_geoloc_profile *profile)
+{
+	struct ast_geoloc_effective_profile *eprofile;
+	const char *profile_id = ast_sorcery_object_get_id(profile);
+
+	eprofile = ast_geoloc_eprofile_alloc(profile_id);
+
+	ao2_lock(profile);
+	ast_string_field_set(eprofile, location_reference, profile->location_reference);
+	eprofile->geolocation_routing = profile->geolocation_routing;
+	eprofile->pidf_element = profile->pidf_element;
+	eprofile->location_refinement = profile->location_refinement ? ast_variables_dup(profile->location_refinement) : NULL;
+	eprofile->location_variables = profile->location_variables ? ast_variables_dup(profile->location_variables) : NULL;
+	eprofile->usage_rules_vars = profile->usage_rules_vars ? ast_variables_dup(profile->usage_rules_vars) : NULL;
+	eprofile->location_disposition = profile->location_disposition;
+	eprofile->send_location = profile->send_location;
+	ao2_unlock(profile);
+
+	if (ast_geoloc_eprofile_refresh_location(eprofile) != 0) {
+		ao2_ref(eprofile, -1);
+		return NULL;
+	}
+
+	return eprofile;
+}
+
+struct ast_geoloc_effective_profile *ast_geoloc_eprofile_create_from_uri(const char *uri,
+	const char *reference_string)
+{
+	struct ast_geoloc_effective_profile *eprofile = NULL;
+	char *local_uri;
+
+	if (ast_strlen_zero(uri)) {
+		return NULL;
+	}
+	local_uri = ast_strdupa(uri);
+
+	if (local_uri[0] == '<') {
+		local_uri++;
+	}
+	if (ast_ends_with(local_uri, ">")) {
+		local_uri[strlen(local_uri)-1] = '\0';
+	}
+	ast_strip(local_uri);
+
+	eprofile = ast_geoloc_eprofile_alloc(local_uri);
+	if (!eprofile) {
+		return NULL;
+	}
+
+	eprofile->format = AST_GEOLOC_FORMAT_URI;
+	eprofile->location_vars = ast_variable_new("URI", local_uri, "");
+
+	return eprofile;
+}
+
+static struct ast_geoloc_effective_profile *geoloc_eprofile_create_from_xslt_result(
+	struct ast_xml_doc *result_doc,
+	const char *reference_string)
+{
+	struct ast_geoloc_effective_profile *eprofile;
+	struct ast_xml_node *presence = NULL;
+	struct ast_xml_node *pidf_element = NULL;
+	struct ast_xml_node *location_info = NULL;
+	struct ast_xml_node *usage_rules = NULL;
+	struct ast_xml_node *method = NULL;
+	const char *id;
+	const char *format_str;
+	const char *pidf_element_str;
+	const char *location_str;
+	const char *usage_str;
+	const char *method_str;
+	char *duped;
+
+	presence = ast_xml_get_root(result_doc);
+	pidf_element = ast_xml_node_get_children(presence);
+	location_info = ast_xml_find_child_element(pidf_element, "location-info", NULL, NULL);
+	usage_rules = ast_xml_find_child_element(pidf_element, "usage-rules", NULL, NULL);
+	method = ast_xml_find_child_element(pidf_element, "method", NULL, NULL);
+
+	id = S_OR(ast_xml_get_attribute(pidf_element, "id"), ast_xml_get_attribute(presence, "entity"));
+	eprofile = ast_geoloc_eprofile_alloc(id);
+	if (!eprofile) {
+		return NULL;
+	}
+
+	format_str = ast_xml_get_attribute(location_info, "format");
+	if (ast_strings_equal(format_str, "gml")) {
+		eprofile->format = AST_GEOLOC_FORMAT_GML;
+	} else if (ast_strings_equal(format_str, "civicAddress")) {
+		eprofile->format = AST_GEOLOC_FORMAT_CIVIC_ADDRESS;
+	} else {
+		ao2_ref(eprofile, -1);
+		ast_log(LOG_ERROR, "%s: Unknown format '%s'\n", reference_string, format_str);
+		return NULL;
+	}
+
+	pidf_element_str = ast_xml_get_attribute(pidf_element, "name");
+	eprofile->pidf_element = geoloc_pidf_element_str_to_enum(pidf_element_str);
+
+	location_str = ast_xml_get_text(location_info);
+	duped = ast_strdupa(location_str);
+	eprofile->location_vars = ast_variable_list_from_string(duped, ",", "=", "\"");
+	if (!eprofile->location_vars) {
+		ao2_ref(eprofile, -1);
+		ast_log(LOG_ERROR, "%s: Unable to create location variables from '%s'\n", reference_string, location_str);
+		return NULL;
+	}
+
+	usage_str = ast_xml_get_text(usage_rules);
+	duped = ast_strdupa(usage_str);
+	eprofile->usage_rules_vars = ast_variable_list_from_string(duped, ",", "=", "\"");
+
+	method_str = ast_xml_get_text(method);
+	ast_string_field_set(eprofile, method, method_str);
+
+	return eprofile;
+}
+
+struct ast_geoloc_effective_profile *ast_geoloc_eprofile_create_from_pidf(
+	struct ast_xml_doc *pidf_xmldoc, const char *reference_string)
+{
+	RAII_VAR(struct ast_xml_doc *, result_doc, NULL, ast_xml_close);
+	struct ast_geoloc_effective_profile *eprofile;
+
+	/*
+	 * The namespace prefixes used here (dm, def, gp, etc) don't have to match
+	 * the ones used in the received PIDF-LO document but they MUST match the
+	 * ones in the embedded pidf_to_eprofile stylesheet.
+	 *
+	 * RFC5491 Rule 8 states that...
+	 * Where a PIDF document contains more than one <geopriv>
+     * element, the priority of interpretation is given to the first
+     * <device> element in the document containing a location.  If no
+     * <device> element containing a location is present in the document,
+     * then priority is given to the first <tuple> element containing a
+     * location.  Locations contained in <person> tuples SHOULD only be
+     * used as a last resort.
+     *
+     * Reminder: xpath arrays are 1-based not 0-based.
+	 */
+	const char *find_device[] = { "path", "/def:presence/dm:device[.//gp:location-info][1]", NULL};
+	const char *find_tuple[] = { "path", "/def:presence/def:tuple[.//gp:location-info][1]", NULL};
+	const char *find_person[] = { "path", "/def:presence/dm:person[.//gp:location-info][1]", NULL};
+
+	result_doc = ast_xslt_apply(pidf_lo_xslt, pidf_xmldoc, find_device);
+	if (!result_doc || !ast_xml_node_get_children((struct ast_xml_node *)result_doc)) {
+		ast_xml_close(result_doc);
+		result_doc = ast_xslt_apply(pidf_lo_xslt, pidf_xmldoc, find_tuple);
+	}
+	if (!result_doc || !ast_xml_node_get_children((struct ast_xml_node *)result_doc)) {
+		ast_xml_close(result_doc);
+		result_doc = ast_xslt_apply(pidf_lo_xslt, pidf_xmldoc, find_person);
+	}
+	if (!result_doc || !ast_xml_node_get_children((struct ast_xml_node *)result_doc)) {
+		return NULL;
+	}
+
+	/*
+	 * The document returned from the stylesheet application looks like this...
+	 * <presence id="presence-entity">
+	 *     <pidf-element name="tuple" id="element-id">
+	 *         <location-info format="gml">format="gml", type="Ellipsoid", crs="3d", ...</location-info>
+	 *         <usage-rules>retransmission-allowed="no", retention-expiry="2010-11-14T20:00:00Z"</usage-rules>
+	 *         <method>Hybrid_A-GPS</method>
+	 *     </pidf-element>
+	 *  </presence>
+	 *
+	 * Regardless of whether the pidf-element was tuple, device or person and whether
+	 * the format is gml or civicAddress, the presence, pidf-element, location-info,
+	 * usage-rules and method elements should be there although usage-rules and method
+	 * may be empty.
+	 *
+	 * The contents of the location-info and usage-rules elements can be passed directly to
+	 * ast_variable_list_from_string().
+	 */
+
+	eprofile = geoloc_eprofile_create_from_xslt_result(result_doc, reference_string);
+
+	return eprofile;
+}
+
+#ifdef TEST_FRAMEWORK
+static void load_tests(void);
+static void unload_tests(void);
+#else
+static void load_tests(void) {}
+static void unload_tests(void) {}
+#endif
+
+
+int geoloc_eprofile_unload(void)
+{
+	unload_tests();
+	if (pidf_lo_xslt) {
+		ast_xslt_close(pidf_lo_xslt);
+	}
+
+	if (geoloc_sorcery) {
+		ast_sorcery_unref(geoloc_sorcery);
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_eprofile_load(void)
+{
+	geoloc_sorcery = geoloc_get_sorcery();
+
+	pidf_to_eprofile_xslt_size =
+		(_binary_res_geolocation_pidf_to_eprofile_xslt_end - _binary_res_geolocation_pidf_to_eprofile_xslt_start);
+
+	pidf_lo_test_xml_size =
+		(_binary_res_geolocation_pidf_lo_test_xml_end - _binary_res_geolocation_pidf_lo_test_xml_start);
+
+	pidf_lo_xslt = ast_xslt_read_memory(
+		(char *)_binary_res_geolocation_pidf_to_eprofile_xslt_start, pidf_to_eprofile_xslt_size);
+	if (!pidf_lo_xslt) {
+		ast_log(LOG_ERROR, "Unable to read pidf_lo_xslt from memory\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	load_tests();
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_eprofile_reload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+
+#ifdef TEST_FRAMEWORK
+#include "asterisk/test.h"
+
+AST_TEST_DEFINE(test_create_from_uri)
+{
+
+	RAII_VAR(struct ast_geoloc_effective_profile *, eprofile,  NULL, ao2_cleanup);
+	const char *uri = NULL;
+	int rc = AST_TEST_PASS;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "create_from_uri";
+		info->category = "/geoloc/";
+		info->summary = "Test create from uri";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	eprofile = ast_geoloc_eprofile_create_from_uri("http://some_uri&a=b", __func__);
+	ast_test_validate(test, eprofile != NULL);
+	ast_test_validate(test, eprofile->format == AST_GEOLOC_FORMAT_URI);
+	ast_test_validate(test, eprofile->location_vars != NULL);
+	uri = ast_variable_find_in_list(eprofile->location_vars, "URI");
+	ast_test_validate(test, uri != NULL);
+	ast_test_validate(test, strcmp(uri, "http://some_uri&a=b") == 0);
+
+	return rc;
+}
+
+static enum ast_test_result_state validate_eprofile(struct ast_test *test,
+	struct ast_xml_doc * pidf_xmldoc,
+	const char *path,
+	const char *id,
+	enum ast_geoloc_pidf_element pidf_element,
+	enum ast_geoloc_format format,
+	const char *method,
+	const char *location,
+	const char *usage
+	)
+{
+	RAII_VAR(struct ast_str *, str, NULL, ast_free);
+	RAII_VAR(struct ast_geoloc_effective_profile *, eprofile,  NULL, ao2_cleanup);
+	RAII_VAR(struct ast_xml_doc *, result_doc, NULL, ast_xml_close);
+	const char *search[] = { "path", path, NULL };
+
+	if (!ast_strlen_zero(path)) {
+		result_doc = ast_xslt_apply(pidf_lo_xslt, pidf_xmldoc, (const char **)search);
+		ast_test_validate(test, (result_doc && ast_xml_node_get_children((struct ast_xml_node *)result_doc)));
+
+		eprofile = geoloc_eprofile_create_from_xslt_result(result_doc, "test_create_from_xslt");
+	} else {
+		eprofile = ast_geoloc_eprofile_create_from_pidf(pidf_xmldoc, "test_create_from_pidf");
+	}
+
+	ast_test_validate(test, eprofile != NULL);
+	ast_test_status_update(test, "ID: '%s'  pidf_element: '%s'  format: '%s'  method: '%s'\n", eprofile->id,
+		geoloc_pidf_element_to_name(eprofile->pidf_element),
+		geoloc_format_to_name(eprofile->format),
+		eprofile->method);
+
+	ast_test_validate(test, ast_strings_equal(eprofile->id, id));
+	ast_test_validate(test, eprofile->pidf_element == pidf_element);
+	ast_test_validate(test, eprofile->format == format);
+	ast_test_validate(test, ast_strings_equal(eprofile->method, method));
+
+	str = ast_variable_list_join(eprofile->location_vars, ",", "=", NULL, NULL);
+	ast_test_validate(test, str != NULL);
+	ast_test_status_update(test, "location_vars: %s\n", ast_str_buffer(str));
+	ast_test_validate(test, ast_strings_equal(ast_str_buffer(str), location));
+	ast_free(str);
+
+	str = ast_variable_list_join(eprofile->usage_rules_vars, ",", "=", "'", NULL);
+	ast_test_validate(test, str != NULL);
+	ast_test_status_update(test, "usage_rules: %s\n", ast_str_buffer(str));
+	ast_test_validate(test, ast_strings_equal(ast_str_buffer(str), usage));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(test_create_from_pidf)
+{
+
+	RAII_VAR(struct ast_xml_doc *, pidf_xmldoc, NULL, ast_xml_close);
+	enum ast_test_result_state res = AST_TEST_PASS;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "create_from_pidf";
+		info->category = "/geoloc/";
+		info->summary = "Test create from pidf scenarios";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	pidf_xmldoc = ast_xml_read_memory((char *)_binary_res_geolocation_pidf_lo_test_xml_start, pidf_lo_test_xml_size);
+	ast_test_validate(test, pidf_xmldoc != NULL);
+
+	res = validate_eprofile(test, pidf_xmldoc,
+		NULL,
+		"arcband-2d",
+		AST_PIDF_ELEMENT_DEVICE,
+		AST_GEOLOC_FORMAT_GML,
+		"TA-NMR",
+		"format=gml,type=ArcBand,crs=2d,pos=-43.5723 153.21760,innerRadius=3594,"
+				"outerRadius=4148,startAngle=20,startAngle_uom=radians,openingAngle=20,"
+				"openingAngle_uom=radians",
+		"retransmission-allowed='yes',ruleset-preference='https:/www/more.com',"
+			"retention-expires='2007-06-22T20:57:29Z'"
+		);
+	ast_test_validate(test, res == AST_TEST_PASS);
+
+
+	res = validate_eprofile(test, pidf_xmldoc,
+		"/def:presence/dm:device[.//ca:civicAddress][1]",
+		"pres:alice at asterisk.org",
+		AST_PIDF_ELEMENT_DEVICE,
+		AST_GEOLOC_FORMAT_CIVIC_ADDRESS,
+		"GPS",
+		"format=civicAddress,country=AU,A1=NSW,A3=Wollongong,A4=North Wollongong,"
+			"RD=Flinders,STS=Street,RDBR=Campbell Street,LMK=Gilligan's Island,"
+			"LOC=Corner,NAM=Video Rental Store,PC=2500,ROOM=Westerns and Classics,"
+			"PLC=store,POBOX=Private Box 15",
+		"retransmission-allowed='yes',ruleset-preference='https:/www/more.com',"
+			"retention-expires='2007-06-22T20:57:29Z'"
+		);
+	ast_test_validate(test, res == AST_TEST_PASS);
+
+
+	return res;
+}
+
+static void load_tests(void) {
+	AST_TEST_REGISTER(test_create_from_uri);
+	AST_TEST_REGISTER(test_create_from_pidf);
+}
+static void unload_tests(void) {
+	AST_TEST_UNREGISTER(test_create_from_uri);
+	AST_TEST_UNREGISTER(test_create_from_pidf);
+}
+
+#endif
diff --git a/res/res_geolocation/geoloc_private.h b/res/res_geolocation/geoloc_private.h
index 194d507..c5ea981 100644
--- a/res/res_geolocation/geoloc_private.h
+++ b/res/res_geolocation/geoloc_private.h
@@ -25,8 +25,18 @@
 #include "asterisk/lock.h"
 #include "asterisk/res_geolocation.h"
 
+#define CONFIG_STR_TO_ENUM_DECL(_stem) int geoloc_ ## _stem ## _str_to_enum(const char *str);
+CONFIG_STR_TO_ENUM_DECL(pidf_element)
+CONFIG_STR_TO_ENUM_DECL(format);
+CONFIG_STR_TO_ENUM_DECL(location_disposition);
+#define GEOLOC_ENUM_TO_NAME_DECL(_stem) const char * geoloc_ ## _stem ## _to_name(int ix);
+GEOLOC_ENUM_TO_NAME_DECL(pidf_element)
+GEOLOC_ENUM_TO_NAME_DECL(format);
+GEOLOC_ENUM_TO_NAME_DECL(location_disposition);
+
+
 #define CONFIG_STR_TO_ENUM(_stem) \
-static int _stem ## _str_to_enum(const char *str) \
+int geoloc_ ## _stem ## _str_to_enum(const char *str) \
 { \
 	int i; \
 	for (i = 0; i < ARRAY_LEN(_stem ## _names); i++) { \
@@ -41,7 +51,7 @@
 static int _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
 { \
 	struct ast_geoloc_ ## _object *_thisobject = obj; \
-	int enumval = _stem ## _str_to_enum(var->value); \
+	int enumval = geoloc_ ## _stem ## _str_to_enum(var->value); \
 	if (enumval == -1) { \
 		return -1; \
 	} \
@@ -49,6 +59,17 @@
 	return 0; \
 }
 
+
+#define GEOLOC_ENUM_TO_NAME(_stem) \
+const char * geoloc_ ## _stem ## _to_name(int ix) \
+{ \
+	if (!ARRAY_IN_BOUNDS(ix, _stem ## _names)) { \
+		return "none"; \
+	} else { \
+		return _stem ## _names[ix]; \
+	} \
+}
+
 #define CONFIG_ENUM_TO_STR(_object, _stem) \
 static int _stem ## _to_str(const void *obj, const intptr_t *args, char **buf) \
 { \
@@ -63,6 +84,7 @@
 
 #define CONFIG_ENUM(_object, _stem) \
 CONFIG_STR_TO_ENUM(_stem) \
+GEOLOC_ENUM_TO_NAME(_stem) \
 CONFIG_ENUM_HANDLER(_object, _stem) \
 CONFIG_ENUM_TO_STR(_object, _stem)
 
@@ -133,6 +155,10 @@
 int geoloc_channel_load(void);
 int geoloc_channel_reload(void);
 
+int geoloc_eprofile_unload(void);
+int geoloc_eprofile_load(void);
+int geoloc_eprofile_reload(void);
+
 struct ast_sorcery *geoloc_get_sorcery(void);
 
 #endif /* GEOLOC_PRIVATE_H_ */
diff --git a/res/res_geolocation/pidf_lo_test.xml b/res/res_geolocation/pidf_lo_test.xml
new file mode 100644
index 0000000..3948063
--- /dev/null
+++ b/res/res_geolocation/pidf_lo_test.xml
@@ -0,0 +1,312 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<presence entity="pres:alice at asterisk.org"
+	xmlns="urn:ietf:params:xml:ns:pidf"
+	xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
+	xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
+	xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
+	xmlns:gml="http://www.opengis.net/gml"
+	xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
+	xmlns:gs="http://www.opengis.net/pidflo/1.0">
+	<tuple id="point-2d">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gml:Point srsName="urn:ogc:def:crs:EPSG::4326">
+						<gml:pos>-34.410649 150.87651</gml:pos>
+					</gml:Point>
+				</gp:location-info>
+				<gp:usage-rules>
+					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+				</gp:usage-rules>
+				<gp:method>Manual</gp:method>
+			</gp:geopriv>
+		</status>
+		<timestamp>2007-06-22T20:57:29Z</timestamp>
+	</tuple>
+	<dm:person id="point-3d">
+		<gp:geopriv>
+			<gp:location-info>
+				<gml:Point srsName="urn:ogc:def:crs:EPSG::4979">
+					<gml:pos>-34.410649 150.87651 1800</gml:pos>
+				</gml:Point>
+			</gp:location-info>
+			<gp:usage-rules>
+				<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+				<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+			</gp:usage-rules>
+			<gp:method>802.11</gp:method>
+		</gp:geopriv>
+		<dm:timestamp>2007-06-24T12:28:04Z</dm:timestamp>
+	</dm:person>
+	<tuple id="circle-2d">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gs:Circle srsName="urn:ogc:def:crs:EPSG::4326">
+						<gml:pos>-34.410649 150.87651</gml:pos>
+						<gs:radius uom="urn:ogc:def:uom:EPSG::9001">30</gs:radius>
+					</gs:Circle>
+				</gp:location-info>
+				<gp:usage-rules>
+					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+				</gp:usage-rules>
+				<gp:method>802.11</gp:method>
+			</gp:geopriv>
+		</status>
+		<timestamp>2007-06-22T20:57:29Z</timestamp>
+	</tuple>
+	<tuple id="circle-3d">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gs:Circle srsName="urn:ogc:def:crs:EPSG::4979">
+						<gml:pos>-34.410649 150.87651 1800</gml:pos>
+						<gs:radius uom="urn:ogc:def:uom:EPSG::9001">30</gs:radius>
+					</gs:Circle>
+				</gp:location-info>
+				<gp:usage-rules>
+					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+				</gp:usage-rules>
+				<gp:method>802.11</gp:method>
+			</gp:geopriv>
+		</status>
+		<timestamp>2007-06-22T20:57:29Z</timestamp>
+	</tuple>
+	<dm:person id="polygon-2d">
+		<gp:geopriv>
+			<gp:location-info>
+				<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4326">
+					<gml:exterior>
+						<gml:LinearRing>
+							<gml:pos>43.311 -73.422</gml:pos>
+							<gml:pos>43.111 -73.322</gml:pos>
+							<gml:pos>43.111 -73.222</gml:pos>
+							<gml:pos>43.311 -73.122</gml:pos>
+							<gml:pos>43.411 -73.222</gml:pos>
+							<gml:pos>43.411 -73.322</gml:pos>
+							<gml:pos>43.311 -73.422</gml:pos>
+						</gml:LinearRing>
+					</gml:exterior>
+				</gml:Polygon>
+			</gp:location-info>
+			<gp:usage-rules>
+				<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+				<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+			</gp:usage-rules>
+			<gp:method>802.11</gp:method>
+		</gp:geopriv>
+		<dm:timestamp>2007-06-24T12:28:04Z</dm:timestamp>
+	</dm:person>
+	<dm:person id="polygon-3d">
+		<gp:geopriv>
+			<gp:location-info>
+				<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4979">
+					<gml:exterior>
+						<gml:LinearRing>
+							<gml:pos>43.311 -73.422 1800</gml:pos>
+							<gml:pos>43.111 -73.322 1800</gml:pos>
+							<gml:pos>43.111 -73.222 1800</gml:pos>
+							<gml:pos>43.311 -73.122 1800</gml:pos>
+							<gml:pos>43.411 -73.222 1800</gml:pos>
+							<gml:pos>43.411 -73.322 1800</gml:pos>
+							<gml:pos>43.311 -73.422 1800</gml:pos>
+						</gml:LinearRing>
+					</gml:exterior>
+				</gml:Polygon>
+			</gp:location-info>
+			<gp:usage-rules>
+				<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+				<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+			</gp:usage-rules>
+			<gp:method>802.11</gp:method>
+		</gp:geopriv>
+		<dm:timestamp>2007-06-24T12:28:04Z</dm:timestamp>
+	</dm:person>
+	<tuple id="polygon-poslist-2d">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4326">
+						<gml:exterior>
+							<gml:LinearRing>
+								<gml:posList>
+									43.311 -73.422 43.111 -73.322
+									43.111 -73.222 43.311 -73.122
+									43.411 -73.222 43.411 -73.322
+									43.311 -73.422
+								</gml:posList>
+							</gml:LinearRing>
+						</gml:exterior>
+					</gml:Polygon>
+				</gp:location-info>
+				<gp:usage-rules>
+					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+				</gp:usage-rules>
+				<gp:method>Wiremap</gp:method>
+			</gp:geopriv>
+		</status>
+		<timestamp>2007-06-22T20:57:29Z</timestamp>
+	</tuple>
+	<tuple id="polygon-poslist-3d">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4979">
+						<gml:exterior>
+							<gml:LinearRing>
+								<gml:posList>
+									43.311 -73.422 1800 43.111 -73.322 1800
+									43.111 -73.222 1800 43.311 -73.122 1800
+									43.411 -73.222 1800 43.411 -73.322 1800
+									43.311 -73.422 1800
+								</gml:posList>
+							</gml:LinearRing>
+						</gml:exterior>
+					</gml:Polygon>
+				</gp:location-info>
+				<gp:usage-rules>
+					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+				</gp:usage-rules>
+				<gp:method>Wiremap</gp:method>
+			</gp:geopriv>
+		</status>
+		<timestamp>2007-06-22T20:57:29Z</timestamp>
+	</tuple>
+	<tuple id="ellipse-2d">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gs:Ellipse srsName="urn:ogc:def:crs:EPSG::4326">
+						<gml:pos>42.5463 -73.2512</gml:pos>
+						<gs:semiMajorAxis uom="urn:ogc:def:uom:EPSG::9001">1275</gs:semiMajorAxis>
+						<gs:semiMinorAxis uom="urn:ogc:def:uom:EPSG::9001">670</gs:semiMinorAxis>
+						<gs:orientation uom="urn:ogc:def:uom:EPSG::9102">43.2</gs:orientation>
+					</gs:Ellipse>
+				</gp:location-info>
+				<gp:usage-rules/>
+				<gp:method>Device-Assisted_A-GPS</gp:method>
+			</gp:geopriv>
+		</status>
+		<timestamp>2007-06-22T20:57:29Z</timestamp>
+	</tuple>
+	<dm:device id="arcband-2d">
+		<gp:geopriv>
+			<gp:location-info>
+				<gs:ArcBand srsName="urn:ogc:def:crs:EPSG::4326">
+					<gml:pos>-43.5723 153.21760</gml:pos>
+					<gs:innerRadius uom="urn:ogc:def:uom:EPSG::9001">3594</gs:innerRadius>
+					<gs:outerRadius uom="urn:ogc:def:uom:EPSG::9001">4148</gs:outerRadius>
+					<gs:startAngle uom="urn:ogc:def:uom:EPSG::9102">20</gs:startAngle>
+					<gs:openingAngle uom="urn:ogc:def:uom:EPSG::9102">20</gs:openingAngle>
+				</gs:ArcBand>
+			</gp:location-info>
+			<gp:usage-rules>
+				<gp:retransmission-allowed>yes</gp:retransmission-allowed>
+				<gp:ruleset-preference>https:/www/more.com</gp:ruleset-preference>
+				<gp:retention-expires>2007-06-22T20:57:29Z</gp:retention-expires>
+			</gp:usage-rules>
+			<gp:method>TA-NMR</gp:method>
+		</gp:geopriv>
+		<dm:deviceID>mac:1234567890ab</dm:deviceID>
+		<dm:timestamp>2007-06-22T20:57:29Z</dm:timestamp>
+	</dm:device>
+	<tuple id="sphere">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gs:Sphere srsName="urn:ogc:def:crs:EPSG::4979">
+						<gml:pos>42.5463 -73.2512 26.3</gml:pos>
+						<gs:radius uom="urn:ogc:def:uom:EPSG::9001">850.24</gs:radius>
+					</gs:Sphere>
+				</gp:location-info>
+				<gp:usage-rules>
+					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+				</gp:usage-rules>
+				<gp:method>Device-Based_A-GPS</gp:method>
+			</gp:geopriv>
+		</status>
+	</tuple>
+	<tuple id="ellipsoid">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gs:Ellipsoid srsName="urn:ogc:def:crs:EPSG::4979">
+						<gml:pos>42.5463 -73.2512 26.3</gml:pos>
+						<gs:semiMajorAxis uom="urn:ogc:def:uom:EPSG::9001">7.7156</gs:semiMajorAxis>
+						<gs:semiMinorAxis uom="urn:ogc:def:uom:EPSG::9001">3.31</gs:semiMinorAxis>
+						<gs:verticalAxis uom="urn:ogc:def:uom:EPSG::9001">28.7</gs:verticalAxis>
+						<gs:orientation uom="urn:ogc:def:uom:EPSG::9102">90</gs:orientation>
+					</gs:Ellipsoid>
+				</gp:location-info>
+				<gp:usage-rules/>
+				<gp:method>Hybrid_A-GPS</gp:method>
+			</gp:geopriv>
+		</status>
+		<timestamp>2007-06-22T20:57:29Z</timestamp>
+	</tuple>
+	<tuple id="prism">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gs:Prism srsName="urn:ogc:def:crs:EPSG::4979">
+						<gs:base>
+							<gml:Polygon>
+								<gml:exterior>
+									<gml:LinearRing>
+										<gml:posList>
+											42.556844 -73.248157 36.6 <!--A -->
+											42.656844 -73.248157 36.6 <!--B -->
+											42.656844 -73.348157 36.6 <!--C -->
+											42.556844 -73.348157 36.6 <!--D -->
+											42.556844 -73.248157 36.6 <!--A -->
+										</gml:posList>
+									</gml:LinearRing>
+								</gml:exterior>
+							</gml:Polygon>
+						</gs:base>
+						<gs:height uom="urn:ogc:def:uom:EPSG::9001">2.4</gs:height>
+					</gs:Prism>
+				</gp:location-info>
+				<gp:usage-rules/>
+				<gp:method>Wiremap</gp:method>
+			</gp:geopriv>
+		</status>
+		<timestamp>2007-06-22T20:57:29Z</timestamp>
+	</tuple>
+	<dm:device>
+		<gp:geopriv>
+			<gp:location-info>
+				<ca:civicAddress xml:lang="en-AU">
+					<ca:country>AU</ca:country>
+					<ca:A1>NSW</ca:A1>
+					<ca:A3>Wollongong</ca:A3>
+					<ca:A4>North Wollongong</ca:A4>
+					<ca:RD>Flinders</ca:RD>
+					<ca:STS>Street</ca:STS>
+					<ca:RDBR>Campbell Street</ca:RDBR>
+					<ca:LMK>Gilligan's Island</ca:LMK>
+					<ca:LOC>Corner</ca:LOC>
+					<ca:NAM> Video Rental Store </ca:NAM>
+					<ca:PC>2500</ca:PC>
+					<ca:ROOM> Westerns and Classics </ca:ROOM>
+					<ca:PLC>store</ca:PLC>
+					<ca:POBOX>Private Box 15</ca:POBOX>
+				</ca:civicAddress>
+			</gp:location-info>
+			<gp:usage-rules>
+				<gp:retransmission-allowed>yes</gp:retransmission-allowed>
+				<gp:ruleset-preference>https:/www/more.com</gp:ruleset-preference>
+				<gp:retention-expires>2007-06-22T20:57:29Z</gp:retention-expires>
+			</gp:usage-rules>
+			<gp:method>GPS</gp:method>
+		</gp:geopriv>
+		<dm:deviceID>mac:1234567890ab</dm:deviceID>
+		<dm:timestamp>2007-06-22T20:57:29Z</dm:timestamp>
+	</dm:device>
+</presence>
diff --git a/res/res_geolocation/pidf_to_eprofile.xslt b/res/res_geolocation/pidf_to_eprofile.xslt
new file mode 100644
index 0000000..409ba9e
--- /dev/null
+++ b/res/res_geolocation/pidf_to_eprofile.xslt
@@ -0,0 +1,124 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0"
+	xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
+	xmlns:def="urn:ietf:params:xml:ns:pidf"
+	xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
+	xmlns:fn="http://www.w3.org/2005/xpath-functions"
+	xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
+	xmlns:gml="http://www.opengis.net/gml"
+	xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
+	xmlns:gs="http://www.opengis.net/pidflo/1.0"
+	xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+	<!--xsl:output method="text" /-->
+	<xsl:strip-space elements="*" />
+	<xsl:param name="path"/>
+
+	<xsl:template name="name-value">
+		<xsl:value-of select="local-name(.)" /><xsl:text>="</xsl:text>
+		<xsl:value-of select="normalize-space(.)" /><xsl:text>"</xsl:text>
+		<xsl:if test="following-sibling::*"><xsl:text>, </xsl:text></xsl:if>
+	</xsl:template>
+
+	<xsl:template name="length"><xsl:call-template name="name-value" /></xsl:template>
+
+	<xsl:template name="angle">
+		<xsl:value-of select="local-name(.)" /><xsl:text>="</xsl:text>
+		<xsl:value-of select="normalize-space(.)" /><xsl:text>"</xsl:text><xsl:text>, </xsl:text>
+		<xsl:value-of select="local-name(.)" /><xsl:text>_uom="</xsl:text>
+		<xsl:choose>
+			<xsl:when test="@uom = 'urn:ogc:def:uom:EPSG::9102'"><xsl:text>radians</xsl:text></xsl:when>
+			<xsl:otherwise><xsl:text>degrees</xsl:text></xsl:otherwise>
+		</xsl:choose>
+		<xsl:text>"</xsl:text>
+		<xsl:if test="following-sibling::*"><xsl:text>, </xsl:text></xsl:if>
+	</xsl:template>
+	<xsl:template match="gs:orientation"><xsl:call-template name="angle" /></xsl:template>
+	<xsl:template match="gs:radius"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:height"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:semiMajorAxis"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:semiMinorAxis"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:verticalAxis"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:innerRadius"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:outerRadius"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:startAngle"><xsl:call-template name="angle" /></xsl:template>
+	<xsl:template match="gs:openingAngle"><xsl:call-template name="angle" /></xsl:template>
+	<xsl:template match="gml:pos"><xsl:call-template name="name-value" /></xsl:template>
+	<xsl:template match="gml:posList"><xsl:call-template name="name-value" /></xsl:template>
+
+	<xsl:template name="shape">
+		<xsl:text>format="gml", type="</xsl:text><xsl:value-of select="local-name(.)" /><xsl:text>", </xsl:text>
+		<xsl:text>crs="</xsl:text>
+		<xsl:choose>
+			<xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4326'"><xsl:text>2d</xsl:text></xsl:when>
+			<xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4979'"><xsl:text>3d</xsl:text></xsl:when>
+			<xsl:otherwise><xsl:text>unknown</xsl:text></xsl:otherwise>
+		</xsl:choose>
+		<xsl:text>", </xsl:text>
+		<xsl:apply-templates />
+	</xsl:template>
+
+	<xsl:template match="ca:civicAddress/*"><xsl:call-template name="name-value" />	</xsl:template>
+	<xsl:template name="civicAddress"><xsl:text>format="civicAddress", </xsl:text>
+		<xsl:apply-templates />
+	</xsl:template>
+
+	<xsl:template match="gp:location-info/gml:*">
+		<xsl:element name="location-info">
+			<xsl:attribute name="format">gml</xsl:attribute>
+			<xsl:call-template name="shape" />
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template match="gp:location-info/gs:*">
+		<xsl:element name="location-info">
+			<xsl:attribute name="format">gml</xsl:attribute>
+			<xsl:call-template name="shape" />
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template match="gp:location-info/ca:*">
+		<xsl:element name="location-info">
+			<xsl:attribute name="format">civicAddress</xsl:attribute>
+			<xsl:call-template name="civicAddress" />
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template match="gp:usage-rules/*">
+		<xsl:call-template name="name-value" />
+	</xsl:template>
+
+	<xsl:template match="gp:usage-rules">
+		<xsl:element name="usage-rules">
+			<xsl:apply-templates />
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template match="gp:method">
+		<xsl:value-of select="normalize-space(.)" />
+	</xsl:template>
+
+	<xsl:template name="topnode">
+		<xsl:element name="pidf-element">
+			<xsl:attribute name="name"><xsl:value-of select="local-name(.)"/></xsl:attribute>
+			<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+			<xsl:apply-templates select=".//gp:location-info"/>
+			<xsl:apply-templates select=".//gp:usage-rules"/>
+			<xsl:element name="method">
+			<xsl:apply-templates select=".//gp:method"/>
+			</xsl:element>
+		</xsl:element>
+		<xsl:text>
+</xsl:text>
+	</xsl:template>
+
+	<xsl:template match="dm:device"><xsl:call-template name="topnode" /></xsl:template>
+	<xsl:template match="def:tuple"><xsl:call-template name="topnode" /></xsl:template>
+	<xsl:template match="dm:person"><xsl:call-template name="topnode" /></xsl:template>
+
+	<xsl:template match="/">
+		<xsl:element name="presence">
+			<xsl:attribute name="entity"><xsl:value-of select="/*/@entity"/></xsl:attribute>
+			<xsl:apply-templates select="$path"/>
+		</xsl:element>
+	</xsl:template>
+</xsl:stylesheet>
diff --git a/tests/test_config.c b/tests/test_config.c
index 1a0ddaf..08c1bb1 100644
--- a/tests/test_config.c
+++ b/tests/test_config.c
@@ -1962,7 +1962,7 @@
 	}
 
 	parse_string = "abc = 'def', ghi = 'j,kl', mno='pq=r', stu = 'vwx=\"yz\", ABC = \"DEF\"'";
-	list = ast_variable_list_from_string(parse_string, ",", "=");
+	list = ast_variable_list_from_string(parse_string, ",", "=", "'");
 	ast_test_validate(test, list != NULL);
 	str = ast_variable_list_join(list, "|", "^", "@", NULL);
 

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/18127
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: development/16/geolocation
Gerrit-Change-Id: I50b66bd041b2a62ab329406f20dbaeef1fa68fc1
Gerrit-Change-Number: 18127
Gerrit-PatchSet: 1
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20220303/b71e1da5/attachment-0001.html>


More information about the asterisk-code-review mailing list