///|
pub fn mapping_segment(finding : Finding, offset : OffsetMap) -> MappingSegment {
{
original: { start: offset.original_start, end: offset.original_end },
redacted: { start: offset.replacement_start, end: offset.replacement_end },
finding_id: finding.id,
kind: finding.kind,
}
}
///|
pub fn build_mapping_index(
input_length : Int,
output_length : Int,
findings : Array[Finding],
offsets : Array[OffsetMap],
) -> MappingIndex {
let segments = []
for offset in offsets {
for finding in findings {
if finding.id == offset.finding_id {
segments.push(mapping_segment(finding, offset))
break
}
}
}
segments.sort_by(fn(left, right) {
left.original.start - right.original.start
})
{ original_length: input_length, redacted_length: output_length, segments }
}
///|
pub fn mapping_from_result(result : DeidResult) -> MappingIndex {
build_mapping_index(
result.audit.input_length,
result.audit.output_length,
result.findings,
result.offsets,
)
}
///|
pub fn MappingIndex::is_monotonic(self : MappingIndex) -> Bool {
for i in 1.. current.original.start ||
before.redacted.end > current.redacted.start {
return false
}
}
true
}
///|
fn map_inside(source : Span, target : Span, position : Int) -> Int {
if position <= source.start {
target.start
} else if position >= source.end {
target.end
} else {
let source_length = source.length()
let target_length = target.length()
let relative = position - source.start
target.start +
(if source_length == 0 {
0
} else {
relative * target_length / source_length
})
}
}
///|
pub fn MappingIndex::original_to_redacted(
self : MappingIndex,
position : Int,
) -> Int {
let bounded = if position < 0 {
0
} else if position > self.original_length {
self.original_length
} else {
position
}
let mut previous_original = 0
let mut previous_redacted = 0
for segment in self.segments {
if bounded < segment.original.start {
return previous_redacted + bounded - previous_original
}
if bounded <= segment.original.end {
return map_inside(segment.original, segment.redacted, bounded)
}
previous_original = segment.original.end
previous_redacted = segment.redacted.end
}
previous_redacted + bounded - previous_original
}
///|
pub fn MappingIndex::redacted_to_original(
self : MappingIndex,
position : Int,
) -> Int {
let bounded = if position < 0 {
0
} else if position > self.redacted_length {
self.redacted_length
} else {
position
}
let mut previous_original = 0
let mut previous_redacted = 0
for segment in self.segments {
if bounded < segment.redacted.start {
return previous_original + bounded - previous_redacted
}
if bounded <= segment.redacted.end {
return map_inside(segment.redacted, segment.original, bounded)
}
previous_original = segment.original.end
previous_redacted = segment.redacted.end
}
previous_original + bounded - previous_redacted
}
///|
pub fn MappingIndex::original_range_to_redacted(
self : MappingIndex,
span : Span,
) -> Span {
{
start: self.original_to_redacted(span.start),
end: self.original_to_redacted(span.end),
}
}
///|
pub fn MappingIndex::redacted_range_to_original(
self : MappingIndex,
span : Span,
) -> Span {
{
start: self.redacted_to_original(span.start),
end: self.redacted_to_original(span.end),
}
}
///|
pub fn MappingIndex::original_line_column(
self : MappingIndex,
original : String,
position : Int,
) -> LineColumn {
let mapped = self.original_to_redacted(position)
ignore(mapped)
line_column(original, position)
}
///|
pub fn MappingIndex::redacted_line_column(
self : MappingIndex,
redacted : String,
position : Int,
) -> LineColumn {
let mapped = self.redacted_to_original(position)
ignore(mapped)
line_column(redacted, position)
}
///|
pub fn MappingIndex::segment_for_original(
self : MappingIndex,
position : Int,
) -> MappingSegment? {
for item in self.segments {
if item.original.contains(position) {
return Some(item)
}
}
None
}
///|
pub fn MappingIndex::segment_for_redacted(
self : MappingIndex,
position : Int,
) -> MappingSegment? {
for item in self.segments {
if item.redacted.contains(position) {
return Some(item)
}
}
None
}
///|
pub fn MappingIndex::changed_original_spans(self : MappingIndex) -> Array[Span] {
self.segments.map(fn(item) { item.original })
}
///|
pub fn MappingIndex::changed_redacted_spans(self : MappingIndex) -> Array[Span] {
self.segments.map(fn(item) { item.redacted })
}
///|
pub fn MappingIndex::describe(self : MappingIndex) -> String {
let lines = [
"original_length=\{self.original_length}",
"redacted_length=\{self.redacted_length}",
"segments=\{self.segments.length()}",
"monotonic=\{self.is_monotonic()}",
]
for segment in self.segments {
lines.push(
"- \{segment.finding_id} \{segment.original.start}..\{segment.original.end} -> \{segment.redacted.start}..\{segment.redacted.end}",
)
}
lines.join("\n")
}
///|
pub fn offsets_are_monotonic(offsets : Array[OffsetMap]) -> Bool {
for i in 1.. offsets[i].original_start ||
offsets[i - 1].replacement_end > offsets[i].replacement_start {
return false
}
}
true
}
///|
pub fn offset_delta(offset : OffsetMap) -> Int {
offset.replacement_end -
offset.replacement_start -
(offset.original_end - offset.original_start)
}
///|
pub fn cumulative_offset_delta(offsets : Array[OffsetMap]) -> Array[Int] {
let result = [0]
let mut current = 0
for offset in offsets {
current += offset_delta(offset)
result.push(current)
}
result
}
///|
pub fn map_position_through_offsets(
offsets : Array[OffsetMap],
position : Int,
) -> Int {
let mut delta = 0
for offset in offsets {
if position < offset.original_start {
break
} else if position <= offset.original_end {
return offset.replacement_start
} else {
delta += offset_delta(offset)
}
}
position + delta
}
///|
pub fn inverse_position_through_offsets(
offsets : Array[OffsetMap],
position : Int,
) -> Int {
let mut delta = 0
for offset in offsets {
if position < offset.replacement_start {
break
} else if position <= offset.replacement_end {
return offset.original_start
} else {
delta += offset_delta(offset)
}
}
position - delta
}