Next / Previous / Contents / Shipman's homepage

27. class SingleField: Generic single-character field

Quite a number of fields in the encounter record share several functional characteristics:

Examples of such fields include the age code, skull ossification, and all the micro-aging fields.

In addition, in two fields (the age code and the sex code), we also allow any of a set of obsolete codes. For example, in the Olden Days, they used 4 for male and 5 for female.

Hence, the SingleField class supports all these functional features, making a number of field-scanner classes such as SexCodeField much easier to write.

Here is the interface:
# - - - - -   c l a s s   S i n g l e F i e l d   - - -

class SingleField(FieldItem):
    '''Generic field-scanner class for single-column fields.

      Exports, other than those inherited:
        VALID_CODES:      [ a string of all valid codes, uppercased ]
          [ if this field does not support a set of obsolete codes ->
            else ->
              a translation table in the form produced by
              string.maketrans() that translates obsolete codes to
              their current equivalents ]
        .encounter:       [ as passed to constructor, read-only ]
        .value:           [ as passed to constructor, read-only ]
        SingleField.scanField(encounter, scan, fieldName, desc): # Static
          [ (encounter is a BaseEncounter object) and
            (scan is a Scan object) and
            (fieldName is an attribute name as a string) and
            (desc is an external description of the field) and
              if  scan starts with a valid field ->
                scan  :=  scan advanced past that field
                encounter.(fieldName)  :=  an object representing
                                           that field
              else ->
                scan   :=   scan advanced no further than end of line
                Log()  +:=  error message(s)
                raise SyntaxError ]
          [ if object is None ->
              return ' '
            else ->
              return object.value ]

We set the default value of the class attribute OLD_TRANSLATOR to None.

To write a field-scanner class for a single-column field:

  1. Inherit from SingleField.

  2. Declare a class attribute VALID_CODES containing a string that has all the valid code values in it, in uppercase.

    If the field can be blank, include a space character in this string. If a nonblank code is required, omit the space character.

  3. If the field supports translation of an obsolete set of codes, define a class attribute OLD_TRANSLATOR that translates old codes to new. You will need to import the Python string module and use its .maketrans() function. Here, for example, is the declaration that translates the old sex codes to the new:

    OLD_TRANSLATOR = string.maketrans("045", "UMF")

    In this example, old sex code 5 will be translated to F (female). The .maketrans() function does not translate any codes other than the ones you specify.

  4. Define the usual .scanField() and .flatten() methods required by the FieldItem interface.

Your derived class's .scanField() method will probably want to use the SingleField.scanField() class method. The calling sequence of this method includes one additional argument, a desc string that is used in error message to identify what kind of field is involved. For example, for the age code field, you might pass the string "age code" to this argument.

27.1. SingleField.scanField()

This static method scans a single-column field. The caller must supply all the arguments required by the FieldItem.scanField() interface, plus an additional desc argument that describes the field in English for error-reporting purposes.
# - - -   S i n g l e F i e l d . s c a n F i e l d   - - -

    def scanField(encounter, scan, fieldName, fieldClass, desc):
        '''Scan a single-column field.

The first step is to get the raw field value from the scan object.
        #-- 1 --
        # [ if the line in scan is not at end of line ->
        #     scan     :=  scan advanced 1
        #     rawText  :=  next character from scan, uppercased
        #   else ->
        #     Log()  +:=  error message
        #     raise SyntaxError ]
            rawText = scan.move(1).upper()
        except IndexError:
            scan.syntax("Expecting a %s field." % desc)

Next we check for single-column ditto, and set the variable dittoFree to the dittoed value if there is one, or to rawText if ditto isn't used. This step is the reason we need to use a class method rather than a static method: we need the derived class so we can pass it to the .copyDitto() method. See Section 74.22, “BaseEncounter.copyDitto(): Implement single-column ditto”.
        #-- 2 --
        # [ if (rawText is DITTO_CHAR) and it validly dittoes a
        #   character from encounter.compiler.oldEncounter ->
        #     dittoFree  :=  the dittoed character
        #   else if (rawText is DITTO_CHAR) not validly used ->
        #     Log()  +:=  error message
        #     raise SyntaxError
        #   else ->
        #     dittoFree  :=  rawText ]
        dittoFree = encounter.copyDitto(rawText, fieldClass,
            fieldName, scan)

If the derived class has defined an OLD_TRANSLATOR attribute, we next use that translator to replace an obsolete code with the current version, if any.
        #-- 3 --
        # [ if fieldClass.OLD_TRANSLATOR is None ->
        #     updated  :=  dittoFree
        #   else ->
        #     updated  :=  dittoFree with any old codes from
        #         fieldClass.OLD_TRANSLATOR replaced by their
        #         current equivalents ]
        if  fieldClass.OLD_TRANSLATOR is not None:
            updated = dittoFree.translate(fieldClass.OLD_TRANSLATOR)
            updated = dittoFree

Next we check that this value is in the VALID_CODES string.
        #-- 4 --
        # [ if updated is not in fieldClass.VALID_CODES ->
        #     Log()  +:=  error message
        #     raise SyntaxError ]
        #   else -> I ]
        if  updated not in fieldClass.VALID_CODES:
            scan.syntax("Code '%s' is not one of the valid "
                        "%s codes (%s)" %
                        (updated, fieldName, fieldClass.VALID_CODES))

We assume that derived classes will use the actual field value as the .value attribute stored in the instance.
        #-- 5 --
        # [ encounter.(fieldName)  :=  a new fieldClass instance
        #       whose value is (updated) ]
        setattr(encounter, fieldName,
                fieldClass(encounter, updated))