================ Parsing PVL text ================ .. contents:: `Table of Contents` :local: ----------- From a File ----------- The :func:`pvl.load()` function parses PVL text from a file or stream and returns a :class:`dict`-like object (:class:`pvl.PVLModule` by default) containing information from that text. This documentation will explain how to use the module as well as some sample code to use the module efficiently. Simple Use +++++++++++ How to use :func:`pvl.load()` to get a single value:: >>> from pathlib import Path >>> import pvl >>> path = Path('tests/data/pds3/simple_image_1.lbl') >>> pvl.load(path)['RECORD_TYPE'] 'FIXED_LENGTH' >>> import pvl >>> img = 'tests/data/pds3/simple_image_1.lbl' >>> pvl.load(img)['RECORD_TYPE'] 'FIXED_LENGTH' >>> import pvl >>> img = 'tests/data/pds3/simple_image_1.lbl' >>> with open(img, 'r+') as r: ... print(pvl.load(r)['RECORD_TYPE']) FIXED_LENGTH Detailed Use ++++++++++++++ To view the image label of an ISIS cube as a dictionary:: >>> import pvl >>> img = 'tests/data/pattern.cub' >>> module = pvl.load(img) >>> print(module) PVLModule([ ('IsisCube', {'Core': {'Dimensions': {'Bands': 1, 'Lines': 90, 'Samples': 90}, 'Format': 'Tile', 'Pixels': {'Base': 0.0, 'ByteOrder': 'Lsb', 'Multiplier': 1.0, 'Type': 'Real'}, 'StartByte': 65537, 'TileLines': 128, 'TileSamples': 128}}) ('Label', PVLObject([ ('Bytes', 65536) ])) ]) Not all image labels are formatted the same so different labels will have different information that you can obtain. To view what information you can extract use the ``.keys()`` function:: >>> import pvl >>> img = 'tests/data/pds3/simple_image_1.lbl' >>> lbl = pvl.load(img) >>> lbl.keys() KeysView(['PDS_VERSION_ID', 'RECORD_TYPE', 'RECORD_BYTES', 'LABEL_RECORDS', 'FILE_RECORDS', '^IMAGE', 'IMAGE']) ... now you can just copy and paste from this list:: >>> lbl['RECORD_TYPE'] 'FIXED_LENGTH' The list ``.keys()`` returns is out of order, to see the keys in the order of the dictionary use ``.items()`` function:: >>> import pvl >>> img = 'tests/data/pds3/simple_image_1.lbl' >>> for item in pvl.load(img).items(): ... print(item[0]) PDS_VERSION_ID RECORD_TYPE RECORD_BYTES LABEL_RECORDS FILE_RECORDS ^IMAGE IMAGE We can take advantage of the fact ``.items()`` returns a list in order and use the index number of the key instead of copying and pasting. This will make extracting more than one piece of information at time more convenient. For example, if you want to print out the first 5 pieces of information:: >>> import pvl >>> img = 'tests/data/pds3/simple_image_1.lbl' >>> pvl_items = pvl.load(img).items() >>> for n in range(0, 5): ... print(pvl_items[n][0], pvl_items[n][1]) PDS_VERSION_ID PDS3 RECORD_TYPE FIXED_LENGTH RECORD_BYTES 824 LABEL_RECORDS 1 FILE_RECORDS 601 ... some values have sub-dictionaries. You can access those by:: >>> print(pvl.load(img)['IMAGE'].keys()) KeysView(['LINES', 'LINE_SAMPLES', 'SAMPLE_TYPE', 'SAMPLE_BITS', 'MEAN', 'MEDIAN', 'MINIMUM', 'MAXIMUM', 'STANDARD_DEVIATION', 'CHECKSUM']) >>> print(pvl.load(img)['IMAGE']['SAMPLE_BITS']) 8 Another way of using :func:`pvl.load` is to use Python's ``with open()`` command. Otherwise using this method is very similar to using the methods described above:: >>> import pvl >>> with open('tests/data/pattern.cub','r') as r: ... print(pvl.load(r)['Label']['Bytes']) 65536 ------------- From a String ------------- The :func:`pvl.loads()` function returns a Python object (typically a :class:`pvl.PVLModule` object which is :class:`dict`-like) based on parsing the PVL text in the string parameter that it is given. Simple Use +++++++++++ How to use :func:`pvl.loads()`:: >>> import pvl >>> s = """String = 'containing the label of the image' ... key = value ... END ... """ >>> pvl.loads(s).keys() KeysView(['String', 'key']) >>> pvl.loads(s)['key'] 'value' Detailed Use ++++++++++++++ To view the image label dictionary:: >>> import pvl >>> string = """Object = IsisCube ... Object = Core ... StartByte = 65537 ... Format = Tile ... TileSamples = 128 ... TileLines = 128 ... ... End_Object ... End_Object ... ... Object = Label ... Bytes = 65536 ... End_Object ... End""" >>> print(pvl.loads(string)) PVLModule([ ('IsisCube', {'Core': {'Format': 'Tile', 'StartByte': 65537, 'TileLines': 128, 'TileSamples': 128}}) ('Label', PVLObject([ ('Bytes', 65536) ])) ]) ... to view the keys available:: >>> print(pvl.loads(string).keys()) KeysView(['IsisCube', 'Label']) ... and to see the information contained in the keys:: >>> print(pvl.loads(string)['Label']) PVLObject([ ('Bytes', 65536) ]) ... and what is in the sub-dictionary:: >>> print(pvl.loads(string)['Label']['Bytes']) 65536 By default, :func:`pvl.loads()` and :func:`pvl.load()` are very permissive, and do their best to attempt to parse a wide variety of PVL 'flavors.' If a parsed label has a parameter with a missing value, the default behavior of these functions will be to assign a :class:`pvl.parser.EmptyValueAtLine` object as the value:: >>> string = """ ... Object = Label ... A = ... End_Object ... End""" >>> print(pvl.loads(string)) PVLModule([ ('Label', {'A': EmptyValueAtLine(3 does not have a value. Treat as an empty string)}) ]) Stricter parsing can be accomplished by passing a different grammar object (e.g. :class:`pvl.grammar.PVLGrammar`, :class:`pvl.grammar.ODLGrammar`) to :func:`pvl.loads()` or :func:`pvl.load()`:: >>> import pvl >>> some_pvl = """Comments = "PVL and ODL only allow /* */ comments" ... /* like this */ ... # but people use hash-comments all the time ... END ... """ >>> print(pvl.loads(some_pvl)) PVLModule([ ('Comments', 'PVL and ODL only allow /* */ comments') ]) >>> pvl.loads(some_pvl, grammar=pvl.grammar.PVLGrammar()) Traceback (most recent call last): ... pvl.exceptions.LexerError: (LexerError(...), 'Expecting an Aggregation Block, an Assignment Statement, or an End Statement, but found "#" : line 3 column 1 (char 67) near "like this */\n# but people"') ---------- From a URL ---------- The :func:`pvl.loadu()` function returns a Python object (typically a :class:`pvl.PVLModule` object which is :class:`dict`-like) based on parsing the PVL text in the data returned from a URL. This is very similar to parsing PVL text from a file, but you use :func:`pvl.loadu()` instead:: >>> import pvl >>> url = 'https://hirise-pds.lpl.arizona.edu/PDS/RDR/ESP/ORB_017100_017199/ESP_017173_1715/ESP_017173_1715_RED.LBL' >>> pvl.loadu(url)['VIEWING_PARAMETERS']['PHASE_ANGLE'] Quantity(value=50.784875, units='DEG') Of course, other kinds of URLs, like file, ftp, rsync, sftp and more can be used. --------------------------- Return non-standard objects --------------------------- The "loaders" return a dict-like filled with Python objects based on the types inferred from the PVL-text. Sometimes you may want the `pvl` library to return different types in the dict-like, and `pvl` has some limited capacity for that (so far just real and quantity types). Normally real number values in the PVL-text will be returned as Python :class:`float` objects. However, what if you wanted all of the real values to be returned in the dict-like as Python :class:`decimal.Decimal` objects (because you wanted to preserve numeric precision)? You can do that by providing the object type you want via the ``real_cls`` argument of a decoder constructor, like so:: >>> from decimal import Decimal >>> import pvl >>> text = "gigawatts = 1.210" >>> >>> flo = pvl.loads(text) >>> print(flo) PVLModule([ ('gigawatts', 1.21) ]) >>> >>> print(type(flo["gigawatts"])) >>> dec = pvl.loads(text, decoder=pvl.decoder.OmniDecoder(real_cls=Decimal)) >>> print(dec) PVLModule([ ('gigawatts', Decimal('1.210')) ]) >>> print(type(dec["gigawatts"])) Any class that can be passed a :class:`str` object to initialize an object can be provided to ``real_cls``, but it should emit a :class:`ValueError` if it is given a string that should not be converted to a real number value. To learn more about quantity classes in `pvl`, please see :ref:`quantities`.