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

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

Added no_header option to allow user to specify writing the body only.

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