Applying Edits
After you've sent the Sapling API text and gotten JSON edits in the response, how do you apply the edits to get updated text?
The simplest way is to use the auto_apply
argument. By setting this to true, the returned response will have an extra field, applied_text
, that contains the text with the edits applied.
However, you can also easily apply edits programmatically.
Programmatically Applying Edits
Recall the Edit data structure:
{
"id": <str, UUID>, // Opaque edit id, used to give feedback
"sentence": <str>, // Unedited sentence
"sentence_start": <int>, // Offset of sentence from start of text
"start": <int>, // Offset of edit start relative to sentence
"end": <int>, // Offset of edit end relative to sentence
"replacement": <str>, // Suggested replacement
"error_type": <str>, // Error type, see "Error Categories"
"general_error_type": <str>, // See "Error Categories"
}
When programmatically applying edits, go in reverse start offset (sentence_start + start
) order so changes don't affect the offsets of the remaining edits.
For example, consider the sentence: Lets go to the housee.
where Sapling returns the following list of edits:
[
{
'sentence_start': 0,
'start': 0,
'end': 4,
'replacement': "Let's",
...
},
{
'sentence_start': 0,
'start': 15,
'end': 21,
'replacement': 'house',
...
}
]
The simplest way to apply the edits to your text is in reverse order:
- Replace characters
15-21
withhouse
. - Replace characters
0-4
withLet's
.
If the characters for Lets
are replaced before housee
, the offsets for other edits would need to be updated.
Sample Code
We provide sample code below for applying edits.
A few things to keep in mind:
- The
edits
array is ordered by starting position, though we include logic below to ensure this is the case. - For some languages where assignment is by reference, you will want to create a copy of the original string before modifying it.
- Python
- JavaScript
- PHP
text = str(text)
edits = sorted(edits, key=lambda e: (e['sentence_start'] + e['start']), reverse=True)
for edit in edits:
start = edit['sentence_start'] + edit['start']
end = edit['sentence_start'] + edit['end']
if start > len(text) or end > len(text):
print(f'Edit start:{start}/end:{end} outside of bounds of text:{text}')
continue
text = text[: start] + edit['replacement'] + text[end:]
return text
function apply_edits(text, edits) {
text = text.slice();
const reversed = edits.sort((a, b) => b['sentence_start'] + b['start'] - a['sentence_start'] - a['start']);
for (const edit of reversed) {
const start = edit['sentence_start'] + edit['start'];
const end = edit['sentence_start'] + edit['end'];
if (start > text.length || end > text.length) {
console.log(`Edit start:${start}/end:${end} outside of bounds of text:${text}`);
continue;
}
text = text.slice(0, start) + edit['replacement'] + text.slice(end);
}
return text;
}
function cmp($a, $b) {
return $b->sentence_start + $b->start - $a->sentence_start - $a->start;
}
function apply_edits($text, $edits) {
usort($edits, 'cmp');
foreach($edits as $edit) {
$start = $edit->sentence_start + $edit->start;
$end = $edit->sentence_start + $edit->end;
if ($start > strlen($text) or $end > strlen($text)) {
echo 'Index out of range';
continue;
}
$text = substr($text, 0, $start) + $edit->replacement + substr($text, $end, null);
}
return $text;
}