# -*- coding: utf-8 -*-
# datafile/asciifile.py
from __future__ import absolute_import # PEP328
import codecs
from abc import ABCMeta, abstractmethod
from numpy import array as np_array
from datafile import DataFile
from utils.error import FileError
from utils import isString, isWindows, mcopen
from future.utils import with_metaclass
[docs]class AsciiFile(with_metaclass(ABCMeta, DataFile)):
"""A generic ascii data file."""
valueFormat = "{0: 14.6E}" # format of data values for ascii export
separator = " "
newline="\n"
# helpers for writing
@classmethod
@classmethod
@classmethod
@staticmethod
def _write(filename, mode, asciiData):
with mcopen(filename, mode) as fd:
fd.write(asciiData)
@classmethod
[docs] def writeFile(cls, filename, data, **kwargs):
cls._write(filename, 'w', cls.formatData(data, **kwargs))
@classmethod
[docs] def appendFile(cls, filename, data, **kwargs):
"""like writeFile but appends data to an existing file"""
cls._write(filename, 'a', cls.formatData(data, **kwargs))
@classmethod
def _formatHeader(cls, header):
if not isString(header):
header = cls.separator.join(header)
header += cls.newline
return header
@classmethod
@classmethod
# helpers for reading
[docs] def readTuple(self, fields, dataType = float, **kwargs):
"""Converts each field to the requested datatype.
Raises an error if it is incompatible,
the line is skipped in that case."""
try:
# just converted to tuple
return tuple((dataType(f) for f in fields))
except:
raise ValueError
[docs] def readFile(self, **kwargs):
asciiLines = None
try:
with mcopen(self.filename, 'r') as fd:
asciiLines = fd.readlines()
except UnicodeDecodeError:
with mcopen(self.filename, 'r', encoding = 'latin1') as fd:
asciiLines = fd.readlines()
self.parseLines(asciiLines, **kwargs)
@abstractmethod
[docs] def parseLines(self, asciiLines, **kwargs):
"""Parses lines of an ASCII file in order to extract a single array
of numbers. Reimplement this in subclasses for different behaviour.
"""
raise NotImplementedError
[docs] def readArray(self, asciiLines, dataType = float,
startLine = 0, endLine = None, **kwargs):
"""Reads a numpy.array from a specified segment (startLine, endLine)
of a line buffer given by asciiLines. Stops at lines incompatible
to previous lines read due to different number of fields or
incompatible data type. Returns the last line successfully parsed and
the populated numpy.array.
"""
recordList = []
for linenr, line in enumerate(asciiLines[startLine:endLine]):
linenr += startLine
# strip trailing white space, replace decimal operators
# eventually, split data fields
# we read floating point numbers only
if '.' in line:
line = line.replace(","," ") # comma separated
else: # no points in line
# convert D/A/CH decimal separator to point
line = line.replace(",",".")
fields = (line.strip()
.replace(";"," ")
.split())
record = None
try:
# may raise exception
record = self.readTuple(fields, lineNumber = linenr,
dataType = dataType)
if not len(record): # ignore empty tuples
record = None
except ValueError:
pass # ignore it for now, record == None
if record is None: # on parse failure of current line
if not len(recordList):
continue # if still no compatible data found
else:
break # data listing ends here
elif len(recordList) and len(recordList[-1]) != len(record):
break # do not append records of different size
recordList.append(record)
endLine = linenr - 1 # could not read last line
recordCount = len(recordList)
if recordCount <= 0:
raise FileError("No data columns found!", self.filename)
return endLine, np_array(recordList, dataType)
# vim: set ts=4 sts=4 sw=4 tw=0: