Next / Previous / Contents / Shipman's homepage

74.22. BaseEncounter.copyDitto(): Implement single-column ditto

This method is a utility function for field scanning classe derived from FieldItem.

Here's an example of a slice of two input records, the age and how-aged fields, that use single-column ditto:


The first line says the age class is 2, with how-codes J and S. In the second line, the bander wants the same age class and the same second how-code, but the first how code is P. The intent is that the second line be interpreted as “2PS”.

There is a critical assumption in the operation of this method: that for any field that can use single-column ditto, the output of the .flatten() method for that field has exactly the same structure as the input field. Given that, our job is pretty straightforward:

  1. See if the current field contains any ditto characters. If not, return it unchanged.

  2. See if there is a previous encounter record stored in the BaseCompiler.oldEncounter. If this attribute is None, any use of ditto is an error: you can't ditto across page boundaries or on the first line of a page.

  3. Pull out the corresponding field from the .oldEncounter object. It is an error if that field is None: you can't ditto a field that doesn't exist in the previous record.

  4. Flatten the corresponding field and call it oldFlat. Scan through the current field, and for every occurrence of DITTO_CHAR, replace that position with the corresponding character from .oldFlat.

    This step can fail if a ditto character in the new field corresponds to a space character in the old field. Dittoing an empty column is not valid.

We can get to the BaseCompiler object containing the .oldEncounter field, because it is the .compiler attribute of the BaseEncounter object.

The arguments to this method are:
# - - -   B a s e E n c o u n t e r . c o p y D i t t o   - - -

    def copyDitto(self, rawField, fClass, fName, scan):
        '''Implement the single-column ditto function for some field.

          [ (rawField is an encounter field in raw input form) and
            (fClass is the FieldItem-derived class of that field) and
            (fName is the attribute name of that field in the
            BaseEncounter class) and
            (scan is a Scan object) ->
              if  rawField contains no DITTO_CHAR ->
                return rawField
              else if rawField can ditto from a previous field in
              the context of self.compiler ->
                return rawField with all occurrences of
                DITTO_CHAR replaced by the corresponding
                character from self.compiler.oldEncounter.(fName)
                flattened by fClass.flatten()
              else ->
                Log()  +:=  error message(s)
                raise SyntaxError ]

First we check to see if there are any ditto characters in the input field; if there are none, we return the rawField value unchanged. The .find() method returns -1 if it does not find the character.
        #-- 1 --
        # [ if rawField contains no copies of DITTO_CHAR ->
        #     return
        #   else -> I ]
        if  rawField.find(DITTO_CHAR) < 0:
            return rawField

Next, we check for various error conditions: no previous encounter record on the same sheet, and no corresponding field value in the previous record. If those tests both pass, we set oldFlat to a flattened version of the FieldItem object representing the old field value.
        #-- 2 --
        # [ if self.compiler.oldEncounter is None ->
        #     Log()  +:=  error message
        #     raise SyntaxError
        #   else -> I ]
        if  self.compiler.oldEncounter is None:
            scan.syntax("There is no previous encounter line from "
                         "which to ditto.")

        #-- 3 --
        # [ if self.compiler.oldEncounter.(fName) is None ->
        #     Log()  +:=  error message
        #     raise SyntaxError
        #   else ->
        #     oldFlat  :=  self.compiler.oldEncounter.(fName),
        #         flattened by fClass.flatten() ]
        oldField = getattr(self.compiler.oldEncounter, fName)
        if  oldField is None:
            scan.syntax("There is no field in the previous "
                "encounter line from which to ditto.")
            oldFlat = fClass.flatten(oldField)

Now we work through rawField, replacing each occurrence of DITTO_CHAR with the corresponding value from oldFlat.
        #-- 4 --
        # [ oldList  :=  a list whose members are the characters
        #                in oldFlat
        #   newList  :=  a list whose members are the characters
        #                in rawField ]
        oldList = list(oldFlat)
        newList = list(rawField)

        #-- 5 --
        # [ if each occurrence of DITTO_CHAR in newList 
        #   corresponds to a non-space character of oldList ->
        #     newList  :=  newList with each occurrence of
        #         DITTO_CHAR replaced by the corresponding
        #         character from oldList
        #   else ->
        #     Log()  +:=  error message
        #     raise SyntaxError ]
        for  i in range(len(newList)):
            if  newList[i] == DITTO_CHAR:
                if  oldList[i] == ' ':
                    scan.syntax("Invalid ditto: previous line is "
                        "blank in that position.")
                    newList[i] = oldList[i]

        #-- 5 --
        return "".join(newList)