// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using IronPython.Runtime;
using IronPython.Runtime.Exceptions;
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;
using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;
[assembly: PythonModule("_csv", typeof(IronPython.Modules.PythonCsvModule))]
namespace IronPython.Modules {
using DialectRegistry = Dictionary;
public static class PythonCsvModule {
public const string __doc__ = "";
public const string __version__ = "1.0";
public const int QUOTE_MINIMAL = 0;
public const int QUOTE_ALL = 1;
public const int QUOTE_NONNUMERIC = 2;
public const int QUOTE_NONE = 3;
private static readonly object _fieldSizeLimitKey = new object();
private static readonly object _dialectRegistryKey = new object();
private const int FieldSizeLimit = 128 * 1024; /* max parsed field size */
[SpecialName]
public static void PerformModuleReload(PythonContext context, PythonDictionary dict) {
if (!context.HasModuleState(_fieldSizeLimitKey)) {
context.SetModuleState(_fieldSizeLimitKey, FieldSizeLimit);
}
if (!context.HasModuleState(_dialectRegistryKey)) {
context.SetModuleState(_dialectRegistryKey,
new DialectRegistry());
}
InitModuleExceptions(context, dict);
}
public static int field_size_limit(CodeContext /*!*/ context, int new_limit) {
PythonContext ctx = context.LanguageContext;
int old_limit = (int)ctx.GetModuleState(_fieldSizeLimitKey);
ctx.SetModuleState(_fieldSizeLimitKey, new_limit);
return old_limit;
}
public static int field_size_limit(CodeContext/*!*/ context) {
return (int)context.LanguageContext.
GetModuleState(_fieldSizeLimitKey);
}
[Documentation(@"Create a mapping from a string name to a dialect class.
dialect = csv.register_dialect(name, dialect)")]
public static void register_dialect(CodeContext/*!*/ context,
[ParamDictionary] IDictionary kwArgs,
params object[] args) {
string name = null;
object dialectObj = null;
Dialect dialect = null;
if (args.Length < 1) {
throw PythonOps.TypeError("expected at least 1 arguments, got {0}",
args.Length);
}
if (args.Length > 2) {
throw PythonOps.TypeError("expected at most 2 arguments, got {0}",
args.Length);
}
name = args[0] as string;
if (name == null) {
throw PythonOps.TypeError(
"dialect name must be a string or unicode");
}
if (args.Length > 1)
dialectObj = args[1];
dialect = (dialectObj != null) ?
Dialect.Create(context, kwArgs, dialectObj) :
Dialect.Create(context, kwArgs);
if (dialect != null)
GetDialects(context)[name] = dialect;
}
///
/// Returns the dialects from the code context.
///
///
///
private static DialectRegistry GetDialects(CodeContext/*!*/ context) {
PythonContext ctx = context.LanguageContext;
if (!ctx.HasModuleState(_dialectRegistryKey)) {
ctx.SetModuleState(_dialectRegistryKey,
new DialectRegistry());
}
return (DialectRegistry)ctx.GetModuleState(_dialectRegistryKey);
}
private static int GetFieldSizeLimit(CodeContext/*!*/ context) {
PythonContext ctx = context.LanguageContext;
if (!ctx.HasModuleState(_fieldSizeLimitKey)) {
ctx.SetModuleState(_fieldSizeLimitKey, FieldSizeLimit);
}
return (int)ctx.GetModuleState(_fieldSizeLimitKey);
}
[Documentation(@"Delete the name/dialect mapping associated with a string name.\n
csv.unregister_dialect(name)")]
public static void unregister_dialect(CodeContext/*!*/ context, string name) {
DialectRegistry dialects = GetDialects(context);
if (name is not null && dialects.Remove(name)) return;
throw MakeError("unknown dialect");
}
[Documentation(@"Return the dialect instance associated with name.
dialect = csv.get_dialect(name)")]
public static object get_dialect(CodeContext/*!*/ context, string name) {
DialectRegistry dialects = GetDialects(context);
if (name is not null && dialects.TryGetValue(name, out var value)) return value;
throw MakeError("unknown dialect");
}
[Documentation(@"Return a list of all know dialect names
names = csv.list_dialects()")]
public static PythonList list_dialects(CodeContext/*!*/ context) {
return new PythonList(GetDialects(context).Keys);
}
[Documentation(@"csv_reader = reader(iterable [, dialect='excel']
[optional keyword args])
for row in csv_reader:
process(row)
The ""iterable"" argument can be any object that returns a line
of input for each iteration, such as a file object or a list. The
optional ""dialect"" parameter is discussed below. The function
also accepts optional keyword arguments which override settings
provided by the dialect.
The returned object is an iterator. Each iteration returns a row
of the CSV file (which can span multiple input lines)")]
public static object reader(CodeContext/*!*/ context,
[ParamDictionary] IDictionary kwArgs,
params object[] args) {
object dialectObj = null;
Dialect dialect = null;
IEnumerator e = null;
DialectRegistry dialects = GetDialects(context);
if (args.Length < 1) {
throw PythonOps.TypeError(
"expected at least 1 arguments, got {0}",
args.Length);
}
if (args.Length > 2) {
throw PythonOps.TypeError(
"expected at most 2 arguments, got {0}",
args.Length);
}
if (!PythonOps.TryGetEnumerator(context, args[0], out e)) {
throw PythonOps.TypeError("argument 1 must be an iterator");
}
if (args.Length > 1)
dialectObj = args[1];
if (dialectObj is string && !dialects.ContainsKey((string)dialectObj))
throw MakeError("unknown dialect");
else if (dialectObj is string) {
dialect = dialects[(string)dialectObj];
dialectObj = dialect;
}
dialect = dialectObj != null ?
Dialect.Create(context, kwArgs, dialectObj) :
Dialect.Create(context, kwArgs);
return new Reader(context, e, dialect);
}
public static object writer(CodeContext/*!*/ context,
[ParamDictionary] IDictionary kwArgs,
params object[] args) {
object output_file = null;
object dialectObj = null;
Dialect dialect = null;
DialectRegistry dialects = GetDialects(context);
if (args.Length < 1) {
throw PythonOps.TypeError("expected at least 1 arguments, got {0}",
args.Length);
}
if (args.Length > 2) {
throw PythonOps.TypeError("expected at most 2 arguments, got {0}",
args.Length);
}
output_file = args[0];
if (args.Length > 1)
dialectObj = args[1];
if (dialectObj is string && !dialects.ContainsKey((string)dialectObj))
throw MakeError("unknown dialect");
else if (dialectObj is string) {
dialect = dialects[(string)dialectObj];
dialectObj = dialect;
}
dialect = dialectObj != null ?
Dialect.Create(context, kwArgs, dialectObj) :
Dialect.Create(context, kwArgs);
return new Writer(context, output_file, dialect);
}
[Documentation(@"CSV dialect
The Dialect type records CSV parsing and generation options.")]
[PythonType]
public class Dialect {
private string _delimiter = ",";
private string _escapechar = null;
private bool _skipinitialspace;
private bool _doublequote = true;
private bool _strict;
private int _quoting = QUOTE_MINIMAL;
private string _quotechar = "\"";
private string _lineterminator = "\r\n";
private static readonly string[] VALID_KWARGS = {
"dialect",
"delimiter",
"doublequote",
"escapechar",
"lineterminator",
"quotechar",
"quoting",
"skipinitialspace",
"strict"};
private Dialect() {
}
public static Dialect Create(CodeContext/*!*/ context,
[ParamDictionary] IDictionary kwArgs,
params object[] args) {
object dialect = null;
object delimiter = null;
object doublequote = null;
object escapechar = null;
object lineterminator = null;
object quotechar = null;
object quoting = null;
object skipinitialspace = null;
object strict = null;
DialectRegistry dialects = GetDialects(context);
if (args.Length > 0 && args[0] != null)
dialect = args[0];
if (dialect == null)
kwArgs.TryGetValue("dialect", out dialect);
kwArgs.TryGetValue("delimiter", out delimiter);
kwArgs.TryGetValue("doublequote", out doublequote);
kwArgs.TryGetValue("escapechar", out escapechar);
kwArgs.TryGetValue("lineterminator", out lineterminator);
kwArgs.TryGetValue("quotechar", out quotechar);
kwArgs.TryGetValue("quoting", out quoting);
kwArgs.TryGetValue("skipinitialspace", out skipinitialspace);
kwArgs.TryGetValue("strict", out strict);
if (dialect != null) {
if (dialect is string dialectName) {
if (dialects.TryGetValue(dialectName, out var value))
dialect = value;
else
throw MakeError("unknown dialect");
}
if (dialect is Dialect &&
delimiter == null &&
doublequote == null &&
escapechar == null &&
lineterminator == null &&
quotechar == null &&
quoting == null &&
skipinitialspace == null &&
strict == null) {
return dialect as Dialect;
}
}
Dialect result = dialect != null ?
new Dialect(context, kwArgs, dialect) :
new Dialect(context, kwArgs);
return result;
}
[SpecialName]
public void DeleteMember(CodeContext/*!*/ context, string name) {
if (string.Equals(name, "delimiter") ||
string.Equals(name, "skipinitialspace") ||
string.Equals(name, "doublequote")||
string.Equals(name, "strict") ||
string.Equals(name, "escapechar") ||
string.Equals(name, "lineterminator") ||
string.Equals(name, "quotechar") ||
string.Equals(name, "quoting")) {
throw PythonOps.AttributeError("attribute '{0}' of " +
"'_csv.Dialect' objects is not writable", name);
} else {
throw PythonOps.AttributeError("'_csv.Dialect' object " +
"has no attribute '{0}'", name);
}
}
[SpecialName]
public void SetMember(CodeContext/*!*/ context, string name, object value) {
if (string.Equals(name, "delimiter") ||
string.Equals(name, "skipinitialspace") ||
string.Equals(name, "doublequote") ||
string.Equals(name, "strict") ||
string.Equals(name, "escapechar") ||
string.Equals(name, "lineterminator") ||
string.Equals(name, "quotechar") ||
string.Equals(name, "quoting")) {
throw PythonOps.AttributeError("attribute '{0}' of " +
"'_csv.Dialect' objects is not writable", name);
} else {
throw PythonOps.AttributeError("'_csv.Dialect' object " +
"has no attribute '{0}'", name);
}
}
#region Parameter Setting
private static int SetInt(string name, object src, bool found, int @default) {
int result = @default;
if (found) {
if (!(src is int)) {
throw PythonOps.TypeError("\"{0}\" must be an integer",
name);
}
result = (int)src;
}
return result;
}
private static bool SetBool(string name, object src, bool found, bool @default) {
bool result = @default;
if (found)
result = PythonOps.IsTrue(src);
return result;
}
private static string SetChar(string name, object src, bool found, string @default) {
string result = @default;
if (found) {
if (src == null)
result = null;
else if (src is string) {
string source = src as string;
if (source.Length == 0)
result = null;
else if (source.Length != 1) {
throw PythonOps.TypeError(
"\"{0}\" must be a 1-character string",
name);
} else
result = source.Substring(0, 1);
} else {
throw PythonOps.TypeError(
"\"{0}\" must be string, not {1}", name, PythonOps.GetPythonTypeName(src));
}
}
return result;
}
private static string SetString(string name, object src, bool found, string @default) {
string result = @default;
if (found) {
if (src == null)
result = null;
else if (!(src is string)) {
throw PythonOps.TypeError(
"\"{0}\" must be a string", name);
} else {
result = src as string;
}
}
return result;
}
#endregion
public Dialect(CodeContext/*!*/ context,
[ParamDictionary] IDictionary kwArgs,
params object[] args) {
object dialect = null;
object delimiter = null;
object doublequote = null;
object escapechar = null;
object lineterminator = null;
object quotechar = null;
object quoting = null;
object skipinitialspace = null;
object strict = null;
Dictionary foundParams =
new Dictionary();
foreach (object key in kwArgs.Keys) {
if (Array.IndexOf(VALID_KWARGS, key) < 0) {
throw PythonOps.TypeError("'{0}' is an invalid " +
"keyword argument for this function", key);
}
}
if (args.Length > 0 && args[0] != null) {
dialect = args[0];
foundParams["dialect"] = true;
}
if (dialect == null) {
foundParams["dialect"] =
kwArgs.TryGetValue("dialect", out dialect);
}
foundParams["delimiter"] = kwArgs.TryGetValue("delimiter", out delimiter);
foundParams["doublequote"] = kwArgs.TryGetValue("doublequote", out doublequote);
foundParams["escapechar"] = kwArgs.TryGetValue("escapechar", out escapechar);
foundParams["lineterminator"] = kwArgs.TryGetValue("lineterminator", out lineterminator);
foundParams["quotechar"] = kwArgs.TryGetValue("quotechar", out quotechar);
foundParams["quoting"] = kwArgs.TryGetValue("quoting", out quoting);
foundParams["skipinitialspace"] = kwArgs.TryGetValue("skipinitialspace", out skipinitialspace);
foundParams["strict"] = kwArgs.TryGetValue("strict", out strict);
if (dialect != null) {
if (!foundParams["delimiter"] && delimiter == null)
foundParams["delimiter"] = PythonOps.TryGetBoundAttr(dialect, "delimiter", out delimiter);
if (!foundParams["doublequote"] && doublequote == null)
foundParams["doublequote"] = PythonOps.TryGetBoundAttr(dialect, "doublequote", out doublequote);
if (!foundParams["escapechar"] && escapechar == null)
foundParams["escapechar"] = PythonOps.TryGetBoundAttr(dialect, "escapechar", out escapechar);
if (!foundParams["lineterminator"] && lineterminator == null)
foundParams["lineterminator"] = PythonOps.TryGetBoundAttr(dialect, "lineterminator", out lineterminator);
if (!foundParams["quotechar"] && quotechar == null)
foundParams["quotechar"] = PythonOps.TryGetBoundAttr(dialect, "quotechar", out quotechar);
if (!foundParams["quoting"] && quoting == null)
foundParams["quoting"] = PythonOps.TryGetBoundAttr(dialect, "quoting", out quoting);
if (!foundParams["skipinitialspace"] && skipinitialspace == null)
foundParams["skipinitialspace"] = PythonOps.TryGetBoundAttr(dialect, "skipinitialspace", out skipinitialspace);
if (!foundParams["strict"] && strict == null)
foundParams["strict"] = PythonOps.TryGetBoundAttr(dialect, "strict", out strict);
}
_delimiter = SetChar("delimiter", delimiter,
foundParams["delimiter"], ",");
_doublequote = SetBool("doublequote", doublequote,
foundParams["doublequote"], true);
_escapechar = SetString("escapechar", escapechar,
foundParams["escapechar"], null);
_lineterminator = SetString("lineterminator",
lineterminator, foundParams["lineterminator"], "\r\n");
_quotechar = SetChar("quotechar", quotechar,
foundParams["quotechar"], "\"");
_quoting = SetInt("quoting", quoting,
foundParams["quoting"], QUOTE_MINIMAL);
_skipinitialspace = SetBool("skipinitialspace",
skipinitialspace, foundParams["skipinitialspace"], false);
_strict = SetBool("strict", strict, foundParams["strict"], false);
// validate options
if (_quoting < QUOTE_MINIMAL || _quoting > QUOTE_NONE)
throw PythonOps.TypeError("bad \"quoting\" value");
if (string.IsNullOrEmpty(_delimiter))
throw PythonOps.TypeError("\"delimiter\" must be a 1-character string");
if ((foundParams["quotechar"] && quotechar == null) && quoting == null)
_quoting = QUOTE_NONE;
if (_quoting != QUOTE_NONE && string.IsNullOrEmpty(_quotechar))
throw PythonOps.TypeError("quotechar must be set if quoting enabled");
if (_lineterminator == null)
throw PythonOps.TypeError("lineterminator must be set");
}
public string escapechar {
get { return _escapechar; }
}
public string delimiter {
get { return _delimiter; }
}
public bool skipinitialspace {
get { return _skipinitialspace; }
}
public bool doublequote {
get { return _doublequote; }
}
public string lineterminator {
get { return _lineterminator; }
}
public bool strict {
get { return _strict; }
}
public int quoting {
get { return _quoting; }
}
public string quotechar {
get { return _quotechar; }
}
}
[Documentation(@"CSV reader
Reader objects are responsible for reading and parsing tabular data
in CSV format.")]
[PythonType]
public class Reader : IEnumerable {
private IEnumerator _input_iter;
private Dialect _dialect;
private int _line_num;
private ReaderIterator _iterator;
public Reader(CodeContext/*!*/ context, IEnumerator input_iter,
Dialect dialect) {
_input_iter = input_iter;
_dialect = dialect;
_iterator = new ReaderIterator(context, this);
}
public object __next__() {
if (!_iterator.MoveNext())
throw PythonOps.StopIteration();
return _iterator.Current;
}
#region IEnumerable Members
public IEnumerator GetEnumerator() {
return _iterator;
}
private sealed class ReaderIterator : IEnumerator, IEnumerable {
private CodeContext _context;
private Reader _reader;
private PythonList _fields = new PythonList();
private bool _is_numeric_field;
private State _state = State.StartRecord;
private StringBuilder _field = new StringBuilder();
private IEnumerator _iterator;
private enum State {
StartRecord,
StartField,
EscapedChar,
InField,
InQuotedField,
EscapeInQuotedField,
QuoteInQuotedField,
EatCrNl
}
public ReaderIterator(CodeContext/*!*/ context, Reader reader) {
_context = context;
_reader = reader;
_iterator = _reader._input_iter;
}
#region IEnumerator Members
public object Current {
get { return new PythonList(_fields); }
}
public bool MoveNext() {
bool result = false;
Reset();
do {
object lineobj = null;
if (!_iterator.MoveNext()) {
// End of input OR exception
if (_field.Length > 0 || _state == State.InQuotedField) {
if (_reader._dialect.strict) {
throw MakeError("unexpected end of data");
} else {
ParseSaveField();
return true;
}
}
return false;
} else {
lineobj = _iterator.Current;
}
_reader._line_num++;
if (lineobj is char)
lineobj = lineobj.ToString();
if (!(lineobj is string)) {
throw MakeError("iterator should return strings, not "
+ PythonOps.GetPythonTypeName(lineobj) +
" (did you open the file in text mode?)");
}
string line = lineobj as string;
if (!string.IsNullOrEmpty(line)) {
for (int i = 0; i < line.Length; i++) {
char c = line[i];
if (c == '\0')
throw MakeError("line contains NULL byte");
ProcessChar(c);
}
// If we ended on an escaped newline, then we want to continue onto
// the nextline without processing the end of this one, as the newline
// is in the middle of the field.
if ((line.EndsWith('\n') || line.EndsWith('\r')) && _state == State.InField) {
continue;
}
}
ProcessChar('\0');
result = true;
} while (_state != State.StartRecord);
return result;
}
public void Reset() {
_state = State.StartRecord;
_fields.Clear();
_is_numeric_field = false;
_field.Clear();
}
#endregion
#region IEnumerable Members
public IEnumerator GetEnumerator() {
return this;
}
#endregion
private void ProcessChar(char c) {
Dialect dialect = _reader._dialect;
switch (_state) {
case State.StartRecord:
// start of record
if (c == '\0') {
// empty line, will return empty list
break;
} else if (c == '\n' || c == '\r') {
_state = State.EatCrNl;
break;
}
// normal character, handle as start of field
_state = State.StartField;
goto case State.StartField;
case State.StartField:
// expecting field
if (c == '\n' || c == '\r' || c == '\0') {
// save empty field - return [fields]
ParseSaveField();
_state = (c == '\0' ?
State.StartRecord : State.EatCrNl);
} else if (!string.IsNullOrEmpty(dialect.quotechar) &&
c == dialect.quotechar[0] &&
dialect.quoting != QUOTE_NONE) {
// start quoted field
_state = State.InQuotedField;
} else if (!string.IsNullOrEmpty(dialect.escapechar) &&
c == dialect.escapechar[0]) {
// possible escaped char
_state = State.EscapedChar;
} else if (c == ' ' && dialect.skipinitialspace) {
// ignore space at start of field
} else if (c == dialect.delimiter[0]) {
// save empty field
ParseSaveField();
} else {
// begin new unquoted field
if (dialect.quoting == QUOTE_NONNUMERIC)
_is_numeric_field = true;
ParseAddChar(c);
_state = State.InField;
}
break;
case State.EscapedChar:
if (c == '\0')
c = '\n';
ParseAddChar(c);
_state = State.InField;
break;
case State.InField:
// in unquoted field
if (c == '\n' || c == '\r' || c == '\0') {
// end of line, return [fields]
ParseSaveField();
_state = (c == '\0' ? State.StartRecord : State.EatCrNl);
} else if (!string.IsNullOrEmpty(dialect.escapechar) &&
c == dialect.escapechar[0]) {
// possible escaped character
_state = State.EscapedChar;
} else if (c == dialect.delimiter[0]) {
// save field - wait for new field
ParseSaveField();
_state = State.StartField;
} else {
// normal character - save in field
ParseAddChar(c);
}
break;
case State.InQuotedField:
// in quoted field
if (c == '\0') {
// ignore null character
} else if (!string.IsNullOrEmpty(dialect.escapechar) &&
c == dialect.escapechar[0]) {
// possible escape character
_state = State.EscapeInQuotedField;
} else if (!string.IsNullOrEmpty(dialect.quotechar) &&
c == dialect.quotechar[0] &&
dialect.quoting != QUOTE_NONE) {
if (dialect.doublequote) {
// doublequote; " represented by ""
_state = State.QuoteInQuotedField;
} else {
// end of quote part of field
_state = State.InField;
}
} else {
// normal character - save in field
ParseAddChar(c);
}
break;
case State.EscapeInQuotedField:
if (c == '\0')
c = '\n';
ParseAddChar(c);
_state = State.InQuotedField;
break;
case State.QuoteInQuotedField:
// doublequote - seen a quote in a quoted field
if (dialect.quoting != QUOTE_NONE &&
c == dialect.quotechar[0]) {
// save "" as "
ParseAddChar(c);
_state = State.InQuotedField;
} else if (c == dialect.delimiter[0]) {
// save field - wait for new field
ParseSaveField();
_state = State.StartField;
} else if (c == '\n' || c == '\r' || c == '\0') {
// end of line - return [fields]
ParseSaveField();
_state = (c == '\0' ? State.StartRecord : State.EatCrNl);
} else if (!dialect.strict) {
ParseAddChar(c);
_state = State.InField;
} else {
// illegal!
throw MakeError("'{0}' expected after '{1}'",
dialect.delimiter, dialect.quotechar);
}
break;
case State.EatCrNl:
if (c == '\n' || c == '\r') {
// eat the CR NL
} else if (c == '\0')
_state = State.StartRecord;
else {
throw MakeError("new-line character seen " +
"in unquoted field - do you need to open" +
" the file in universal-newline mode?");
}
break;
}
}
private void ParseAddChar(char c) {
int field_size_limit = GetFieldSizeLimit(_context);
if (_field.Length >= field_size_limit) {
throw MakeError(
string.Format("field larger than field " +
"limit ({0})", field_size_limit));
}
_field.Append(c);
}
private void ParseSaveField() {
string field = _field.ToString();
if (_is_numeric_field) {
_is_numeric_field = false;
if (double.TryParse(field, NumberStyles.Float, CultureInfo.InvariantCulture, out double tmp)) {
if (field.Contains("."))
_fields.Add(tmp);
else
_fields.Add((int)tmp);
} else {
throw PythonOps.ValueError(
"invalid literal for float(): {0}", field);
}
} else
_fields.Add(field);
_field.Clear();
}
}
#endregion
public object dialect {
get { return _dialect; }
}
public int line_num {
get { return _line_num; }
}
}
[Documentation(@"CSV writer
Writer objects are responsible for generating tabular data
in CSV format from sequence input.")]
[PythonType]
public class Writer {
private Dialect _dialect;
private object _writeline;
private StringBuilder _rec = new StringBuilder();
private int _num_fields;
public Writer(CodeContext/*!*/ context, object output_file,
Dialect dialect) {
_dialect = dialect;
if (!PythonOps.TryGetBoundAttr(
output_file, "write", out _writeline) ||
_writeline == null ||
!PythonOps.IsCallable(context, _writeline)) {
throw PythonOps.TypeError(
"argument 1 must have a \"write\" method");
}
}
public object dialect {
get { return _dialect; }
}
[Documentation(@"writerow(sequence)
Construct and write a CSV record from a sequence of fields. Non-string
elements will be converted to string.")]
public void writerow(CodeContext/*!*/ context, object sequence) {
IEnumerator e;
if (!PythonOps.TryGetEnumerator(context, sequence, out e))
throw MakeError($"iterable expected, not {PythonOps.GetPythonTypeName(sequence)}");
// join all fields in internal buffer
JoinReset();
while (e.MoveNext()) {
object field = e.Current;
var quoted = _dialect.quoting switch {
QUOTE_NONNUMERIC => !(PythonOps.CheckingConvertToFloat(field) ||
PythonOps.CheckingConvertToInt(field)),
QUOTE_ALL => true,
_ => false,
};
if (field is string) {
JoinAppend((string)field, quoted);
} else if (field is double) {
JoinAppend(DoubleOps.__repr__(context, (double)field), quoted);
} else if (field is float) {
JoinAppend(SingleOps.__repr__(context, (float)field), quoted);
} else if (field is null) {
JoinAppend(string.Empty, quoted);
} else {
JoinAppend(PythonOps.ToString(context, field), quoted);
}
}
if (_num_fields > 0 && _rec.Length == 0) {
if (_dialect.quoting == QUOTE_NONE) {
throw MakeError("single empty field record must be quoted");
}
_num_fields--;
JoinAppend(string.Empty, quoted: true);
}
_rec.Append(_dialect.lineterminator);
PythonOps.CallWithContext(context, _writeline, _rec.ToString());
}
[Documentation(@"writerows(sequence of sequences)
Construct and write a series of sequences to a csv file. Non-string
elements will be converted to string.")]
public void writerows(CodeContext/*!*/ context, object sequence) {
IEnumerator e = null;
if (!PythonOps.TryGetEnumerator(context, sequence, out e)) {
throw PythonOps.TypeError(
"writerows() argument must be iterable");
}
while (e.MoveNext()) {
writerow(context, e.Current);
}
}
private void JoinReset() {
_num_fields = 0;
_rec.Clear();
}
private void JoinAppend(string field, bool quoted) {
// if this is not the first field, we need a field separator
if (_num_fields > 0)
_rec.Append(_dialect.delimiter);
List need_escape = new List();
if (_dialect.quoting == QUOTE_NONE) {
if (!string.IsNullOrEmpty(_dialect.escapechar))
need_escape.Add(_dialect.escapechar[0]);
need_escape.AddRange(_dialect.lineterminator.ToCharArray());
if (!string.IsNullOrEmpty(_dialect.delimiter))
need_escape.Add(_dialect.delimiter[0]);
if (!string.IsNullOrEmpty(_dialect.quotechar))
need_escape.Add(_dialect.quotechar[0]);
} else {
List temp = new List();
if (!string.IsNullOrEmpty(_dialect.escapechar))
temp.Add(_dialect.escapechar[0]);
temp.AddRange(_dialect.lineterminator.ToCharArray());
if (!string.IsNullOrEmpty(_dialect.delimiter))
temp.Add(_dialect.delimiter[0]);
if (field.IndexOfAny(temp.ToArray()) >= 0)
quoted = true;
need_escape.Clear();
if (!string.IsNullOrEmpty(_dialect.quotechar) && field.Contains(_dialect.quotechar)) {
if (_dialect.doublequote) {
field = field.Replace(_dialect.quotechar,
_dialect.quotechar + _dialect.quotechar);
quoted = true;
} else {
need_escape.Add(_dialect.quotechar[0]);
}
}
}
foreach (char c in need_escape) {
if (field.IndexOf(c) >= 0) {
if (string.IsNullOrEmpty(_dialect.escapechar))
throw MakeError("need to escape, but no escapechar set");
field = field.Replace(c.ToString(), _dialect.escapechar + c);
}
}
if (quoted)
field = _dialect.quotechar + field + _dialect.quotechar;
_rec.Append(field);
_num_fields++;
}
}
public static PythonType Error;
internal static Exception MakeError(params object[] args) {
return PythonOps.CreateThrowable(Error, args);
}
private static void InitModuleExceptions(PythonContext context,
PythonDictionary dict) {
Error = context.EnsureModuleException("csv.Error",
PythonExceptions.Exception, dict, "Error", "_csv");
}
}
}