=pod

=head1 NAME

Data::NDS - routines to work with a perl nested data structure

=head1 SYNOPSIS

  use Data::NDS;
  $obj = new Data::NDS;

=head1 DESCRIPTION

This is a module for working with a perl nested data structure (NDS).
A data structure may consist of any number of nested perl data types
including:

   lists
   hashes
   scalars
   other (everything else)

This module can easily perform the following operations:

=over 4

=item Access parts of the NDS

It is very easy to get a value stored somewhere in an NDS, or to set a
value somewhere in an NDS.

=item Verify structural integrity

Often, a data structure may have constraints on it (certain parts of
it may be lists, hashes, or scalars). This module can enforce those
constraints when setting parts of the NDS.

=item Merge multiple NDSs into a single NDS

Two different NDSs may be merged into a single NDS using a series of
rules (described below).

=back

A reasonably complete set of examples for how to do these and
other tasks is included below.

=head1 ACCESSING AN NDS

Typically, when accessing a nested data structure, you might use
something like:

  $nds{foo}[5]{bar}

Although it is very direct, this necessitates putting a great deal of
information about the structure directly in the program. It also
relies on the fact that the structure is correctly defined, and all
parts are present. If there's any possiblitity that this is not the
case, you have to recurse through the structure to determine this or
face some unexpected side effects. For example, referencing
$nds{foo}[5]{bar} will create a lot of structure if it didn't already
exist.

This module will replace access to the value (or substructure) stored
somewhere in an NDS with a call to a method which will automatically
check that the structure is correct. It can be used to access, set, or
delete parts of an NDS. The above example could be replaced with:

  $obj->val($nds,"/foo/5/bar");

Here, the string "/foo/5/bar"is called a path. It is a series of
indices separated by a delimiter (which defaults to "/", but which can
be set to other values using the delim method described below). The
indices of describe how to traverse through an NDS.

=head1 NDS STRUCTURE

An NDS can have a great deal of structural information associated with
it. The data stored at every path in a data structure is of a certain
type, and the some types of structural information imply or prohibit
other structural characteristics.

When specifying structural information for a path, some elements in
the path may be given as a wildcard or as a specific value.

For example, in a data structure which consists of a hash containing
a foo key, and that key contains a list of elements, you might specifify
structural information for specific paths:

  /foo/0
  /foo/1

or for all elements using a wildcard:

  /foo/*

In the second case, structural information is set for ALL elements in
the list.

It is not allowed to have structural information simultaneously for
both types of paths. For example, there will never be structural
information for both:

  /foo/1
  /foo/*

There MAY be structural information for:

  /foo/1
  /bar/*

since there is no requirement that /foo and /bar are uniform.

Most paths in an NDS can have the following pieces of information:

=over 4

=item type

This refers to the what type of data is storead at the path. Known
types are scalar, list, hash, and other (which encompasses all other
types of perl data types).

=item uniform or non-uniform lists or hashes

Hashes and lists can be either uniform or non-uniform. A uniform list
has elements which are all the same structure.  It is not required
that all elements have every piece of the structure, but two elements
cannot have a different structure at any level.

When specifying structural information for uniform list or hashes, the
wildcard will always be used. When specifying structural information
for non-uniform lists or hashes, paths will always consist of specific
elements.

=item ordered or unordered lists

Lists can be ordered or unordered. An ordered list is one in which the
position in the list has meaning. For example, the 1st and 2nd elements
in the list are not interchangeable.

Unordered lists are those in which the order and placement of the
elements is not important. Because they are interchangeable, all
unordered lists are uniform.

=back

=head1 MERGING NDSes

One of the more complex tasks of this module is the ability to take
two NDSes and merge them together recursively. At every level of the
merge, the data is combined based on the merge method for that path
and that type of data.

There are several different methods that can be used for merging
NDSes.

=over 4

=item Merging hashes

Merging hashes is the easiest. Allowed methods are merge, keep,
keep_warn, replace, replace_warn, or error.

Merging the two hashes:

  %nds1 = ( a  => NDS1,
            b  => NDS2 )

  %nds2 = ( b  => NDS3,
            c  => NDS4 )

will give a resulting hash:

  %nds  = ( a => NDS1,
            b => ???
            c => NDS4 )

The "a" and "c" keys are the easiest. Since they are only defined in
one of the two initial hashes, their value is the value they were
defined with, and it is not necessary to recurse deeper into those
values.

The "b" key value depends on the merge method. If the method is
keep, the first value is used, so

  b => NDS2

If the method is replace, an existing value will be replaced with
a second value, so:

  b => NDS3

In both of these cases, it is not necessary to recurse into the
structure.

If the method is merge, the resulting value is obtained by merging
NDS2 and NDS3.

If the method is error, an error will occur if the key is defined in
both hashes, and the program will exit.

The methods keep_warn and replace_warn are equivalent to keep and
replace respectively except that a warning will be issued when a key
is defined in both hashes.

When merging two hashes, if a value for a key in the first hash is
empty, or an empty string (""), it is replaced by the value in the
second hash.

=item Merging lists

When merging lists, allowed methods are: merge, keep, keep_warn,
replace, replace_warn, append, and error.

Merging the two lists:

  @list1 = ( NDS1a NDS1b ... )

  @list2 = ( NDS2a NDS2b ... )

will give the following results depending on the merge method.

With the keep method, the resulting list will be:

  @list = @list1

With the replace method, the resulting list will be:

  @list = @list2

With the append method, the resulting list will be:

  @list = (@list1 @list2)

With the merge method, the resulting list will be

  @list = ( NDS1 NDS2 ... )

where NDS1 is a merger of NDS1a and NDS1b, NDS2 is a merger of NDS2a
and NDS2b, etc.

If the method is error, an error will occur if both lists have
elements, and the program will exit.

The methods keep_warn and replace_warn are equivalent to keep and
replace respectively except that a warning will be issued when both
lists have elements.

The append method is only available with unordered lists.  The merge
method is only available with ordered lists.

When merging two lists, if a value for an element in the first list is
empty, or an empty string (""), it is replaced by the value in the
second list.

=item Merging scalars (or other)

When data of type scalar or other are merged, allowed methods of
merging are keep, keep_warn, replace, replace_warn, and error.

Scalars or other types are merged when the parent structures are
merged recursively, and they include scalars at some level.

For example, given the two hashes:

  %nds1 = ( a  => 1 )

  %nds2 = ( a  => 2 )

which are merged using the "merge" method. The "a" key exists in both, so
the values (1 and 2) are merged. Since they are scalars, they will be
merged using one of the scalar merge methods listed above.

With the keep and replace methods, the first or second value are
returned respectively. With the error method, an error is triggered if
both are defined and the program will exit. With the keep_warn and
replace_warn methods, a warning will be triggered.

=back

=head1 STRUCTURAL INFORMATION

When handling a data structure, structural information can be kept
which will allow you to test a data structure to see if it's valid and
how it should be merged with another NDS. Structural information is
optional, and reasonable defaults exist.

Structural information may be given as global defaults (i.e. it applies
to all paths), or on a path-specific basis (in which case it applies
only to that one specific 

Structural information may be given for a specific path, in which case
it applies only to that exact path. It does not apply to structures
lower OR higher an NDS. Structural information may also be given with
no path, in which case it provides a default for all paths unless
overridden with information specific to a path.

The following structural information may be set:

=over 4

=item ordered BOOLEAN [PATH]

By default, all lists are treated as unordered, but that can be
overridden, either on the global level, or path specific level,
with this descriptor.

If this is set to 1, the list at the given PATH is ordered, or if PATH
is omitted, all lists will default to ordered unless explicitly set
otherwise.

If this is set to 0, the list(s) will be unordered.

=item uniform_hash BOOLEAN

By default, hash keys are not uniform. By setting this descriptor
to 1, they will default to uniform.

=item uniform_ol BOOLEAN

By default, all ordered lists are uniform. By setting this descriptor
to 0, they will be treated as non-uniform.

Note that there is no uniform_ul descriptor because ALL unordered
lists are treated as uniform (if structural information is being used)
since there is no consistent way for structural information to apply
to an unordered list which does not have uniform elements.

=item uniform BOOLEAN PATH

This can apply either to an ordered list or a hash. It is invalid
for other data types. It sets the element at the given path to
be explicitly uniform or not uniform.

With respect to ordered lists, there are two caveats.

Caveat 1:

Hashes underneath the list element are uniform if the same key has the
same structure. It is not required that different keys have the same
structure.

For example, if the path "/a" refers to a uniform ordered list, and
"/a/0" is a hash with a key "key1" in it, and "/a/1" is a hash with
the key "key1" in it, both "/a/0/key1" and "/a/1/key1" have the same
structure. "/a/0/key2" can have a different structure however (unless
the hash is also defined as "uniform").

Caveat 2:

Ordered lists underneath the list element are uniform if the elements
at the same position have the same structure.

For example, if the path "/a" refers to a uniform ordered list, and
"/a/0" refers to an ordered list (so "/a/1" must also refer to an
ordered list, then both "/a/0/0" and "/a/1/0" must have the same
structure, but "/a/0/1" may be different (unless the second ordered
list is "uniform" also).

=back

When specifying the path to set these items, any element in either a
uniform list or uniform hash should be defined with an asterix (*).

For example, if "/a" refers to a uniform list of data structures,
setting values for these elements should be done with "/a/*" instead
of "/a/0" or some other number. Likewise, uniform hashes should use an
asterix instead of a hash key.

=head1 MERGE INFORMATION

Merge information is used to specify how different parts of a data
structure are merged with other structures. Merge information may be
given for a specific path or as a global default.

In addition, merge information may be specified for different sets of
circumstances. For example, one type of "merger" would be to use one
data structure to provide defaults for another structure, but only
when that structure didn't already include a value. An alternate type
of circumstances would be to have the second data structure override
values in the first structure. Each set of circumstances may be given
a ruleset name, and merge information can be set (either as global
defaults or for a specific path) for that set set of circumstances.
The named circumstances is called a ruleset and is described in more
detail below.

The following merge information may be set:

=over 4

=item merge_hash METHOD [RULESET]

This provides the default merge method for hash mergers. If RULESET is
given, it is the default when that set of rules is specified in the
merger.

If not specified, the "merge" method is the default.

=item merge_ol METHOD [RULESET]

This provides the default merge method for ordered list mergers. If
RULESET is given, it is the default when that set of rules is
specified in the merger.

If not specified, the "merge" method is the default.

=item merge_ul METHOD [RULESET]

This provides the default merge method for unordered list mergers. If
RULESET is given, it is the default when that set of rules is
specified in the merger.

If not specified, the "append" method is the default.

=item merge_scalar METHOD [RULESET]

This provides the default merge method for ordered list mergers. If
RULESET is given, it is the default when that set of rules is
specified in the merger.

If not specified, the "merge" method is the default.

=item merge METHOD PATH [RULESET]

This provides the merge method for a specific path.

=back

=head1 RULE SETS

It is sometimes desirable to have multiple ways defined to merge two
NDSes for different sets of circumstances.

For example, sometimes you want to do a full merge of the NDSes, and
another time you want one of the NDSes to provide default values for
anything not defined in the other NDS, but you don't want to override
any value that is currently there.

A set of all of the different rules (including both global defaults,
and path specific methods) which should be applied under a given set
of circumstances is called a ruleset.

By default, a single unnamed ruleset is used, and all merging is done
using the rules defined there. Additional named rulesets may also be
added. One important difference is that default rules are
automatically supplied for the unnamed ruleset, but NOT for a named
ruleset. If a merge method cannot be determined in a named ruleset, it
will default to that of the unnamed ruleset.

Any number of named rulesets may be created. There are four reserved
rule sets named "default", "override", "keep" and "replace" that may
not be used.

The "default" ruleset has the following settings:

   merge_hash   = merge
   merge_ul     = keep
   merge_ol     = merge
   merge_scalar = keep

If you merge two data structures using the "default" ruleset, the
second structure will provide defaults for the first. In other words,
if the first includes a scalar at some path, it will keep it, but
otherwise, it will take the value from the second structure.

The only exception is that unordered lists are not recursed into. If a
value is an unordered list, it will use an existing list in it's
entirety.

The "override" ruleset has the following settings:

   merge_hash   = merge
   merge_ul     = replace
   merge_ol     = merge
   merge_scalar = replace

If you merge two data structures using the "override" ruleset, the
second structure will override the first.

The "keep" and "replace" rulesets are used to set a value at a given
path to a new value, possibly completely replacing any existing
structure.  The "keep" ruleset will set the structure to a new value
only if it doesn't already exist. The "replace" ruleset will remove
any existing structure and replace it with the new value.

The "keep" ruleset has all settings set to "keep". The "replace"
ruleset has them all set to "replace".

=head1 METHODS

When referring to the arguments passed to a method, $path always
refers to the path in an NDS. $path can be passed in as a delimited
string, or as a list reference where the list contains the elements
of the path. So the following are equivalent:

  "/a/b/c"

  [ "a", "b", "c" ]

When the argument $nds is passed in, it refers to an NDS. The NDS can
either be a reference to a structure, or the name of an NDS stored in
the object using the "nds" method.

=over 4

=item new

  $obj = new Data::NDS;

=item version

  $version = $obj->version();

Returns the version of the module.

=item warnings

  $obj->warnings(BOOLEAN);

If a true value is passed in, the module will issue warnings when
they are encountered.

=item structure

  $obj->structure(BOOLEAN);

If a true value is pushed in, the module will keep track of the structure
of the NDS and do checks on it where possible. If a false value is pushed
in, it will not keep track of structure.

The default is to keep track of structure.

=item delim

  $obj->delim();
  $obj->delim($delim);

When expressing the path as a string, the default delimiter is a
slash (/). This can be changed using this function. Any string can
be used as the delimiter. If called with no argument, it returns the
delimiter.

=item ruleset

  $err = $obj->ruleset($name);

This creates a ruleset of the given name. $name must be alphanumeric, and
must be created only a single time. The following names are reserved and
may not be used:

  keep
  replace

Error codes are:

  0     no error
  1     name not alphanumeric
  2     name previously created
  3     using a reserved ruleset name

=item ruleset_valid

  $flag = $obj->ruleset_valid($name);

This returns 1 if $name is a valid ruleset, 0 otherwise.

=item path

  @path = $obj->path($path);
  @path = $obj->path(\@path);

  $path = $obj->path(\@path);
  $path = $obj->path($path);

A path can be expressed in two different ways: a string with elements
separted by the path delimiter, or as a list of elements.

This method will convert between the two. In array context,it will
return a list of path elements. In scalar context,it will return the
path as a string with elements separated by the path delimiter.

It is safe to pass in the list reference in list context, or the
string version in scalar context. In both cases, the path will be
returned unmodified.

In string form, the path can be empty, or can consist only of the
delimiter, and all of these will return an empty list (i.e. they point
at the top level).

In string form, a path may include the delimiter as the first
character in the path, but it is optional, and the leading delimiter
does NOT imply anything about where the path starts. In other
words:

   /foo/1/bar
   foo/1/bar

are identical.

=item nds

There are several different ways in which this method can be called.

  $err = $obj->nds($name,$nds);
  $err = $obj->nds($name,$nds,$new);

These forms stores an NDS in the object under a given NAME ($name). If
structural information is kept, it will check the structure of the NDS
for problems. It will update structural information based on the NDS if
$new is passed in and is true.

The error value is the value of the check_structure method or -1 if a
named NDS doesn't exist.

If $nds is passed in as a reference, the actual structure (NOT a copy
of it) is stored. If $nds is passed in as the name of a stored NDS,
a copy of the structure will be stored under the new name.

  $nds = $obj->nds($name);

This retrieves the named NDS from the object. If it does not exist, it
will return nothing. It returns the actual stored NDS, NOT a copy of it,
so modifying the returned NDS in any way will modify the stored copy.

  $nds = $obj->nds($name,"_copy");

This is similar to the previous function but it will return a copy
of the named NDS.

  $obj->nds($name,"_delete");

This will delete the named NDS from the object. If the named NDS does
not exist, it will return 0, otherwise it will return 1.

=item empty

  $isempty = $obj->empty($nds);

An NDS is empty if it only contains undef values.

A scalar is empty if it is undef. The empty string "" is NOT
treated as empty.

A list is empty if it contains 0 elements, or if every element
in it is empty.

A hash is empty if it contains 0 keys, or if the value of every
key is empty.

The return value of the method is:

  0   if the NDS is not empty
  1   if the NDS is empty

=item valid

  ($valid,$val,$where) = $obj->valid($nds,$path [,$copy]);

This checks the $nds that is passed in (which can be either a structure
or a named element) to see if $path is valid.

If $path exists in $nds, it returns two values. The first is 1 meaning
that the path exists. The second is the value at that path. The value
at the path is the actual value, NOT a copy of it (unless $copy is passed
in, in which case it IS a copy).

If $path does not exist in $nds, it returns three values. The first is 0
meaning that the path is not valid. The second one is an error code
with more information about the error:

 -1   an NDS was passed in by name, but it is
      not valid (in this case, only two elements
      are returned)

  0   the path doesn't exist in $nds
  1   a hash key doesn't exist
  2   a list element doesn't exist

 10   the $nds has a scalar at a point which
      should refer to either a hash or array
 11   the $nds has a reference to an unsupported
      data type where it should refer to either
      a hash or array
 12   a non-integer value was used to access a
      list

The third value is the path at which the error occurred.

This method does NOT do any structural checking.

=item value

  $val = $obj->value($nds,$path);

This returns the value of the NDS at the given path. If any error
ocurs, undef is returned.

Note: this is simply a wrapper around the valid method, so to figure
out what error occured, call valid directly.

=item keys, values

  @ele = $obj->keys($nds,$path);
  @ele = $obj->values($nds,$path);

This takes an NDS and returns a list of items at the given path.

If the object at the path is a scalar, the keys method returns nothing.
The values method returns the scalar.

If the object at the path is a list, the keys method returns some of
the integers 0..N where N is the index of the last element in the
list. The indices for empty elements are omitted. The values method
returns the non-empty members of the list.

If the object at the path is a hash, the kyes method returns the
non-empty keys of the hash. The values method returns the members of
the list. The values method returns the non-empty values of the hash.

Undef is returned in the case of an error.

=item erase

  $err = $obj->erase($nds,$path);

This will delete the given path from the NDS. It will delete elements from
lists, clear elements from ordered lists, or delete entries from hashes.

The return value of the method is:

  0   if there is no error
  1   if an undefined NDS is passed in by name
  2   if the path is invalid in this NDS

=item set_structure

  $err = $obj->set_structure($item,$val [,$path]);

This sets the given item of structural information. If the path is
given, it sets items for that path, otherwise it sets default
structural items.

It returns 0 if there was no error, or else an error code. Several
warnings may be issued if $obj->warnings has been called. The following
error codes are used:

    1  Trying to set "type" to an invalid value
    2  Trying to reset "type"
    3  Trying to set "type" to a non-array/hash type when scalar/other
       is not valid

   10  Trying to set an unknown default structural item
   11  Trying to set an unknown structural item for a path

  100  Trying to set an "ordered" flag to something other than 0/1
  101  Trying to use an "ordered" flag on something other than an array
  102  Trying to reset "ordered" (or trying to set a non-uniform list to
       unordered)

  110  Trying to set an "uniform" flag to something other than 0/1
  111  Trying to use an "uniform" flag on something other than an array/hash
  112  Trying to reset "uniform" (or trying to set an unordered list to
       non-uniform)

  130  Trying to set structural information for a child with
       a scalar/other parent

  140  Trying to set structural information for a specific element
       in a "uniform" array
  141  Trying to set structural information for all list elements in
       a non-uniform array

  150  Trying to access a list with a non-integer index

  160  Trying to set structural information for a specific element
       in a uniform hash/array
  161  Trying to set structural information for all elements of a
       non-uniform hash/array

  170  Trying to set the default ordered value to something other than 0/1

  180  Trying to set the default uniform_hash value to something other
       than 0/1
  181  Trying to set the default uniform_ol value to something other
       than 0/1

=item get_structure

  $val = $obj->get_structure($path [,$info]);

This gets a piece of structural information for a path. $info can be
any of the following:

  type      (this is the default) (returns "unknown" if not set)
  ordered
  uniform
  merge
  keys

The appropriate value is returned. If information for a specific path
is not available, default values will be returned. It returns nothing
if the path has no structural information available.

The keys information is a list of all known keys that can appear in a
hash. This may only be used to query keys in a non-uniform hash.

=item check_structure

  ($err,$val) = $obj->check_structure($nds [,$new]);

This will take an NDS and traverse through it, checking the structure
of every part of it. If $new is passed in, it is allowed to contribute
new structural information. Otherwise, it must be completely defined
by previously declared structural information.

Error codes are:

  1   New structure found (but not allowed)
  2   Structure of invalid type found

The value returned in the case of an error is the path where the
error occurred.

=item check_value

  ($err,$val) = $obj->check_value($path,$val [,$new]);

This will check to see if $val has the correct structure to be
stored at $path in an NDS. It will traverse through the structure
of $val, similar to how the check_structure method traverses
through an entire NDS.

The values of $new, $err, and $val are the same as in the
check_structure method.

=item set_merge

  $err = $obj->set_merge($item,$method [,$ruleset]);
  $err = $obj->set_merge($item,$path,$method [,$ruleset]);

This will define how to merge values. In the first form,
it will set the default. $item can be merge_hash, merge_ol,
merge_ul, or merge_scalar. In the second form, it will set
the merge method for the given path. Currently, $item must
be "merge".

  10   Trying to set an unknown value

  100  Trying to set merge_hash to an invalid value
  101  Trying to set merge_ol to an invalid value
  102  Trying to set merge_ul to an invalid value
  103  Trying to set merge_scalar to an invalid value

  120  Trying to reset "merge" value for a path
  121  Trying to set "merge" for a path with no known type

  130  Invalid merge method for ordered list merging
  131  Invalid merge method for unordered list merging
  132  Invalid merge method for hash merging
  133  Invalid merge method for scalar/other merging

=item get_merge

  $method = $obj->get_merge($path [,$ruleset]);

This gets the merge method for a path.

The appropriate value is returned. If the method for a specific path
is not available, default values will be returned. Nothing will be
returned in the event of a problem.

=item merge

  $err = $obj->merge($nds1,$nds2 [,$ruleset] [,$new]);

This will take two NDSes (each of which can be passed in by name or by
reference) and will recursively merge the second one into the first
based on the rules of merging.

The second NDS will be copied, so no part of the merged NDS will
contain actual parts of the seconds NDS.

The name of a ruleset can be passed in. If it is, that set of merge
rules will be used to do the merging.

If $new is passed in, it must be 0 or 1. If it is 1, $nds2 may provide
new structural information. If $new is 0, $nds2 must be totally
described by existing structural descriptions.

  0   no error
  1   $nds1 refers to a named NDS that does not exist
  2   $nds2 refers to a named NDS that does not exist
  3   $nds1 has an invalid structure
  4   $nds2 has an invalid structure
  5   $nds1/$nds2 not list/hash ref

=item merge_path

  $err = $obj->merge_path($nds,$val,$path [,$ruleset] [,$new]);

This will take an NDS (which can be passed in by name or reference)
and merge $val into it at the given path. Using the special rulesets
"replace", the value will replace whatever is there.

$path must be valid, and $val must be structurally correct if
structural information is kept.

It will update structural information based on the NDS if $new is
passed in and is true.

The actual value passed in (not a copy) will be merged in.

The error code is:

  0   no error
  1   a named NDS does not exist
  2   $nds has an invalid structure
  3   $val has an invalid structure

=item identical, contains

  $flag = $obj->identical($nds1,$nds2 [,$new] [,$path]);
  $flag = $obj->contains ($nds1,$nds2 [,$new] [,$path]);

The identical method checks to see if two NDSes are identical. If $path
is given, only the part that starts at $path is checked.

When comparing ordered lists, every element must be identical and in the
same ordered. Unordered lists need to contain the same elements, but not
necessarily in the same order. This works even if the unordered list
contains structures instead of scalars.

The contains method checks to see that $nds2 is a subset (i.e. contained
in) $nds1. In other words, every scalar in $nds2 is identical to one in
$nds1.

undef is returned if there is any error.

NOTE: because unordered lists must be compared in every possible
combination, and recursively if the structure contains unordered lists
which contain other unordered lists deeper in the structure, comparing
NDSes with unordered lists can be extremely slow.

=item which

   %hash = $obj->which($nds,@args)

This returns a hash of { PATH => VAL } where PATH is is a path in $nds
and VAL is the value at that path.

The paths returned all fit the criteria specified in the arguments.

If no arguments are passed in, a hash of all paths to non-empty
scalars is returned (note that this means that scalars set to the
empty string '' ARE returned).

If @args is passed in, it is a list of criteria. If a scalar matches
any one of them, it passes. Currently, @args may consist of a list of
values (scalars) or regular expressions (set using the qr// operator).
If the value at a path is equal to any of the values passed in in
@args, or matches any of the regular expressions, then it passes.

=item print

   $string = $obj->print($nds,%opts);

This formats an NDS as a string based on a set of options. Known options
are:

   indent => NUMBER     Specifies the amount of indentation to add
                        at each level. Indent must be 1 or more.
                        Default:  3
   width  => NUMBER     Specifies the width of a printing area. A
                        value of 0 means to not impose any width
                        limit. The minimum allowed width (other than
                        0) is 20.
                        Default:  79
   maxlevel => NUMBER   Specifies the maximum level to print. A value of
                        0 is all levels, but may only be used then a
                        width of 0 is used. Levels beyond this will
                        be pruned.
                        Default:  the number of levels that can be
                                  displayed given indent and width

The NDS passed in must be a structure, not a named NDS.

=back

=head1 EXAMPLES

All examples assume the following lines:

   use Data::NDS;
   $obj = new Data::NDS;

=over 4

=item path method

The path function can be used to switch back and forth between a path
in string format and a path in list format.

   @path = $obj->path("/a/b");
      => ( a b )

   @path = $obj->path("a/b");
      => ( a b )

   @path = $obj->path(["a","b"]);
      => ( a b )

   $path = $obj->path("/a/b");
      => /a/b

   $path = $obj->path("a/b");
      => /a/b

   $path = $obj->path(["a","b"]);
      => /a/b

   @path = $obj->path("/");
      => ( )

   $path = $obj->path([]);
      => "/"

=item nds method

The nds method can be used to store or access a named NDS.

   $nds = { "a" => [ "a1", "a2" ],
            "b" => [ "b1", "b2" ] };

   $obj->nds("ele1",$nds,1);

   $nds2 = $obj->nds("ele1");
      => { "a" => [ "a1", "a2" ],
           "b" => [ "b1", "b2" ] }

=item valid, value methods

The valid method is used to check to see if a path is valid in the
given NDS. It returns a 0/1 if the path is valid. If it is valid, it
also returns the value at the path. If it is not valid, it returns an
error code and the path where the error occurred.

   $nds = { "a" => undef,
            "b" => "foo",
            "c" => [ "c1", "c2" ],
            "d" => { "d1k" => "d1v", "d2k" => "d2v" },
          };

   $obj->valid($nds,"/a");
      => ( 1 undef )

   $obj->valid($nds,"/d/d3k");
      => ( 0 1 /d/d3k )

   $obj->valid($nds,"/f/1/2");
      => ( 0 1 /f )

   $obj->valid($nds,"/c/1");
      => ( 1 c2 )

   $obj->valid($nds,"/c/x");
      => ( 0 12 /c/x )

The value method is simply a wrapper around valid.

   $val = $obj->value($nds,"/c/1");
      => c2

   $val = $obj->value($nds,"/c/x");
      => undef

=item keys, values methods

Using the samd NDS as defined in the "valid" examples.

   $obj->keys($nds,"/b");
      => ( )

   $obj->keys($nds,"/c");
      => ( 0 1 )

   $obj->keys($nds,"/d");
      => ( d1k d2k )

   $obj->values($nds,"/b");
      => ( foo )

   $obj->values($nds,"/c");
      => ( c1 c2 )

   $obj->values($nds,"/d");
      => ( d1v d2v )

=item set_structure, get_structure methods

These set or report the structure at a path.

set_structure sets a piece of structural information for a path and
returns an error code (0 if successful).

To make sure that the path "/a" refers to a uniform hash, make the
following two calls:

   $err = $obj->set_structure("type","hash","/a");
   $err = $obj->set_structure("uniform",1,"/a");

To make sure that "/b" is an ordered list, and all elements in it
are hashes, use the following calls:

   $err = $obj->set_structure("type","array","/b");
   $err = $obj->set_structure("ordered",1,"/b");
   $err = $obj->set_structure("type","hash","/b/*");

get_structure will return the structural information for a path:

   $info = $obj->get_structure("/b","type");
      => array

=item erase method

   $obj->set_structure("ordered","1","/o");
   $obj->set_structure("ordered","0","/u");

   $nds = { "h" => { "x" => 11, "y" => 22 },
            "o" => [ qw(alpha beta gamma delta) ],
            "u" => [ qw(alpha beta gamma delta) ],
          };

Erasing a hash key removes the key and value.

   $obj->erase($nds,"/h/x");
     => $nds = { h => { y => 22 },
               	 o => [ alpha beta gamma delta ],
               	 u => [ alpha beta gamma delta ],
               }

Erasing an element in an ordered list replaces it with an
undef place holder.

   $obj->erase($nds,"/o/1");
     => $nds = { h => { y => 22 },
               	 o => [ alpha UNDEF gamma delta ],
               	 u => [ alpha beta gamma delta ],
               }

Erasing an element from an unordered list removes it completely.

   $obj->erase($nds,"/u/1");
     => $nds = { h => { y => 22 },
               	 o => [ alpha UNDEF gamma delta ],
               	 u => [ alpha gamma delta ],
               }

=item check_structure method

You can use the set_structure routine to enforce structure. For
example, if you want an NDS to be a hash, and in that hash are
two keys "hu" who's value is a uniform hash, and "ul" who's
value is an unordered list, use the following:

   $obj->set_structure("type","hash","/hu");
   $obj->set_structure("uniform",1,"/hu");

   $obj->set_structure("type","array","/ul");
   $obj->set_structure("ordered",0,"/ul");

To check a structure to see if it fits this structure, use the
check_structure method:

   $a = { "hu" => { "h1" => "h1v" } };
   $obj->check_structure($a,1);
      => ( 0 )

   $b = { "hu" => [ 1, 2 ] };
   $obj->check_structure($b,1);
      => ( 2 /hu )

You can also add structural information by passing in an NDS that goes
beyond whatever structure you have defined with set_structure. Additional
structure will be determined from that structure IF you pass in a non-null
value as the second argument. If no second argument is passed in (or a
null value is passed in), the NDS being checked must have only the structure
that has already been defined.

For example:

   $b = { "ul" => [ { "aa" => 11 } ] };
   $obj->check_structure($b,0);
      => ( 1 /ul/* )

   $b = { "ul" => [ { "aa" => 11 } ] };
   $obj->check_structure($b,1);
      => ( 0 )

In the first instance, the check_structure function returns an error
code since the structure passed in contains structure that was not
defined in the set_structure calls above.

In the second instance, the added structure is examined and additional
structural information is deternubed.

Since "ul" is defined as an unordered (and therefore uniform) list, all
of it's members must be identical. They are set to hashes based on the
above check_structure call, so the following will fail since it tries to
set them to scalars:

   $c = { "ul" => [ "foo" ] };
   $obj->check_structure($c,1);
      => ( 2 /ul/* )

=item set_merge, get_merge methods

To set the default merge method for a hash to be "keep" (see above for
description of the various merge methods):

   $obj->set_merge("merge_hash","keep");
      => 0

To set the merge method for a single element in an NDS, use something
like the following:

   $err = $obj->set_structure("type","hash","/h");
   $obj->set_merge("merge","/h","keep");
      => 0

The get_merge method can be used to query the type of merge that is
done for a path:

   $obj->get_merge("/h");
      => keep

=item identical, contains methods

   $a = { "a"  => "foo",
          "b"  => "bar",
          "c"  => "baz" };
   $b = { "a"  => "foo",
          "b"  => "bar",
          "c"  => "baz" };

   $obj->identical($a,$b,1);
      => 1

   $obj->contains($a,$b,1);
      => 1

   $c = { "a"  => "foo",
          "c"  => "baz" };

   $obj->identical($a,$c,1);
      => 0

   $obj->contains($a,$c,1);
      => 1

When looking at unordered lists, elements do not need to be in the
same order:

   $a = [ qw(a b c) ];
   $b = [ qw(b a c) ];

   $obj->identical($a,$b,1);
      => 1

Unordered lists can contain unordered lists and they still work:

   $a = [ [ qw(a b c) ], [ qw(d e f) ], [ qw(g h i) ] ];
   $b = [ [ qw(d e f) ], [ qw(a b c) ], [ qw(i g h) ] ];

   $obj->identical($a,$b,1);
      => 1

This works regardless of the number of unordered lists and the
intermediate structure (for example: unordered list of hashes pointing to
unordered lists). Every time an unordered list is encountered, every
possible combination will be tried. This can be very slow so care should
be excercised in comparing structures containing unordered lists.

=item merge method

Merging hashes using keep, replace, and merge:

   $obj->set_merge("merge_hash","keep");
   $a = { "a"  => 1,
          "b" => 2 };
   $b = { "a"  => 3,
          "c" => 4 };
   $obj->merge($a,$b,1);
      => $a = { a => 1,
                b => 2 }

   $obj->set_merge("merge_hash","replace");
   $a = { "a"  => 1,
          "b" => 2 };
   $b = { "a"  => 3,
          "c" => 4 };
   $obj->merge($a,$b,1);
      => $a = { a => 3,
                c => 4 }

   $obj->set_merge("merge_hash","merge");
   $a = { "a"  => 1,
          "b" => 2 };
   $b = { "a"  => 3,
          "c" => 4 };
   $obj->merge($a,$b,1);
      => $a = { a => 1,
                b => 2,
                c => 4 }

Merging unordered lists using keep, replace, and append:

   $obj->set_structure("ordered",0);
   $obj->set_merge("merge_ul","keep");
   $a = [ qw(a b c) ];
   $b = [ qw(d e f) ];
   $obj->merge($a,$b,1);
      => $a = [ a b c ]

   $obj->set_structure("ordered",0);
   $obj->set_merge("merge_ul","replace");
   $a = [ qw(a b c) ];
   $b = [ qw(d e f) ];
   $obj->merge($a,$b,1);
      => $a = [ a b c ]

   $obj->set_structure("ordered",0);
   $obj->set_merge("merge_ul","append");
   $a = [ qw(a b c) ];
   $b = [ qw(d e f) ];
   $obj->merge($a,$b,1);
      => $a = [ a b c d e f ]

Merging ordered lists using keep, replace, and merge:

   $obj->set_structure("ordered",1);
   $obj->set_merge("merge_ol","keep");
   $a = [ "a", "", "b" ];
   $b = [ "c", "d", "" ];
   $obj->merge($a,$b,1);
      => $a = [ a '' b ]

   $obj->set_structure("ordered",1);
   $obj->set_merge("merge_ol","replace");
   $a = [ "a", "", "b" ];
   $b = [ "c", "d", "" ];
   $obj->merge($a,$b,1);
      => $a = [ c d '' ]

   $obj->set_structure("ordered",1);
   $obj->set_merge("merge_ol","merge");
   $a = [ "a", "", "b" ];
   $b = [ "c", "d", "" ];
   $obj->merge($a,$b,1);
      => $a = [ a d b ]

A more complex example. Given structures consisting of ordered
lists of hashes, merge them recursively.

   $a = [ { "a"  => 1,
            "b"  => 2 },
          { "c"  => 3 },
          {},
          { "d"  => 4,
            "e"  => 5 } ];

   $b = [ { "a"  => 11,
            "w"  => 22 },
          {},
          { "x"  => 33 },
          { "d"  => 44 } ];

   $obj->set_structure("type",    "array", "/");
   $obj->set_structure("ordered", 1,       "/");

   $obj->set_structure("type",    "hash",  "/*");

   $obj->set_merge("merge",  "/",  "merge");
   $obj->set_merge("merge",  "/*", "merge");

   $obj->merge($a,$b,1);
      => $a = [ { a => 1, b => 2, w => 22 },
                { c => 3 },
                { x => 33 },
                { d => 4, e => 5 } ]
   
=item merge_path method

merge_path is very similar to merge except that it merges a value into
a full NDS starting at a specific path. For example:

   $a = { "a"  => [ 1,2,3 ],
          "b"  => [ 4,5,6 ] };
   $obj->merge_path($a,[7,8,9],"/c",1);
      => $a = { a => [ 1 2 3 ],
                b => [ 4 5 6 ],
                c => [ 7 8 9 ] };

=item which method

   $nds = { "b" => "foo",
            "c" => [ "c1", "c2" ],
            "d" => { "d1k" => "d1v", "d2k" => "d2v" },
          };

You can search for paths for a list of all scalars:

   %p = $obj->which($nds);
      => %p = ( /b     => foo
                /c/0   => c1
                /c/1   => c2
                /d/d1k => d1v
                /d/d2k => d2v )

For a subset of scalars:

   %p = $obj->which($nds,"c2","d1v");
      => %p = ( /c/1   => c2
                /d/d1k => d1v )

For a set that matches regular expressions:

   %p = $obj->which($nds,qr/^c/);
      => %p = ( /c/0   => c1
                /c/1   => c2 )

=item using rulesets

Rulesets are powerful tools for determining how you merge data structures.

There are four very common uses of rulesets. They are so commonly used
that pre-existing rulesets have been defined for them, but any number of
other rulesets may also be defined.

The "replace" ruleset may be used to set the structure stored at
a path overriding any value currently there (but it will NOT replace
structural information, so it can't be used to redefine what constitutes
a valid structure).

   $a = { "a"  => [ 1,2,3 ],
          "b"  => [ 4,5,6 ] };
   $obj->merge_path($a,[7,8,9],"/b","replace",1);
      => $a = { a => [ 1 2 3 ],
                b => [ 7 8 9 ] }

The "keep" ruleset will set the structure only if it isn't already set.

   $a = { "a"  => [ 1,2,3 ],
          "b"  => [ 4,5,6 ] };
   $obj->merge_path($a,[7,8,9],"/b","keep",1);
      => $a = { a => [ 1 2 3 ],
                b => [ 4 5 6 ] }

The "default" ruleset will set defaults for a structure.

   $a = { "a" => 1,
          "b" => 2 };
   $d = { "a" => 11,
          "b" => 22,
          "c" => 33 };
   $obj->merge($a,$d,"default",1);
      => $a = { a => 1,
                b => 2,
                c => 33 }

The "override" ruleset will recursively override all values.

   $a = { "a" => 1,
          "b" => 2 };
   $d = { "a" => 11,
          "c" => 33 };
   $obj->merge($a,$d,"override",1);
      => $a = { a => 11,
                b => 2,
                c => 33 }

=back

=head1 BACKWARDS INCOMPATIBILITIES

=over 4

=item 1.01

The keys and values methods now only return non-empty elements.

=item 1.04

When working with an NDS, sometimes operations were performed on
the actual structure, sometimes on copies of the structure. It is
now documented which is which (and some behaviors were changed to
be more consistent).

=back

=head1 KNOWN PROBLEMS

None at this point.

=head1 LICENSE

This script is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 AUTHOR

Sullivan Beck (sbeck@cpan.org)
