From: Grant M. <gr...@us...> - 2002-10-13 01:11:08
|
Update of /cvsroot/perl-xml/xml-simple In directory usw-pr-cvs1:/tmp/cvs-serv894 Modified Files: Simple.pm Log Message: - POD update: alphabetised options Index: Simple.pm =================================================================== RCS file: /cvsroot/perl-xml/xml-simple/Simple.pm,v retrieving revision 1.4 retrieving revision 1.5 diff -u -d -r1.4 -r1.5 --- Simple.pm 14 Feb 2002 21:32:44 -0000 1.4 +++ Simple.pm 13 Oct 2002 01:11:04 -0000 1.5 @@ -4,7 +4,7 @@ =head1 NAME -XML::Simple - Easy API to read/write XML (esp config files) +XML::Simple - Easy API to maintain XML (esp config files) =head1 SYNOPSIS @@ -964,7 +964,7 @@ # Arguments expected are: # - the data structure to be encoded (usually a reference) # - the XML tag name to use for this item -# - a hashref of references already encoded (to detect recursive structures) +# - a hashref of references already encoded (to detect circular structures) # - a string of spaces for use as the current indent level # @@ -984,7 +984,7 @@ # Convert to XML if(ref($ref)) { - croak "recursive data structures not supported" if($encoded->{$ref}); + croak "circular data structures not supported" if($encoded->{$ref}); $encoded->{$ref} = $ref; } else { @@ -1510,7 +1510,7 @@ approach). Note also that although you can nest hashes and arrays to arbitrary levels, -recursive data structures are not supported and will cause C<XMLout()> to die. +circular data structures are not supported and will cause C<XMLout()> to die. Refer to L<"WHERE TO FROM HERE?"> if C<XMLout()> is too simple for your needs. @@ -1534,8 +1534,9 @@ =item * -make sure you know what the 'keyattr' option does and what its default value -is because it may surprise you otherwise +make sure you know what the 'keyattr' option does and what its default value is +because it may surprise you otherwise (note in particular that 'keyattr' +affects both C<XMLin> and C<XMLout>) =back @@ -1544,134 +1545,100 @@ listed below are marked with 'B<in>' if they are recognised by C<XMLin()> and 'B<out>' if they are recognised by C<XMLout()>. -=over 4 +Each option is also flagged to indicate whether it is: -=item keyattr => [ list ] (B<in+out>) + 'important' - don't use the module until you understand this + 'handy' - you can skip this on the first time through + 'advanced' - you can skip this on the second time through + 'SAX only' - don't worry about this unless you're using SAX + 'seldom used' - you'll probably never use this unless you were the + person that requested the feature -This option controls the 'array folding' feature which translates nested -elements from an array to a hash. For example, this XML: +The options are listed alphabetically: - <opt> - <user login="grep" fullname="Gary R Epstein" /> - <user login="stty" fullname="Simon T Tyson" /> - </opt> +=over 4 -would, by default, parse to this: +=item cache => [ cache scheme(s) ] (B<in>) (B<advanced>) - { - 'user' => [ - { - 'login' => 'grep', - 'fullname' => 'Gary R Epstein' - }, - { - 'login' => 'stty', - 'fullname' => 'Simon T Tyson' - } - ] - } +Because loading the B<XML::Parser> module and parsing an XML file can consume a +significant number of CPU cycles, it is often desirable to cache the output of +C<XMLin()> for later reuse. -If the option 'keyattr => "login"' were used to specify that the 'login' -attribute is a key, the same XML would parse to: +When parsing from a named file, B<XML::Simple> supports a number of caching +schemes. The 'cache' option may be used to specify one or more schemes (using +an anonymous array). Each scheme will be tried in turn in the hope of finding +a cached pre-parsed representation of the XML file. If no cached copy is +found, the file will be parsed and the first cache scheme in the list will be +used to save a copy of the results. The following cache schemes have been +implemented: - { - 'user' => { - 'stty' => { - 'fullname' => 'Simon T Tyson' - }, - 'grep' => { - 'fullname' => 'Gary R Epstein' - } - } - } +=over 4 -The key attribute names should be supplied in an arrayref if there is more -than one. C<XMLin()> will attempt to match attribute names in the order -supplied. C<XMLout()> will use the first attribute name supplied when -'unfolding' a hash into an array. +=item storable -Note: the keyattr option controls the folding of arrays. By default a single -nested element will be rolled up into a scalar rather than an array and -therefore will not be folded. Use the 'forcearray' option (below) to force -nested elements to be parsed into arrays and therefore candidates for folding -into hashes. +Utilises B<Storable.pm> to read/write a cache file with the same name as the +XML file but with the extension .stor -The default value for 'keyattr' is ['name', 'key', 'id']. Setting this option -to an empty list will disable the array folding feature. +=item memshare -=item keyattr => { list } (B<in+out>) +When a file is first parsed, a copy of the resulting data structure is retained +in memory in the B<XML::Simple> module's namespace. Subsequent calls to parse +the same file will return a reference to this structure. This cached version +will persist only for the life of the Perl interpreter (which in the case of +mod_perl for example, may be some significant time). -This alternative method of specifiying the key attributes allows more fine -grained control over which elements are folded and on which attributes. For -example the option 'keyattr => { package => 'id' } will cause any package -elements to be folded on the 'id' attribute. No other elements which have an -'id' attribute will be folded at all. +Because each caller receives a reference to the same data structure, a change +made by one caller will be visible to all. For this reason, the reference +returned should be treated as read-only. -Note: C<XMLin()> will generate a warning if this syntax is used and an element -which does not have the specified key attribute is encountered (eg: a 'package' -element without an 'id' attribute, to use the example above). Warnings will -only be generated if B<-w> is in force. +=item memcopy -Two further variations are made possible by prefixing a '+' or a '-' character -to the attribute name: +This scheme works identically to 'memshare' (above) except that each caller +receives a reference to a new data structure which is a copy of the cached +version. Copying the data structure will add a little processing overhead, +therefore this scheme should only be used where the caller intends to modify +the data structure (or wishes to protect itself from others who might). This +scheme uses B<Storable.pm> to perform the copy. -The option 'keyattr => { user => "+login" }' will cause this XML: +=back - <opt> - <user login="grep" fullname="Gary R Epstein" /> - <user login="stty" fullname="Simon T Tyson" /> - </opt> +Warning! The memory-based caching schemes compare the timestamp on the file to +the time when it was last parsed. If the file is stored on an NFS filesystem +(or other network share) and the clock on the file server is not exactly +synchronised with the clock where your script is run, updates to the source XML +file may appear to be ignored. -to parse to this data structure: +=item contentkey => 'keyname' (B<in+out>) (B<seldom used>) - { - 'user' => { - 'stty' => { - 'fullname' => 'Simon T Tyson', - 'login' => 'stty' - }, - 'grep' => { - 'fullname' => 'Gary R Epstein', - 'login' => 'grep' - } - } - } +When text content is parsed to a hash value, this option let's you specify a +name for the hash key to override the default 'content'. So for example: -The '+' indicates that the value of the key attribute should be copied rather than -moved to the folded hash key. + XMLin('<opt one="1">Text</opt>', contentkey => 'text') -A '-' prefix would produce this result: +will parse to: - { - 'user' => { - 'stty' => { - 'fullname' => 'Simon T Tyson', - '-login' => 'stty' - }, - 'grep' => { - 'fullname' => 'Gary R Epstein', - '-login' => 'grep' - } - } - } + { 'one' => 1, 'text' => 'Text' } -As described earlier, C<XMLout> will ignore hash keys starting with a '-'. +instead of: -=item searchpath => [ list ] (B<in>) + { 'one' => 1, 'content' => 'Text' } -If you pass C<XMLin()> a filename, but the filename include no directory -component, you can use this option to specify which directories should be -searched to locate the file. You might use this option to search first in the -user's home directory, then in a global directory such as /etc. +C<XMLout()> will also honour the value of this option when converting a hashref +to XML. -If a filename is provided to C<XMLin()> but searchpath is not defined, the -file is assumed to be in the current directory. +=item datahandler => code_ref (B<in>) (B<SAX only>) -If the first parameter to C<XMLin()> is undefined, the default searchpath -will contain only the directory in which the script itself is located. -Otherwise the default searchpath will be empty. +When you use an B<XML::Simple> object as a SAX handler, it will return a +'simple tree' data structure in the same format as C<XMLin()> would return. If +this option is set (to a subroutine reference), then when the tree is built the +subroutine will be called and passed two arguments: a reference to the +B<XML::Simple> object and a reference to the data tree. The return value from +the subroutine will be returned to the SAX driver. (See L<"SAX SUPPORT"> for +more details). -=item forcearray => 1 (B<in>) +You can specify 'DataHandler' as a synonym for 'datahandler'. + +=item forcearray => 1 (B<in>) (B<IMPORTANT!>) This option should be set to '1' to force nested elements to be represented as arrays even when there is only one. Eg, with forcearray enabled, this @@ -1705,74 +1672,51 @@ the default value of 'keyattr' enables array folding, the default value of this option should probably also have been enabled too - sorry). -=item forcearray => [ name(s) ] (B<in>) - -This alternative form of the 'forcearray' option allows you to specify a list -of element names which should always be forced into an array representation, -rather than the 'all or nothing' approach above. - -=item noattr => 1 (B<in+out>) - -When used with C<XMLout()>, the generated XML will contain no attributes. -All hash key/values will be represented as nested elements instead. - -When used with C<XMLin()>, any attributes in the XML will be ignored. - -=item suppressempty => 1 | '' | undef (B<in>) - -This option controls what C<XMLin()> should do with empty elements (no -attributes and no content). The default behaviour is to represent them as -empty hashes. Setting this option to a true value (eg: 1) will cause empty -elements to be skipped altogether. Setting the option to 'undef' or the empty -string will cause empty elements to be represented as the undefined value or -the empty string respectively. The latter two alternatives are a little -easier to test for in your code than a hash with no keys. - -=item cache => [ cache scheme(s) ] (B<in>) +=item forcearray => [ name(s) ] (B<in>) (B<IMPORTANT!>) -Because loading the B<XML::Parser> module and parsing an XML file can consume a -significant number of CPU cycles, it is often desirable to cache the output of -C<XMLin()> for later reuse. +This alternative (and preferred) form of the 'forcearray' option allows you to +specify a list of element names which should always be forced into an array +representation, rather than the 'all or nothing' approach above. -When parsing from a named file, B<XML::Simple> supports a number of caching -schemes. The 'cache' option may be used to specify one or more schemes (using -an anonymous array). Each scheme will be tried in turn in the hope of finding -a cached pre-parsed representation of the XML file. If no cached copy is -found, the file will be parsed and the first cache scheme in the list will be -used to save a copy of the results. The following cache schemes have been -implemented: +=item forcecontent (B<in>) (B<seldom used>) -=over 4 +When C<XMLin()> parses elements which have text content as well as attributes, +the text content must be represented as a hash value rather than a simple +scalar. This option allows you to force text content to always parse to +a hash value even when there are no attributes. So for example: -=item storable + XMLin('<opt><x>text1</x><y a="2">text2</y></opt>', forcecontent => 1) -Utilises B<Storable.pm> to read/write a cache file with the same name as the -XML file but with the extension .stor +will parse to: -=item memshare + { + 'x' => { 'content' => 'text1' }, + 'y' => { 'a' => 2, 'content' => 'text2' } + } -When a file is first parsed, a copy of the resulting data structure is retained -in memory in the B<XML::Simple> module's namespace. Subsequent calls to parse -the same file will return a reference to this structure. This cached version -will persist only for the life of the Perl interpreter (which in the case of -mod_perl for example, may be some significant time). +instead of: -Because each caller receives a reference to the same data structure, a change -made by one caller will be visible to all. For this reason, the reference -returned should be treated as read-only. + { + 'x' => 'text1', + 'y' => { 'a' => 2, 'content' => 'text2' } + } -=item memcopy +=item handler => object_ref (B<out>) (B<SAX only>) -This scheme works identically to 'memshare' (above) except that each caller -receives a reference to a new data structure which is a copy of the cached -version. Copying the data structure will add a little processing overhead, -therefore this scheme should only be used where the caller intends to modify -the data structure (or wishes to protect itself from others who might). This -scheme uses B<Storable.pm> to perform the copy. +Use the 'handler' option to have C<XMLout()> generate SAX events rather than +returning a string of XML. For more details see L<"SAX SUPPORT"> below. +You can specify 'Handler' as a synonym for 'handler' for compatability with +the SAX specification. -=back +Note: the current implementation of this option generates a string of XML +and uses a SAX parser to translate it into SAX events. The normal encoding +rules apply here - your data must be UTF8 encoded unless you specify an +alternative encoding via the 'xmldecl' option; and by the time the data reaches +the handler object, it will be in UTF8 form regardless of the encoding you +supply. A future implementation of this option may generate the events +directly. -=item keeproot => 1 (B<in+out>) +=item keeproot => 1 (B<in+out>) (B<handy>) In its attempt to return a data structure free of superfluous detail and unnecessary levels of indirection, C<XMLin()> normally discards the root @@ -1789,82 +1733,145 @@ data structure already contains a root element name and it is not necessary to add another. -=item rootname => 'string' (B<out>) +=item keyattr => [ list ] (B<in+out>) (B<IMPORTANT!>) -By default, when C<XMLout()> generates XML, the root element will be named -'opt'. This option allows you to specify an alternative name. +This option controls the 'array folding' feature which translates nested +elements from an array to a hash. It also controls the 'unfolding' of hashes +to arrays. -Specifying either undef or the empty string for the rootname option will -produce XML with no root elements. In most cases the resulting XML fragment -will not be 'well formed' and therefore could not be read back in by C<XMLin()>. -Nevertheless, the option has been found to be useful in certain circumstances. +For example, this XML: -=item forcecontent (B<in>) + <opt> + <user login="grep" fullname="Gary R Epstein" /> + <user login="stty" fullname="Simon T Tyson" /> + </opt> -When C<XMLin()> parses elements which have text content as well as attributes, -the text content must be represented as a hash value rather than a simple -scalar. This option allows you to force text content to always parse to -a hash value even when there are no attributes. So for example: +would, by default, parse to this: - XMLin('<opt><x>text1</x><y a="2">text2</y></opt>', forcecontent => 1) + { + 'user' => [ + { + 'login' => 'grep', + 'fullname' => 'Gary R Epstein' + }, + { + 'login' => 'stty', + 'fullname' => 'Simon T Tyson' + } + ] + } -will parse to: +If the option 'keyattr => "login"' were used to specify that the 'login' +attribute is a key, the same XML would parse to: - { - 'x' => { 'content' => 'text1' }, - 'y' => { 'a' => 2, 'content' => 'text2' } - } + { + 'user' => { + 'stty' => { + 'fullname' => 'Simon T Tyson' + }, + 'grep' => { + 'fullname' => 'Gary R Epstein' + } + } + } -instead of: +The key attribute names should be supplied in an arrayref if there is more +than one. C<XMLin()> will attempt to match attribute names in the order +supplied. C<XMLout()> will use the first attribute name supplied when +'unfolding' a hash into an array. - { - 'x' => 'text1', - 'y' => { 'a' => 2, 'content' => 'text2' } - } +Note 1: The default value for 'keyattr' is ['name', 'key', 'id']. If you do +not want folding on input or unfolding on output you must setting this option +to an empty list to disable the feature. -=item contentkey => 'keyname' (B<in+out>) +Note 2: If you wish to use this option, you should also enable the 'forcearray' +option. Without 'forcearray', a single nested element will be rolled up into a +scalar rather than an array and therefore will not be folded (since only arrays +get folded). -When text content is parsed to a hash value, this option let's you specify a -name for the hash key to override the default 'content'. So for example: +=item keyattr => { list } (B<in+out>) (B<IMPORTANT!>) - XMLin('<opt one="1">Text</opt>', contentkey => 'text') +This alternative (and preferred) method of specifiying the key attributes +allows more fine grained control over which elements are folded and on which +attributes. For example the option 'keyattr => { package => 'id' } will cause +any package elements to be folded on the 'id' attribute. No other elements +which have an 'id' attribute will be folded at all. -will parse to: +Note: C<XMLin()> will generate a warning if this syntax is used and an element +which does not have the specified key attribute is encountered (eg: a 'package' +element without an 'id' attribute, to use the example above). Warnings will +only be generated if B<-w> is in force. - { 'one' => 1, 'text' => 'Text' } +Two further variations are made possible by prefixing a '+' or a '-' character +to the attribute name: -instead of: +The option 'keyattr => { user => "+login" }' will cause this XML: - { 'one' => 1, 'content' => 'Text' } + <opt> + <user login="grep" fullname="Gary R Epstein" /> + <user login="stty" fullname="Simon T Tyson" /> + </opt> -C<XMLout()> will also honour the value of this option when converting a hashref -to XML. +to parse to this data structure: -=item xmldecl => 1 or xmldecl => 'string' (B<out>) + { + 'user' => { + 'stty' => { + 'fullname' => 'Simon T Tyson', + 'login' => 'stty' + }, + 'grep' => { + 'fullname' => 'Gary R Epstein', + 'login' => 'grep' + } + } + } -If you want the output from C<XMLout()> to start with the optional XML -declaration, simply set the option to '1'. The default XML declaration is: +The '+' indicates that the value of the key attribute should be copied rather +than moved to the folded hash key. - <?xml version='1.0' standalone='yes'?> +A '-' prefix would produce this result: -If you want some other string (for example to declare an encoding value), set -the value of this option to the complete string you require. + { + 'user' => { + 'stty' => { + 'fullname' => 'Simon T Tyson', + '-login' => 'stty' + }, + 'grep' => { + 'fullname' => 'Gary R Epstein', + '-login' => 'grep' + } + } + } -=item outputfile => <file specifier> (B<out>) +As described earlier, C<XMLout> will ignore hash keys starting with a '-'. -The default behaviour of C<XMLout()> is to return the XML as a string. If you -wish to write the XML to a file, simply supply the filename using the -'outputfile' option. Alternatively, you can supply an IO handle object instead -of a filename. +=item noattr => 1 (B<in+out>) (B<handy>) -=item noescape => 1 (B<out>) +When used with C<XMLout()>, the generated XML will contain no attributes. +All hash key/values will be represented as nested elements instead. + +When used with C<XMLin()>, any attributes in the XML will be ignored. + +=item rootname => 'string' (B<out>) (B<handy>) + +By default, when C<XMLout()> generates XML, the root element will be named +'opt'. This option allows you to specify an alternative name. + +Specifying either undef or the empty string for the rootname option will +produce XML with no root elements. In most cases the resulting XML fragment +will not be 'well formed' and therefore could not be read back in by C<XMLin()>. +Nevertheless, the option has been found to be useful in certain circumstances. + +=item noescape => 1 (B<out>) (B<seldom used>) By default, C<XMLout()> will translate the characters 'E<lt>', 'E<gt>', '&' and '"' to '<', '>', '&' and '"' respectively. Use this option to suppress escaping (presumably because you've already escaped the data in some more sophisticated manner). -=item nsexpand => 1 (B<in+out>) +=item nsexpand => 1 (B<in+out>) (B<handy - SAX only>) This option controls namespace expansion - the translation of element and attribute names of the form 'prefix:name' to '{uri}name'. For example the @@ -1886,40 +1893,55 @@ I<Note: You must have the XML::NamespaceSupport module installed if you want C<XMLout()> to translate URIs back to prefixes>. -=item handler => object_ref (B<out>) +=item outputfile => <file specifier> (B<out>) (B<handy>) -Use the 'handler' option to have C<XMLout()> generate SAX events rather than -returning a string of XML. For more details see L<"SAX SUPPORT"> below. -You can specify 'Handler' as a synonym for 'handler' for compatability with -the SAX specification. +The default behaviour of C<XMLout()> is to return the XML as a string. If you +wish to write the XML to a file, simply supply the filename using the +'outputfile' option. Alternatively, you can supply an IO handle object instead +of a filename. -Note: the current implementation of this option generates a string of XML -and uses a SAX parser to translate it into SAX events. The normal encoding -rules apply here - your data must be UTF8 encoded unless you specify an -alternative encoding via the 'xmldecl' option; and by the time the data reaches -the handler object, it will be in UTF8 form regardless of the encoding you -supply. A future implementation of this option may generate the events -directly. +=item parseropts => [ XML::Parser Options ] (B<in>) (B<don't use this>) -=item datahandler => code_ref (B<in>) +I<Note: This option is now officially deprecated. If you find it useful, +email the author with an example of what you use it for>. -When you use an B<XML::Simple> object as a SAX handler, it will return a -'simple tree' data structure in the same format as C<XMLin()> would return. If -this option is set (to a subroutine reference), then when the tree is built the -subroutine will be called and passed two arguments: a reference to the -B<XML::Simple> object and a reference to the data tree. The return value from -the subroutine will be returned to the SAX driver. (See L<"SAX SUPPORT"> for -more details). +Use this option to specify parameters that should be passed to the constructor +of the underlying XML::Parser object (which of course assumes you're not using +SAX). -You can specify 'DataHandler' as a synonym for 'datahandler'. +=item searchpath => [ list ] (B<in>) (B<handy>) -=item parseropts => [ XML::Parser Options ] (B<in>) +If you pass C<XMLin()> a filename, but the filename include no directory +component, you can use this option to specify which directories should be +searched to locate the file. You might use this option to search first in the +user's home directory, then in a global directory such as /etc. -I<Note: This option is now officially deprecated. If you find it useful, -email the author with an example of what you use it for>. +If a filename is provided to C<XMLin()> but searchpath is not defined, the +file is assumed to be in the current directory. -Use this option to specify parameters that should be passed to the constructor -of the underlying XML::Parser object. +If the first parameter to C<XMLin()> is undefined, the default searchpath +will contain only the directory in which the script itself is located. +Otherwise the default searchpath will be empty. + +=item suppressempty => 1 | '' | undef (B<in>) (B<handy>) + +This option controls what C<XMLin()> should do with empty elements (no +attributes and no content). The default behaviour is to represent them as +empty hashes. Setting this option to a true value (eg: 1) will cause empty +elements to be skipped altogether. Setting the option to 'undef' or the empty +string will cause empty elements to be represented as the undefined value or +the empty string respectively. The latter two alternatives are a little +easier to test for in your code than a hash with no keys. + +=item xmldecl => 1 or xmldecl => 'string' (B<out>) (B<handy>) + +If you want the output from C<XMLout()> to start with the optional XML +declaration, simply set the option to '1'. The default XML declaration is: + + <?xml version='1.0' standalone='yes'?> + +If you want some other string (for example to declare an encoding value), set +the value of this option to the complete string you require. =back @@ -1970,7 +1992,7 @@ From version 1.08_01, B<XML::Simple> includes support for SAX (the Simple API for XML) - specifically SAX2. -In a typical SAX application, an XML Parser (or SAX 'driver') module generates +In a typical SAX application, an XML parser (or SAX 'driver') module generates SAX events (start of element, character data, end of element, etc) as it parses an XML document and a 'handler' module processes the events to extract the required data. This simple model allows for some interesting and powerful @@ -2113,6 +2135,11 @@ =back +Note: The B<XML::SAX> distribution includes an XML parser written entirely in +Perl. It is very portable but it is not very fast. You should consider +installing B<XML::LibXML> or B<XML::SAX::Expat> if they are available for your +platform. + =head1 ERROR HANDLING The XML standard is very clear on the issue of non-compliant documents. An @@ -2268,23 +2295,52 @@ =head1 WHERE TO FROM HERE? -This section is going to be re-written. It will offer advice on what to do do -when your parsing needs outgrow the capabilities of B<XML::Simple> (as they -surely will). This advice will boil down to a quick explanation of tree versus -event based parsers and then recommend: +B<XML::Simple> is able to present a simple API because it makes some +assumptions on your behalf. These include: + +=over 4 + +=item * + +You're not interested in text content consisting only of whitespace + +=item * + +You don't mind that when things get slurped into a hash the order is lost + +=item * + +You don't want fine-grained control of the formatting of generated XML + +=item * + +You would never use a hash key that was not a legal XML element name + +=item * + +You don't need help converting between different encodings + +=back + +In a serious XML project, you'll probably outgrow these assumptions fairly +quickly. This section of the document used to offer some advice on chosing a +more powerful option. That advice has now grown into the 'Perl-XML FAQ' +document which you can find at: L<http://www.perlxml.net/perl-xml-faq.dkb> + +The advice in the FAQ boils down to a quick explanation of tree versus +event based parsers and then recommends: For event based parsing, use SAX (do not set out to write any new code for -XML::Parser's handler API). +XML::Parser's handler API - it is obselete). For tree-based parsing, you could choose between the 'Perlish' approach of -XML::Twig and more standards based DOM implementations - preferably including +XML::Twig and more standards based DOM implementations - preferably one with XPath support. =head1 STATUS -This version (1.08_01) is a beta (development) release. The current stable -version is 1.08. +This version (1.09) is the current stable version. =head1 SEE ALSO |