Diff for /XML/valid.c between versions 1.61 and 1.62

version 1.61, 2000/04/03 18:45:48 version 1.62, 2000/06/26 07:32:33
Line 154  void xmlValidDebug(xmlNodePtr cur, xmlEl Line 154  void xmlValidDebug(xmlNodePtr cur, xmlEl
 xmlElementPtr xmlGetDtdElementDesc(xmlDtdPtr dtd, const xmlChar *name);  xmlElementPtr xmlGetDtdElementDesc(xmlDtdPtr dtd, const xmlChar *name);
 xmlAttributePtr xmlScanAttributeDecl(xmlDtdPtr dtd, const xmlChar *elem);  xmlAttributePtr xmlScanAttributeDecl(xmlDtdPtr dtd, const xmlChar *elem);
   
   /************************************************************************
    *                                                                      *
    *                      QName handling helper                           *
    *                                                                      *
    ************************************************************************/
   
   /**
    * xmlSplitQName2:
    * @name:  an XML parser context
    * @prefix:  a xmlChar ** 
    *
    * parse an XML qualified name string
    *
    * [NS 5] QName ::= (Prefix ':')? LocalPart
    *
    * [NS 6] Prefix ::= NCName
    *
    * [NS 7] LocalPart ::= NCName
    *
    * Returns NULL if not a QName, otherwise the local part, and prefix
    *   is updated to get the Prefix if any.
    */
   
   xmlChar *
   xmlSplitQName2(const xmlChar *name, xmlChar **prefix) {
       int len = 0;
       xmlChar *ret = NULL;
   
       *prefix = NULL;
   
       /* xml: prefix is not really a namespace */
       if ((name[0] == 'x') && (name[1] == 'm') &&
           (name[2] == 'l') && (name[3] == ':'))
           return(NULL);
   
       /* nasty but valid */
       if (name[0] == ':')
           return(NULL);
   
       /*
        * we are not trying to validate but just to cut, and yes it will
        * work even if this is as set of UTF-8 encoded chars
        */
       while ((name[len] != 0) && (name[len] != ':')) 
           len++;
       
       if (name[len] == 0)
           return(NULL);
   
       *prefix = xmlStrndup(name, len);
       ret = xmlStrdup(&name[len + 1]);
   
       return(ret);
   }
   
 /****************************************************************  /****************************************************************
  *                                                              *   *                                                              *
  *      Util functions for data allocation/deallocation         *   *      Util functions for data allocation/deallocation         *
Line 428  xmlAddElementDecl(xmlValidCtxtPtr ctxt, Line 483  xmlAddElementDecl(xmlValidCtxtPtr ctxt,
                   xmlElementContentPtr content) {                    xmlElementContentPtr content) {
     xmlElementPtr ret, cur;      xmlElementPtr ret, cur;
     xmlElementTablePtr table;      xmlElementTablePtr table;
       xmlChar *ns, *uqname;
     int i;      int i;
   
     if (dtd == NULL) {      if (dtd == NULL) {
Line 473  xmlAddElementDecl(xmlValidCtxtPtr ctxt, Line 529  xmlAddElementDecl(xmlValidCtxtPtr ctxt,
     }      }
   
     /*      /*
        * check if name is a QName
        */
       uqname = xmlSplitQName2(name, &ns);
       if (uqname != NULL)
           name = uqname;
   
       /*
      * Create the Element table if needed.       * Create the Element table if needed.
      */       */
     table = dtd->elements;      table = dtd->elements;
Line 489  xmlAddElementDecl(xmlValidCtxtPtr ctxt, Line 552  xmlAddElementDecl(xmlValidCtxtPtr ctxt,
      */       */
     for (i = 0;i < table->nb_elements;i++) {      for (i = 0;i < table->nb_elements;i++) {
         cur = table->table[i];          cur = table->table[i];
         if (!xmlStrcmp(cur->name, name)) {          if ((ns != NULL) && (cur->prefix == NULL)) continue;
           if ((ns == NULL) && (cur->prefix != NULL)) continue;
           if ((!xmlStrcmp(cur->name, name)) &&
               ((ns == NULL) || (!xmlStrcmp(cur->prefix, ns)))) {
             /*              /*
              * The element is already defined in this Dtd.               * The element is already defined in this Dtd.
              */               */
Line 527  xmlAddElementDecl(xmlValidCtxtPtr ctxt, Line 593  xmlAddElementDecl(xmlValidCtxtPtr ctxt,
      */       */
     ret->etype = type;      ret->etype = type;
     ret->name = xmlStrdup(name);      ret->name = xmlStrdup(name);
       ret->prefix = ns;
     ret->content = xmlCopyElementContent(content);      ret->content = xmlCopyElementContent(content);
     ret->attributes = xmlScanAttributeDecl(dtd, name);      ret->attributes = xmlScanAttributeDecl(dtd, name);
     table->nb_elements++;      table->nb_elements++;
Line 543  xmlAddElementDecl(xmlValidCtxtPtr ctxt, Line 610  xmlAddElementDecl(xmlValidCtxtPtr ctxt,
         ret->prev = dtd->last;          ret->prev = dtd->last;
         dtd->last = (xmlNodePtr) ret;          dtd->last = (xmlNodePtr) ret;
     }      }
       if (uqname != NULL)
           xmlFree(uqname);
     return(ret);      return(ret);
 }  }
   
Line 559  xmlFreeElement(xmlElementPtr elem) { Line 628  xmlFreeElement(xmlElementPtr elem) {
     xmlFreeElementContent(elem->content);      xmlFreeElementContent(elem->content);
     if (elem->name != NULL)      if (elem->name != NULL)
         xmlFree((xmlChar *) elem->name);          xmlFree((xmlChar *) elem->name);
       if (elem->prefix != NULL)
           xmlFree((xmlChar *) elem->prefix);
     memset(elem, -1, sizeof(xmlElement));      memset(elem, -1, sizeof(xmlElement));
     xmlFree(elem);      xmlFree(elem);
 }  }
Line 896  xmlScanIDAttributeDecl(xmlValidCtxtPtr c Line 967  xmlScanIDAttributeDecl(xmlValidCtxtPtr c
  *   *
  * Register a new attribute declaration   * Register a new attribute declaration
  *   *
  * Returns NULL if not, othervise the entity   * Returns NULL if not new, othervise the attribute decl
  */   */
 xmlAttributePtr  xmlAttributePtr
 xmlAddAttributeDecl(xmlValidCtxtPtr ctxt, xmlDtdPtr dtd, const xmlChar *elem,  xmlAddAttributeDecl(xmlValidCtxtPtr ctxt, xmlDtdPtr dtd, const xmlChar *elem,
Line 981  xmlAddAttributeDecl(xmlValidCtxtPtr ctxt Line 1052  xmlAddAttributeDecl(xmlValidCtxtPtr ctxt
              */               */
             VWARNING(ctxt->userData, "Attribute %s on %s: already defined\n",              VWARNING(ctxt->userData, "Attribute %s on %s: already defined\n",
                    elem, name);                     elem, name);
               return(NULL);
         }          }
     }      }
   
Line 1157  xmlDumpAttributeDecl(xmlBufferPtr buf, x Line 1229  xmlDumpAttributeDecl(xmlBufferPtr buf, x
     xmlBufferWriteChar(buf, "<!ATTLIST ");      xmlBufferWriteChar(buf, "<!ATTLIST ");
     xmlBufferWriteCHAR(buf, attr->elem);      xmlBufferWriteCHAR(buf, attr->elem);
     xmlBufferWriteChar(buf, " ");      xmlBufferWriteChar(buf, " ");
       if (attr->prefix != NULL) {
           xmlBufferWriteCHAR(buf, attr->prefix);
           xmlBufferWriteChar(buf, ":");
       }
     xmlBufferWriteCHAR(buf, attr->name);      xmlBufferWriteCHAR(buf, attr->name);
     switch (attr->atype) {      switch (attr->atype) {
         case XML_ATTRIBUTE_CDATA:          case XML_ATTRIBUTE_CDATA:
Line 2066  xmlElementPtr Line 2142  xmlElementPtr
 xmlGetDtdElementDesc(xmlDtdPtr dtd, const xmlChar *name) {  xmlGetDtdElementDesc(xmlDtdPtr dtd, const xmlChar *name) {
     xmlElementTablePtr table;      xmlElementTablePtr table;
     xmlElementPtr cur;      xmlElementPtr cur;
       xmlChar *uqname = NULL, *prefix = NULL;
     int i;      int i;
   
     if (dtd == NULL) return(NULL);      if (dtd == NULL) return(NULL);
Line 2077  xmlGetDtdElementDesc(xmlDtdPtr dtd, cons Line 2154  xmlGetDtdElementDesc(xmlDtdPtr dtd, cons
         if (!xmlStrcmp(cur->name, name))          if (!xmlStrcmp(cur->name, name))
             return(cur);              return(cur);
     }      }
   
       /*
        * Specific case if name is a QName.
        */
       uqname = xmlSplitQName2(name, &prefix);
       if (uqname == NULL) return(NULL);
   
       for (i = 0;i < table->nb_elements;i++) {
           cur = table->table[i];
           if ((!xmlStrcmp(cur->name, uqname)) &&
               ((prefix == cur->prefix) ||
                ((prefix != NULL) && (cur->prefix != NULL) &&
                 (!xmlStrcmp(cur->prefix, prefix))))) {
               if (prefix != NULL) xmlFree(prefix);
               if (uqname != NULL) xmlFree(uqname);
               return(cur);
           }
       }
       if (prefix != NULL) xmlFree(prefix);
       if (uqname != NULL) xmlFree(uqname);
       return(NULL);
   }
   
   /**
    * xmlGetDtdQElementDesc:
    * @dtd:  a pointer to the DtD to search
    * @name:  the element name
    * @prefix:  the element namespace prefix
    *
    * Search the Dtd for the description of this element
    *
    * returns the xmlElementPtr if found or NULL
    */
   
   xmlElementPtr
   xmlGetDtdQElementDesc(xmlDtdPtr dtd, const xmlChar *name,
                         const xmlChar *prefix) {
       xmlElementTablePtr table;
       xmlElementPtr cur;
       int i;
   
       if (dtd == NULL) return(NULL);
       if (dtd->elements == NULL) return(NULL);
       table = dtd->elements;
   
       for (i = 0;i < table->nb_elements;i++) {
           cur = table->table[i];
           if (!xmlStrcmp(cur->name, name) &&
               ((prefix == cur->prefix) ||
                ((prefix != NULL) && (cur->prefix != NULL) &&
                 (!xmlStrcmp(cur->prefix, prefix)))))
               return(cur);
       }
     return(NULL);      return(NULL);
 }  }
   
Line 2096  xmlAttributePtr Line 2226  xmlAttributePtr
 xmlGetDtdAttrDesc(xmlDtdPtr dtd, const xmlChar *elem, const xmlChar *name) {  xmlGetDtdAttrDesc(xmlDtdPtr dtd, const xmlChar *elem, const xmlChar *name) {
     xmlAttributeTablePtr table;      xmlAttributeTablePtr table;
     xmlAttributePtr cur;      xmlAttributePtr cur;
       xmlChar *uqname = NULL, *prefix = NULL;
     int i;      int i;
   
     if (dtd == NULL) return(NULL);      if (dtd == NULL) return(NULL);
Line 2108  xmlGetDtdAttrDesc(xmlDtdPtr dtd, const x Line 2239  xmlGetDtdAttrDesc(xmlDtdPtr dtd, const x
             (!xmlStrcmp(cur->elem, elem)))              (!xmlStrcmp(cur->elem, elem)))
             return(cur);              return(cur);
     }      }
   
       /*
        * Specific case if name is a QName.
        */
       uqname = xmlSplitQName2(name, &prefix);
       if (uqname == NULL) return(NULL);
   
       for (i = 0;i < table->nb_attributes;i++) {
           cur = table->table[i];
           if ((!xmlStrcmp(cur->name, uqname)) &&
               (!xmlStrcmp(cur->elem, elem)) &&
               ((prefix == cur->prefix) ||
                ((prefix != NULL) && (cur->prefix != NULL) &&
                 (!xmlStrcmp(cur->prefix, prefix))))) {
               if (prefix != NULL) xmlFree(prefix);
               if (uqname != NULL) xmlFree(uqname);
               return(cur);
           }
       }
       if (prefix != NULL) xmlFree(prefix);
       if (uqname != NULL) xmlFree(uqname);
       return(NULL);
   }
   
   /**
    * xmlGetDtdQAttrDesc:
    * @dtd:  a pointer to the DtD to search
    * @elem:  the element name
    * @name:  the attribute name
    * @prefix:  the attribute namespace prefix
    *
    * Search the Dtd for the description of this qualified attribute on
    * this element.
    *
    * returns the xmlAttributePtr if found or NULL
    */
   
   xmlAttributePtr
   xmlGetDtdQAttrDesc(xmlDtdPtr dtd, const xmlChar *elem, const xmlChar *name,
                     const xmlChar *prefix) {
       xmlAttributeTablePtr table;
       xmlAttributePtr cur;
       int i;
   
       if (dtd == NULL) return(NULL);
       if (dtd->attributes == NULL) return(NULL);
       table = dtd->attributes;
   
       for (i = 0;i < table->nb_attributes;i++) {
           cur = table->table[i];
           if ((!xmlStrcmp(cur->name, name)) &&
               (!xmlStrcmp(cur->elem, elem)) &&
               ((prefix == cur->prefix) ||
                ((prefix != NULL) && (cur->prefix != NULL) &&
                 (!xmlStrcmp(cur->prefix, prefix)))))
               return(cur);
       }
     return(NULL);      return(NULL);
 }  }
   
Line 2586  xmlValidNormalizeAttributeValue(xmlDocPt Line 2774  xmlValidNormalizeAttributeValue(xmlDocPt
                                 const xmlChar *name, const xmlChar *value) {                                  const xmlChar *name, const xmlChar *value) {
     xmlChar *ret, *dst;      xmlChar *ret, *dst;
     const xmlChar *src;      const xmlChar *src;
     xmlAttributePtr attrDecl;      xmlAttributePtr attrDecl = NULL;
   
     if (doc == NULL) return(NULL);      if (doc == NULL) return(NULL);
     if (elem == NULL) return(NULL);      if (elem == NULL) return(NULL);
     if (name == NULL) return(NULL);      if (name == NULL) return(NULL);
     if (value == NULL) return(NULL);      if (value == NULL) return(NULL);
   
       if ((elem->ns != NULL) && (elem->ns->prefix != NULL)) {
           xmlChar qname[500];
   #ifdef HAVE_SNPRINTF
           snprintf((char *) qname, sizeof(qname), "%s:%s",
                    elem->ns->prefix, elem->name);
   #else
           sprintf(qname, "%s:%s", elem->name, elem->ns->prefix);
   #endif
           attrDecl = xmlGetDtdAttrDesc(doc->intSubset, qname, name);
           if ((attrDecl == NULL) && (doc->extSubset != NULL))
               attrDecl = xmlGetDtdAttrDesc(doc->extSubset, qname, name);
       }
     attrDecl = xmlGetDtdAttrDesc(doc->intSubset, elem->name, name);      attrDecl = xmlGetDtdAttrDesc(doc->intSubset, elem->name, name);
     if ((attrDecl == NULL) && (doc->extSubset != NULL))      if ((attrDecl == NULL) && (doc->extSubset != NULL))
         attrDecl = xmlGetDtdAttrDesc(doc->extSubset, elem->name, name);          attrDecl = xmlGetDtdAttrDesc(doc->extSubset, elem->name, name);
Line 2850  int Line 3050  int
 xmlValidateOneAttribute(xmlValidCtxtPtr ctxt, xmlDocPtr doc,  xmlValidateOneAttribute(xmlValidCtxtPtr ctxt, xmlDocPtr doc,
                         xmlNodePtr elem, xmlAttrPtr attr, const xmlChar *value) {                          xmlNodePtr elem, xmlAttrPtr attr, const xmlChar *value) {
     /* xmlElementPtr elemDecl; */      /* xmlElementPtr elemDecl; */
     xmlAttributePtr attrDecl;      xmlAttributePtr attrDecl =  NULL;
     int val;      int val;
     int ret = 1;      int ret = 1;
   
Line 2858  xmlValidateOneAttribute(xmlValidCtxtPtr Line 3058  xmlValidateOneAttribute(xmlValidCtxtPtr
     if ((elem == NULL) || (elem->name == NULL)) return(0);      if ((elem == NULL) || (elem->name == NULL)) return(0);
     if ((attr == NULL) || (attr->name == NULL)) return(0);      if ((attr == NULL) || (attr->name == NULL)) return(0);
   
     attrDecl = xmlGetDtdAttrDesc(doc->intSubset, elem->name, attr->name);      if ((elem->ns != NULL) && (elem->ns->prefix != NULL)) {
     if ((attrDecl == NULL) && (doc->extSubset != NULL))          xmlChar qname[500];
         attrDecl = xmlGetDtdAttrDesc(doc->extSubset, elem->name, attr->name);  #ifdef HAVE_SNPRINTF
           snprintf((char *) qname, sizeof(qname), "%s:%s",
                    elem->ns->prefix, elem->name);
   #else
           sprintf(qname, "%s:%s", elem->name, elem->ns->prefix);
   #endif
           if (attr->ns != NULL) {
               attrDecl = xmlGetDtdQAttrDesc(doc->intSubset, qname,
                                             attr->name, attr->ns->prefix);
               if ((attrDecl == NULL) && (doc->extSubset != NULL))
                   attrDecl = xmlGetDtdQAttrDesc(doc->extSubset, qname,
                                                 attr->name, attr->ns->prefix);
           } else {
               attrDecl = xmlGetDtdAttrDesc(doc->intSubset, qname, attr->name);
               if ((attrDecl == NULL) && (doc->extSubset != NULL))
                   attrDecl = xmlGetDtdAttrDesc(doc->extSubset,
                                                qname, attr->name);
           }
       }
       if (attrDecl == NULL) {
           if (attr->ns != NULL) {
               attrDecl = xmlGetDtdQAttrDesc(doc->intSubset, elem->name,
                                             attr->name, attr->ns->prefix);
               if ((attrDecl == NULL) && (doc->extSubset != NULL))
                   attrDecl = xmlGetDtdQAttrDesc(doc->extSubset, elem->name,
                                                 attr->name, attr->ns->prefix);
           } else {
               attrDecl = xmlGetDtdAttrDesc(doc->intSubset,
                                            elem->name, attr->name);
               if ((attrDecl == NULL) && (doc->extSubset != NULL))
                   attrDecl = xmlGetDtdAttrDesc(doc->extSubset,
                                                elem->name, attr->name);
           }
       }
   
   
     /* Validity Constraint: Attribute Value Type */      /* Validity Constraint: Attribute Value Type */
Line 3274  xmlSprintfElementChilds(char *buf, xmlNo Line 3507  xmlSprintfElementChilds(char *buf, xmlNo
 int  int
 xmlValidateOneElement(xmlValidCtxtPtr ctxt, xmlDocPtr doc,  xmlValidateOneElement(xmlValidCtxtPtr ctxt, xmlDocPtr doc,
                       xmlNodePtr elem) {                        xmlNodePtr elem) {
     xmlElementPtr elemDecl;      xmlElementPtr elemDecl = NULL;
     xmlElementContentPtr cont;      xmlElementContentPtr cont;
     xmlAttributePtr attr;      xmlAttributePtr attr;
     xmlNodePtr child;      xmlNodePtr child;
Line 3347  xmlValidateOneElement(xmlValidCtxtPtr ct Line 3580  xmlValidateOneElement(xmlValidCtxtPtr ct
     }      }
     if (elem->name == NULL) return(0);      if (elem->name == NULL) return(0);
   
     elemDecl = xmlGetDtdElementDesc(doc->intSubset, elem->name);      /*
     if ((elemDecl == NULL) && (doc->extSubset != NULL))       * Fetch the declaration for the qualified name
         elemDecl = xmlGetDtdElementDesc(doc->extSubset, elem->name);       */
       if ((elem->ns != NULL) && (elem->ns->prefix != NULL)) {
           elemDecl = xmlGetDtdQElementDesc(doc->intSubset,
                                            elem->name, elem->ns->prefix);
           if ((elemDecl == NULL) && (doc->extSubset != NULL))
               elemDecl = xmlGetDtdQElementDesc(doc->extSubset,
                                                elem->name, elem->ns->prefix);
       }
   
       /*
        * Fetch the declaration for the non qualified name
        */
       if (elemDecl == NULL) {
           elemDecl = xmlGetDtdElementDesc(doc->intSubset, elem->name);
           if ((elemDecl == NULL) && (doc->extSubset != NULL))
               elemDecl = xmlGetDtdElementDesc(doc->extSubset, elem->name);
       }
     if (elemDecl == NULL) {      if (elemDecl == NULL) {
         VERROR(ctxt->userData, "No declaration for element %s\n",          VERROR(ctxt->userData, "No declaration for element %s\n",
                elem->name);                 elem->name);
Line 3375  xmlValidateOneElement(xmlValidCtxtPtr ct Line 3624  xmlValidateOneElement(xmlValidCtxtPtr ct
             while (child != NULL) {              while (child != NULL) {
                 if (child->type == XML_ELEMENT_NODE) {                  if (child->type == XML_ELEMENT_NODE) {
                     name = child->name;                      name = child->name;
                       if ((child->ns != NULL) && (child->ns->prefix != NULL)) {
                           xmlChar qname[500];
   #ifdef HAVE_SNPRINTF
                           snprintf((char *) qname, sizeof(qname), "%s:%s",
                                    child->ns->prefix, child->name);
   #else
                           sprintf(qname, "%s:%s", child->name, child->ns->prefix);
   #endif
                           cont = elemDecl->content;
                           while (cont != NULL) {
                               if (cont->type == XML_ELEMENT_CONTENT_ELEMENT) {
                                   if (!xmlStrcmp(cont->name, qname)) break;
                               } else if ((cont->type == XML_ELEMENT_CONTENT_OR) &&
                                  (cont->c1 != NULL) &&
                                  (cont->c1->type == XML_ELEMENT_CONTENT_ELEMENT)){
                                   if (!xmlStrcmp(cont->c1->name, qname)) break;
                               } else if ((cont->type != XML_ELEMENT_CONTENT_OR) ||
                                   (cont->c1 == NULL) ||
                                   (cont->c1->type != XML_ELEMENT_CONTENT_PCDATA)){
                                   /* Internal error !!! */
                                   fprintf(stderr, "Internal: MIXED struct bad\n");
                                   break;
                               }
                               cont = cont->c2;
                           }
                           if (cont != NULL)
                               goto child_ok;
                       }
                     cont = elemDecl->content;                      cont = elemDecl->content;
                     while (cont != NULL) {                      while (cont != NULL) {
                         if (cont->type == XML_ELEMENT_CONTENT_ELEMENT) {                          if (cont->type == XML_ELEMENT_CONTENT_ELEMENT) {
Line 3399  xmlValidateOneElement(xmlValidCtxtPtr ct Line 3676  xmlValidateOneElement(xmlValidCtxtPtr ct
                         ret = 0;                          ret = 0;
                     }                      }
                 }                  }
   child_ok:
                 child = child->next;                  child = child->next;
             }              }
             break;              break;
Line 3518  xmlValidateRoot(xmlValidCtxtPtr ctxt, xm Line 3796  xmlValidateRoot(xmlValidCtxtPtr ctxt, xm
         VERROR(ctxt->userData, "Not valid: no root element\n");          VERROR(ctxt->userData, "Not valid: no root element\n");
         return(0);          return(0);
     }      }
   
       /*
        * Check first the document root against the NQName
        */
     if (xmlStrcmp(doc->intSubset->name, root->name)) {      if (xmlStrcmp(doc->intSubset->name, root->name)) {
         if ((xmlStrcmp(doc->intSubset->name, BAD_CAST "HTML")) ||          if ((root->ns != NULL) && (root->ns->prefix != NULL)) {
             (xmlStrcmp(root->name, BAD_CAST "html"))) {              xmlChar qname[500];
             VERROR(ctxt->userData,  #ifdef HAVE_SNPRINTF
                    "Not valid: root and DtD name do not match '%s' and '%s'\n",              snprintf((char *) qname, sizeof(qname), "%s:%s",
                    root->name, doc->intSubset->name);                       root->ns->prefix, root->name);
             return(0);  #else
         }              sprintf(qname, "%s:%s", root->name, root->ns->prefix);
   #endif
               if (!xmlStrcmp(doc->intSubset->name, qname))
                   goto name_ok;
           } 
           if ((!xmlStrcmp(doc->intSubset->name, BAD_CAST "HTML")) &&
               (!xmlStrcmp(root->name, BAD_CAST "html")))
               goto name_ok;
           VERROR(ctxt->userData,
                  "Not valid: root and DtD name do not match '%s' and '%s'\n",
                  root->name, doc->intSubset->name);
           return(0);
           
     }      }
   name_ok:
     return(1);      return(1);
 }  }
   

Removed from v.1.61  
changed lines
  Added in v.1.62


Webmaster