diff --git a/qse/include/qse/cmn/str.h b/qse/include/qse/cmn/str.h index 2a1ba239..d14c2239 100644 --- a/qse/include/qse/cmn/str.h +++ b/qse/include/qse/cmn/str.h @@ -125,6 +125,10 @@ struct qse_wcs_t qse_size_t capa; /**< buffer capacity */ }; + +#define QSE_MBSSUBST_NOBUF ((qse_mchar_t*)1) +#define QSE_WCSSUBST_NOBUF ((qse_wchar_t*)1) + /** * The qse_mbssubst_t type defines a callback function for qse_mbsxsubst() * and qse_mbsxnsubst() to substitue a new value for an identifier \a ident. @@ -148,8 +152,10 @@ typedef qse_wchar_t* (*qse_wcssubst_t) ( ); #if defined(QSE_CHAR_IS_MCHAR) +# define QSE_STRSUBST_NOBUF QSE_MBSSUBST_NOBUF # define qse_strsubst_t qse_mbssubst_t #else +# define QSE_STRSUBST_NOBUF QSE_WCSSUBST_NOBUF # define qse_strsubst_t qse_wcssubst_t #endif @@ -696,6 +702,45 @@ QSE_EXPORT qse_size_t qse_wcsxfncpy ( * qse_mchar_t buf[25]; * qse_mbsxsubst (buf, i, QSE_MT("user=${USER},group=${GROUP}"), subst, QSE_NULL); * \endcode + * + * If \a buf is #QSE_MBSSUBST_NOBUF, the function returns the length of the + * resulting string that a buffer large enough would be able to hold. You can + * pass #QSE_MBSSUBST_NOBUF to find out how large a buffer you need to perform + * substitution without truncation. The substitution callback function should + * handle this special case for buffer size prediction. The sample callback shown + * above can be extended to return the buffer pointer without actual copying + * if the buffer points to #QSE_MBSSUBST_NOBUF as shown belown. + * + * \code + * qse_mchar_t* subst (qse_mchar_t* buf, qse_size_t bsz, const qse_mcstr_t* ident, void* ctx) + * { + * if (qse_mcsxcmp (ident->ptr, ident->len, QSE_MT("USER")) == 0) + * return buf + (buf == QSE_MBSSUBST_NOBUF? 3: qse_mcsxput (buf, bsz, QSE_MT("sam"))); + * else if (qse_mcsxcmp (ident->ptr, ident->len, QSE_MT("GROUP")) == 0) + * return buf + (buf == QSE_MBSSUBST_NOBUF? 6: qse_mcsxput (buf, bsz, QSE_MT("coders"))); + * return buf; + * } + * + * len = qse_mbsxsubst (QSE_MBSSUBST_NOBUF, 0, QSE_MT("user=${USER},group=${GROUP}"), subst, QSE_NULL); + * buf = malloc (QSE_SIZEOF(qse_mbs_t) * (len + 1)); + * qse_mbsxsubst (buf, len + 1, QSE_MT("user=${USER},group=${GROUP}"), subst, QSE_NULL); + * \endcode + * + * A ${} segment may contain a default value. For example, the segment + * ${USER:=Unknown} translates to Unknown if the callback function returns + * #QSE_NULL for USER. The default value part may contain ${} segments + * recursively. + * + * \code + * qse_mchar_t* subst (qse_mchar_t* buf, qse_size_t bsz, const qse_mcstr_t* ident, void* ctx) + * { + * if (qse_mbsxcmp (ident->ptr, ident->len, QSE_MT("USER")) == 0) + * return QSE_NULL; + * else if (qse_mbsxcmp (ident->ptr, ident->len, QSE_MT("GROUP")) == 0) + * return buf + (buf == QSE_MBSSUBST_NOBUF? 6: qse_wcsxput (buf, bsz, QSE_MT("coders"))); + * return buf; + * } + * \endcode */ QSE_EXPORT qse_size_t qse_mbsxsubst ( qse_mchar_t* buf, @@ -731,6 +776,44 @@ QSE_EXPORT qse_size_t qse_mbsxnsubst ( * qse_wchar_t buf[25]; * qse_wcsxsubst (buf, i, QSE_WT("user=${USER},group=${GROUP}"), subst, QSE_NULL); * \endcode + * + * If \a buf is #QSE_WCSSUBST_NOBUF, the function returns the length of the + * resulting string that a buffer large enough would be able to hold. You can + * pass #QSE_WCSSUBST_NOBUF to find out how large a buffer you need to perform + * substitution without truncation. The substitution callback function should + * handle this special case for buffer size prediction. The sample callback shown + * above can be extended to return the buffer pointer without actual copying + * + * \code + * qse_mchar_t* subst (qse_mchar_t* buf, qse_size_t bsz, const qse_wcstr_t* ident, void* ctx) + * { + * if (qse_wcsxcmp (ident->ptr, ident->len, QSE_WT("USER")) == 0) + * return buf + (buf == QSE_WCSSUBST_NOBUF? 3: qse_wcsxput (buf, bsz, QSE_WT("sam"))); + * else if (qse_wcsxcmp (ident->ptr, ident->len, QSE_WT("GROUP")) == 0) + * return buf + (buf == QSE_WCSSUBST_NOBUF? 6: qse_wcsxput (buf, bsz, QSE_WT("coders"))); + * return buf; + * } + * + * len = qse_wcsxsubst (QSE_WCSSUBST_NOBUF, 0, QSE_WT("user=${USER},group=${GROUP}"), subst, QSE_NULL); + * buf = malloc (QSE_SIZEOF(qse_wcs_t) * (len + 1)); + * qse_wcsxsubst (buf, len + 1, QSE_WT("user=${USER},group=${GROUP}"), subst, QSE_NULL); + * \endcode + * + * A ${} segment may contain a default value. For example, the segment + * ${USER:=Unknown} translates to Unknown if the callback function returns + * #QSE_NULL for USER. The default value part may contain ${} segments + * recursively. + * + * \code + * qse_mchar_t* subst (qse_mchar_t* buf, qse_size_t bsz, const qse_wcstr_t* ident, void* ctx) + * { + * if (qse_wcsxcmp (ident->ptr, ident->len, QSE_WT("USER")) == 0) + * return QSE_NULL; + * else if (qse_wcsxcmp (ident->ptr, ident->len, QSE_WT("GROUP")) == 0) + * return buf + (buf == QSE_WCSSUBST_NOBUF? 6: qse_wcsxput (buf, bsz, QSE_WT("coders"))); + * return buf; + * } + * \endcode */ QSE_EXPORT qse_size_t qse_wcsxsubst ( qse_wchar_t* buf, diff --git a/qse/lib/cmn/str-subst.c b/qse/lib/cmn/str-subst.c index 2b9ffe1b..fcc48cf9 100644 --- a/qse/lib/cmn/str-subst.c +++ b/qse/lib/cmn/str-subst.c @@ -25,6 +25,7 @@ #undef char_t #undef cstr_t #undef T +#undef NOBUF #undef strlen #undef scan_dollar #undef expand_dollar @@ -35,6 +36,7 @@ #define char_t qse_mchar_t #define cstr_t qse_mcstr_t #define T(x) QSE_MT(x) +#define NOBUF QSE_MBSSUBST_NOBUF #define strlen qse_mbslen #define scan_dollar mbs_scan_dollar #define expand_dollar mbs_expand_dollar @@ -48,6 +50,7 @@ #undef char_t #undef cstr_t #undef T +#undef NOBUF #undef strlen #undef scan_dollar #undef expand_dollar @@ -58,6 +61,7 @@ #define char_t qse_wchar_t #define cstr_t qse_wcstr_t #define T(x) QSE_WT(x) +#define NOBUF QSE_WCSSUBST_NOBUF #define strlen qse_wcslen #define scan_dollar wcs_scan_dollar #define expand_dollar wcs_expand_dollar diff --git a/qse/lib/cmn/str-subst.h b/qse/lib/cmn/str-subst.h index e2298e79..b8962ab1 100644 --- a/qse/lib/cmn/str-subst.h +++ b/qse/lib/cmn/str-subst.h @@ -122,7 +122,7 @@ qse_size_t strxnsubst ( const char_t* f = fmt; const char_t* fend = fmt + fsz; - if (bsz <= 0) return 0; + if (buf != NOBUF && bsz <= 0) return 0; while (f < fend) { @@ -144,8 +144,18 @@ qse_size_t strxnsubst ( if (tmp == QSE_NULL || ident.len <= 0) goto normal; f = tmp; - b = expand_dollar (b, end - b + 1, &ident, &dfl, subst, ctx); - if (b >= end) goto fini; + if (buf != NOBUF) + { + b = expand_dollar (b, end - b + 1, &ident, &dfl, subst, ctx); + if (b >= end) goto fini; + } + else + { + /* the buffer points to NOBUF. */ + tmp = expand_dollar (buf, bsz, &ident, &dfl, subst, ctx); + /* increment b by the length of the expanded string */ + b += (tmp - buf); + } continue; } @@ -157,12 +167,16 @@ qse_size_t strxnsubst ( } normal: - if (b >= end) break; - *b++ = *f++; + if (buf != NOBUF) + { + if (b >= end) break; + *b = *f; + } + b++; f++; } fini: - *b = T('\0'); + if (buf != NOBUF) *b = T('\0'); return b - buf; } @@ -172,3 +186,4 @@ qse_size_t strxsubst ( { return strxnsubst (buf, bsz, fmt, strlen(fmt), subst, ctx); } +