(file) Return to XmlParser.cpp CVS log (file) (dir) Up to [Pegasus] / pegasus / src / Pegasus / Common

Diff for /pegasus/src/Pegasus/Common/XmlParser.cpp between version 1.42 and 1.46.4.2

version 1.42, 2007/07/30 06:50:57 version 1.46.4.2, 2008/09/17 18:31:16
Line 172 
Line 172 
     "Unterminated comment",     "Unterminated comment",
     "Unterminated CDATA block",     "Unterminated CDATA block",
     "Unterminated DOCTYPE",     "Unterminated DOCTYPE",
     "Too many attributes: parser only handles 10",  
     "Malformed reference",     "Malformed reference",
     "Expected a comment or CDATA following \"<!\" sequence",     "Expected a comment or CDATA following \"<!\" sequence",
     "Closing element does not match opening element",     "Closing element does not match opening element",
     "One or more tags are still open",     "One or more tags are still open",
     "More than one root element was encountered",     "More than one root element was encountered",
     "Validation error",     "Validation error",
     "Semantic error"      "Semantic error",
       "Namespace not declared"
 }; };
  
 static const char* _xmlKeys[] = static const char* _xmlKeys[] =
Line 193 
Line 193 
     "Common.XmlParser.UNTERMINATED_COMMENT",     "Common.XmlParser.UNTERMINATED_COMMENT",
     "Common.XmlParser.UNTERMINATED_CDATA",     "Common.XmlParser.UNTERMINATED_CDATA",
     "Common.XmlParser.UNTERMINATED_DOCTYPE",     "Common.XmlParser.UNTERMINATED_DOCTYPE",
     "Common.XmlParser.TOO_MANY_ATTRIBUTES",  
     "Common.XmlParser.MALFORMED_REFERENCE",     "Common.XmlParser.MALFORMED_REFERENCE",
     "Common.XmlParser.EXPECTED_COMMENT_OR_CDATA",     "Common.XmlParser.EXPECTED_COMMENT_OR_CDATA",
     "Common.XmlParser.START_END_MISMATCH",     "Common.XmlParser.START_END_MISMATCH",
     "Common.XmlParser.UNCLOSED_TAGS",     "Common.XmlParser.UNCLOSED_TAGS",
     "Common.XmlParser.MULTIPLE_ROOTS",     "Common.XmlParser.MULTIPLE_ROOTS",
     "Common.XmlParser.VALIDATION_ERROR",     "Common.XmlParser.VALIDATION_ERROR",
     "Common.XmlParser.SEMANTIC_ERROR"      "Common.XmlParser.SEMANTIC_ERROR",
       "Common.XmlParser.UNDECLARED_NAMESPACE"
 }; };
  
 // l10n replace _formMessage (comment out the old one)  
 /*  
 static String _formMessage(Uint32 code, Uint32 line, const String& message)  
 {  
     String result = _xmlMessages[Uint32(code) - 1];  
   
     char buffer[32];  
     sprintf(buffer, "%d", line);  
     result.append(": on line ");  
     result.append(buffer);  
   
     if (message.size())  
     {  
         result.append(": ");  
         result.append(message);  
     }  
   
     return result;  
 }  
 */  
  
 static MessageLoaderParms _formMessage( static MessageLoaderParms _formMessage(
     Uint32 code,     Uint32 code,
Line 328 
Line 308 
 // //
 //////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
  
 XmlParser::XmlParser(char* text)  XmlParser::XmlParser(char* text, XmlNamespace* ns)
     : _line(1),     : _line(1),
       _current(text),       _current(text),
       _restoreChar('\0'),       _restoreChar('\0'),
       _foundRoot(false)        _foundRoot(false),
         _supportedNamespaces(ns),
         // Start valid indexes with -2. -1 is reserved for not found.
         _currentUnsupportedNSType(-2)
 { {
 } }
  
Line 347 
Line 330 
     }     }
 } }
  
   #if defined(PEGASUS_PLATFORM_WIN64_IA64_MSVC) || \
       defined(PEGASUS_PLATFORM_WIN64_X86_64_MSVC)
   #pragma optimize( "", off )
   #endif
 static int _getEntityRef(char*& p) static int _getEntityRef(char*& p)
 { {
     if ((p[0] == 'g') && (p[1] == 't') && (p[2] == ';'))     if ((p[0] == 'g') && (p[1] == 't') && (p[2] == ';'))
Line 383 
Line 370 
  
     return -1;     return -1;
 } }
   #if defined(PEGASUS_PLATFORM_WIN64_IA64_MSVC) || \
       defined(PEGASUS_PLATFORM_WIN64_X86_64_MSVC)
   #pragma optimize( "", on )
   #endif
  
 static inline int _getCharRef(char*& p, bool hex)  static inline int _getCharRef(char*& p)
 { {
     char* end;     char* end;
     unsigned long ch;     unsigned long ch;
       Boolean hex = false;
  
     if (hex)      if (*p == 'x')
     {     {
         ch = strtoul(p, &end, 16);          hex = true;
           ch = strtoul(++p, &end, 16);
     }     }
     else     else
     {     {
Line 413 
Line 406 
     return ch;     return ch;
 } }
  
 static void _normalize(Uint32& line, char*& p, char end_char, char*& start)  // Parse an entity reference or a character reference
   static inline int _getRef(Uint32 line, char*& p)
 { {
     // Skip over leading whitespace:      int ch;
  
     _skipWhitespace(line, p);      if (*p == '#')
     start = p;      {
           ch = _getCharRef(++p);
       }
       else
       {
           ch = _getEntityRef(p);
       }
   
       if (ch == -1)
       {
           throw XmlException(XmlException::MALFORMED_REFERENCE, line);
       }
   
       return ch;
   }
  
   static inline void _normalizeElementValue(
       Uint32& line,
       char*& p)
   {
     // Process one character at a time:     // Process one character at a time:
  
     char* q = p;     char* q = p;
  
     while (*p && (*p != end_char))      while (*p && (*p != '<'))
     {     {
         if (_isspace(*p))         if (_isspace(*p))
         {         {
             // Compress sequences of whitespace characters to a single space              // Trim whitespace from the end of the value, but do not compress
             // character. Update line number when newlines encountered.              // whitespace within the value.
   
               const char* start = p;
  
             if (*p++ == '\n')             if (*p++ == '\n')
             {             {
                 line++;                 line++;
             }             }
  
             *q++ = ' ';  
   
             _skipWhitespace(line, p);             _skipWhitespace(line, p);
         }  
         else if (*p == '&')  
         {  
             // Process entity characters and entity references:  
   
             p++;  
             int ch;  
  
             if (*p == '#')              if (*p && (*p != '<'))
             {             {
                 *p++;                  // Transfer internal whitespace to q without compressing it.
                   const char* i = start;
                 if (*p == 'x')                  while (i < p)
                 {                 {
                     p++;                      *q++ = *i++;
                     ch = _getCharRef(p, true);                  }
                 }                 }
                 else                 else
                 {                 {
                     ch = _getCharRef(p, false);                  // Do not transfer trailing whitespace to q.
                   break;
               }
                 }                 }
           else if (*p == '&')
           {
               // Process an entity reference or a character reference.
   
               *q++ = _getRef(line, ++p);
             }             }
             else             else
             {             {
                 ch = _getEntityRef(p);              *q++ = *p++;
           }
             }             }
  
             if (ch == -1)      // If q got behind p, it is safe and necessary to null-terminate q
   
       if (q != p)
             {             {
                 throw XmlException(XmlException::MALFORMED_REFERENCE, line);          *q = '\0';
       }
             }             }
  
             *q++ = ch;  static inline void _normalizeAttributeValue(
       Uint32& line,
       char*& p,
       char end_char,
       char*& start)
   {
       // Skip over leading whitespace:
   
       _skipWhitespace(line, p);
       start = p;
   
       // Process one character at a time:
   
       char* q = p;
   
       while (*p && (*p != end_char))
       {
           if (_isspace(*p))
           {
               // Compress sequences of whitespace characters to a single space
               // character. Update line number when newlines encountered.
   
               if (*p++ == '\n')
               {
                   line++;
               }
   
               *q++ = ' ';
   
               _skipWhitespace(line, p);
           }
           else if (*p == '&')
           {
               // Process an entity reference or a character reference.
   
               *q++ = _getRef(line, ++p);
         }         }
         else         else
         {         {
Line 479 
Line 530 
         }         }
     }     }
  
     // We encountered a the end_char or a zero-terminator.  
   
     *q = *p;  
   
     // Remove single trailing whitespace (consecutive whitespaces already     // Remove single trailing whitespace (consecutive whitespaces already
     // compressed above).  Since p >= q, we can tell if we need to strip a     // compressed above).  Since p >= q, we can tell if we need to strip a
     // trailing space from q by looking at the end of p.  We must not look at     // trailing space from q by looking at the end of p.  We must not look at
     // the last character of p, though, if p is an empty string.     // the last character of p, though, if p is an empty string.
       Boolean adjust_q = (p != start) && _isspace(p[-1]);
   
       // We encountered a the end_char or a zero-terminator.
  
     if ((p != start) && _isspace(p[-1]))      *q = *p;
   
       if (adjust_q)
     {     {
         q--;         q--;
     }     }
Line 501 
Line 553 
     }     }
 } }
  
 Boolean XmlParser::next(XmlEntry& entry, Boolean includeComment)  Boolean XmlParser::next(
       XmlEntry& entry,
       Boolean includeComment)
 { {
     if (!_putBackStack.isEmpty())     if (!_putBackStack.isEmpty())
     {     {
Line 523 
Line 577 
         _restoreChar = '\0';         _restoreChar = '\0';
     }     }
  
       entry.attributes.clear();
   
       if (_supportedNamespaces)
       {
           // Remove namespaces of a deeper scope level from the stack.
           while (!_nameSpaces.isEmpty() &&
                  _nameSpaces.top().scopeLevel > _stack.size())
           {
               _nameSpaces.pop();
           }
       }
   
     // Loop until we are done with comments if includeComment is false.     // Loop until we are done with comments if includeComment is false.
     do     do
     {     {
Line 573 
Line 639 
         {         {
             // Normalize the content:             // Normalize the content:
  
             char* start;              char* start = _current;
             _normalize(_line, _current, '<', start);              _normalizeElementValue(_line, _current);
  
             // Get the content:             // Get the content:
  
Line 591 
Line 657 
         }         }
     }while (!includeComment && entry.type == XmlEntry::COMMENT);     }while (!includeComment && entry.type == XmlEntry::COMMENT);
  
       if (_supportedNamespaces &&
           (entry.type == XmlEntry::START_TAG ||
            entry.type == XmlEntry::EMPTY_TAG ||
            entry.type == XmlEntry::END_TAG))
       {
           // Determine the namespace type for this entry
   
           if (entry.type == XmlEntry::START_TAG ||
               entry.type == XmlEntry::EMPTY_TAG)
           {
               // Process namespace declarations and determine the namespace type
               // for the attributes.
   
               Uint32 scopeLevel = _stack.size();
               if (entry.type == XmlEntry::EMPTY_TAG)
               {
                   // Empty tags are deeper scope, but not pushed onto the stack
                   scopeLevel++;
               }
   
               for (Uint32 i = 0, n = entry.attributes.size(); i < n; i++)
               {
                   XmlAttribute& attr = entry.attributes[i];
                   if ((strncmp(attr.name, "xmlns:", 6) == 0) ||
                       (strcmp(attr.name, "xmlns") == 0))
                   {
                       // Process a namespace declaration
                       XmlNamespace ns;
                       if (attr.name[5] == ':')
                       {
                           ns.localName = attr.localName;
                       }
                       else
                       {
                           // Default name space has no local name
                           ns.localName = 0;
                       }
                       ns.extendedName = attr.value;
                       ns.scopeLevel = scopeLevel;
                       ns.type = _getSupportedNamespaceType(ns.extendedName);
   
                       // If the namespace is not supported, assign it a unique
                       // negative identifier.
                       if (ns.type == -1)
                       {
                           ns.type = _currentUnsupportedNSType--;
                       }
   
                       _nameSpaces.push(ns);
                   }
                   else
                   {
                       // Get the namespace type for this attribute.
                       attr.nsType = _getNamespaceType(attr.name);
                   }
               }
           }
   
           entry.nsType = _getNamespaceType(entry.text);
       }
       else
       {
           entry.nsType = -1;
       }
   
     return true;     return true;
 } }
  
   // Get the namespace type of the given tag
   int XmlParser::_getNamespaceType(const char* tag)
   {
       const char* pos = strchr(tag, ':');
   
       // If ':' is not found, the tag is not namespace qualified and we
       // need to look for the default name space.
   
       // Search the namespace stack from the top
       for (Sint32 i = _nameSpaces.size() - 1; i >=0; i--)
       {
           // If ':' is found, look for the name space with the matching
           // local name...
           if ((pos && _nameSpaces[i].localName &&
                !strncmp(_nameSpaces[i].localName, tag, pos - tag)) ||
               // ... otherwise look for the default name space. It's the
               // one with localName set to NULL
               (!pos && !_nameSpaces[i].localName))
           {
               return _nameSpaces[i].type;
           }
       }
   
       // If the tag is namespace qualified, but the name space has not been
       // declared, it's malformed XML and we must throw an exception.
       // Note:  The "xml" namespace is specifically defined by the W3C as a
       // reserved prefix ("http://www.w3.org/XML/1998/namespace").
       if (pos && (strncmp(tag, "xml:", 4) != 0))
       {
           throw XmlException(XmlException::UNDECLARED_NAMESPACE, _line);
       }
   
       // Otherwise it's OK not to have a name space.
       return -1;
   }
   
   // Given the extended namespace name, find it in the table of supported
   // namespaces and return its type.
   int XmlParser::_getSupportedNamespaceType(const char* extendedName)
   {
       for (Sint32 i = 0;
            _supportedNamespaces[i].localName != 0;
            i++)
       {
           PEGASUS_ASSERT(_supportedNamespaces[i].type == i);
           if (!strcmp(_supportedNamespaces[i].extendedName, extendedName))
           {
               return _supportedNamespaces[i].type;
           }
       }
       return -1;
   }
   
   XmlNamespace* XmlParser::getNamespace(int nsType)
   {
       for (Sint32 i = _nameSpaces.size() - 1; i >=0; i--)
       {
           if (_nameSpaces[i].type == nsType)
           {
               return &_nameSpaces[i];
           }
       }
       return 0;
   }
   
 void XmlParser::putBack(XmlEntry& entry) void XmlParser::putBack(XmlEntry& entry)
 { {
     _putBackStack.push(entry);     _putBackStack.push(entry);
Line 604 
Line 800 
     // Nothing to do!     // Nothing to do!
 } }
  
 // A-Za-z0-9_-:.  // A-Za-z0-9_-.  (Note that ':' is not included and must be checked separately)
 static unsigned char _isInnerElementChar[] = static unsigned char _isInnerElementChar[] =
 { {
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,      0,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,
     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
     1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,     1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 }; };
  
 Boolean XmlParser::_getElementName(char*& p)  inline Boolean _getQName(char*& p, const char*& localName)
 { {
       localName = p;
   
     if (!CharSet::isAlNumUnder(Uint8(*p)))     if (!CharSet::isAlNumUnder(Uint8(*p)))
         throw XmlException(XmlException::BAD_START_TAG, _line);          return false;
   
       p++;
  
       // No explicit test for NULL termination is needed.
       // On position 0 of the array false is returned.
       while (_isInnerElementChar[Uint8(*p)])
     p++;     p++;
  
     while (*p && _isInnerElementChar[Uint8(*p)])      // We've validated the prefix, now validate the local name
       if (*p == ':')
       {
           localName = ++p;
   
           if (!CharSet::isAlNumUnder(Uint8(*p)))
               return false;
   
           p++;
           // No explicit test for NULL termination is needed.
           // On position 0 of the array false is returned.
           while (_isInnerElementChar[Uint8(*p)])
         p++;         p++;
       }
   
       return true;
   }
   
   Boolean XmlParser::_getElementName(char*& p, const char*& localName)
   {
       if (!_getQName(p, localName))
           throw XmlException(XmlException::BAD_START_TAG, _line);
  
     // The next character must be a space:     // The next character must be a space:
  
Line 643 
Line 866 
     return false;     return false;
 } }
  
 Boolean XmlParser::_getOpenElementName(char*& p, Boolean& openCloseElement)  Boolean XmlParser::_getOpenElementName(
       char*& p,
       const char*& localName,
       Boolean& openCloseElement)
 { {
     openCloseElement = false;     openCloseElement = false;
  
     if (!CharSet::isAlNumUnder(Uint8(*p)))      if (!_getQName(p, localName))
         throw XmlException(XmlException::BAD_START_TAG, _line);         throw XmlException(XmlException::BAD_START_TAG, _line);
  
     p++;  
   
     while (*p && _isInnerElementChar[Uint8(*p)])  
         p++;  
   
     // The next character must be a space:     // The next character must be a space:
  
     if (_isspace(*p))     if (_isspace(*p))
Line 680 
Line 901 
     return false;     return false;
 } }
  
 void XmlParser::_getAttributeNameAndEqual(char*& p)  void XmlParser::_getAttributeNameAndEqual(char*& p, const char*& localName)
 { {
     if (!CharSet::isAlNumUnder((Uint8)*p))      if (!_getQName(p, localName))
         throw XmlException(XmlException::BAD_ATTRIBUTE_NAME, _line);         throw XmlException(XmlException::BAD_ATTRIBUTE_NAME, _line);
  
     p++;  
   
     while (*p && _isInnerElementChar[Uint8(*p)])  
         p++;  
   
     char* term = p;     char* term = p;
  
     _skipWhitespace(_line, p);     _skipWhitespace(_line, p);
Line 770 
Line 986 
  
 void XmlParser::_getElement(char*& p, XmlEntry& entry) void XmlParser::_getElement(char*& p, XmlEntry& entry)
 { {
     entry.attributeCount = 0;  
   
     //--------------------------------------------------------------------------     //--------------------------------------------------------------------------
     // Get the element name (expect one of these: '?', '!', [A-Za-z_])     // Get the element name (expect one of these: '?', '!', [A-Za-z_])
     //--------------------------------------------------------------------------     //--------------------------------------------------------------------------
Line 781 
Line 995 
         entry.type = XmlEntry::XML_DECLARATION;         entry.type = XmlEntry::XML_DECLARATION;
         entry.text = ++p;         entry.text = ++p;
  
         Boolean openCloseElement = false;          if (_getElementName(p, entry.localName))
   
         if (_getElementName(p))  
             return;             return;
     }     }
     else if (*p == '!')     else if (*p == '!')
Line 822 
Line 1034 
         entry.type = XmlEntry::END_TAG;         entry.type = XmlEntry::END_TAG;
         entry.text = ++p;         entry.text = ++p;
  
         if (!_getElementName(p))          if (!_getElementName(p, entry.localName))
             throw(XmlException(XmlException::BAD_END_TAG, _line));             throw(XmlException(XmlException::BAD_END_TAG, _line));
  
         return;         return;
     }     }
     else if ((((*p >= 'A') && (*p <= 'Z')) ||      else if (CharSet::isAlphaUnder(Uint8(*p)))
               ((*p >= 'a') && (*p <= 'z')) ||  
               (*p == '_')))  
     {     {
         entry.type = XmlEntry::START_TAG;         entry.type = XmlEntry::START_TAG;
         entry.text = p;         entry.text = p;
  
         Boolean openCloseElement = false;         Boolean openCloseElement = false;
  
         if (_getOpenElementName(p, openCloseElement))          if (_getOpenElementName(p, entry.localName, openCloseElement))
         {         {
             if (openCloseElement)             if (openCloseElement)
                 entry.type = XmlEntry::EMPTY_TAG;                 entry.type = XmlEntry::EMPTY_TAG;
Line 873 
Line 1083 
         }         }
  
         XmlAttribute attr;         XmlAttribute attr;
           attr.nsType = -1;
         attr.name = p;         attr.name = p;
         _getAttributeNameAndEqual(p);          _getAttributeNameAndEqual(p, attr.localName);
  
         // Get the attribute value (e.g., "some value")         // Get the attribute value (e.g., "some value")
         {         {
Line 886 
Line 1097 
             char quote = *p++;             char quote = *p++;
  
             char* start;             char* start;
             _normalize(_line, p, quote, start);              _normalizeAttributeValue(_line, p, quote, start);
             attr.value = start;             attr.value = start;
  
             if (*p != quote)             if (*p != quote)
Line 918 
Line 1129 
  
         _skipWhitespace(_line, p);         _skipWhitespace(_line, p);
  
         if (entry.attributeCount == XmlEntry::MAX_ATTRIBUTES)          entry.attributes.append(attr);
             throw XmlException(XmlException::TOO_MANY_ATTRIBUTES, _line);  
   
         entry.attributes[entry.attributeCount++] = attr;  
     }     }
 } }
  
Line 953 
Line 1161 
  
     PEGASUS_STD(cout) << '\n';     PEGASUS_STD(cout) << '\n';
  
     for (Uint32 i = 0; i < attributeCount; i++)      for (Uint32 i = 0, n = attributes.size(); i < n; i++)
     {     {
         PEGASUS_STD(cout) << "    " << attributes[i].name << "=\"";         PEGASUS_STD(cout) << "    " << attributes[i].name << "=\"";
         _printValue(attributes[i].value);         _printValue(attributes[i].value);
Line 964 
Line 1172 
 const XmlAttribute* XmlEntry::findAttribute( const XmlAttribute* XmlEntry::findAttribute(
     const char* name) const     const char* name) const
 { {
     for (Uint32 i = 0; i < attributeCount; i++)      for (Uint32 i = 0, n = attributes.size(); i < n; i++)
     {     {
         if (strcmp(attributes[i].name, name) == 0)         if (strcmp(attributes[i].name, name) == 0)
             return &attributes[i];             return &attributes[i];
Line 973 
Line 1181 
     return 0;     return 0;
 } }
  
   const XmlAttribute* XmlEntry::findAttribute(
       int nsType,
       const char* name) const
   {
       for (Uint32 i = 0, n = attributes.size(); i < n; i++)
       {
           if ((attributes[i].nsType == nsType) &&
               (strcmp(attributes[i].localName, name) == 0))
           {
               return &attributes[i];
           }
       }
   
       return 0;
   }
   
 // Find first non-whitespace character (set first) and last non-whitespace // Find first non-whitespace character (set first) and last non-whitespace
 // character (set last one past this). For example, consider this string: // character (set last one past this). For example, consider this string:
 // //


Legend:
Removed from v.1.42  
changed lines
  Added in v.1.46.4.2

No CVS admin address has been configured
Powered by
ViewCVS 0.9.2