using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
namespace Npgsql
{
internal class NpgsqlTextWriter
{
NpgsqlBuffer _outBuf;
List _buffers;
List _writePositions;
byte[] _currentBuffer;
int _writePosition;
int _startPosition;
byte[] _doubleQuoteReplace;
byte[] _singleQuoteReplace;
byte[] _backslashReplace;
string _doubleQuoteReplaceString;
string _singleQuoteReplaceString;
string _backslashReplaceString;
bool _hasWrittenToOutputStream;
bool HasReplacers { get { return _doubleQuoteReplace != null || _singleQuoteReplace != null || _backslashReplace != null; } }
int WriteSpaceLeft { get { return _currentBuffer.Length - _writePosition; } }
internal NpgsqlTextWriter(NpgsqlBuffer npgsqlBuffer = null)
{
_outBuf = npgsqlBuffer;
if (npgsqlBuffer != null)
{
Contract.Assert(npgsqlBuffer.WriteSpaceLeft >= 1000, "Sholud have write space in NpgsqlBuffer");
_writePosition = npgsqlBuffer.WritePosition;
_startPosition = _writePosition;
_currentBuffer = npgsqlBuffer._buf;
}
else
{
_buffers = new List();
_writePositions = new List();
_currentBuffer = new byte[32];
_buffers.Add(_currentBuffer);
}
}
public EscapeState PushEscapeDoubleQuoteWithDoubleQuote()
{
var state = GetCurrentEscapeState();
if (_backslashReplaceString == null)
{
_singleQuoteReplaceString = "'";
_doubleQuoteReplaceString = "\"\"";
_backslashReplaceString = @"\\";
_singleQuoteReplace = ASCIIByteArrays.SingleQuote;
}
else
{
_doubleQuoteReplaceString += _doubleQuoteReplaceString;
_backslashReplaceString += _backslashReplaceString;
}
_doubleQuoteReplace = Encoding.UTF8.GetBytes(_doubleQuoteReplaceString);
_backslashReplace = Encoding.UTF8.GetBytes(_backslashReplaceString);
return state;
}
public EscapeState PushEscapeSingleQuoteWithSingleQuote()
{
var state = GetCurrentEscapeState();
if (_backslashReplaceString == null)
{
_singleQuoteReplaceString = @"''";
_doubleQuoteReplaceString = "\"";
_backslashReplaceString = @"\";
_doubleQuoteReplace = ASCIIByteArrays.DoubleQuote;
_backslashReplace = ASCIIByteArrays.BackSlash;
}
else
{
_singleQuoteReplaceString += _singleQuoteReplaceString;
_backslashReplaceString += _backslashReplaceString;
}
_singleQuoteReplace = Encoding.UTF8.GetBytes(_singleQuoteReplaceString);
return state;
}
public EscapeState PushEscapeDoubleQuoteWithBackspace()
{
var state = GetCurrentEscapeState();
if (_backslashReplaceString == null)
{
_singleQuoteReplaceString = "'";
_doubleQuoteReplaceString = "\\\"";
_backslashReplaceString = @"\\";
_singleQuoteReplace = ASCIIByteArrays.SingleQuote;
}
else
{
_doubleQuoteReplaceString = _backslashReplaceString + _doubleQuoteReplaceString;
_backslashReplaceString += _backslashReplaceString;
}
_doubleQuoteReplace = Encoding.UTF8.GetBytes(_doubleQuoteReplaceString);
_backslashReplace = Encoding.UTF8.GetBytes(_backslashReplaceString);
return state;
}
public EscapeState PushEscapeSingleQuoteWithBackspace()
{
var state = GetCurrentEscapeState();
if (_backslashReplaceString == null)
{
_singleQuoteReplaceString = @"\'";
_doubleQuoteReplaceString = "\"";
_backslashReplaceString = @"\\";
_doubleQuoteReplace = ASCIIByteArrays.DoubleQuote;
}
else
{
_singleQuoteReplaceString = _backslashReplaceString + _singleQuoteReplaceString;
_backslashReplaceString += _backslashReplaceString;
}
_singleQuoteReplace = Encoding.UTF8.GetBytes(_singleQuoteReplaceString);
_backslashReplace = Encoding.UTF8.GetBytes(_backslashReplaceString);
return state;
}
void AllocateNewBuffer()
{
if (_buffers == null)
{
_buffers = new List();
_writePositions = new List();
}
_currentBuffer = new byte[Math.Min(_currentBuffer.Length * 2, 8192)];
_buffers.Add(_currentBuffer);
_writePositions.Add(_writePosition);
_writePosition = 0;
}
public void WriteSingleChar(char c)
{
if (c == '\'' && _singleQuoteReplace != null)
{
WriteRawByteArray(_singleQuoteReplace);
}
else if (c == '"' && _doubleQuoteReplace != null)
{
WriteRawByteArray(_doubleQuoteReplace);
}
else if (c == '\\' && _backslashReplace != null)
{
WriteRawByteArray(_backslashReplace);
}
else if (c < 0x80)
{
if (WriteSpaceLeft == 0)
AllocateNewBuffer();
_currentBuffer[_writePosition++] = (byte)c;
}
else
{
throw new ArgumentException("Char out of range to encode as a single byte: " + (int)c);
}
}
public void WriteRawByteArray(byte[] array)
{
if (WriteSpaceLeft < array.Length)
AllocateNewBuffer();
Buffer.BlockCopy(array, 0, _currentBuffer, _writePosition, array.Length);
_writePosition += array.Length;
}
public void WriteRawByteArray(byte[] array, int offset, int len)
{
if (WriteSpaceLeft < len)
AllocateNewBuffer();
Buffer.BlockCopy(array, offset, _currentBuffer, _writePosition, len);
_writePosition += len;
}
public void WriteString(string s)
{
if (!HasReplacers)
{
WriteRawString(s);
}
else
{
if (_backslashReplaceString != null)
s = s.Replace("\\", _backslashReplaceString);
if (_doubleQuoteReplaceString != null)
s = s.Replace("\"", _doubleQuoteReplaceString);
if (_singleQuoteReplaceString != null)
s = s.Replace("'", _singleQuoteReplaceString);
WriteRawString(s);
}
}
public void WriteString(string s, int charOffset, int charLen)
{
if (!HasReplacers)
{
WriteRawString(s, charOffset, charLen);
}
else
{
s = s.Substring(charOffset, charLen);
if (_backslashReplaceString != null)
s = s.Replace("\\", _backslashReplaceString);
if (_doubleQuoteReplaceString != null)
s = s.Replace("\"", _doubleQuoteReplaceString);
if (_singleQuoteReplaceString != null)
s = s.Replace("'", _singleQuoteReplaceString);
WriteRawString(s);
}
}
void WriteRawString(string s)
{
WriteRawString(s, 0, s.Length);
}
void WriteRawString(string s, int charPos, int charLen)
{
for (; ; )
{
if (charLen * 3 <= WriteSpaceLeft)
{
_writePosition += Encoding.UTF8.GetBytes(s, charPos, charLen, _currentBuffer, _writePosition);
return;
}
int numCharsCanBeWritten = WriteSpaceLeft / 3;
if (numCharsCanBeWritten >= 20) // Don't do this if the buffer is almost full
{
char lastChar = s[charPos + numCharsCanBeWritten - 1];
if (lastChar >= 0xD800 && lastChar <= 0xDBFF)
{
--numCharsCanBeWritten; // Don't use high/lead surrogate pair as last char in block
}
int wrote = Encoding.UTF8.GetBytes(s, charPos, numCharsCanBeWritten, _currentBuffer, _writePosition);
_writePosition += wrote;
charPos += numCharsCanBeWritten;
charLen -= numCharsCanBeWritten;
}
else
{
AllocateNewBuffer();
}
}
}
public int GetTotalByteLength()
{
int len = _writePosition;
if (_writePositions != null)
{
for (var i = 0; i < _writePositions.Count; i++)
{
len += _writePositions[i];
}
}
len -= _startPosition;
return len;
}
internal byte[] GetTruncatedCopy()
{
Contract.Assert(_outBuf != null, "NpgsqlBuffer missing");
int len = GetTotalByteLength();
if (len <= 1000)
{
var arr = new byte[len];
Buffer.BlockCopy(_currentBuffer, _startPosition, arr, 0, len);
return arr;
}
else
{
var arr = new byte[1003];
Buffer.BlockCopy(_outBuf._buf, _startPosition, arr, 0, 1000);
arr[1000] = arr[1001] = arr[1002] = (byte)'.';
return arr;
}
}
// TODO: [GenerateAsync]
internal void WriteToOutputBuffer()
{
Contract.Assert(_outBuf != null, "NpgsqlBuffer missing");
Contract.Assert(!_hasWrittenToOutputStream, "Has already written once to output stream");
_outBuf.WritePosition = _writePositions != null ? _writePositions[0] : _writePosition;
if (_buffers == null)
// No buffered data
return;
for (var i = 0; i < _buffers.Count - 1; i++)
{
_outBuf.Write(_buffers[i], 0, _writePositions[i + 1]);
}
_outBuf.Write(_buffers[_buffers.Count - 1], 0, _writePosition);
}
// TODO: [GenerateAsync]
internal void WriteToOutputBuffer(NpgsqlBuffer buffer)
{
Contract.Assert(_outBuf == null, "This version of WriteToOutputBuffer requires that no NpgsqlBuffer has been set upon initialization");
for (var i = 0; i < _buffers.Count - 1; i++)
{
buffer.Write(_buffers[i], 0, _writePositions[i]);
}
buffer.Write(_buffers[_buffers.Count - 1], 0, _writePosition);
}
public EscapeState GetCurrentEscapeState()
{
return new EscapeState(_doubleQuoteReplace, _singleQuoteReplace, _backslashReplace, _doubleQuoteReplaceString, _singleQuoteReplaceString, _backslashReplaceString);
}
public void ResetEscapeState(EscapeState s)
{
_doubleQuoteReplace = s.DoubleQuote;
_singleQuoteReplace = s.SingleQuote;
_backslashReplace = s.Backslash;
_doubleQuoteReplaceString = s.DoubleQuoteString;
_singleQuoteReplaceString = s.SingleQuoteString;
_backslashReplaceString = s.BackslashString;
}
public struct EscapeState
{
public byte[] DoubleQuote;
public byte[] SingleQuote;
public byte[] Backslash;
public string DoubleQuoteString;
public string SingleQuoteString;
public string BackslashString;
public EscapeState(byte[] dq, byte[] sq, byte[] b, string dqs, string sqs, string bs)
{
DoubleQuote = dq;
SingleQuote = sq;
Backslash = b;
DoubleQuoteString = dqs;
SingleQuoteString = sqs;
BackslashString = bs;
}
}
}
}