/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// It's not worthwhile to reallocate the buffer and memcpy the // contents over when the size difference isn't large. With // power-of-two allocation buckets and 64 as the typical inline // capacity, considering that above 1000 there performance aspects // of realloc and memcpy seem to be absorbed, relative to the old // code, by the performance benefits of the new code being exact, // we need to choose which transitions of 256 to 128, 512 to 256 // and 1024 to 512 to allow. As a guess, let's pick the middle // one as the the largest potential transition that we forgo. So // we'll shrink from 1024 bucket to 512 bucket but not from 512 // bucket to 256 bucket. We'll decide by comparing the difference // of capacities. As bucket differences, the differences are 256 // and 512. Since the capacities have various overheads, we // can't compare with 256 or 512 exactly but it's easier to // compare to some number that's between the two, so it's // far away from either to ignore the overheads. const uint32_t kNsStringBufferShrinkingThreshold = 384;
/** * helper function for down-casting a nsTSubstring to an nsTAutoString.
*/ template <typename T> inlineconst nsTAutoString<T>* AsAutoString(const nsTSubstring<T>* aStr) { returnstatic_cast<const nsTAutoString<T>*>(aStr);
}
template <typename T>
mozilla::Result<mozilla::BulkWriteHandle<T>, nsresult>
nsTSubstring<T>::BulkWrite(size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking) { auto r = StartBulkWriteImpl(aCapacity, aPrefixToPreserve, aAllowShrinking); if (MOZ_UNLIKELY(r.isErr())) { return r.propagateErr();
} return mozilla::BulkWriteHandle<T>(this, r.unwrap());
}
template <typename T> auto nsTSubstring<T>::StartBulkWriteImpl(
size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking,
size_type aSuffixLength, size_type aOldSuffixStart,
size_type aNewSuffixStart) -> mozilla::Result<size_type, nsresult> { // Note! Capacity does not include room for the terminating null char.
MOZ_ASSERT(aPrefixToPreserve <= aCapacity, "Requested preservation of an overlong prefix.");
MOZ_ASSERT(aNewSuffixStart + aSuffixLength <= aCapacity, "Requesed move of suffix to out-of-bounds location."); // Can't assert aOldSuffixStart, because mLength may not be valid anymore, // since this method allows itself to be called more than once.
// If zero capacity is requested, set the string to the special empty // string. if (MOZ_UNLIKELY(!aCapacity)) {
ReleaseData(this->mData, this->mDataFlags);
SetToEmptyBuffer(); return 0;
}
// Note! Capacity() returns 0 when the string is immutable. const size_type curCapacity = Capacity();
bool shrinking = false;
// We've established that aCapacity > 0. // |curCapacity == 0| means that the buffer is immutable or 0-sized, so we // need to allocate a new buffer. We cannot use the existing buffer even // though it might be large enough.
// If this is an nsTAutoStringN, it's possible that we can use the inline // buffer. if ((this->mClassFlags & ClassFlags::INLINE) &&
(aCapacity <= AsAutoString(this)->mInlineCapacity)) {
newCapacity = AsAutoString(this)->mInlineCapacity;
newData = (char_type*)AsAutoString(this)->mStorage;
newDataFlags = DataFlags::TERMINATED | DataFlags::INLINE;
} else { // If |aCapacity > kMaxCapacity|, then our doubling algorithm may not be // able to allocate it. Just bail out in cases like that. We don't want // to be allocating 2GB+ strings anyway.
static_assert((sizeof(mozilla::StringBuffer) & 0x1) == 0, "bad size for mozilla::StringBuffer"); if (MOZ_UNLIKELY(!this->CheckCapacity(aCapacity))) { return mozilla::Err(NS_ERROR_OUT_OF_MEMORY);
}
// We increase our capacity so that the allocated buffer grows // exponentially, which gives us amortized O(1) appending. Below the // threshold, we use powers-of-two. Above the threshold, we grow by at // least 1.125, rounding up to the nearest MiB. const size_type slowGrowthThreshold = 8 * 1024 * 1024;
// Round up to the next multiple of MiB, but ensure the expected // capacity doesn't include the extra space required by // mozilla::StringBuffer and null-termination. const size_t MiB = 1 << 20;
temp = (MiB * ((temp + MiB - 1) / MiB)) - neededExtraSpace;
} else { // Round up to the next power of two.
temp =
mozilla::RoundUpPow2(aCapacity + neededExtraSpace) - neededExtraSpace;
}
newCapacity = XPCOM_MIN(temp, base_string_type::kMaxCapacity);
MOZ_ASSERT(newCapacity >= aCapacity, "should have hit the early return at the top"); // Avoid shrinking if the new buffer size is close to the old. Note that // unsigned underflow is defined behavior. if ((curCapacity - newCapacity) <= kNsStringBufferShrinkingThreshold &&
(this->mDataFlags & DataFlags::REFCOUNTED)) {
MOZ_ASSERT(aAllowShrinking, "How come we didn't return earlier?"); // We're already close enough to the right size.
newData = oldData;
newCapacity = curCapacity;
} else {
size_type storageSize = (newCapacity + 1) * sizeof(char_type); // Since we allocate only by powers of 2 we always fit into a full // mozjemalloc bucket, it's not useful to use realloc, which may spend // time uselessly copying too much.
mozilla::StringBuffer* newHdr =
mozilla::StringBuffer::Alloc(storageSize).take(); if (newHdr) {
newData = (char_type*)newHdr->Data();
} elseif (shrinking) { // We're still in a consistent state. // // Since shrinking is just a memory footprint optimization, we // don't propagate OOM if we tried to shrink in order to avoid // OOM crashes from infallible callers. If we're lucky, soon enough // a fallible caller reaches OOM and is able to deal or we end up // disposing of this string before reaching OOM again.
newData = oldData;
newCapacity = curCapacity;
} else { return mozilla::Err(NS_ERROR_OUT_OF_MEMORY);
}
}
newDataFlags = DataFlags::TERMINATED | DataFlags::REFCOUNTED;
}
mozilla::Result<size_type, nsresult> r = StartBulkWriteImpl(
aNewLen, aCutStart, false, suffixLength, oldSuffixStart, newSuffixStart); if (r.isErr()) { returnfalse;
}
FinishBulkWriteImpl(aNewLen); returntrue;
}
template <typename T> typename nsTSubstring<T>::size_type nsTSubstring<T>::Capacity() const { // return 0 to indicate an immutable or 0-sized buffer
size_type capacity; if (this->mDataFlags & DataFlags::REFCOUNTED) { // if the string is readonly, then we pretend that it has no capacity.
mozilla::StringBuffer* hdr = mozilla::StringBuffer::FromData(this->mData); if (hdr->IsReadonly()) {
capacity = 0;
} else {
capacity = (size_t(hdr->StorageSize()) / sizeof(char_type)) - 1;
}
} elseif (this->mDataFlags & DataFlags::INLINE) {
MOZ_ASSERT(this->mClassFlags & ClassFlags::INLINE);
capacity = AsAutoString(this)->mInlineCapacity;
} elseif (this->mDataFlags & DataFlags::OWNED) { // we don't store the capacity of an adopted buffer because that would // require an additional member field. the best we can do is base the // capacity on our length. remains to be seen if this is the right // trade-off.
capacity = this->mLength;
} else {
capacity = 0;
}
// This version of Assign is optimized for single-character assignment. template <typename T> void nsTSubstring<T>::Assign(char_type aChar) { if (MOZ_UNLIKELY(!Assign(aChar, mozilla::fallible))) {
AllocFailed(1);
}
}
template <typename T> bool nsTSubstring<T>::Assign(char_type aChar, const fallible_t&) { auto r = StartBulkWriteImpl(1, 0, true); if (MOZ_UNLIKELY(r.isErr())) { returnfalse;
}
*this->mData = aChar;
FinishBulkWriteImpl(1); returntrue;
}
// A Unicode string can't depend on an ASCII string buffer, // so this dependence check only applies to CStrings. if constexpr (std::is_same_v<T, char>) { if (this->IsDependentOn(aData, aData + aLength)) { return Assign(string_type(aData, aLength), aFallible);
}
}
auto r = StartBulkWriteImpl(aLength, 0, true); if (MOZ_UNLIKELY(r.isErr())) { returnfalse;
}
char_traits::copyASCII(this->mData, aData, aLength);
FinishBulkWriteImpl(aLength); returntrue;
}
template <typename T> bool nsTSubstring<T>::Assign(const self_type& aStr, const fallible_t& aFallible) { // |aStr| could be sharable. We need to check its flags to know how to // deal with it.
// get an owning reference to the this->mData
mozilla::StringBuffer::FromData(this->mData)->AddRef(); returntrue;
} if (aStr.mDataFlags & DataFlags::LITERAL) {
MOZ_ASSERT(aStr.mDataFlags & DataFlags::TERMINATED, "Unterminated literal");
template <typename T> bool nsTSubstring<T>::Assign(self_type&& aStr, const fallible_t& aFallible) { // We're moving |aStr| in this method, so we need to try to steal the data, // and in the fallback perform a copy-assignment followed by a truncation of // the original string.
if (&aStr == this) {
NS_WARNING("Move assigning a string to itself?"); returntrue;
}
if (aStr.mDataFlags & (DataFlags::REFCOUNTED | DataFlags::OWNED)) {
AssignOwned(std::move(aStr)); returntrue;
}
// Otherwise treat this as a normal assignment, and truncate the moved string. // We don't truncate the source string if the allocation failed. if (!Assign(aStr, aFallible)) { returnfalse;
}
aStr.Truncate(); returntrue;
}
// Treat this as construction of a "StringAdopt" object for leak // tracking purposes.
MOZ_LOG_CTOR(this->mData, "StringAdopt", 1);
} else {
SetIsVoid(true);
}
}
// This version of Replace is optimized for single-character replacement. template <typename T> void nsTSubstring<T>::Replace(index_type aCutStart, size_type aCutLength,
char_type aChar) {
aCutStart = XPCOM_MIN(aCutStart, this->Length());
if (ReplacePrep(aCutStart, aCutLength, 1)) {
this->mData[aCutStart] = aChar;
}
}
template <typename T> bool nsTSubstring<T>::SetCapacity(size_type aCapacity, const fallible_t&) {
size_type length = this->mLength; // This method can no longer be used to shorten the // logical length.
size_type capacity = XPCOM_MAX(aCapacity, length);
auto r = StartBulkWriteImpl(capacity, length, true); if (r.isErr()) { returnfalse;
}
if (MOZ_UNLIKELY(!capacity)) { // Zero capacity was requested on a zero-length // string. In this special case, we are pointing // to the special empty buffer, which is already // zero-terminated and not writable, so we must // not attempt to zero-terminate it.
AssertValid(); returntrue;
}
// FinishBulkWriteImpl with argument zero releases // the heap-allocated buffer. However, SetCapacity() // is a special case that allows mLength to be zero // while a heap-allocated buffer exists. // By calling FinishBulkWriteImplImpl, we skip the // zero case handling that's inappropriate in the // SetCapacity() case.
FinishBulkWriteImplImpl(length); returntrue;
}
if (untaggedPrefixLength == this->mLength) { return;
}
if (!EnsureMutable()) {
AllocFailed(this->mLength);
}
char_type* to = this->mData + untaggedPrefixLength;
char_type* from = to;
char_type* end = this->mData + this->mLength;
while (from < end) {
uint32_t theChar = (uint32_t)*from++; // Replacing this with a call to ASCIIMask::IsMasked // regresses performance somewhat, so leaving it inlined. if (!mozilla::ASCIIMask::IsMasked(aToStrip, theChar)) { // Not stripped, copy this char.
*to++ = (char_type)theChar;
}
}
*to = char_type(0); // add the null
this->mLength = to - this->mData;
}
template <typename T> void nsTSubstring<T>::StripCRLF() { // Expanding this call to copy the code from StripTaggedASCII // instead of just calling it does somewhat help with performance // but it is not worth it given the duplicated code.
StripTaggedASCII(mozilla::ASCIIMask::MaskCRLF());
}
// An adapter type for a `nsTSubstring<T>` to provide a std::string-like // interface for `{fmt}`. template <typename T> class nsTSubstringStdCollectionAdapter { public: using value_type = T;
// Mark the collection as contiguous for {fmt} so that the buffer will be used // directly. namespace fmt { template <typename T> struct is_contiguous<nsTSubstringStdCollectionAdapter<T>> : std::true_type {};
} // namespace fmt
template <typename T> void nsTSubstring<T>::AppendPrintf(constchar* aFormat, ...) {
PrintfAppend<T> appender(this);
va_list ap;
va_start(ap, aFormat); bool r = appender.vprint(aFormat, ap); if (!r) {
MOZ_CRASH("Allocation or other failure in PrintfTarget::print");
}
va_end(ap);
}
template <typename T> void nsTSubstring<T>::AppendVprintf(constchar* aFormat, va_list aAp) {
PrintfAppend<T> appender(this); bool r = appender.vprint(aFormat, aAp); if (!r) {
MOZ_CRASH("Allocation or other failure in PrintfTarget::print");
}
}
template <typename T> void nsTSubstring<T>::AppendIntDec(int32_t aInteger) {
PrintfAppend<T> appender(this); bool r = appender.appendIntDec(aInteger); if (MOZ_UNLIKELY(!r)) {
MOZ_CRASH("Allocation or other failure while appending integers");
}
}
template <typename T> void nsTSubstring<T>::AppendIntDec(uint32_t aInteger) {
PrintfAppend<T> appender(this); bool r = appender.appendIntDec(aInteger); if (MOZ_UNLIKELY(!r)) {
MOZ_CRASH("Allocation or other failure while appending integers");
}
}
template <typename T> void nsTSubstring<T>::AppendIntOct(uint32_t aInteger) {
PrintfAppend<T> appender(this); bool r = appender.appendIntOct(aInteger); if (MOZ_UNLIKELY(!r)) {
MOZ_CRASH("Allocation or other failure while appending integers");
}
}
template <typename T> void nsTSubstring<T>::AppendIntHex(uint32_t aInteger) {
PrintfAppend<T> appender(this); bool r = appender.appendIntHex(aInteger); if (MOZ_UNLIKELY(!r)) {
MOZ_CRASH("Allocation or other failure while appending integers");
}
}
template <typename T> void nsTSubstring<T>::AppendIntDec(int64_t aInteger) {
PrintfAppend<T> appender(this); bool r = appender.appendIntDec(aInteger); if (MOZ_UNLIKELY(!r)) {
MOZ_CRASH("Allocation or other failure while appending integers");
}
}
template <typename T> void nsTSubstring<T>::AppendIntDec(uint64_t aInteger) {
PrintfAppend<T> appender(this); bool r = appender.appendIntDec(aInteger); if (MOZ_UNLIKELY(!r)) {
MOZ_CRASH("Allocation or other failure while appending integers");
}
}
template <typename T> void nsTSubstring<T>::AppendIntOct(uint64_t aInteger) {
PrintfAppend<T> appender(this); bool r = appender.appendIntOct(aInteger); if (MOZ_UNLIKELY(!r)) {
MOZ_CRASH("Allocation or other failure while appending integers");
}
}
template <typename T> void nsTSubstring<T>::AppendIntHex(uint64_t aInteger) {
PrintfAppend<T> appender(this); bool r = appender.appendIntHex(aInteger); if (MOZ_UNLIKELY(!r)) {
MOZ_CRASH("Allocation or other failure while appending integers");
}
}
// Returns the length of the formatted aDouble in aBuf. staticint FormatWithoutTrailingZeros(char (&aBuf)[40], double aDouble, int aPrecision) { staticconst DoubleToStringConverter converter(
DoubleToStringConverter::UNIQUE_ZERO |
DoubleToStringConverter::NO_TRAILING_ZERO |
DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN, "Infinity", "NaN", 'e', -6, 21, 6, 1);
double_conversion::StringBuilder builder(aBuf, sizeof(aBuf));
converter.ToPrecision(aDouble, aPrecision, &builder); int length = builder.position();
builder.Finalize(); return length;
}
// If we reach here, exactly one of the following must be true: // - DataFlags::VOIDED is set, and this->mData points to sEmptyBuffer; // - DataFlags::INLINE is set, and this->mData points to a buffer within a // string object (e.g. nsAutoString); // - None of DataFlags::REFCOUNTED, DataFlags::OWNED, DataFlags::INLINE is // set, and this->mData points to a buffer owned by something else. // // In all three cases, we don't measure it. return 0;
}
template <typename T>
size_t nsTSubstring<T>::SizeOfExcludingThisEvenIfShared(
mozilla::MallocSizeOf aMallocSizeOf) const { // This is identical to SizeOfExcludingThisIfUnshared except for the // DataFlags::REFCOUNTED case. if (this->mDataFlags & DataFlags::REFCOUNTED) { return mozilla::StringBuffer::FromData(this->mData)
->SizeOfIncludingThisEvenIfShared(aMallocSizeOf);
} if (this->mDataFlags & DataFlags::OWNED) { return aMallocSizeOf(this->mData);
} return 0;
}
// Common logic for nsTSubstring<T>::ToInteger and nsTSubstring<T>::ToInteger64. template <typename T, typename int_type>
int_type ToIntegerCommon(const nsTSubstring<T>& aSrc, nsresult* aErrorCode,
uint32_t aRadix) {
MOZ_ASSERT(aRadix == 10 || aRadix == 16);
// Initial value, override if we find an integer.
*aErrorCode = NS_ERROR_ILLEGAL_VALUE;
// Begin by skipping over leading chars that shouldn't be part of the number. auto cp = aSrc.BeginReading(); auto endcp = aSrc.EndReading(); bool negate = false; bool done = false;
// NB: For backwards compatibility I'm not going to change this logic but // it seems really odd. Previously there was logic to auto-detect the // radix if kAutoDetect was passed in. In practice this value was never // used, so it pretended to auto detect and skipped some preceding // letters (excluding valid hex digits) but never used the result. // // For example if you pass in "Get the number: 10", aRadix = 10 we'd // skip the 'G', and then fail to parse "et the number: 10". If aRadix = // 16 we'd skip the 'G', and parse just 'e' returning 14. while ((cp < endcp) && (!done)) { switch (*cp++) { // clang-format off case'a': case'b': case'c': case'd': case'e': case'f': case'A': case'B': case'C': case'D': case'E': case'F': case'0': case'1': case'2': case'3': case'4': case'5': case'6': case'7': case'8': case'9':
done = true; break; // clang-format on case'-': if constexpr (!std::is_signed_v<int_type>) { return 0;
}
negate = true; break; default: break;
}
}
if (!done) { // No base 16 or base 10 digits were found. return 0;
}
// Step back.
cp--;
mozilla::CheckedInt<int_type> result;
// Now iterate the numeric chars and build our result. while (cp < endcp) { auto theChar = *cp++; if (('0' <= theChar) && (theChar <= '9')) {
result = (aRadix * result) + (theChar - '0');
} elseif ((theChar >= 'A') && (theChar <= 'F')) { if (10 == aRadix) { // Invalid base 10 digit, error out. return 0;
}
result = (aRadix * result) + ((theChar - 'A') + 10);
} elseif ((theChar >= 'a') && (theChar <= 'f')) { if (10 == aRadix) { // Invalid base 10 digit, error out. return 0;
}
result = (aRadix * result) + ((theChar - 'a') + 10);
} elseif ((('X' == theChar) || ('x' == theChar)) && result == 0) { // For some reason we support a leading 'x' regardless of radix. For // example: "000000x500", aRadix = 10 would be parsed as 500 rather // than 0. continue;
} else { // We've encountered a char that's not a legal number or sign and we can // terminate processing. break;
}
if (!result.isValid()) { // Overflow! return 0;
}
}
template <typename T> void nsTSubstring<T>::ReplaceChar(char_type aOldChar, char_type aNewChar) {
int32_t i = this->FindChar(aOldChar); if (i == kNotFound) { return;
}
if (!this->EnsureMutable()) {
this->AllocFailed(this->mLength);
} for (; i != kNotFound; i = this->FindChar(aOldChar, i + 1)) {
this->mData[i] = aNewChar;
}
}
template <typename T> void nsTSubstring<T>::ReplaceChar(const string_view& aSet, char_type aNewChar) {
int32_t i = this->FindCharInSet(aSet); if (i == kNotFound) { return;
}
if (!this->EnsureMutable()) {
this->AllocFailed(this->mLength);
} for (; i != kNotFound; i = this->FindCharInSet(aSet, i + 1)) {
this->mData[i] = aNewChar;
}
}
template <typename T> void nsTSubstring<T>::ReplaceSubstring(const self_type& aTarget, const self_type& aNewValue) { if (!ReplaceSubstring(aTarget, aNewValue, mozilla::fallible)) { // Note that this may wildly underestimate the allocation that failed, as // we could have been replacing multiple copies of aTarget.
this->AllocFailed(this->mLength + (aNewValue.Length() - aTarget.Length()));
}
}
// Remember all of the non-matching parts.
AutoTArray<Segment, 16> nonMatching;
uint32_t i = 0;
mozilla::CheckedUint32 newLength; while (true) {
int32_t r = this->Find(aTarget, i);
int32_t until = (r == kNotFound) ? this->Length() - i : r - i;
nonMatching.AppendElement(Segment(i, until));
newLength += until; if (r == kNotFound) { break;
}
newLength += aNewValue.Length();
i = r + aTarget.Length(); if (i >= this->Length()) { // Add an auxiliary entry at the end of the list to help as an edge case // for the algorithms below.
nonMatching.AppendElement(Segment(this->Length(), 0)); break;
}
}
if (!newLength.isValid()) { returnfalse;
}
// If there's only one non-matching segment, then the target string was not // found, and there's nothing to do. if (nonMatching.Length() == 1) {
MOZ_ASSERT(
nonMatching[0].mBegin == 0 && nonMatching[0].mLength == this->Length(), "We should have the correct non-matching segment."); returntrue;
}
// Make sure that we can mutate our buffer. // Note that we always allocate at least an this->mLength sized buffer, // because the rest of the algorithm relies on having access to all of the // original string. In other words, we over-allocate in the shrinking case.
uint32_t oldLen = this->Length(); auto r =
this->StartBulkWriteImpl(XPCOM_MAX(oldLen, newLength.value()), oldLen); if (r.isErr()) { returnfalse;
}
if (aTarget.Length() >= aNewValue.Length()) { // In the shrinking case, start filling the buffer from the beginning. const uint32_t delta = (aTarget.Length() - aNewValue.Length()); for (i = 1; i < nonMatching.Length(); ++i) { // When we move the i'th non-matching segment into position, we need to // account for the characters deleted by the previous |i| replacements by // subtracting |i * delta|. const char_type* sourceSegmentPtr = this->mData + nonMatching[i].mBegin;
char_type* destinationSegmentPtr =
this->mData + nonMatching[i].mBegin - i * delta; // Write the i'th replacement immediately before the new i'th non-matching // segment.
char_traits::copy(destinationSegmentPtr - aNewValue.Length(),
aNewValue.Data(), aNewValue.Length());
char_traits::move(destinationSegmentPtr, sourceSegmentPtr,
nonMatching[i].mLength);
}
} else { // In the growing case, start filling the buffer from the end. const uint32_t delta = (aNewValue.Length() - aTarget.Length()); for (i = nonMatching.Length() - 1; i > 0; --i) { // When we move the i'th non-matching segment into position, we need to // account for the characters added by the previous |i| replacements by // adding |i * delta|. const char_type* sourceSegmentPtr = this->mData + nonMatching[i].mBegin;
char_type* destinationSegmentPtr =
this->mData + nonMatching[i].mBegin + i * delta;
char_traits::move(destinationSegmentPtr, sourceSegmentPtr,
nonMatching[i].mLength); // Write the i'th replacement immediately before the new i'th non-matching // segment.
char_traits::copy(destinationSegmentPtr - aNewValue.Length(),
aNewValue.Data(), aNewValue.Length());
}
}
// Adjust the length and make sure the string is null terminated.
this->FinishBulkWriteImpl(newLength.value());
// walk forward from start to end for (; start != end; ++start, ++cutLength) { if ((*start & ~0x7F) || // non-ascii
aSet.find(char(*start)) == std::string_view::npos) { break;
}
}
if (aTrimTrailing) {
uint32_t cutEnd = end - this->mData;
uint32_t cutLength = 0;
// walk backward from end to start
--end; for (; end >= start; --end, ++cutLength) { if ((*end & ~0x7F) || // non-ascii
aSet.find(char(*end)) == std::string_view::npos) { break;
}
}
if (cutLength) {
this->Cut(cutEnd - cutLength, cutLength);
}
}
}
char_type* to = this->mData;
char_type* from = this->mData;
char_type* end = this->mData + this->mLength;
// Compresses runs of whitespace down to a normal space ' ' and convert // any whitespace to a normal space. This assumes that whitespace is // all standard 7-bit ASCII. bool skipWS = aTrimLeading; while (from < end) {
uint32_t theChar = *from++; if (mozilla::ASCIIMask::IsMasked(mask, theChar)) { if (!skipWS) {
*to++ = ' ';
skipWS = true;
}
} else {
*to++ = theChar;
skipWS = false;
}
}
// If we need to trim the trailing whitespace, back up one character. if (aTrimTrailing && skipWS && to > this->mData) {
to--;
}
*to = char_type(0); // add the null
this->mLength = to - this->mData;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.