/*
* LibAxl: Another XML library
* Copyright (C) 2006 Advanced Software Production Line, S.L.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*
* You may find a copy of the license under this software is released
* at COPYING file. This is LGPL software: you are welcome to
* develop proprietary applications using this library without any
* royalty or fee but returning back any change, improvement or
* addition in the form of source code, project image, documentation
* patches, etc.
*
* For commercial support on build XML enabled solutions contact us:
*
* Postal address:
* Advanced Software Production Line, S.L.
* Edificio Alius A, Oficina 102,
* C/ Antonio Suarez Nº 10,
* Alcalá de Henares 28802 Madrid
* Spain
*
* Email address:
* info@aspl.es - http://www.aspl.es/xml
*/
#include <axl_ns.h>
#define LOG_DOMAIN "axl-ns-doc"
/**
* \defgroup axl_ns_doc_module Axl Doc Namespace: Xml 1.0 namespace support for XML documents
*/
/**
* \addtogroup axl_ns_doc_module
* @{
*/
/**
* @internal Name of the key used to access and store the ns table for
* each node.
*/
#define NS_TABLE "__axl:ns-table"
/**
* @internal Table used to hold bindings from prefix declared and
* namespaces used. It also holds the default namespace declared for a
* particular node, refering to it and all its content until found
* another declaration.
*/
typedef struct _axlNsTable {
/* bindings between prefixes and namespaces */
axlHash * table;
/* default namespace to be used */
const char * defaultNs;
} axlNsTable;
void __axl_ns_free_table (axlNsTable * table)
{
if (table == NULL)
return;
/* free table */
axl_hash_free (table->table);
/* free node */
axl_free (table);
return;
} /* end __axl_ns_free_table */
/* check and install all ns declerations found on this node */
axl_bool __axl_ns_node_check_and_install_ns_decls (axlNode * node, axlAttrCursor * cursor, axlError ** error)
{
const char * attr;
axl_bool default_found = axl_false;
axlNsTable * ns_table = NULL;
/* for each attribute installed on the node */
while (axl_node_attr_cursor_has_item (cursor)) {
/* get the key */
attr = axl_node_attr_cursor_get_key (cursor);
if (! axl_memcmp (attr, "xmlns", 5)) {
/* no xml namespace declaration found, go next */
axl_node_attr_cursor_next (cursor);
continue;
} /* end if */
/* create the table (without creating the hash for now) */
if (ns_table == NULL) {
ns_table = axl_new (axlNsTable, 1);
/* install the table in the node */
axl_node_annotate_data_full (node,
/* key and its destroy function */
NS_TABLE, NULL,
/* value and its destroy function */
ns_table, (axlDestroyFunc)__axl_ns_free_table);
} /* end if */
/* check if we have a default xmlns declaration */
if (strlen (attr) == 5) {
/* check if there were previous xmlns default declarations */
if (default_found) {
/* free cursor */
axl_node_attr_cursor_free (cursor);
axl_error_new (-1, "Found that the document defines several default xmlns declarations at the same node", NULL, error);
return axl_false;
} /* end if */
default_found = axl_true;
/* copy the default namespace */
ns_table->defaultNs = axl_node_attr_cursor_get_value (cursor);
/* go next */
axl_node_attr_cursor_next (cursor);
continue;
} /* end if */
/* found preffixed namespace declaration */
if (ns_table->table == NULL) {
/* create the table with 1 step */
ns_table->table = axl_hash_new_full (axl_hash_string, axl_hash_equal_string, 1);
} /* end if */
/* check that the prefix wasn't declared at the same node */
attr = attr + 6;
if (axl_hash_exists (ns_table->table, (axlPointer) attr)) {
/* free cursor */
axl_node_attr_cursor_free (cursor);
axl_error_new (-1, "Found that the document already declares the same prefix for the same node several times",
NULL, error);
return axl_false;
} /* end if */
/* install it in the current ns hash table */
axl_hash_insert (ns_table->table, (axlPointer) attr, (axlPointer) axl_node_attr_cursor_get_value (cursor));
/* get next attribute */
axl_node_attr_cursor_next (cursor);
} /* end while */
/* all declarations done */
return axl_true;
} /* end __axl_ns_node_check_and_install_ns_decls */
/**
* @internal Function that validates the node against current status
* of the namespace support.
*/
axl_bool __axl_ns_node_validate (axlNode * node, axlError ** error)
{
int iterator = 0;
char * name;
axl_bool found = axl_false;
axlNsTable * ns_table;
axlNode * parent;
axlNode * child;
/* create the cursor */
axlAttrCursor * cursor = NULL;
/* check if the node has a namespace declaration */
if (axl_node_has_attributes (node)) {
/* the node has attributes, check if there are some
* namespace declaration */
cursor = axl_node_attr_cursor_new (node);
/* check and install all ns declerations found on this
* node (cursor is dellocated by this function on
* error) */
if (! __axl_ns_node_check_and_install_ns_decls (node, cursor, error))
return axl_false;
} /* end if */
/* now we have all namespace declarations, check that the node
* name and its attributes are properly declared */
name = (char*) axl_node_get_name (node);
/* check if the node name uses a namespace scope declaration
* (prefix:name) */
while (name[iterator] != 0) {
if (name[iterator] == ':') {
/* set the terminator */
name[iterator] = 0;
/* namespace node found */
found = axl_true;
break;
}
/* get next position */
iterator++;
} /* end if */
if (found) {
/* namespace declaration found (this supposes that the
* default ns is not used) */
parent = node;
while (parent != NULL) {
/* try to get the ns table having the prefix declaration */
ns_table = axl_node_annotate_get (parent, NS_TABLE, axl_false);
/* check the namespace */
if (ns_table != NULL && axl_hash_exists (ns_table->table, name)) {
/* prefix found, restore node name */
name[iterator] = ':';
break;
} /* end if */
/* get the parent */
parent = axl_node_get_parent (parent);
/* no more parents */
if (parent == NULL) {
/* prefix not found, restore node name */
name[iterator] = ':';
/* free cursor */
axl_node_attr_cursor_free (cursor);
axl_error_new (-1, "Found prefix declaration for which a namespace binding wasn't found in the node or any parent (xmlns:prefix='ns')",
NULL, error);
return axl_false;
} /* end if */
} /* end while */
} /* end if */
/* check if the node has a namespace declaration */
if (axl_node_has_attributes (node)) {
/* now check attributes */
axl_node_attr_cursor_first (cursor);
while (axl_node_attr_cursor_has_item (cursor)) {
/* get the key */
name = (char*) axl_node_attr_cursor_get_key (cursor);
if (axl_memcmp (name, "xmlns", 5)) {
/* skip namespace declarations */
axl_node_attr_cursor_next (cursor);
continue;
} /* end if */
__axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "checking attribute name='%s'", name);
/* check if the node name uses a namespace scope declaration
* (prefix:name) */
found = axl_false;
iterator = 0;
while (name[iterator] != 0) {
if (name[iterator] == ':') {
/* set the terminator */
name[iterator] = 0;
/* namespace node found */
found = axl_true;
break;
}
/* get next position */
iterator++;
} /* end while */
if (found) {
parent = node;
while (parent != NULL) {
/* try to get the ns table having the prefix declaration */
ns_table = axl_node_annotate_get (parent, NS_TABLE, axl_false);
/* check the namespace */
if (ns_table != NULL && axl_hash_exists (ns_table->table, name)) {
/* prefix found, restore attribute name */
name[iterator] = ':';
break;
} /* end if */
/* get the parent */
parent = axl_node_get_parent (parent);
/* no more parents */
if (parent == NULL) {
/* prefix not found, restore attribute name */
name[iterator] = ':';
/* free cursor */
axl_node_attr_cursor_free (cursor);
axl_error_new (-1, "Found prefix declaration, for an attribute, for which a namespace binding wasn't found in the node or any parent (xmlns:prefix='ns')",
NULL, error);
return axl_false;
} /* end if */
} /* end while */
} /* end if */
/* next attribute */
axl_node_attr_cursor_next (cursor);
} /* end while */
/* free cursor */
axl_node_attr_cursor_free (cursor);
} /* end if */
/* do the same for the rest of the child nodes */
child = axl_node_get_first_child (node);
while (child != NULL) {
/* check childs */
if (! __axl_ns_node_validate (child, error))
return axl_false;
/* go next child node */
child = axl_node_get_next (child);
} /* end while */
/* return axl_true, if reached this place */
return axl_true;
}
/**
* @brief Allows to check XML 1.0 namespace rules for the document
* loaded.
*
* XML 1.0 Namespaces definition allows you to define documents that
* are resistent to tag clashing, making your product to be more
* usable at any level, being mixed with other products.
*
* This function allows you to check a xml document, already loaded
* with the Axl Doc base API (\ref axl_doc_parse_from_file, \ref
* axl_doc_parse or \ref axl_doc_create), if it follows the namespaces
* contrains (XML 1.0 Namespaces http://www.w3.org/TR/REC-xml-names/).
*
* The idea behind this function is to ensure that the document has
* the proper XML 1.0 Namespace declarations, which will be used by
* the following function to help you detect those tags recognized by
* your XML software, ensuring this matching is done inside your
* namespace:
*
* - \ref axl_ns_node_cmp
* - \ref axl_ns_node_find_called
* - See \ref axl_ns_node_module "Axl namespace support for nodes" to get more information about function used to read your xml documents in a namespace aware manner.
*
* Here is a simple example on how to load a xml document, add
* namespace checking support, and read content inside without
* compromissing your code to node tag collitions:
*
* \include ns_example.c
*
* As a gold rule, your code must not use prefixed names (full
* qualified names) to check xml node names (tags), because they will
* make your code fragile to changes introduced in xml documents read
* by your application.
*
* Instead, you must provide your namespace where the validation will take
* place and the local name of the node being checked (knowing that
* the prefix and the local name for <b><shaper:xml-rpc-invoke></b> is
* <b>shaper</b> and <b>xml-rpc-invoke</b> respectively).
*
* Let's see an an example to clarify this. Assuming the following xml document:
*
* <div class="xml-doc">
* \include ns_shaper.xml
* </div>
*
* Previous examples shows a simple shaper description, which is using
* <b>shaper</b> as prefix for its nodes. Under this situation you
* must not use the following to check for an xml node name:
*
* \code
* if (NODE_CMP_NAME (node, "shaper:xml-rpc-invoke")) {
* // found shaper:xml-rpc-invoke tag
* }
* \endcode
*
* This is because, as we have said, you are placing direct references
* to the namespace prefix declared at the document, but this is
* wrong. You can't ensure the user won't change the namespace binding
* that links your namespace with the prefix <b>shaper</b>.
*
* The proper way to check xml node names (tags) in a namespace aware
* manner is:
*
* \code
* // supposing SHAPER_NS defines the namespace string
* if (axl_ns_node_cmp (node, SHAPER_NS, "xml-rpc-invoke")) {
* // found xml-rpc-invoke tag
* }
* \endcode
*
* Now, the user will be able to change the binding between your
* namespace and the prefix used. This will enable him to use the
* prefix <b>shaper</b> for its products, without breaking your
* software.
*
* <i><b>NOTE:</b> providing a document without content (at least one
* root node configured), will cause the function to return \ref
* axl_true.</i>
*
* @param doc The document that is being required to be checked
* against the XML Namespaces 1.0 rules.
*
* @param error An optional variable where errors will be reported. If
* the function returns \ref axl_false, you can call to \ref axl_error_get
* to get a textual diagnostic.
*
* @return \ref axl_true if the document is namespace-valid, otherwise,
* \ref axl_false is returned.
*/
axl_bool axl_ns_doc_validate (axlDoc * doc, axlError ** error)
{
axlNode * node;
/* check references */
if (doc == NULL) {
axl_error_new (-1, "Document provided is a null reference", NULL, error);
return axl_false;
} /* end if */
/* get root document */
node = axl_doc_get_root (doc);
if (node == NULL) {
/* document has no nodes, return axl_true */
return axl_true;
} /* end if */
/* call to produce and validate namespace content */
return __axl_ns_node_validate (node, error);
}
/**
* @internal Internal API that allows to check if the provided prefix
* (ns_prefix) is bound to the provided namespace, using the node
* provided as a reference for the parent lookup.
*
* @param node The node that is used as reference for the
* lookup. Starting from this node the lookup will be performed,
* following with parents.
*
* @param ns_prefix The ns prefix to be checked.
*
* @param ns The namespace that must be bound the prefix.
*
* @return axl_true if the prefix is bound to the namespace provided,
* using the node as reference for all declarations found inside the
* particular node (or its parents).
*/
axl_bool axl_ns_doc_node_check (axlNode * node,
const char * ns_prefix,
const char * ns)
{
axlNode * parent = node;
axlNsTable * ns_table = NULL;
/* foreach node, up to the root parent, starting from the node
* provided, do: */
while (parent != NULL) {
/* try to get the ns table having the prefix declaration */
ns_table = axl_node_annotate_get (parent, NS_TABLE, axl_false);
/* check the namespace */
if (ns_table != NULL && axl_hash_exists (ns_table->table, (axlPointer) ns_prefix)) {
/* prefix found, check its value */
return axl_cmp (axl_hash_get (ns_table->table, (axlPointer) ns_prefix), ns);
} /* end if */
/* not found, get the parent */
parent = axl_node_get_parent (parent);
} /* end while */
/* reached this point, return axl_false */
return axl_false;
}
/**
* @internal Function that allows to check if the default namespace is
* the value provided, using as reference, the node received.
*
* @param node The node to check for its default namespace to match
* the value provided.
*
* @param ns The namespace that is provided to match.
*
* @return \ref axl_true if the node has as default namespace the value
* received. Otherwise \ref axl_false is returned.
*/
axl_bool axl_ns_doc_check_default (axlNode * node,
const char * ns)
{
axlNode * parent = node;
axlNsTable * ns_table = NULL;
/* foreach node, up to the root parent, starting from the node
* provided, do: */
while (parent != NULL) {
/* try to get the ns table having the prefix declaration */
ns_table = axl_node_annotate_get (parent, NS_TABLE, axl_false);
/* check the namespace */
if (ns_table != NULL && ns_table->defaultNs != NULL) {
/* prefix found, check its value */
return axl_cmp (ns_table->defaultNs, ns);
} /* end if */
/* not found, get the parent */
parent = axl_node_get_parent (parent);
} /* end while */
/* reached this point, return axl_false */
return axl_false;
}
/**
* @}
*/
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>