"""Contains the classes that are used to write to and read from restart files. Copyright (C) 2013, Joshua More and Michele Ceriotti This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . The classes defined in this module define the base functions which parse the data in the restart files. Each restart object defined has a fields and an attributes dictionary, which are filled with the tags and attributes that are allowed to be present, along with their default values and data type. These are then filled with the data from the xml file when the program is initialised, and are filled by the values calculated in the program which are then output to the checkpoint file when a restart file is required. Also deals with checking for user input errors, of the form of misspelt tags, bad data types, and failure to input required fields. Classes: Input: Base input class. InputAttribute: Input class for attribute data. InputValue: Input class for scalar objects. InputArray: Input class for arrays. input_default: Class used to create mutable objects dynamically. """ __all__ = ['Input', 'InputValue', 'InputAttribute', 'InputArray', 'input_default'] import numpy as np from copy import copy from ipi.utils.io.io_xml import * from ipi.utils.units import unit_to_internal, unit_to_user class input_default(object): """Contains information required to dynamically create objects Used so that we can define mutable default input values to various tags without the usual trouble with having a class object that is also mutable, namely that all members of that class share the same mutable object, so that changing it for one instance of that class changes it for all others. It does this by not holding the mutable default value, but instead the information to create it, so that each instance of an input class can have a separate instance of the default value. Attributes: type: Either a class type or function call from which to create the default object. args: A tuple giving positional arguments to be passed to the function. kwargs: A dictionary giving key word arguments to be passed to the function. """ def __init__(self, factory, args = None, kwargs = None): """Initialises input_default. Args: type: The class or function to be used to create the default object. args: A tuple giving the arguments to be used to initialise the default value. kwargs: A dictionary giving the key word arguments to be used to initialise the default value. """ if args is None: args = () if kwargs is None: kwargs = {} # a default will be generated by factory(*args, **kwargs) # *args unpacks the tuple, and is used for positional arguments # **kwargs unpacks the dictionary, and is used for keyword arguments self.factory = factory self.args = args self.kwargs = kwargs class Input(object): """Base class for input handling. Has the generic methods for dealing with the xml input file. Parses the input data, outputs the output data, and deals with storing and returning the data obtained during the simulation for the restart files. Attributes: fields: A dictionary holding the possible tags contained within the tags for this restart object, which are then turned into the objects held by the object given by this restart object. The dictionary is of the form: {"tag name": ( Input_object, {"default": default value, "dtype": data type, "options": list of available options, "help": help string, "dimension": dimensionality of data}), ... }. dynamic: A dictionary holding the possible tags contained within the tags for this restart object, which are then turned into the objects held by the object given by this restart object. These are used for tags that can be specified more than once. The dictionary is of the form: {"tag name": ( Input_object, {"default": default value, "dtype": data type, "options": list of available options, "help": help string, "dimension": dimensionality of data}), ... }. attribs: A dictionary holding the attribute data for the tag for this restart object. The dictionary is of the form: {"attribute name": ( Input_object, {"default": default value, "dtype": data type, "options": list of available options, "help": help string, "dimension": dimensionality of data}), ... }. extra: A list of tuples ( "name", Input_object ) that may be used to extend the capabilities of the class, i.e. to hold several instances of a field with the same name, or to hold variable numbers of elements. default_help: The default help string. _help: The help string of the object. Defaults to default_help. _default: Optional default value. _optional: A bool giving whether the field is a required field. _explicit: A bool giving whether the field has been specified by the user. _text: All text written between the tags of the object. _label: A label to be used to identify the class in the latex user manual. _defwrite: The string which would be output if the class has its default value. """ fields = {} attribs = {} dynamic = {} default_help = "Generic input value" default_label = "" #used as a way to reference a particular class using #hyperlinks def __init__(self, help=None, default=None): """Initialises Input. Automatically adds all the fields and attribs names to the input object's dictionary, then initialises all the appropriate input objects as the corresponding values. Args: help: A help string. default: A default value. """ # list of extended (dynamic) fields self.extra = [] if help is None: self._help = self.default_help else: self._help = help if isinstance(default,input_default): #creates default dynamically if a suitable template is defined. self._default = default.factory(*default.args, **default.kwargs) else: self._default = default self._optional = not (self._default is None) self._label = self.default_label #For each tag name in the fields and attribs dictionaries, #creates and object of the type given, expanding the dictionary to give #the arguments of the __init__() function, then adds it to the input #object's dictionary. for f, v in self.fields.iteritems(): self.__dict__[f] = v[0](**v[1]) for a, v in self.attribs.iteritems(): self.__dict__[a] = v[0](**v[1]) self.set_default() self._text = "" # stores what we would write out if the default was set self._defwrite = "" if not self._default is None: self._defwrite = self.write(name="%%NAME%%") def set_default(self): """Sets the default value of the object.""" if not self._default is None: self.store(self._default) elif not hasattr(self, 'value'): self.value = None #Makes sure we don't get exceptions when we #look for self.value self._explicit = False #Since the value was not set by the user def store(self, value=None): """Dummy function for storing data.""" self._explicit = True pass def fetch(self): """Dummy function to retrieve data.""" self.check() pass def check(self): """Base function to check for input errors. Raises: ValueError: Raised if the user does not specify a required field. """ if not (self._explicit or self._optional): raise ValueError("Uninitialized Input value of type " + type(self).__name__) def extend(self, name, xml): """ Dynamically add elements to the 'extra' list. Picks from one of the templates in the self.dynamic dictionary, then parses. Args: name: The tag name of the dynamically stored tag. xml: The xml_node object used to parse the data stored in the tags. """ newfield = self.dynamic[name][0](**self.dynamic[name][1]) newfield.parse(xml) self.extra.append((name,newfield)) def write(self, name="", indent="", text="\n"): """Writes data in xml file format. Writes the tag, attributes, data and closing tag appropriate to the particular fields and attribs data. Writes in a recursive manner, so that objects contained in the fields dictionary have their write function called, so that their tags are written between the start and end tags of this object, as is required for the xml format. This also adds an indent to the lower levels of the xml heirarchy, so that it is easy to see which tags contain other tags. Args: name: An optional string giving the tag name. Defaults to "". indent: An optional string giving the string to be added to the start of the line, so usually a number of tabs. Defaults to "". text: Additional text to be output between the tags. Returns: A string giving all the data contained in the fields and attribs dictionaries, in the appropriate xml format. """ rstr = indent + "<" + name; for a in self.attribs: # only write out attributes that are not defaults # have a very simple way to check whether they actually add something: # we compare with the string that would be output if the argument was set # to its default defstr = self.__dict__[a]._defwrite.replace("%%NAME%%",a) outstr = self.__dict__[a].write(name=a) if outstr != defstr: rstr += " " + outstr rstr += ">" rstr += text for f in self.fields: #only write out fields that are not defaults defstr = self.__dict__[f]._defwrite.replace("%%NAME%%",f) if defstr != self.__dict__[f].write(f): # here we must compute the write string twice not to be confused by indents. rstr += self.__dict__[f].write(f, " " + indent) for (f,v) in self.extra: # also write out extended (dynamic) fields if present rstr += v.write(f, " " + indent) if text.find('\n') >= 0: rstr += indent + "\n" else: rstr += "\n" return rstr def parse(self, xml=None, text=""): """Parses an xml file. Uses the xml_node class defined in io_xml to read all the information contained within the root tags, and uses it to give values for the attribs and fields data recursively. It does this by giving all the data between the appropriate field tag to the appropriate field restart object as a string, and the appropriate attribute data to the appropriate attribs restart object as a string. These data are then parsed by these objects until all the information is read, or an input error is found. Args: xml: An xml_node object containing all the data for the parent tag. text: The data held between the start and end tags. Raises: NameError: Raised if one of the tags in the xml input file is incorrect. ValueError: Raised if the user does not specify a required field. """ # before starting, sets everything to its default -- if a default is set! for a in self.attribs: self.__dict__[a].set_default() for f in self.fields: self.__dict__[f].set_default() self.extra = [] self._explicit = True if xml is None: self._text = text else: for a, v in xml.attribs.iteritems(): if a in self.attribs: self.__dict__[a].parse(text=v) elif a == "_text": pass else: raise NameError("Attribute name '" + a + "' is not a recognized property of '" + xml.name + "' objects") for (f, v) in xml.fields: #reads all field and dynamic data. if f in self.fields: self.__dict__[f].parse(xml=v) elif f == "_text": self._text = v elif f in self.dynamic: self.extend(f, v) else: raise NameError("Tag name '" + f + "' is not a recognized property of '" + xml.name + "' objects") #checks for missing arguments. for a in self.attribs: va = self.__dict__[a] if not (va._explicit or va._optional): raise ValueError("Attribute name '" + a + "' is mandatory and was not found in the input for the property " + xml.name) for f in self.fields: vf = self.__dict__[f] if not (vf._explicit or vf._optional): raise ValueError("Field name '" + f + "' is mandatory and was not found in the input for the property " + xml.name) def detail_str(self): """Prints out the supplementary information about a particular input class. Used to print out the dimensions, default value, possible options and data type of an input value to the LaTeX helf file. """ xstr = "" if hasattr(self, '_dimension') and self._dimension != "undefined": #gives dimension xstr += "dimension: " + self._dimension + "; " if self._default != None and issubclass(self.__class__, InputAttribute): #We only print out the default if it has a well defined value. #For classes such as InputCell, self._default is not the value, #instead it is an object that is stored to give the default value in #self.value. For this reason we print out self.value at this stage, #and not self._default xstr += "default: " + self.pprint(self.value) + "; " if issubclass(self.__class__, InputAttribute): #if possible, prints out the type of data that is being used xstr += "data type: " + self.type_print(self.type) + "; " if hasattr(self, "_valid"): if self._valid is not None: xstr += "options: " #prints out valid options, if for option in self._valid: #required. xstr += "`" + str(option) + "', " xstr = xstr.rstrip(", ") xstr += "; " return xstr def help_latex(self, name="", level=0, stop_level=None, standalone=True): """Function to generate a LaTeX formatted help file. Args: name: Name of the tag that has to be written out. level: Current level of the hierarchy being considered. stop_level: The depth to which information will be given. If not given, will give all information. standalone: A boolean giving whether the latex file produced will be a stand-alone document, or will be intended as a section of a larger document with cross-references between the different sections. Returns: A LaTeX formatted string. """ #stops when we've printed out the prerequisite number of levels if (not stop_level is None and level > stop_level): return "" rstr = "" if level == 0: if standalone: #assumes that it is a stand-alone document, so must have #document options. rstr += r"\documentclass[12pt,fleqn]{report}" rstr += r""" \usepackage{etoolbox} \usepackage{suffix} \newcommand{\ipiitem}[3]{% \setul{1pt}{.4pt}\ifblank{#1}{}{\ifstrequal{#1}{\underline{\smash{}}}{}{ {\noindent\textbf{#1}:\rule{0.0pt}{1.05\baselineskip}\quad}}}% uses a strut to add a bit of vertical space {#2}\parskip=0pt\par \ifblank{#3}{}% { {\hfill\raggedleft\textit{\small #3}\par} } } \makeatletter \newenvironment{ipifield}[4]{% \ifblank{#1}{}{\vspace{0.5em}} \noindent\parskip=0pt\begin{tabular}[t]{|p{1.0\linewidth}} %cell without border \multicolumn{1}{@{}p{1.0\linewidth}}{ \ipiitem{\underline{\smash{#1}}}{#2}{} \ifblank{#4}{ % \ifblank{#3}{}{{\hfill\raggedleft\textit{\small #3}}\par}}{} } \vspace{-1em}\\ % % cell with border \ifblank{#4}{} % { \ifblank{#3}{}{\vspace{-1em}{\hfill\raggedleft\textit{\small #3}}\par} % {#4}\vspace{-1em}\\\hline } % negative vspace to undo the line break \end{tabular} \parskip=0pt\list{}{\listparindent 1.5em% \leftmargin \listparindent \rightmargin 0pt \parsep 0pt \itemsep 0pt \topsep 0pt }% \item\relax } {\endlist} \makeatother """ rstr += "\n\\begin{document}\n" if self._label != "" and not standalone: #assumes that it is part of a cross-referenced document, so only #starts a new section. rstr += "\\section{" + self._label + "}\n" rstr += "\\label{" + self._label + "}\n" rstr += "\\begin{ipifield}{}%\n" else: if self._label != "" and not standalone: rstr += "\\begin{ipifield}{\hyperref["+self._label+"]{"+name+"}}%\n" else: rstr += "\\begin{ipifield}{"+name+"}%\n" rstr += "{"+self._help+"}%\n" rstr += "{"+self.detail_str()+"}%\n" rstr += "{" # Prints out the attributes if len(self.attribs) != 0: #don't print out units if not necessary if len(self.attribs) == 1 and (("units" in self.attribs) and self._dimension == "undefined"): pass else: for a in self.attribs: #don't print out units if not necessary if not (a == "units" and self._dimension == "undefined"): rstr += "\\ipiitem{" + a + "}%\n{" + self.__dict__[a]._help + "}%\n{"+self.__dict__[a].detail_str()+"}%\n" #!!MUST ADD OTHER STUFF rstr+="}\n" #As above, for the fields. Only prints out if we have not reached the #user-specified limit. if len(self.fields) != 0 and level != stop_level: for f in self.fields: rstr += self.__dict__[f].help_latex(name=f, level=level+1, stop_level=stop_level, standalone=standalone) if len(self.dynamic) != 0 and level != stop_level: for f, v in self.dynamic.iteritems(): dummy_obj = v[0](**v[1]) rstr += dummy_obj.help_latex(name=f, level=level+1, stop_level=stop_level, standalone=standalone) rstr += "\\end{ipifield}\n" if level == 0 and standalone: #ends the created document if it is not part of a larger document rstr += "\\end{document}" #Some escape characters are necessary for the proper latex formatting rstr = rstr.replace('_', '\\_') rstr = rstr.replace('\\\\_', '\\_') rstr = rstr.replace('...', '\\ldots ') rstr = rstr.replace('<', '$<$') rstr = rstr.replace('>', '$>$') return rstr def pprint(self, default, indent="", latex = True): """Function to convert arrays and other objects to human-readable strings. Args: default: The object that needs to be converted to a string. indent: The indent at the beginning of a line. latex: A boolean giving whether the string will be latex-format. Returns: A formatted string. """ if type(default) is np.ndarray: if default.shape == (0,): return " [ ] " #proper treatment of empty arrays. else: #indents new lines for multi-D arrays properly rstr = "\n" + indent + " " rstr += str(default).replace("\n", "\n" + indent + " ") if not latex: rstr += "\n" + indent + " " return rstr elif type(default) == str: if latex: return "`" + default + "'" #indicates that it is a string else: return " " + default + " " elif default == []: return " [ ] " elif default == {}: if latex: return " \\{ \\} " #again, escape characters needed for latex else: #formatting return " { } " else: #in most cases standard formatting will do return " " + str(default) + " " def type_print(self, dtype): """Function to convert a data types to human-readable strings. Args: dtype: A data type. """ if dtype == bool: return "boolean" elif dtype == float or dtype == np.float64: return "float" elif dtype == int or dtype == np.uint64 or dtype == np.int64: return "integer" elif dtype == dict: return "dictionary" elif dtype == str: return "string" elif dtype == tuple: return "tuple" else: raise TypeError("Unrecognized data type " + str(dtype)) def help_xml(self, name="", indent="", level=0, stop_level=None): """Function to generate an xml formatted help file. Args: name: A string giving the name of the root node. indent: The indent at the beginning of a line. level: Current level of the hierarchy being considered. stop_level: The depth to which information will be given. If not given, all information will be given Returns: An xml formatted string. """ #stops when we've printed out the prerequisite number of levels if (not stop_level is None and level > stop_level): return "" #these are booleans which tell us whether there are any attributes #and fields to print out show_attribs = (len(self.attribs) != 0) show_fields = (not (len(self.fields) == 0 and len(self.dynamic) == 0)) and level != stop_level rstr = "" rstr = indent + "<" + name; #prints tag name for a in self.attribs: if not (a == "units" and self._dimension == "undefined"): #don't print out units if not necessary rstr += " " + a + "=''" #prints attribute names rstr += ">\n" #prints help string rstr += indent + " " + self._help + " \n" if show_attribs: for a in self.attribs: if not (a == "units" and self._dimension == "undefined"): #information about tags is found in tags beginning with the name #of the attribute rstr += indent + " <" + a + "_help> " + self.__dict__[a]._help + " \n" #prints dimensionality of the object if hasattr(self, '_dimension') and self._dimension != "undefined": rstr += indent + " " + self._dimension + " \n" if self._default != None and issubclass(self.__class__, InputAttribute): #We only print out the default if it has a well defined value. #For classes such as InputCell, self._default is not the value, #instead it is an object that is stored, putting the default value in #self.value. For this reason we print out self.value at this stage, #and not self._default rstr += indent + " " + self.pprint(self.value, indent=indent, latex=False) + "\n" if show_attribs: for a in self.attribs: if not (a == "units" and self._dimension == "undefined"): if self.__dict__[a]._default is not None: rstr += indent + " <" + a + "_default>" + self.pprint(self.__dict__[a]._default, indent=indent, latex=False) + "\n" #prints out valid options, if required. if hasattr(self, "_valid"): if self._valid is not None: rstr += indent + " " + str(self._valid) + " \n" if show_attribs: for a in self.attribs: if not (a == "units" and self._dimension == "undefined"): if hasattr(self.__dict__[a], "_valid"): if self.__dict__[a]._valid is not None: rstr += indent + " <" + a + "_options> " + str(self.__dict__[a]._valid) + " \n" #if possible, prints out the type of data that is being used if issubclass(self.__class__, InputAttribute): rstr += indent + " " + self.type_print(self.type) + " \n" if show_attribs: for a in self.attribs: if not (a == "units" and self._dimension == "undefined"): rstr += indent + " <" + a + "_dtype> " + self.type_print(self.__dict__[a].type) + " \n" #repeats the above instructions for any fields or dynamic tags. #these will only be printed if their level in the hierarchy is not above #the user specified limit. if show_fields: for f in self.fields: rstr += self.__dict__[f].help_xml(f, " " + indent, level+1, stop_level) for f, v in self.dynamic.iteritems(): #we must create the object manually, as dynamic objects are #not automatically added to the input object's dictionary dummy_obj = v[0](**v[1]) rstr += dummy_obj.help_xml(f, " " + indent, level+1, stop_level) rstr += indent + "\n" return rstr class InputAttribute(Input): """Class for handling attribute data. Has the methods for dealing with attribute data of the form: ..., where data is just a value. Takes the data and converts it to the required data_type, so that it can be used in the simulation. Attributes: type: Data type of the data. value: Value of data. Also specifies data type if type is None. _valid: An optional list of valid options. """ def __init__(self, help=None, default=None, dtype=None, options=None): """Initialises InputAttribute. Args: help: A help string. default: A default value. dtype: An optional data type. Defaults to None. options: An optional list of valid options. """ if not dtype is None: self.type = dtype else: raise TypeError("You must provide dtype") super(InputAttribute,self).__init__(help, default) if options is not None: self._valid = options if not default is None and not self._default in self._valid: #This makes sure that the programmer has set the default value #so that it is a valid value. raise ValueError("Default value '" + str(self._default) + "' not in option list " + str(self._valid)+ "\n" + self._help) else: self._valid = None def parse(self, text=""): """Reads the data for a single attribute value from an xml file. Args: text: The data held between the start and end tags. """ super(InputAttribute, self).parse(text=text) self.value = read_type(self.type, self._text) def store(self, value): """Stores the input data. Args: value: The raw data to be stored. """ super(InputAttribute,self).store(value) self.value = value def fetch(self): """Returns the stored data.""" super(InputAttribute,self).fetch() return self.value def check(self): """Function to check for input errors. Raises: ValueError: Raised if the value chosen is not one of the valid options. """ super(InputAttribute,self).check() if not (self._valid is None or self.value in self._valid): #This checks that the user has set the value to a valid value. raise ValueError(str(self.value) + " is not a valid option (" + str(self._valid) + ")") def write(self, name=""): """Writes data in xml file format. Writes the attribute data in the appropriate format. Args: name: An optional string giving the attribute name. Defaults to "". Returns: A string giving the stored value in the appropriate format. """ return name + "='" + write_type(self.type, self.value) + "'" class InputValue(InputAttribute): """Scalar class for input handling. Has the methods for dealing with simple data tags of the form: data , where data is just a value. Takes the data and converts it to the required data_type, so that it can be used in the simulation. Attributes: units: The units that the input data is given in. _dimension: The dimensionality of the data. """ default_dimension = "undefined" default_units = "" attribs= { "units" : ( InputAttribute, { "dtype" : str, "help" : "The units the input data is given in.", "default" : default_units } ) } def __init__(self, help=None, default=None, dtype=None, options=None, dimension=None): """Initialises InputValue. Args: help: A help string. dimension: The dimensionality of the value. default: A default value. dtype: An optional data type. Defaults to None. options: An optional list of valid options. """ # a note on units handling: # 1) units are only processed at parse/fetch time: # internally EVERYTHING is in internal units # 2) if one adds an explicit "units" attribute to a derived class, # the internal units handling will be just ignored if dimension is None: self._dimension = self.default_dimension else: self._dimension = dimension super(InputValue,self).__init__(help, default, dtype, options) def store(self, value, units=""): """Converts the data to the appropriate data type and units and stores it. Args: value: The raw data to be stored. units: Optional string giving the units that the data should be stored in. """ super(InputValue,self).store(value) if units != "": self.units.store(units) #User can define in the code the units to be #printed self.value = value if self._dimension != "undefined": self.value *= unit_to_user(self._dimension, units, 1.0) def fetch(self): """Returns the stored data in the user defined units.""" super(InputValue,self).fetch() rval = self.value if self._dimension != "undefined": rval *= unit_to_internal(self._dimension, self.units.fetch(), 1.0) return rval def write(self, name="", indent=""): """Writes data in xml file format. Writes the data in the appropriate format between appropriate tags. Args: name: An optional string giving the tag name. Defaults to "". indent: An optional string giving the string to be added to the start of the line, so usually a number of tabs. Defaults to "". Returns: A string giving the stored value in the appropriate xml format. """ return Input.write(self, name=name, indent=indent, text=write_type(self.type, self.value)) def parse(self, xml=None, text=""): """Reads the data for a single value from an xml file. Args: xml: An xml_node object containing the all the data for the parent tag. text: The data held between the start and end tags. """ Input.parse(self, xml=xml, text=text) self.value = read_type(self.type, self._text) ELPERLINE = 5 class InputArray(InputValue): """Array class for input handling. Has the methods for dealing with simple data tags of the form: data , where data is an array of the form [data[0], data[1], ... , data[length]]. Takes the data and converts it to the required data type, so that it can be used in the simulation. Also holds the shape of the array, so that we can use a simple 1D list of data to specify a multi-dimensional array. Attributes: shape: The shape of the array. """ attribs = copy(InputValue.attribs) attribs["shape"] = (InputAttribute, {"dtype": tuple, "help": "The shape of the array.", "default": (0,)}) def __init__(self, help=None, default=None, dtype=None, dimension=None): """Initialises InputArray. Args: help: A help string. dimension: The dimensionality of the value. default: A default value. dtype: An optional data type. Defaults to None. """ super(InputArray,self).__init__(help, default, dtype, dimension=dimension) def store(self, value, units=""): """Converts the data to the appropriate data type, shape and units and stores it. Args: value: The raw data to be stored. units: Optional string giving the units that the data should be stored in. """ super(InputArray,self).store(value=np.array(value, dtype=self.type).flatten().copy(), units=units) self.shape.store(value.shape) #if the shape is not specified, assume the array is linear. if self.shape.fetch() == (0,): self.shape.store((len(self.value),)) def fetch(self): """Returns the stored data in the user defined units.""" value = super(InputArray,self).fetch() #if the shape is not specified, assume the array is linear. if self.shape.fetch() == (0,): value = np.resize(self.value,0).copy() else: value = self.value.reshape(self.shape.fetch()).copy() return value def write(self, name="", indent=""): """Writes data in xml file format. Writes the data in the appropriate format between appropriate tags. Note that only ELPERLINE values are printed on each line if there are more than this in the array. If the values are floats, or another data type with a fixed width of data output, then they are aligned in columns. Args: name: An optional string giving the tag name. Defaults to "". indent: An optional string giving the string to be added to the start of the line, so usually a number of tabs. Defaults to "". Returns: A string giving the stored value in the appropriate xml format. """ rstr = "" if (len(self.value) > ELPERLINE): rstr += "\n" + indent + " [ " else: rstr += " [ " #inlines the array if it is small enough for i, v in enumerate(self.value): if (len(self.value) > ELPERLINE and i > 0 and i%ELPERLINE == 0): rstr += "\n" + indent + " " rstr += write_type(self.type, v) + ", " rstr = rstr.rstrip(", ") #get rid of trailing commas if (len(self.value) > ELPERLINE): rstr += " ]\n" else: rstr += " ] " return Input.write(self, name=name, indent=indent, text=rstr) def parse(self, xml=None, text=""): """Reads the data for an array from an xml file. Args: xml: An xml_node object containing the all the data for the parent tag. text: The data held between the start and end tags. """ Input.parse(self, xml=xml, text=text) self.value = read_array(self.type, self._text) #if the shape is not specified, assume the array is linear. if self.shape.fetch() == (0,): self.shape.store((len(self.value),))