private static Int32 WriteJSONString( StreamWriterWithResizableBuffer stream, IEncodingInfo encoding, String str ) { // Write starting quote var asciiSize = encoding.BytesPerASCIICharacter; var eencoding = encoding.Encoding; // Allocate enough bytes for whole string and 2 quotes, we will allocate more as we encounter escapable characters var range = stream.ReserveBufferSegment(eencoding.GetByteCount(str) + 2 * asciiSize); var array = stream.Buffer; var start = range.Offset; var idx = start; encoding.WriteASCIIByte(array, ref idx, (Byte)STR_START); // Write contents in chunks var prevStrIdx = 0; for (var i = 0; i < str.Length; ++i) { var c = str[i]; if (NeedsEscaping(c)) { // Append previous chunk idx += eencoding.GetBytes(str, prevStrIdx, i - prevStrIdx, array, idx); // Make sure we have room for escape character stream.ReserveBufferSegment(asciiSize); array = stream.Buffer; // Append escape character encoding.WriteASCIIByte(array, ref idx, (Byte)STR_ESCAPE_PREFIX); // Append escape sequence latter character TransformToEscape(encoding, array, ref idx, c); // Update index prevStrIdx = i + 1; } } // Append final chunk var finalChunkSize = str.Length - prevStrIdx; if (finalChunkSize > 0) { idx += eencoding.GetBytes(str, prevStrIdx, finalChunkSize, array, idx); } // Append closing quote encoding.WriteASCIIByte(array, ref idx, (Byte)STR_END); return(idx - start); }
private static void TransformToEscape( IEncodingInfo encoding, Byte[] array, ref Int32 idx, Char charRead ) { Byte replacementByte = 0; switch (charRead) { case STR_END: case STR_ESCAPE_PREFIX: case '/': // Actual value is just just read char minus the '\' replacementByte = (Byte)charRead; break; case '\b': // Backspace replacementByte = (Byte)'b'; break; case '\f': // Form feed replacementByte = (Byte)'f'; break; case '\n': // New line replacementByte = (Byte)'n'; break; case '\r': // Carriage return replacementByte = (Byte)'r'; break; case '\t': // Horizontal tab replacementByte = (Byte)'t'; break; // \u escape is not needed - it is provided mainly for humans. default: throw new NotSupportedException($"Unsupported escape sequence: '{charRead}'."); } if (replacementByte != 0) { encoding.WriteASCIIByte(array, ref idx, replacementByte); } }
/// <summary> /// This method will write SASL string to given byte array using given <see cref="IEncodingInfo"/>, while escaping and transforming string as needed. /// See <see cref="CheckString"/> for more information about escaping and transforming. /// </summary> /// <param name="str">The string to write.</param> /// <param name="encodingInfo">The <see cref="IEncodingInfo"/> to use.</param> /// <param name="array">The byte array where to write the string.</param> /// <param name="offset">The offset in <paramref name="array"/> where to start writing.</param> /// <param name="normalizer">The optional custom normalizer callback to use.</param> /// <param name="checkResult">The result of <see cref="CheckString"/>, if it was used before calling this method.</param> /// <seealso cref="CheckString"/> /// <remarks> /// This method will not allocate any strings. /// </remarks> public static void WriteString( String str, IEncodingInfo encodingInfo, ResizableArray <Byte> array, ref Int32 offset, CustomStringNormalizer normalizer = null, Int32?checkResult = null ) { var checkResultValue = checkResult ?? CheckString(str, normalizer); var encoding = encodingInfo.Encoding; if (checkResultValue == -1) { // String may be written as-is array.CurrentMaxCapacity = offset + encoding.GetByteCount(str); encodingInfo.WriteString(array.Array, ref offset, str); } else { var maxCharByteCountDiff = encodingInfo.MaxCharByteCount - 1; String tmpStr; // Calculate byte size (actually, we are calculating the upper bound for byte size, but that's ok) var byteSize = encoding.GetByteCount(str); for (var i = checkResultValue; i < str.Length; ++i) { var c = str[i]; if (c >= MIN_SPECIAL_CHAR) { // It is possible that we need to replace character if (IsNonAsciiSpace(c)) { // Mapping special space into "normal" space (0x20) // Byte size doesn't grow at least. } else if (IsCommonlyMappedToNothing(c)) { --byteSize; } else if ((tmpStr = normalizer?.Invoke(str, i)) != null) { byteSize += encoding.GetByteCount(tmpStr) - 1; } } } // Now we know that the string will take max 'byteSize' amount of bytes array.CurrentMaxCapacity = offset + byteSize; // We can get the actual array now, since we won't be resizing the ResizableArray anymore var buffer = array.Array; // Helper method to write chunks of "normal" characters var prev = 0; void WritePreviousChunk(Int32 charIdx, ref Int32 writeIdx) { writeIdx += encoding.GetBytes(str, prev, charIdx - prev, buffer, writeIdx); prev = charIdx + 1; } for (var i = checkResultValue; i < str.Length; ++i) { var c = str[i]; if (c >= MIN_SPECIAL_CHAR) { // It is possible that we need to replace character if (IsNonAsciiSpace(c)) { // Mapping special space into "normal" space (0x20) WritePreviousChunk(i, ref offset); encodingInfo.WriteASCIIByte(buffer, ref offset, 0x20); } else if (IsCommonlyMappedToNothing(c)) { // Nothing additional to write WritePreviousChunk(i, ref offset); } else if ((tmpStr = normalizer?.Invoke(str, i)) != null) { WritePreviousChunk(i, ref offset); if (tmpStr.Length > 0) { encodingInfo.WriteString(buffer, ref offset, tmpStr); } } } } if (prev < str.Length) { // Write final chunk WritePreviousChunk(str.Length - 1, ref offset); } } }
private static async ValueTask <Int32> PerformWriteJSONTTokenAsync( StreamWriterWithResizableBuffer stream, IEncodingInfo encoding, JToken jsonValue ) { ArgumentValidator.ValidateNotNull(nameof(jsonValue), jsonValue); Int32 bytesWritten; var asciiSize = encoding.BytesPerASCIICharacter; Int32 max; (Int32 Offset, Int32 Count)range; switch (jsonValue) { case JArray array: range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)ARRAY_START); bytesWritten = range.Count; max = array.Count; if (max > 0) { for (var i = 0; i < max; ++i) { bytesWritten += await PerformWriteJSONTTokenAsync(stream, encoding, array[i]); if (i < max - 1) { range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)ARRAY_VALUE_DELIM); bytesWritten += range.Count; } } } range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)ARRAY_END); bytesWritten += await stream.FlushAsync(); break; case JObject obj: range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_START); bytesWritten = range.Count; max = obj.Count; if (max > 0) { var j = 0; foreach (var kvp in obj) { bytesWritten += WriteJSONString(stream, encoding, kvp.Key); range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_KEY_VALUE_DELIM); bytesWritten += range.Count; await stream.FlushAsync(); bytesWritten += await PerformWriteJSONTTokenAsync(stream, encoding, kvp.Value); if (++j < max) { range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_VALUE_DELIM); bytesWritten += range.Count; } } } range = stream.ReserveBufferSegment(asciiSize); encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_END); bytesWritten += await stream.FlushAsync(); break; case JValue value: var val = value.Value; switch (val) { case String str: bytesWritten = WriteJSONString(stream, encoding, str); break; case Boolean boolean: // Write 'true' or 'false' range = stream.ReserveBufferSegment(boolean ? 4 : 5); encoding.WriteString(stream.Buffer, ref range.Offset, boolean ? "true" : "false"); bytesWritten = range.Count; break; case Int64 i64: range = stream.ReserveBufferSegment(encoding.GetTextualIntegerRepresentationSize(i64)); encoding.WriteIntegerTextual(stream.Buffer, ref range.Offset, i64); bytesWritten = range.Count; break; case Double dbl: // Have to allocate string :/ var dblStr = dbl.ToString(System.Globalization.CultureInfo.InvariantCulture); range = stream.ReserveBufferSegment(encoding.Encoding.GetByteCount(dblStr)); encoding.WriteString(stream.Buffer, ref range.Offset, dblStr); bytesWritten = range.Count; break; case null: // Write 'null' range = stream.ReserveBufferSegment(asciiSize * 4); encoding.WriteString(stream.Buffer, ref range.Offset, "null"); bytesWritten = range.Count; break; default: throw new NotSupportedException($"Unsupported primitive value {val}."); } // Remember to flush stream await stream.FlushAsync(); break; default: throw new NotSupportedException($"Unrecognized JToken type: {jsonValue?.GetType()}."); } return(bytesWritten); }