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

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

Added comparison to unit tests and set annotation=False in na_file.py write method.

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