prop/lib/microwaveprop/radio/text_sanitizer.ex
Graham McIntire d67186176f
fix: resolve all dialyzer type errors across project (46→0 project errors)
Type spec fixes:
- duct_usable_* return boolean→float (delegate to duct_usable_for_band)
- sanitize/1 spec includes :unicode error tuples
- match_delete/1 broadened from :ets.match_spec()
- telemetry_event local type replaces :telemetry.event/0
- wgrib2 parse_lon_val_segment corrected to tuple spec
- preloaded Ecto assoc types in get_mission/get_contact! specs

Unmatched returns:
- _ = prefix on Task.start, Oban.insert, Repo.query!, PubSub.subscribe,
  :ets.new, and if-expression returns across 18 files

Pattern match fixes:
- markdown: restructure acc!=[] guard as direct pattern match
- path_compute/pskr/skewt_location_resolver: remove dead clauses
- calibrate.aprs_144: remove unreachable format_float catch-all
- unused.ex: suppress MapSet.union no_opaque

Also: remove unused unicode_util_compat from mix.lock
2026-06-08 17:51:13 -05:00

52 lines
1.8 KiB
Elixir

defmodule Microwaveprop.Radio.TextSanitizer do
@moduledoc """
Strip non-printable characters from user-uploaded log text. Removes BOM,
zero-width spaces/joiners, C0 control bytes (except CR/LF/TAB), and DEL;
collapses non-breaking and other unicode whitespace into a regular space.
Used by CSV and ADIF importers because field values frequently arrive
with stray NBSPs from website copy-paste, BOM markers from Windows
exports, etc.
"""
@spec sanitize(nil) :: nil
@spec sanitize(String.t()) :: String.t() | {:error, String.t(), String.t()} | {:incomplete, String.t(), String.t()}
def sanitize(nil), do: nil
def sanitize(""), do: ""
def sanitize(string) when is_binary(string) do
string
|> :unicode.characters_to_list()
|> Enum.reduce([], fn cp, acc ->
case sanitize_codepoint(cp) do
:strip -> acc
replacement -> [replacement | acc]
end
end)
|> Enum.reverse()
|> :unicode.characters_to_binary()
end
defp sanitize_codepoint(?\t), do: ?\t
defp sanitize_codepoint(?\n), do: ?\n
defp sanitize_codepoint(?\r), do: ?\r
# C0 control characters and DEL — strip.
defp sanitize_codepoint(cp) when cp < 0x20 or cp == 0x7F, do: :strip
# NBSP and other unicode whitespace → regular space.
defp sanitize_codepoint(0x00A0), do: ?\s
defp sanitize_codepoint(cp) when cp >= 0x2000 and cp <= 0x200A, do: ?\s
defp sanitize_codepoint(0x202F), do: ?\s
defp sanitize_codepoint(0x205F), do: ?\s
defp sanitize_codepoint(0x3000), do: ?\s
# Zero-width characters (ZWSP, ZWNJ, ZWJ, word-joiner, BOM) — strip.
defp sanitize_codepoint(0x200B), do: :strip
defp sanitize_codepoint(0x200C), do: :strip
defp sanitize_codepoint(0x200D), do: :strip
defp sanitize_codepoint(0x2060), do: :strip
defp sanitize_codepoint(0xFEFF), do: :strip
defp sanitize_codepoint(cp), do: cp
end