source: nappy/trunk/nappy/na_file/na_file.py @ 3628

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/nappy/trunk/nappy/na_file/na_file.py@3628
Revision 3628, 14.4 KB checked in by astephen, 12 years ago (diff)

Fixing defaults for delimiter and float format throughout.

Line 
1#   Copyright (C) 2004 CCLRC & NERC( Natural Environment Research Council ).
2#   This software may be distributed under the terms of the
3#   Q Public License, version 1.0 or later. http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt
4
5"""
6naFile.py
7=========
8
9A containter module for the mixin base class NAFile that is subclassed
10for individual FFIs. Each FFI class is held in an individual file.
11
12"""
13   
14# 08/05/04 updated by selatham for bug fixes and new write methods
15
16# Imports from python standard library
17import sys
18import time
19import re
20import StringIO
21
22# Imports from nappy package
23import nappy.na_file.na_core
24import nappy.utils.text_parser
25import nappy.utils.common_utils
26import nappy.na_error
27
28default_delimiter = nappy.utils.getDefault("default_delimiter")
29default_float_format = nappy.utils.getDefault("default_float_format")
30getAnnotation = nappy.utils.common_utils.getAnnotation
31wrapLine = nappy.utils.common_utils.annotateLine
32wrapLines = nappy.utils.common_utils.annotateLines
33
34
35class NAFile(nappy.na_file.na_core.NACore):
36
37    """
38    NAFile class is a sub-class of NACore abstract classes.
39    NAFile is also an abstract class and should not be called directly.
40   
41    NAFile holds all the methods that are common to either all or more than
42    one NASA Ames FFI class. These methods set up the main read and write
43    functionality for the NASA Ames format.
44
45    When a sub-class of NAFile is called with a read ('r' - default)
46    mode the header in the file is automatically read. To read the data
47    section the user must call the 'readData' method.
48     
49    When a sub-class of NAFile is called with the write ('w') mode
50    then the file is opened but nothing is written. The user must then
51    send an 'na_dict' object to the 'write' method to write the output.
52    The output file is then flushed to ensure the data is written even if
53    the user forgets to close it.
54    """
55
56    def __init__(self, filename, mode="r", na_dict={}): 
57        """
58        Initialization of class, decides if user wishes to read or write
59        NASA Ames file.
60        """
61        nappy.na_file.na_core.NACore.__init__(self)
62        self.filename = filename
63        self._open(mode)
64        self.mode = mode
65        self.na_dict = na_dict
66
67        if self.mode == "r":
68            self._normalized_X = True
69            self.readHeader()
70        elif self.mode == "w":
71            # Self flag to check if data written
72            self.data_written = False
73        else:
74            raise "Unknown file mode '%s'." % self.mode
75
76       
77    def _open(self, mode):
78        "Wrapper to builtin open file function."
79        self.file = open(self.filename, mode)
80        self.is_open = True
81
82    def write(self, delimiter=default_delimiter, float_format=default_float_format,
83              annotation=False, no_header=False):
84        """
85        Writes an na_dict to the file and then flushes it to ensure data not
86        being buffered.
87        If annotation is True then add annotation column to left of file.
88        If no_header is True then suppress writing the header and only write the data section.
89        """ 
90        self.delimiter = delimiter
91        self.float_format = float_format
92        self.format = float_format + delimiter
93        self.annotation = annotation
94       
95        # Raise errors if dangerous behaviour
96        if self.mode != "w":
97            raise Exception("WARNING: Cannot write to read-only file. Can only write to NA file object when mode='w'.")
98
99        if self.data_written == True:
100            raise Exception("WARNING: Cannot write multiple NASA Ames dictionaries to a single file. Please open a new NASA Ames file instance to write new data to.")
101
102        if self.is_open == False:
103            raise Exception("WARNING: NASA Ames file instance is closed and cannot be written to.")
104   
105        # Parse na_dict then write header and data
106        self._parseDictionary()
107        self.header = StringIO.StringIO()
108
109        if no_header == False:
110            self.writeHeader()
111
112        self.writeData()
113        self.file.flush()
114       
115        # Set flag to make sure cannot try and write more data
116        self.data_written = True
117         
118    def close(self):
119        "Wrapper to builtin close file function."
120        self.file.close()
121        self.is_open = False
122
123    def _parseDictionary(self):
124        """
125        Parser for the optional na_dict argument containing a dictionary
126        of NASA Ames internal variables. These are saved as instance attributes
127        with the name used in the NASA Ames documentation.
128        """
129        for i in self.na_dict.keys():
130            setattr(self, i, self.na_dict[i])
131
132    def _readTopLine(self):
133        """
134        Reads number of header lines and File Format Index from top line.
135        Also assigns a value to NIV for the number of independent variables
136        based on the first character in the FFI.
137
138        Returns NLHEAD and FFI in a tuple.
139        """
140        (self.NLHEAD, self.FFI) = nappy.utils.text_parser.readItemsFromLine(self.file.readline(), 2, int)
141        self.NIV = int(self.FFI/1000)
142        return (self.NLHEAD, self.FFI)
143
144    def _readLines(self, nlines):
145        "Reads nlines lines from a file and returns them in a list."
146        lines = []
147        for i in range(nlines):
148            lines.append(self.file.readline().strip())
149        return lines
150
151    def _checkForBlankLines(self, datalines):
152        """
153        Searches for empty lines in the middle of the data section and raises
154        as error if found. It ignores empty lines at the end of the file but
155        strips them out before returning a list of lines for reading.
156        """
157        empties = None
158        count = 0
159        rtlines = []
160        for line in datalines:
161            if line.strip() == "":
162                empties = 1
163            else:
164                if empties == 1:   # If data line found after empty line then raise
165                    raise Exception("Empty line found in data section at line: " + `count`)
166                else:
167                    rtlines.append(line)
168            count = count + 1
169        return rtlines
170
171
172    def _readCommonHeader(self):
173        """
174        Reads the header section common to all NASA Ames files.
175        """
176        self._readTopLine()
177        self.ONAME = nappy.utils.text_parser.readItemFromLine(self.file.readline(), str)
178        self.ORG = nappy.utils.text_parser.readItemFromLine(self.file.readline(), str)
179        self.SNAME = nappy.utils.text_parser.readItemFromLine(self.file.readline(), str)
180        self.MNAME = nappy.utils.text_parser.readItemFromLine(self.file.readline(), str)
181        (self.IVOL, self.NVOL) = nappy.utils.text_parser.readItemsFromLine(self.file.readline(), 2, int)
182        dates = nappy.utils.text_parser.readItemsFromLine(self.file.readline(), 6, int)
183        (self.DATE, self.RDATE) = (dates[:3], dates[3:])
184
185    def _writeCommonHeader(self):
186        """
187        Writes the header section common to all NASA Ames files.
188        """
189        #Line 1 if often overwritten at _fixHeaderLength
190        self.header.write(wrapLine("NLHEAD_FFI", self.annotation, self.delimiter, "%d%s%d\n" % (self.NLHEAD, self.delimiter, self.FFI)))
191        self.header.write(getAnnotation("ONAME", self.annotation, delimiter = self.delimiter) + self.ONAME + "\n")
192        self.header.write(getAnnotation("ORG", self.annotation, delimiter = self.delimiter) + self.ORG + "\n")
193        self.header.write(getAnnotation("SNAME", self.annotation, delimiter = self.delimiter) + self.SNAME + "\n")
194        self.header.write(getAnnotation("MNAME", self.annotation, delimiter = self.delimiter) + self.MNAME + "\n")
195        self.header.write(wrapLine("IVOL_NVOL", self.annotation, self.delimiter, "%d%s%d\n" % (self.IVOL, self.delimiter, self.NVOL)))
196        line = "%d %d %d%s%d %d %d\n" % (self.DATE[0], self.DATE[1], self.DATE[2], self.delimiter, self.RDATE[0], self.RDATE[1], self.RDATE[2])
197        self.header.write(wrapLine("DATE_RDATE", self.annotation, self.delimiter, line))
198
199    def _readVariablesHeaderSection(self):
200        """
201        Reads the variables section of the header.
202        Assumes we are at the right point in the file.
203        """
204        self.NV = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
205        self.VSCAL = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NV, float)
206        self.VMISS = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NV, float)
207        self.VNAME = nappy.utils.text_parser.readItemsFromLines(self._readLines(self.NV), self.NV, str)
208
209    def _writeVariablesHeaderSection(self):
210        """
211        Writes the variables section of the header.
212        Assumes we are at the right point in the file.
213        """
214        self.header.write(wrapLine("NV", self.annotation, self.delimiter, "%d\n" % self.NV))
215        self.header.write(wrapLine("VSCAL", self.annotation, self.delimiter, (("%s" + self.delimiter) * (self.NV - 1) + "%s\n") % tuple(self.VSCAL)))
216        self.header.write(wrapLine("VMISS", self.annotation, self.delimiter, (("%s" + self.delimiter) * (self.NV - 1) + "%s\n") % tuple(self.VMISS)))
217        self.header.write(wrapLines("VNAME", self.annotation, self.delimiter, "%s\n" * self.NV % tuple(self.VNAME)))
218
219    def _readAuxVariablesHeaderSection(self):
220        """
221        Reads the auxiliary variables section of the header.
222        Assumes we are at the right point in the file.
223        """
224        self.NAUXV = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
225        if self.NAUXV > 0:       
226            self.ASCAL = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NAUXV, float)
227            self.AMISS = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NAUXV, float)
228            self.ANAME = nappy.utils.text_parser.readItemsFromLines(self._readLines(self.NAUXV), self.NAUXV, str)
229
230    def _writeAuxVariablesHeaderSection(self):
231        """
232        Writes the auxiliary variables section of the header.
233        Assumes we are at the right point in the file.
234        """
235        self.header.write(wrapLine("NAUXV", self.annotation, self.delimiter, "%d\n" % self.NAUXV))
236        if self.NAUXV > 0:
237            line = (("%s" + self.delimiter) * (self.NAUXV - 1) + "%s\n")  % tuple(self.ASCAL)
238            self.header.write(wrapLine("ASCAL", self.annotation, self.delimiter, line))
239            line = (("%s" + self.delimiter) * (self.NAUXV - 1) + "%s\n")  % tuple(self.AMISS)
240            self.header.write(wrapLine("AMISS", self.annotation, self.delimiter, line))
241            line = "%s\n" * self.NAUXV % tuple(self.ANAME)
242            self.header.write(wrapLines("ANAME", self.annotation, self.delimiter, line))
243
244    def _readCharAuxVariablesHeaderSection(self):
245        """
246        Reads the character-encoded auxiliary variables section of the header.
247        Assumes we are at the right point in the file.
248        """
249        self.NAUXV = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
250        self.NAUXC = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
251        nonCharAuxVars = self.NAUXV - self.NAUXC
252        if self.NAUXV > 0:
253            self.ASCAL = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, nonCharAuxVars, float)
254            self.AMISS = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, nonCharAuxVars, float)
255            self.LENA = nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NAUXC, int)
256            for i in range(nonCharAuxVars):
257                self.LENA.insert(0, None)
258            self.AMISS = self.AMISS + nappy.utils.text_parser.readItemsFromUnknownLines(self.file, self.NAUXC, str)   
259            self.ANAME = nappy.utils.text_parser.readItemsFromLines(self._readLines(self.NAUXV), self.NAUXV, str)       
260           
261    def _readComments(self):
262        """
263        Reads the special and normal comments sections.
264        Assumes we are at the right point in the file.
265        """       
266        self.NSCOML = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
267        self._readSpecialComments()
268        self.NNCOML = nappy.utils.text_parser.readItemFromLine(self.file.readline(), int)
269        self._readNormalComments()
270
271    def _writeComments(self):
272        """
273        Writes the special and normal comments sections.
274        Assumes we are at the right point in the file.
275        """
276        self.header.write(wrapLine("NSCOML", self.annotation, self.delimiter, "%d\n" % self.NSCOML))
277        self.header.write(wrapLines("SCOM", self.annotation, self.delimiter, "%s\n" * self.NSCOML % tuple(self.SCOM)))
278        self.header.write(wrapLine("NNCOML", self.annotation, self.delimiter, "%d\n" % self.NNCOML))
279        self.header.write(wrapLines("NCOM", self.annotation, self.delimiter, "%s\n" * self.NNCOML % tuple(self.NCOM)))
280
281    def _fixHeaderLength(self):
282        """
283        Takes the self.header StringIO object and counts the number of lines
284        and corrects the NLHEAD value in the header line.
285        Resets to start of self.header.
286        """
287        self.header.seek(0)
288        lines = self.header.readlines()
289        headlength = len(lines)
290        lines[0] = wrapLine("NLHEAD_FFI", self.annotation, self.delimiter, "%d%s%d\n" % (headlength, self.delimiter, self.FFI))
291        self.header = StringIO.StringIO("".join(lines))
292        self.header.seek(0) 
293
294    def _readSpecialComments(self):
295        """
296        Reads the special comments section.       
297        Assumes that we are at the right point in the file and that NSCOML
298        variable is known.
299        """
300        self.SCOM = self._readLines(self.NSCOML)
301        return self.SCOM
302
303    def _readNormalComments(self):
304        """
305        Reads the normal comments section.       
306        Assumes that we are at the right point in the file and that NNCOML
307        variable is known.
308        """
309        self.NCOM = self._readLines(self.NNCOML)
310        return self.NCOM
311
312    def readData(self):
313        """
314        Reads the data section of the file. This method actually calls a number
315        of FFI specific methods to setup the data arrays (lists of lists) and
316        read the various data sections.
317
318        This method can be called directly by the user.
319        """
320        self._setupArrays()
321        datalines = open(self.filename).readlines()[self.NLHEAD:]
322        datalines = self._checkForBlankLines(datalines)
323
324        # Set up loop over unbounded indpendent variable
325        m = 0   # Unbounded independent variable mark       
326        while len(datalines) > 0:
327            datalines = self._readData1(datalines, m)
328            datalines = self._readData2(datalines, m)
329            m = m + 1
330           
Note: See TracBrowser for help on using the repository browser.