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:
...2JS... ..."P"...
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:
See if the current field contains any ditto characters. If not, return it unchanged.
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.
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.
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:
rawField: The raw input field that may
contain ditto characters.
fClass: The class of the field
(derived from FieldItem), necessary so
we can access that field's .flatten()
method.
fName: The field's attribute name in
the BaseEncounter object, so we can
pull out the corresponding field from the previous
record.
scan: The Scan object
being used to parse input, so that we can use its
.error() method to report any
invalid use of the ditto convention.
# - - - 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.")
else:
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.")
else:
newList[i] = oldList[i]
#-- 5 --
return "".join(newList)