/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    Pseudo_QC_String.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/ql_crypto.h"
#include "qore/intern/QoreLibIntern.h"

#include <cctype>

//! Methods in this pseudo-class can be executed on @ref string "strings"
/**
 */
qclass <string> [arg=const QoreStringNode* str = v.get<const QoreStringNode>();vparent=<value>];

//! Returns Qore::NT_STRING
/**
    @return Qore::NT_STRING

    @par Example:
    @code{.py}
switch (str.typeCode()) {
    case NT_STRING:
        printf("%y: is a string\n", str);
        break;
}
    @endcode
*/
int <string>::typeCode() [flags=CONSTANT] {
   return NT_STRING;
}

//! Returns the number of bytes in the string (not including the terminating null character (\c '\0')
/**
    @return the number of bytes in the string (not including the terminating null character (\c '\0')

    @par Example:
    @code{.py}
int len = str.strlen();
    @endcode

    @note
    - this operation is always executed in constant time (ie <i>O(1)</i>) because the string's byte length is always stored with the string
    - equivalent to <string>::size() and @ref Qore::strlen(softstring)

    @see
    - <string>::size()
    - <string>::length()
    - <string>::width()
*/
int <string>::strlen() [flags=CONSTANT] {
   return str->strlen();
}

//! Returns the number of bytes in the string (not including the terminating null character (\c '\0')
/**
    @return the number of bytes in the string (not including the terminating null character (\c '\0')

    @par Example:
    @code{.py}
int len = str.size();
    @endcode

    @note
    - this operation is always executed in constant time (ie <i>O(1)</i>) because the string's byte length is always stored with the string
    - equivalent to <string>::strlen() and @ref Qore::strlen(softstring)

    @see
    - <string>::sizep()
    - <string>::length()
    - <string>::width()
*/
int <string>::size() [flags=CONSTANT] {
    return str->size();
}

//! Returns the number of characters in the string; may not be equal to the byte length (returned by <string>::strlen() and <string>::size()) for @ref character_encoding "multi-byte character encodings"
/**
    @return the number of characters in the string; may not be equal to the byte length (returned by <string>::strlen() and <string>::size()) for @ref character_encoding "multi-byte character encodings"

    @par Example:
    @code{.py}
int len = str.length();
    @endcode

    @note
    - this operation has <i>O(n)</i> complexity if the string has a multi-byte @ref character_encoding "character encoding", otherwise it is <i>O(1)</i>
    - equivalent to @ref Qore::length(softstring)

    @see
    - <string>::strlen()
    - <string>::size()
    - <string>::width()
*/
int <string>::length() [flags=CONSTANT] {
    return str->length();
}

//! Returns the width of characters in the string; some unicode characters take up multiple spaces on output
/**
    @return the width of characters in the string; some unicode characters take up multiple spaces on output;
    for example the string "ステップの例" is made up of 6 double-wide characters, so the width is 12, while the length in
    characters is 6, and the size (string byte length) is 18

    @par Example:
    @code{.py}
int width = str.width();
    @endcode

    @note
    - this operation has <i>O(n)</i> complexity if the string has a multi-byte @ref character_encoding "character encoding", otherwise it is <i>O(1)</i>
    - equivalent to @ref Qore::char_width(softstring)

    @see
    - <string>::length()
    - <string>::strlen()
    - <string>::size()

    @since %Qore 0.9.4
*/
int <string>::width() [flags=RET_VALUE_ONLY] {
    return str->getCharWidth(xsink);
}

//! Returns @ref True if the string is empty, @ref False if not
/** The opposite of <string>::val()

    @return @ref True if the string is empty, @ref False if not

    @par Example:
    @code{.py}
bool b = str.empty();
    @endcode
*/
bool <string>::empty() [flags=CONSTANT] {
    return str->empty();
}

//! Returns the name of the string's @ref character_encoding "character encoding"
/**
    @return the name of the string's @ref character_encoding "character encoding"

    @par Example:
    @code{.py}
string enc = str.encoding();
    @endcode

    @note equivalent to @ref Qore::get_encoding(string)
*/
string <string>::encoding() [flags=CONSTANT] {
    return new QoreStringNode(str->getEncoding()->getCode());
}

//! Returns the string in lower case
/** @par Example:
    @code{.py}
printf("%y", "PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY".lwr());
# outputs: "příliš žluťoučký kůň úpěl ďábelské ódy"
    @endcode

    This pseudo-method operates on a very wide range of non-ASCII characters using a Unicode lookup table for mapping Latin, Cyrillic, Greek, Armenian, Georgian, etc characters.

    @return the string in lower case

    @note
    - equivalent to @ref Qore::tolower(string)

    @see
    - <string>::upr()
    - tolower()
    - toupper()

    @since %Qore 0.8.8 this pseudo-method operates on a wide range of characters and is no longer limited to ASCII characters
*/
string <string>::lwr() [flags=CONSTANT] {
   SimpleRefHolder<QoreStringNode> rv(new QoreStringNode(str->getEncoding()));
   if (do_tolower(*(*rv), *str, xsink))
      return QoreValue();

   return rv.release();
}

//! Returns the string in upper case
/** @par Example:
    @code{.py}
printf("%y", "příliš žluťoučký kůň úpěl ďábelské ódy".upr());
# outputs: "PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY"
    @endcode

    This pseudo-method operates on a very wide range of non-ASCII characters using a Unicode lookup table for mapping Latin, Cyrillic, Greek, Armenian, Georgian, etc characters.

    @return the string in upper case

    @note
    - equivalent to @ref Qore::toupper(string)

    @see
    - <string>::lwr()
    - tolower()
    - toupper()

    @since %Qore 0.8.8 this pseudo-method operates on a wide range of characters and is no longer limited to ASCII characters
*/
string <string>::upr() [flags=CONSTANT] {
   SimpleRefHolder<QoreStringNode> rv(new QoreStringNode(str->getEncoding()));
   if (do_toupper(*(*rv), *str, xsink))
      return QoreValue();

   return rv.release();
}

//! Retrieves the character position of a substring within a string
/** The \a pos argument and the return value are in character positions; byte offsets may differ from the character offsets with multi-byte @ref character_encoding "character encodings".

    @param substr the substring to find in the string; if the @ref character_encoding "character encoding" of this string does not match \a str, then it will be converted to <em>str</em>'s @ref character_encoding "character encoding" before processing
    @param pos the starting character position for the search

    @return the character position of a substring within a string, -1 is returned if the substring is not found

    @par Example:
    @code{.py}
int i = str.find(substr);
if (i == -1)
    printf("could not find %y in %y\n", substr, str);
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different @ref character_encoding "character encodings" and an error occurs during encoding conversion
    @throw INVALID-ENCODING this exception could be thrown if a character offset calculation fails due to invalid encoding of multi-byte character data

    @note equivalent to @ref Qore::index(softstring, softstring, softint)

    @see
    - <string>::rfind(softstring, softint)
    - rindex(softstring, softstring, softint)
    - bindex(softstring, softstring, softint)
    - brindex(softstring, softstring, softint)
 */
int <string>::find(softstring substr, softint pos = 0) [flags=RET_VALUE_ONLY] {
   return str->index(*substr, pos, xsink);
}

//! Retrieves the character position of a substring within a string, starting the search from the end of the string
/** The \a pos argument and the return value are in character positions; byte offsets may differ from the character offsets with multi-byte @ref character_encoding "character encodings".

    @param substr the substring to find in \a str; if the @ref character_encoding "character encoding" of this string does not match \a str, then it will be converted to <em>str</em>'s @ref character_encoding "character encoding" before processing
    @param pos the starting character position for the search, -1 means start from the end of the string

    @return the character position of a substring within a string, -1 is returned if the substring is not found

    @par Example:
    @code{.py}
int i = str.rfind(substr);
if (i == -1)
    printf("could not find %y in %y\n", substr, str);
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different @ref character_encoding "character encodings" and an error occurs during encoding conversion
    @throw INVALID-ENCODING this exception could be thrown if a character offset calculation fails due to invalid encoding of multi-byte character data

    @note equivalent to @ref Qore::rindex(softstring, softstring, softint)

    @see
    - <string>::find(softstring, softint)
    - index(softstring, softstring, softint)
    - bindex(softstring, softstring, softint)
    - brindex(softstring, softstring, softint)
 */
int <string>::rfind(softstring substr, softint pos = -1) [flags=RET_VALUE_ONLY] {
   return str->rindex(*substr, (qore_offset_t)pos, xsink);
}

//! Returns @ref False if the string is empty, @ref True if not
/** @return @ref False if the string is empty, @ref True if not

    @par Example:
    @code{.py}
bool b = str.val();
    @endcode
*/
bool <string>::val() [flags=CONSTANT] {
   return !str->empty();
}

//! Returns @ref True if the string can be converted to an integer, @ref False if not, this depends on the first (or possibly second) character of the string, if it's 0 - 9 (possibly preceded by \c "-"), then the method returns @ref True
/**
    @return @ref True if the string can be converted to an integer, @ref False if not, this depends on the first (or possibly second) character of the string, if it's 0 - 9 (possibly preceded by \c "-"), then the method returns @ref True

    @par Example:
    @code{.py}
if (n.intp())
    printf("%y: can be converted to an integer: %d\n", n, int(n));
    @endcode
*/
bool <string>::intp() [flags=CONSTANT] {
    if (str->empty()) {
        return false;
    }
    char c = (*str)[0];
    if (c == '-') {
        c = (*str)[1];
    }
    return (isdigit(c)) ? true : false;
}

//! Converts the string to an integer value with respect to the <b>base</b>
/** If a character is out of range of the corresponding <b>base</b> only the substring preceeding the character is
    taken into account.\n
    Base 0 means to interpret as a code literal, so that the actual base is 8, 10, or 16.

    @param base the base of the integer in the string; this value must be 0 or 2 - 36 inclusive or an \c INVALID-BASE
    exception will be thrown

    @return the integer value corresponding to the arguments

    @par Example:
    @code{.py}
"fe".toInt(16);   // returns 254
"34".toInt(5);    // returns 19
"2p".toInt(25);   // returns 2  (p means 25, which is out of range)
"2p".toInt(26);   // returns 77
"2z".toInt(36);   // returns 107
"21".toInt();     // returns 21 (base = 10)
"21".toInt(0);    // returns 21 (base = 10)
"021".toInt(0);   // returns 17 (base =  8)
"0x21".toInt(0);  // returns 33 (base = 16)
    @endcode

    @throw INVALID-BASE the base is invalid; must be 0 or 2 - 36 inclusive
    @throw UNSUPPORTED-ENCODING only ASCII-compatible encodings are currently supported

    @see <value>::toInt()
    @see Qore::int(string str, int base = 10)

    @since %Qore 0.8.13; in previous versions the \link <value>::toInt() \endlink function (without the <b>base</b>
    argument) applied also to strings
  */
int <string>::toInt(int base = 10) [flags=RET_VALUE_ONLY] {
    if (base < 0 || base == 1 || base > 36) {
        return xsink -> raiseException("INVALID-BASE", "base " QLLD " is invalid; base must be 0 or 2 - 36 inclusive",
            base);
    }
    if (!str->getEncoding()->isAsciiCompat()) {
        return xsink -> raiseException("UNSUPPORTED-ENCODING", "cannot convert string in non-ASCII-compatible "
            "encoding \"%s\" to an integer", str->getEncoding()->getCode());
    }
    return strtoll(str -> c_str(), 0, (int)base);
}

//! Returns @ref True by default
/**
    @return @ref True by default

    @par Example:
    @code{.py}
if (n.strp())
    printf("%y: can be converted to a string: '%s'\n", n, string(n));
    @endcode
*/
bool <string>::strp() [flags=CONSTANT] {
    return true;
}

//! Returns a portion of a string starting from an integer offset
/** Arguments can be negative, giving offsets from the end of the string. All offsets are character positions, not byte positions.

    @param start The starting character for the substring where the first character is at offset 0; if the offset is negative, it designates the number of characters from the end of the string.  If the offset is 0, then the entire string is returned.

    @return the substring of the string starting from an integer character offset; the rest of the string is returned after this offset; an emtpy string is returned if the argument cannot be satisfied

    @par Example:
    @code{.py}
# get the last 10 characters of a string
string substr = str.substr(-10);
    @endcode

    @throw INVALID-ENCODING this exception could be thrown if a character offset calculation fails due to invalid encoding of multi-byte character data

    @note equivalent to @ref Qore::substr(softstring, softint)
 */
string <string>::substr(softint start) [flags=RET_VALUE_ONLY] {
    QoreStringNode* rv = str->substr(start, xsink);
    if (!rv)
        rv = new QoreStringNode(str->getEncoding());
    return rv;
}

//! Returns a portion of a string starting from an integer offset, with a length parameter
/** Arguments can be negative, giving offsets from the end of the string. All offsets are character positions, not byte positions.

    @param start The starting character for the substring where the first character is at offset 0; if the offset is negative, it designates the number of characters from the end of the string
    @param len The maximum number of characters to copy; if this value is negative, the rest of the string from \a start will be copied to the substring, except without - \a len characters from the end of the string

    @return the substring of the string according to the arguments passed; an emtpy string is returned if the argument cannot be satisfied

    @par Example:
    @code{.py}
# get a substring 10 characters into the string except omitting the last 2 characters of the string
string substr = str.substr(10, -2);
    @endcode

    @throw INVALID-ENCODING this exception could be thrown if a character offset calculation fails due to invalid encoding of multi-byte character data

    @note equivalent to @ref Qore::substr(softstring, softint, softint)

    @since %Qore 0.8.5
 */
string <string>::substr(softint start, softint len) [flags=RET_VALUE_ONLY] {
    QoreStringNode* rv = str->substr(start, len, xsink);
    if (!rv)
        rv = new QoreStringNode(str->getEncoding());
    return rv;
}

//! Splits a string into a list of components based on a separator string
/**
    @param sep the separator string; if the separator string is not found in the string to split, then a list with only one element containing the entire string argument is returned'; if this string has a different @ref character_encoding "character encoding" than \a str, then it will be converted to <em>str</em>'s @ref character_encoding "character encoding"
    @param with_separator include the separator string in every element

    @return a list of each component of a string separated by a separator string, with the separator removed; the separator pattern will not be included in the elements of the list returned unless the \a with_separator argument is @ref True

    @par Example:
    @code{.py}
string str = "some:text:here";
list<string> list = str.split(":"); # returns ("some", "text", "here")
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different @ref character_encoding "character encodings" and an error occurs during encoding conversion

    @note equivalent to @ref Qore::split(string, string, bool)

    @since %Qore 0.8.5
 */
list<string> <string>::split(string sep, bool with_separator = False) [flags=RET_VALUE_ONLY] {
    // convert pattern encoding to string if necessary
    TempEncodingHelper temp(sep, str->getEncoding(), xsink);
    if (*xsink)
        return QoreValue();

    return split_intern(temp->c_str(), temp->strlen(), str->c_str(), str->strlen(), str->getEncoding(),
        with_separator);
}

//! Splits a string into a list of components based on a separator string and a quote character
/** The quote character can appear as the first part of a field, in which case it is assumed to designate the entire
    field. If instances of the quote character are found in the field preceded by a backquote character (\c "\"), then
    these quote characters are included as part of the field's text and not treated as quote characters.
    Also the separator character can appear as a part of a field with this variant.
    This variant is useful for parsing CSV files, for example.

    @param sep the separator string; if the separator string is not found in the string to split, then a list with
    only one element containing the entire string argument is returned'; if this string has a different
    @ref character_encoding "character encoding" than \a str, then it will be converted to <em>str</em>'s
    @ref character_encoding "character encoding"
    @param quote the quote character
    @param trim_unquoted remove leading and trailing whitespace from unquoted fields

    @return a list of each component of a string separated by a separator string, with the separator and any enclosing
    quote characters removed

    @par Example:
    @code{.py}
# returns ("some", ", and commas, here is another one! ,", "here")
list<string> list = "some,'text with spaces, and commas, here is another one! ,',here".split(",", "'");
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different
    @ref character_encoding "character encodings" and an error occurs during encoding conversion

    @throw SPLIT-ERROR field missing closing quote character; extra text following quoted field

    @note equivalent to @ref Qore::split(string, string, string, bool)

    @since
    - %Qore 0.8.5 added this pseudo-method
    - %Qore 0.8.6 added the \c trim_unquoted parameter
 */
list<string> <string>::split(string sep, string quote, bool trim_unquoted = False) [flags=RET_VALUE_ONLY] {
    return split_with_quote(xsink, sep, str, quote, trim_unquoted);
}

//! Splits a string into a list of components based on a separator regular expression
/**
    @par Example:
    @code{.py}
string str = "some\\:text:here";
list<string> list = str.splitRegex("(?<!\\\\)\\:"); # returns ("some\\:text", "here")
    @endcode

    @param regex_sep the separator regular expression; if the separator regular expression is not matched in the
    string to split, then a list with only one element containing the entire string argument is returned'; if this
    string has a different @ref character_encoding "character encoding" than \a str, then it will be converted to
    <em>str</em>'s @ref character_encoding "character encoding"
    @param options regular expression options; see @ref regex_constants for possible values
    @param with_separator include the separator string in every element

    @return a list of each component of a string separated by a separator regular expression, with the separator
    removed; the separator pattern will not be included in the elements of the list returned unless the
    \a with_separator argument is @ref True; the strings in the return value are always returned in UTF-8 encoding

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different
    @ref character_encoding "character encodings" and an error occurs during encoding conversion

    @since %Qore 1.0.7
 */
list<string> <string>::splitRegex(string regex_sep, int options = 0, bool with_separator = False) [flags=RET_VALUE_ONLY] {
    QoreRegex qr(*regex_sep, options, xsink);
    if (*xsink) {
        return QoreValue();
    }

    return qr.extractWithPattern(*str, with_separator, xsink);
}

//! Splits a string into a list of components based on a separator regular expression
/**
    @par Example:
    @code{.py}
string str = "some\\:text:here";
list<string> list = str.splitRegex("(?<!\\\\)\\:"); # returns ("some\\:text", "here")
    @endcode

    @param regex_sep the separator regular expression; if the separator regular expression is not matched in the
    string to split, then a list with only one element containing the entire string argument is returned'; if this
    string has a different @ref character_encoding "character encoding" than \a str, then it will be converted to
    <em>str</em>'s @ref character_encoding "character encoding"
    @param with_separator include the separator string in every element

    @return a list of each component of a string separated by a separator regular expression, with the separator
    removed; the separator pattern will not be included in the elements of the list returned unless the
    \a with_separator argument is @ref True; the strings in the return value are always returned in UTF-8 encoding

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different
    @ref character_encoding "character encodings" and an error occurs during encoding conversion

    @since %Qore 1.0.7
 */
list<string> <string>::splitRegex(string regex_sep, bool with_separator = False) [flags=RET_VALUE_ONLY] {
    QoreRegex qr(*regex_sep, 0, xsink);
    if (*xsink) {
        return QoreValue();
    }

    return qr.extractWithPattern(*str, with_separator, xsink);
}

//! Returns @ref True if the regular expression matches the string passed, otherwise returns @ref False
/** Strings are converted to UTF-8 for pattern-matching; if any invalid encodings are encountered, an
    \c ENCODING-CONVERSION-ERROR is raised

    @param regex the regular expression pattern
    @param options regular expression options; see @ref regex_constants for possible values

    @return @ref True if the regular expression matches the string passed, otherwise returns @ref False

    @par Example:
    @code{.py}
bool b = "hello".regex("^hel"); # returns True
    @endcode

    @throw REGEX-COMPILATION-ERROR There was an error compiling the regular expression
    @throw REGEX-OPTION-ERROR the option argument contains invalid option bits
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if an encoding error is encountered when
    converting the given strings to UTF-8

    @note equivalent to @ref Qore::regex(string, string, int)

    @see @ref qore_regex for more information about regular expression support in Qore

    @since %Qore 0.8.5
 */
bool <string>::regex(string regex, int options = 0) [flags=RET_VALUE_ONLY] {
    QoreRegex qr(*regex, options, xsink);
    if (*xsink) {
        return QoreValue();
    }

    return qr.exec(str, xsink);
}

//! Returns a list of substrings in a string based on matching patterns defined by a regular expression
/** Strings are converted to UTF-8 for pattern-matching; if any invalid encodings are encountered, an
    \c ENCODING-CONVERSION-ERROR is raised

    @param regex the regular expression to use for matching, elements should be given in parentheses
    @param options regular expression options; see @ref regex_constants for possible values

    @return a list of substrings in a string based on matching patterns defined by a regular expression or
    @ref nothing if no match was made

    @par Example:
    @code{.py}
string str = "ns:element";
*list<*string> rv = str.regexExtract("(\\w+):(\\w+)");
    @endcode

    @throw REGEX-COMPILATION-ERROR There was an error compiling the regular expression
    @throw REGEX-OPTION-ERROR the option argument contains invalid option bits
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if an encoding error is encountered when
    converting the given strings to UTF-8

    @note equivalent Qore::regex_extract(string, string, int)

    @see @ref qore_regex for more information about regular expression support in Qore

    @since %Qore 0.8.5

    @since %Qore 0.8.8 this function accepts the @ref Qore::RE_Global option to extract all occurrences of the
    pattern(s) in a string
 */
*list<*string> <string>::regexExtract(string regex, int options = 0) [flags=RET_VALUE_ONLY] {
    QoreRegex qr(*regex, options, xsink);
    if (*xsink)
        return QoreValue();

    return qr.extractSubstrings(str, xsink);
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MD5">MD5 message digest</a> of the string as a hex string
/** The trailing null character is not included in the digest returned.

    @return a hex string of the digest (ex: \c "5d41402abc4b2a76b9719d911017c592")

    @par Example:
    @code{.py}
string str = "hello".toMD5(); # returns "5d41402abc4b2a76b9719d911017c592"
    @endcode

    @throw MD5-DIGEST-ERROR error calculating digest (should not normally happen)

    @note
    - equivalent to @ref Qore::MD5()
    - the MD5 algorithm is not collision-resistant; it's recommended to use another hash algorithm (like SHA-256) if
      cryptographic security is important

    @see
    - MD5_bin()
    - <binary>::toMD5()

    @since %Qore 0.8.5
 */
string <string>::toMD5() [flags=RET_VALUE_ONLY] {
    DigestHelper dh(*str);
    if (dh.doDigest(MD5_ERR, EVP_md5(), xsink))
        return QoreValue();

    return dh.getString();
}

//! Returns the <a href="http://en.wikipedia.org/wiki/SHA-1">SHA1</a> message digest of the string as a hex string
/** The trailing null character is not included in the digest returned.

    @return a hex string of the digest (ex: \c "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d")

    @par Example:
    @code{.py}
string str = "hello".toSHA1(); # returns "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
    @endcode

    @throw SHA1-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to @ref Qore::SHA1()

    @see
    - SHA1_bin()
    - <binary>::toSHA1()

    @since %Qore 0.8.5
 */
string <string>::toSHA1() [flags=RET_VALUE_ONLY] {
    DigestHelper dh(*str);
    if (dh.doDigest(SHA1_ERR, EVP_sha1(), xsink))
        return QoreValue();

    return dh.getString();
}

//! Returns the SHA-224 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the string as a hex string
/** The trailing null character is not included in the digest returned.

    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA224

    @return a hex string of the digest (ex: \c "ea09ae9cc6768c50fcee903ed054556e5bfc8347907f12598aa24193")

    @par Example:
    @code{.py}
string str = "hello".toSHA224("hello"); # returns "ea09ae9cc6768c50fcee903ed054556e5bfc8347907f12598aa24193"
    @endcode

    @throw SHA224-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to @ref Qore::SHA224()

    @see
    - SHA224_bin()
    - <binary>::toSHA224()

    @since %Qore 0.8.5
 */
string <string>::toSHA224() [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(*str);
    if (dh.doDigest(SHA224_ERR, EVP_sha224(), xsink))
        return QoreValue();

    return dh.getString();
#else
    missing_openssl_feature("SHA224", xsink);
    return QoreValue();
#endif
}

//! Returns the SHA-256 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the string as a hex string
/** The trailing null character is not included in the digest returned.

    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA256

    @return a hex string of the digest (ex: \c "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")

    @par Example:
    @code{.py}
string str = "hello".toSHA256(); # returns "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    @endcode

    @throw SHA256-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to @ref Qore::SHA256()

    @see
    - SHA256_bin()
    - <binary>::toSHA256()

    @since %Qore 0.8.5
 */
string <string>::toSHA256() [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(*str);
    if (dh.doDigest(SHA256_ERR, EVP_sha256(), xsink))
        return QoreValue();

    return dh.getString();
#else
    missing_openssl_feature("SHA256", xsink);
    return QoreValue();
#endif
}

//! Returns the SHA-384 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the string as a hex string
/** The trailing null character is not included in the digest returned.

    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA384

    @return a hex string of the digest (ex:
    \c "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f")

    @par Example:
    @code{.py}
# returns "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f"
string str = "hello".toSHA384();
    @endcode

    @throw SHA384-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to @ref Qore::SHA384()

    @see
    - SHA384_bin()
    - <binary>::toSHA224()

    @since %Qore 0.8.5
 */
string <string>::toSHA384() [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA512) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(*str);
    if (dh.doDigest(SHA384_ERR, EVP_sha384(), xsink))
        return QoreValue();

    return dh.getString();
#else
    missing_openssl_feature("SHA384", xsink);
    return QoreValue();
#endif
}

//! Returns the SHA-512 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the string as a hex string
/** The trailing null character is not included in the digest returned.

    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA512

    @return a hex string of the digest (ex:
    \c "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043")

    @par Example:
    @code{.py}
# returns "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
string str = "hello".toSHA512();
    @endcode

    @throw SHA512-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to @ref Qore::SHA512()

    @see
    - SHA512_bin()
    - <binary>::toSHA512()

    @since %Qore 0.8.5
 */
string <string>::toSHA512() [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA512) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(*str);
    if (dh.doDigest(SHA512_ERR, EVP_sha512(), xsink))
        return QoreValue();

    return dh.getString();
#else
    missing_openssl_feature("SHA512", xsink);
    return QoreValue();
#endif
}

//! Returns a binary value with the string's data
/** @par Example:
    @code{.py}
binary bin = str.toBinary();
    @endcode

    @return a binary value with the string's data

    @see Qore::binary(softstring) "binary(softstring)"

    @since %Qore 0.9.3
*/
binary <string>::toBinary() [flags=CONSTANT] {
    SimpleRefHolder<BinaryNode> b(new BinaryNode);
    b->append(str->c_str(), str->size());
    return b.release();
}

//! returns @ref True if the string is empty or has no characters with the high bit set (ie all characters < 128)
/** @return @ref True if the string is empty or has no characters with the high bit set (ie all characters < 128)

    @par Example:
    @code{.py}
bool b = str.isDataAscii();
    @endcode

    @since %Qore 0.8.6
 */
bool <string>::isDataAscii() [flags=CONSTANT] {
    return str->isDataAscii();
}

//! returns @ref True if the string is empty or only contains printable non-control ASCII characters (ie all characters > 31 && < 127)
/** @return @ref True if the string is empty or only contains printable non-control ASCII characters (ie all
    characters > 31 && < 127)

    @par Example:
    @code{.py}
bool b = str.isDataPrintableAscii();
    @endcode

    @since %Qore 0.8.6
 */
bool <string>::isDataPrintableAscii() [flags=CONSTANT] {
    return str->isDataPrintableAscii();
}

//! returns a string of hexadecimal digits corresponding to the contents of the string
/** @par Example:
    @code{.py}
string str = str.toHex();
    @endcode

    @return a string of hexadecimal digits corresponding to the contents of the string

    @since %Qore 0.8.8

    @see
    - <binary>::toHex()
    - make_hex_string(string)
 */
string <string>::toHex() [flags=CONSTANT] {
    QoreStringNode *rv = new QoreStringNode;
    rv->concatHex(str);
    return rv;
}

//! Returns the base64-encoded representation of the string
/** @par Example:
    @code{.py}
string base64 = str.toBase64(64);
    @endcode

    Implementation based on <a href="http://www.ietf.org/rfc/rfc1421.txt">RFC-1421</a> and
    <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC-2045</a>

    @param maxlinelen the maximum length of a line in the resulting output string in bytes; if this value is > 0 then
    output lines will be separated by CRLF characters

    @return the base64-encoded string of the data passed

    @since %Qore 0.8.8

    @see
    - <binary>::toBase64()
    - make_base64_string(string, softint)
*/
string <string>::toBase64(softint maxlinelen = -1) [flags=CONSTANT] {
    QoreStringNode *rv = new QoreStringNode;
    rv->concatBase64(str, maxlinelen);
    return rv;
}

//! Returns the base64-url-encoded representation of the string
/** Base64-URL encoding is defined by <a href="https://datatracker.ietf.org/doc/html/rfc4648">RFC-4648</a>

    @par Example:
    @code{.py}
string base64 = str.toBase64Url();
    @endcode

    @return the base64-url-encoded string of the data passed

    @since %Qore 1.19.0

    @see
    - <binary>::toBase64Url()
    - make_base64_url_string(string)
*/
string <string>::toBase64Url(softint maxlinelen = -1) [flags=CONSTANT] {
    QoreStringNode* rv = new QoreStringNode;
    rv->concatBase64Url(*str);
    return rv;
}

//! Compares the beginning of the current string with a shorter string passed as an argument
/** @par Example:
    @code{.py}
int i = str.comparePartial(ostr);
    @endcode

    @param ostr the partial string to compare the current string to

    @return -1, 0, or 1 if the argument string is less than, equal, or greater than the beginning of the current
    string; returns also -1 if the argument string is equal to the beginning of the current string but the argument
    string is longer than the current string

    @since %Qore 0.8.8
*/
int <string>::comparePartial(string ostr) [flags=RET_VALUE_ONLY] {
    // convert pattern encoding to string if necessary
    TempEncodingHelper temp(ostr, str->getEncoding(), xsink);
    if (*xsink)
        return QoreValue();

    int rc = strncmp(str->c_str(), temp->c_str(), temp->size());
    return !rc ? 0 : (rc < 0 ? -1 : 1);
}

//! Compares the beginning of the current string with a shorter string passed as an argument for equality only, returns @ref True "True" if the string argument matches the beginning of the string, @ref False "False" if not
/** @par Example:
    @code{.py}
bool b = str.equalPartial(ostr);
    @endcode

    This pseudo-method is slightly faster than comparePartial() since the length of the substring can be used to
    determine if the strings can match or not.

    @param ostr the partial string to compare the current string to

    @return @ref True "True" if the string argument matches the beginning of the string, @ref False "False" if not

    @since %Qore 0.8.8
*/
bool <string>::equalPartial(string ostr) [flags=RET_VALUE_ONLY] {
    return str->equalPartialSoft(ostr, xsink);
}

//! Compares the beginning of the current string assumed to be a path with a shorter string passed as an argument for equality only, returns @ref True "True" if the string argument matches the beginning of the string where either both strings are the same size or the current string has a '/' or '?' character after the point where the argument string stops, @ref False "False" if not
/** @par Example:
    @code{.py}
bool b = str.equalPartialPath(ostr);
    @endcode

    @param ostr the partial string to compare the current string to

    @return @ref True "True" if the string argument matches the beginning of the string where either both strings are
    the same size or the current string has a '/' or '?' character after the point where the argument string stops,
    @ref False "False" if not

    @since %Qore 0.8.8
*/
bool <string>::equalPartialPath(string ostr) [flags=RET_VALUE_ONLY] {
    return str->equalPartialPath(ostr, xsink);
}

//! Returns a string with all accented characters removed
/** @par Example:
    @code{.py}
string s = "příliš žluťoučký kůň úpěl ďábelské ódy";
printf("%y\n", s.unaccent()); # result: "prilis zlutoucky kun upel dabelske ody"
    @endcode

    The returned string has the same encoding as the original input.

    @return the string with accents replaced

    @since %Qore 0.8.8
*/
string <string>::unaccent() [flags=RET_VALUE_ONLY] {
    SimpleRefHolder<QoreStringNode> rv(new QoreStringNode(str->getEncoding()));
    if (do_unaccent(*(*rv), *str, xsink))
        return QoreValue();

    return rv.release();
}

//! returns the Unicode code for the given character offset in the string
/** @par Example:
    @code{.py}
int uc = str.getUnicode(0);
    @endcode

    @param offset the offset in characters in the string; negative numbers give offsets from the end of the string

    @return the Unicode code for the given character offset in the string

    @since %Qore 0.8.8
*/
int <string>::getUnicode(int offset = 0) [flags=RET_VALUE_ONLY] {
    return str->getUnicodePoint(offset, xsink);
}

//! Returns @ref True since strings can return a non-zero size
/** @return @ref True since strings can return a non-zero size

    @see <string>::size()

    @since %Qore 0.8.9
*/
bool <string>::sizep() [flags=CONSTANT] {
    return true;
}

//! returns a string for the next line in the string buffer starting at the given offset (or at the beginning if no offset is given)
/** @par Example:
    @code{.py}
*string line = string.getLine(pos);
    @endcode

    @param offset the offset in bytes from the beginning of the string; negative numbers give an offset from the end
    of the string
    @param eol the optional end of line character(s) to use to detect lines in the buffer; if this string is not
    passed, then the end of line character(s) are detected automatically, and can be either \c "\n", \c "\r", or
    \c "\r\n"; if this string is passed and has a different @ref character_encoding "character encoding" from this
    object's (as determined by the \c encoding parameter), then it will be converted to the string's
    @ref character_encoding "character encoding"
    @param trim if @ref True the string return values for the lines iterated will be trimmed of the eol bytes
    @param size an optional reference to an integer that returns the number of bytes in the line including the end of
    line characters

    @return the line found or @ref nothing if no end of line character(s) were found or no data was found after the
    offset

    @since %Qore 0.8.9
 */
*string <string>::getLine(int offset = 0, *string eol, bool trim = True, *reference<int> size) [flags=CONSTANT] {
    if (str->empty())
        return QoreValue();

    if (offset < 0) {
        offset = str->size() + offset;
        if (offset < 0)
            offset = 0;
    } else if (offset >= (int64)str->size())
        return QoreValue();

    TempEncodingHelper eolstr;
    if (eol) {
        eolstr.set(eol, str->getEncoding(), xsink);
        if (*xsink)
            return QoreValue();

        if (eolstr->size() > str->size())
            return QoreValue();
    }

    int64 strsize = 0;

    const char* start = str->c_str() + offset;
    SimpleRefHolder<QoreStringNode> rv(new QoreStringNode(str->getEncoding()));
    if (eolstr) {
        const char* p = strstr(start, eolstr->c_str());
        if (p) {
            strsize = p - start + eolstr->size();
            if (trim) {
                if (p != start)
                rv->concat(start, p - start);
            } else
                rv->concat(start, strsize);
        } else {
            strsize = rv->size();
            rv->concat(start);
            strsize = rv->size() - strsize;
        }
    } else {
        while (*start) {
            if (*start == '\r') {
                ++strsize;
                if (!trim)
                rv->concat(*start);
                if (*(start + 1) == '\n') {
                ++strsize;
                if (!trim)
                    rv->concat(*start);
                }
                break;
            }
            if (*start == '\n') {
                ++strsize;
                if (!trim)
                rv->concat(*start);
                break;
            }
            rv->concat(*start);
            ++strsize;
            ++start;
        }
    }

    if (size) {
        QoreTypeSafeReferenceHelper r(size, xsink);
        if (!r)
            return QoreValue();

        if (r.assign(strsize))
            return QoreValue();
    }

    return rv.release();
}

//! returns a string based on the string value with encodings as per the \a code argument
/** @par
    @code{.py}
string ns = string.getEncoded(CE_XML);
    @endcode

    @param code an encoding bitfield argument; see @ref StringConcatEncoding for more information

    @see <string>::getDecoded()

    @since %Qore 0.8.12
 */
string <string>::getEncoded(int code = CE_XHTML) {
    QoreStringNodeHolder rv(new QoreStringNode(str->getEncoding()));
    return rv->concatEncode(xsink, *str, code) ? nullptr : rv.release();
}

//! returns a string based on the string value, decoded as per the \a code argument
/** @par
    @code{.py}
string ns = string.getDecoded(CE_XML);
    @endcode

    @param code a decoding bitfield argument; see @ref StringConcatDecoding for more information

    @see <string>::getEncoded()

    @since %Qore 0.8.12
 */
string <string>::getDecoded(int code = CD_ALL) {
    QoreStringNodeHolder rv(new QoreStringNode(str->getEncoding()));
    return rv->concatDecode(xsink, *str, code) ? nullptr : rv.release();
}
