Diff for /parser3/src/classes/xdoc.C between versions 1.20 and 1.32

version 1.20, 2001/10/18 06:47:20 version 1.32, 2001/10/22 16:44:42
Line 6 Line 6
   
         $Id$          $Id$
 */  */
   #include "pa_types.h"
 #include "classes.h"  #include "classes.h"
 #ifdef XML  #ifdef XML
   
 #include "pa_request.h"  #include "pa_request.h"
 #include "pa_vxdoc.h"  #include "pa_vxdoc.h"
 #include "pa_xslt_stylesheet_manager.h"  #include "pa_stylesheet_manager.h"
 #include "pa_stylesheet_connection.h"  #include "pa_stylesheet_connection.h"
 #include "pa_vfile.h"  #include "pa_vfile.h"
 #include "xnode.h"  #include "xnode.h"
Line 38 Line 39
 #include <XalanDOM/XalanDocumentFragment.hpp>  #include <XalanDOM/XalanDocumentFragment.hpp>
 #include <XalanDOM/XalanCDATASection.hpp>  #include <XalanDOM/XalanCDATASection.hpp>
 #include <XalanDOM/XalanEntityReference.hpp>  #include <XalanDOM/XalanEntityReference.hpp>
   #include <dom/DOM_Document.hpp>
   #include <XercesParserLiaison/XercesDocumentBridge.hpp>
   #include <XalanTransformer/XercesDOMParsedSource.hpp>
   
 // defines  // defines
   
Line 56 Line 60
   
 class MXdoc : public MXnode {  class MXdoc : public MXnode {
 public: // VStateless_class  public: // VStateless_class
         Value *create_new_value(Pool& pool) { return new(pool) VXdoc(pool); }          Value *create_new_value(Pool& pool) { return new(pool) VXdoc(pool, 0, false); }
   
 public:  public:
         MXdoc(Pool& pool);          MXdoc(Pool& pool);
Line 73  static void _createElement(Request& r, c Line 77  static void _createElement(Request& r, c
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         const char *tagName=params->as_string(0, "tagName must be string").cstr(String::UL_XML);          const String& tagName=params->as_string(0, "tagName must be string");
   
         try {          try {
                 XalanNode *node=                  XalanNode *node=
                         vdoc.get_document(pool, &method_name).                          vdoc.get_document(pool, &method_name).
                         createElement(XalanDOMString(tagName));                          createElement(*pool.transcode(tagName));
                 // write out result                  // write out result
                 VXnode& result=*new(pool) VXnode(pool, node);                  VXnode& result=*new(pool) VXnode(pool, node, false);
                 r.write_no_lang(result);                  r.write_no_lang(result);
         } catch(const XalanDOMException& e)     {          } catch(const XalanDOMException& e)     {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
 }  }
   
 // DocumentFragment createDocumentFragment()  // DocumentFragment createDocumentFragment()
 static void _createDocumentFragment(Request& r, const String& method_name, MethodParams *params) {  static void _createDocumentFragment(Request& r, const String& method_name, MethodParams *) {
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
Line 96  static void _createDocumentFragment(Requ Line 100  static void _createDocumentFragment(Requ
                 vdoc.get_document(pool, &method_name).                  vdoc.get_document(pool, &method_name).
                 createDocumentFragment();                  createDocumentFragment();
         // write out result          // write out result
         VXnode& result=*new(pool) VXnode(pool, node);          VXnode& result=*new(pool) VXnode(pool, node, false);
         r.write_no_lang(result);          r.write_no_lang(result);
 }  }
   
Line 105  static void _createTextNode(Request& r, Line 109  static void _createTextNode(Request& r,
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         const char *data=params->as_string(0, "data must be string").cstr(String::UL_XML);          const String& data=params->as_string(0, "data must be string");
   
         XalanNode *node=          XalanNode *node=
                 vdoc.get_document(pool, &method_name).                  vdoc.get_document(pool, &method_name).
                 createTextNode(XalanDOMString(data));                  createTextNode(*pool.transcode(data));
         // write out result          // write out result
         VXnode& result=*new(pool) VXnode(pool, node);          VXnode& result=*new(pool) VXnode(pool, node, false);
         r.write_no_lang(result);          r.write_no_lang(result);
 }  }
   
Line 120  static void _createComment(Request& r, c Line 124  static void _createComment(Request& r, c
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         const char *data=params->as_string(0, "data must be string").cstr(String::UL_XML);          const String& data=params->as_string(0, "data must be string");
   
         XalanNode *node=          XalanNode *node=
                 vdoc.get_document(pool, &method_name).                  vdoc.get_document(pool, &method_name).
                 createComment(XalanDOMString(data));                  createComment(*pool.transcode(data));
         // write out result          // write out result
         VXnode& result=*new(pool) VXnode(pool, node);          VXnode& result=*new(pool) VXnode(pool, node, false);
         r.write_no_lang(result);          r.write_no_lang(result);
 }  }
   
Line 135  static void _createCDATASection(Request& Line 139  static void _createCDATASection(Request&
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         const char *data=params->as_string(0, "data must be string").cstr(String::UL_XML);          const String& data=params->as_string(0, "data must be string");
   
         try {          try {
                 XalanNode *node=                  XalanNode *node=
                         vdoc.get_document(pool, &method_name).                          vdoc.get_document(pool, &method_name).
                         createCDATASection(XalanDOMString(data));                          createCDATASection(*pool.transcode(data));
                 // write out result                  // write out result
                 VXnode& result=*new(pool) VXnode(pool, node);                  VXnode& result=*new(pool) VXnode(pool, node, false);
                 r.write_no_lang(result);                  r.write_no_lang(result);
         } catch(const XalanDOMException& e)     {          } catch(const XalanDOMException& e)     {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
 }  }
   
Line 154  static void _createProcessingInstruction Line 158  static void _createProcessingInstruction
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         const char *target=params->as_string(0, "target must be string").cstr(String::UL_XML);          const String& target=params->as_string(0, "data must be string");
         const char *data=params->as_string(1, "data must be string").cstr(String::UL_XML);          const String& data=params->as_string(1, "data must be string");
   
         try {          try {
                 XalanNode *node=                  XalanNode *node=
                         vdoc.get_document(pool, &method_name).                          vdoc.get_document(pool, &method_name).
                         createProcessingInstruction(XalanDOMString(target), XalanDOMString(data));                          createProcessingInstruction(*pool.transcode(target), *pool.transcode(data));
                 // write out result                  // write out result
                 VXnode& result=*new(pool) VXnode(pool, node);                  VXnode& result=*new(pool) VXnode(pool, node, false);
                 r.write_no_lang(result);                  r.write_no_lang(result);
         } catch(const XalanDOMException& e)     {          } catch(const XalanDOMException& e)     {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
 }  }
   
Line 174  static void _createAttribute(Request& r, Line 178  static void _createAttribute(Request& r,
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         const char *name=params->as_string(0, "name must be string").cstr(String::UL_XML);          const String& name=params->as_string(0, "name must be string");
   
         try {          try {
                 XalanNode *node=                  XalanNode *node=
                         vdoc.get_document(pool, &method_name).                          vdoc.get_document(pool, &method_name).
                         createAttribute(XalanDOMString(name));                          createAttribute(*pool.transcode(name));
                 // write out result                  // write out result
                 VXnode& result=*new(pool) VXnode(pool, node);                  VXnode& result=*new(pool) VXnode(pool, node, false);
                 r.write_no_lang(result);                  r.write_no_lang(result);
         } catch(const XalanDOMException& e)     {          } catch(const XalanDOMException& e)     {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
 }  }
 // EntityReference createEntityReference(in DOMString name) raises(DOMException);  // EntityReference createEntityReference(in DOMString name) raises(DOMException);
Line 192  static void _createEntityReference(Reque Line 196  static void _createEntityReference(Reque
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         const char *name=params->as_string(0, "name must be string").cstr(String::UL_XML);          const String& name=params->as_string(0, "name must be string");
   
         try {          try {
                 XalanNode *node=                  XalanNode *node=
                         vdoc.get_document(pool, &method_name).                          vdoc.get_document(pool, &method_name).
                         createEntityReference(XalanDOMString(name));                          createEntityReference(*pool.transcode(name));
                 // write out result                  // write out result
                 VXnode& result=*new(pool) VXnode(pool, node);                  VXnode& result=*new(pool) VXnode(pool, node, false);
                 r.write_no_lang(result);                  r.write_no_lang(result);
         } catch(const XalanDOMException& e)     {          } catch(const XalanDOMException& e)     {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
 }  }
   
Line 417  static void create_optioned_listener( Line 421  static void create_optioned_listener(
                         xalan_encoding  // encoding                          xalan_encoding  // encoding
                 );                  );
         } else          } else
                 PTHROW(0, 0,                  throw Exception(0, 0,
                         method,                          method,
                         XDOC_OUTPUT_METHOD_OPTION_NAME " option is invalid; valid methods are: "                          XDOC_OUTPUT_METHOD_OPTION_NAME " option is invalid; valid methods are: "
                                 "'" XDOC_OUTPUT_METHOD_OPTION_VALUE_XML "', "                                  "'" XDOC_OUTPUT_METHOD_OPTION_VALUE_XML "', "
Line 448  static void _save(Request& r, const Stri Line 452  static void _save(Request& r, const Stri
                 FormatterTreeWalker treeWalker(*formatterListener);                  FormatterTreeWalker treeWalker(*formatterListener);
                 treeWalker.traverse(&node); // Walk that node and produce the XML...                  treeWalker.traverse(&node); // Walk that node and produce the XML...
         } catch(const XSLException& e) {          } catch(const XSLException& e) {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
 }  }
   
 static void _string(Request& r, const String& method_name, MethodParams *params) {  static void _string(Request& r, const String& method_name, MethodParams *params) {
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXnode& vnode=*static_cast<VXnode *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         // node          // node
         XalanNode& node=vnode.get_node(pool, &method_name);          XalanNode *node=&vdoc.get_document(pool, &method_name);//.getDocumentElement();
           if(!node)
                   throw Exception(0, 0,
                           &method_name,
                           "no documentElement");
   
         try {          try {
                 String parserString=*new(pool) String(pool);                  String parserString=*new(pool) String(pool);
Line 468  static void _string(Request& r, const St Line 476  static void _string(Request& r, const St
                 create_optioned_listener(content_type, charset, formatterListener,                   create_optioned_listener(content_type, charset, formatterListener, 
                         pool, method_name, params, 0, writer);                          pool, method_name, params, 0, writer);
                 FormatterTreeWalker treeWalker(*formatterListener);                  FormatterTreeWalker treeWalker(*formatterListener);
                 treeWalker.traverse(&node); // Walk that node and produce the XML...                  treeWalker.traverse(node); // Walk that node and produce the XML...
   
                 // write out result                  // write out result
                 r.write_no_lang(parserString);                  r.write_no_lang(parserString);
         } catch(const XSLException& e) {          } catch(const XSLException& e) {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
 }  }
   
Line 512  static void _file(Request& r, const Stri Line 520  static void _file(Request& r, const Stri
                 vfile.set(false/*tainted*/, cstr, strlen(cstr), 0/*file_name*/, vcontent_type);                  vfile.set(false/*tainted*/, cstr, strlen(cstr), 0/*file_name*/, vcontent_type);
                 r.write_no_lang(vfile);                  r.write_no_lang(vfile);
         } catch(const XSLException& e) {          } catch(const XSLException& e) {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
 }  }
   
Line 532  static void _set(Request& r, const Strin Line 540  static void _set(Request& r, const Strin
                 parsedSource = new XalanDefaultParsedSource2(&stream);                  parsedSource = new XalanDefaultParsedSource2(&stream);
         }          }
         catch (XSLException& e) {          catch (XSLException& e) {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
         catch (SAXParseException& e)    {          catch (SAXParseException& e)    {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
         catch (SAXException& e) {          catch (SAXException& e) {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
         catch (XMLException& e) {          catch (XMLException& e) {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
         catch(const XalanDOMException& e)       {          catch(const XalanDOMException& e)       {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
   
         // replace any previous parsed source          // replace any previous parsed source
Line 552  static void _set(Request& r, const Strin Line 560  static void _set(Request& r, const Strin
 }  }
   
 static void _create(Request& r, const String& method_name, MethodParams *params) {  static void _create(Request& r, const String& method_name, MethodParams *params) {
           //_asm int 3;
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         const String& squalifiedName=params->as_string(0, "qualifiedName must be string");          const String& qualifiedName=params->as_string(0, "qualifiedName must be string");
   
         String xml(pool, "<?xml version=\"1.0\"?>\n");  
         xml << "<" << squalifiedName.cstr(String::UL_XML) << " />";  
   
         std::istrstream stream(xml.cstr());  
   
         const XalanParsedSource* parsedSource;          XalanDocument& document=*new XercesDocumentBridge(
                   DOM_Document::createDocument(),
                   0,
                   false /*threadSafe*/,
                   false /*don' buildBridge -- too early, empty document*/);
   
         try {          /// +createXMLDecl ?
                 parsedSource = new XalanDefaultParsedSource2(&stream);          document.appendChild(document.createElement(*pool.transcode(qualifiedName)));
         }  
         catch (XSLException& e) {  
                 pool.exception()._throw(pool, &method_name, e);  
         }  
         catch (SAXParseException& e)    {  
                 pool.exception()._throw(pool, &method_name, e);  
         }  
         catch (SAXException& e) {  
                 pool.exception()._throw(pool, &method_name, e);  
         }  
         catch (XMLException& e) {  
                 pool.exception()._throw(pool, &method_name, e);  
         }  
         catch(const XalanDOMException& e)       {  
                 pool.exception()._throw(pool, &method_name, e);  
         }  
   
         // replace any previous parsed source          // replace any previous document
         vdoc.set_parsed_source(*parsedSource);          vdoc.set_document(document, true/*owns*/);
 }  }
   
 static void _load(Request& r, const String& method_name, MethodParams *params) {  static void _load(Request& r, const String& method_name, MethodParams *params) {
Line 600  static void _load(Request& r, const Stri Line 592  static void _load(Request& r, const Stri
                 parsedSource = new XalanDefaultParsedSource2(filespec);                  parsedSource = new XalanDefaultParsedSource2(filespec);
         }          }
         catch (XSLException& e) {          catch (XSLException& e) {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
         catch (SAXParseException& e)    {          catch (SAXParseException& e)    {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
         catch (SAXException& e) {          catch (SAXException& e) {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
         catch (XMLException& e) {          catch (XMLException& e) {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
         catch(const XalanDOMException& e)       {          catch(const XalanDOMException& e)       {
                 pool.exception()._throw(pool, &method_name, e);                  Exception::provide_source(pool, &method_name, e);
         }          }
   
         // replace any previous parsed source          // replace any previous parsed source
Line 629  static void add_xslt_param(const Hash::K Line 621  static void add_xslt_param(const Hash::K
                 XalanDOMString(attribute_cstr),                    XalanDOMString(attribute_cstr),  
                 XalanDOMString(meaning_cstr));                  XalanDOMString(meaning_cstr));
 }  }
   /*
 static void _transform(Request& r, const String& method_name, MethodParams *params) {  static void _transform(Request& r, const String& method_name, MethodParams *params) {
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
Line 640  static void _transform(Request& r, const Line 633  static void _transform(Request& r, const
                         if(Hash *params=vparams.get_hash())                          if(Hash *params=vparams.get_hash())
                                 params->for_each(add_xslt_param, &vdoc.transformer());                                  params->for_each(add_xslt_param, &vdoc.transformer());
                         else                          else
                                 PTHROW(0, 0,                                  throw Exception(0, 0,
                                         &method_name,                                          &method_name,
                                         "transform parameters parameter must be hash");                                          "transform parameters parameter must be hash");
         }          }
Line 661  static void _transform(Request& r, const Line 654  static void _transform(Request& r, const
         try {          try {
                 vdoc.transformer().transform2(                  vdoc.transformer().transform2(
                         parsed_source,                           parsed_source, 
                         &connection.stylesheet(true/*nocache*/),                           &connection.stylesheet(true/*nocache* /), 
                         target);                          target);
         }          }
         catch (XSLException& e) {          catch (XSLException& e) {
                 connection.close();                  connection.close();
                 pool.exception()._throw(pool, &stylesheet_file_name, e);                  Exception::provide_source(pool, &stylesheet_file_name, e);
         }          }
         catch (SAXParseException& e)    {          catch (SAXParseException& e)    {
                 connection.close();                  connection.close();
                 pool.exception()._throw(pool, &stylesheet_file_name, e);                  Exception::provide_source(pool, &stylesheet_file_name, e);
         }          }
         catch (SAXException& e) {          catch (SAXException& e) {
                 connection.close();                  connection.close();
                 pool.exception()._throw(pool, &stylesheet_file_name, e);                  Exception::provide_source(pool, &stylesheet_file_name, e);
         }          }
         catch (XMLException& e) {          catch (XMLException& e) {
                 connection.close();                  connection.close();
                 pool.exception()._throw(pool, &stylesheet_file_name, e);                  Exception::provide_source(pool, &stylesheet_file_name, e);
         }          }
         catch(const XalanDOMException& e)       {          catch(const XalanDOMException& e)       {
                 connection.close();                  connection.close();
                 pool.exception()._throw(pool, &stylesheet_file_name, e);                  Exception::provide_source(pool, &stylesheet_file_name, e);
         }          }
   
         // write out result          // write out result
Line 690  static void _transform(Request& r, const Line 683  static void _transform(Request& r, const
         result.set_document(*target);          result.set_document(*target);
         r.write_no_lang(result);          r.write_no_lang(result);
 }  }
   */
   static void _transform(Request& r, const String& method_name, MethodParams *params) {
           Pool& pool=r.pool();
           VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
           // params
           if(params->size()>1) {
                   Value& vparams=params->as_no_junction(1, "transform parameters parameter must be string");
                   if(vparams.is_defined())
                           if(Hash *params=vparams.get_hash())
                                   params->for_each(add_xslt_param, &vdoc.transformer());
                           else
                                   throw Exception(0, 0,
                                           &method_name,
                                           "transform parameters parameter must be hash");
           }
   
           // source
           XalanDocument &xalan_document=vdoc.get_document(pool, &method_name);
   
           // stylesheet
           const String& stylesheet_file_name=params->as_string(0, "file name must be string");
           const String& stylesheet_filespec=r.absolute(stylesheet_file_name);
           //_asm int 3;
           Stylesheet_connection& connection=stylesheet_manager->get_connection(stylesheet_filespec);
   
           // target
           XalanDocument* target=vdoc.parser_liaison().createDocument();
   
           // transform
           try {
                   vdoc.transformer().transform2(
                           &xalan_document, 
                           &connection.stylesheet(true/*nocache*/), 
                           target);
           }
           catch (XSLException& e) {
                   connection.close();
                   Exception::provide_source(pool, &stylesheet_file_name, e);
           }
           catch (SAXParseException& e)    {
                   connection.close();
                   Exception::provide_source(pool, &stylesheet_file_name, e);
           }
           catch (SAXException& e) {
                   connection.close();
                   Exception::provide_source(pool, &stylesheet_file_name, e);
           }
           catch (XMLException& e) {
                   connection.close();
                   Exception::provide_source(pool, &stylesheet_file_name, e);
           }
           catch(const XalanDOMException& e)       {
                   connection.close();
                   Exception::provide_source(pool, &stylesheet_file_name, e);
           }
   
           // write out result
           VXdoc& result=*new(pool) VXdoc(pool, target, false/*owns not*/);
           r.write_no_lang(result);
   }
   
 static void _getElementById(Request& r, const String& method_name, MethodParams *params) {  static void _getElementById(Request& r, const String& method_name, MethodParams *params) {
         Pool& pool=r.pool();          Pool& pool=r.pool();
         VXdoc& vdoc=*static_cast<VXdoc *>(r.self);          VXdoc& vdoc=*static_cast<VXdoc *>(r.self);
   
         // elementId          // elementId
         const char *elementId=params->as_string(0, "elementID must be string").cstr(String::UL_XML);          const String& elementId=params->as_string(0, "elementID must be string");
   
         if(XalanNode *node=          if(XalanNode *node=
                 vdoc.get_document(pool, &method_name).getElementById(XalanDOMString(elementId))) {                  vdoc.get_document(pool, &method_name).getElementById(*pool.transcode(elementId))) {
                 // write out result                  // write out result
                 VXnode& result=*new(pool) VXnode(pool, node);                  VXnode& result=*new(pool) VXnode(pool, node, false);
                 r.write_no_lang(result);                  r.write_no_lang(result);
         }          }
 }  }

Removed from v.1.20  
changed lines
  Added in v.1.32


E-mail: