#!/usr/bin/env python3 # Standalone synthetic fixture; no user files or external network. import codecs,csv,io,hashlib source='코드,이름\r\nK-21,바다\r\nK-22,한빛\r\n' blobs={'utf8':source.encode('utf-8'),'utf8-bom':source.encode('utf-8-sig'),'cp949':source.encode('cp949'),'ascii':b'id,name\r\nK21,sea\r\n'} results=[] for name,raw in blobs.items(): candidates=[] for encoding in ['utf-8','cp949']: try: text=raw.decode(encoding,errors='strict') candidates.append({'encoding':encoding,'roundTripBytes':text.encode(encoding)==raw,'firstHeader':next(csv.reader(io.StringIO(text)))[0]}) except UnicodeError:pass encoding='utf-8-sig' if raw.startswith(codecs.BOM_UTF8) else ('cp949' if name=='cp949' else 'utf-8') decoded=raw.decode(encoding,errors='strict');rows=list(csv.DictReader(io.StringIO(decoded,newline=''))) results.append({'case':name,'bytes':len(raw),'prefixHex':raw[:12].hex(' '),'sha256':hashlib.sha256(raw).hexdigest(),'strictCandidates':candidates,'selectedByFixtureContract':encoding,'headers':list(rows[0]),'rows':rows}) assert results[0]['rows']==results[1]['rows']==results[2]['rows'] assert results[1]['strictCandidates'][0]['firstHeader']=='\ufeff코드' assert len(results[3]['strictCandidates'])==2 emoji='바다 🌊';emojiBlocked=False try:emoji.encode('cp949',errors='strict') except UnicodeEncodeError:emojiBlocked=True lossy=emoji.encode('cp949',errors='replace').decode('cp949') ignored=emoji.encode('cp949',errors='ignore').decode('cp949') assert emojiBlocked and lossy=='바다 ?' and ignored=='바다 ' assert ignored!=emoji and '?'not in ignored result={'source':source,'cases':results,'emoji':{'source':emoji,'cp949StrictBlocked':emojiBlocked,'replacementOutput':lossy,'ignoreOutput':ignored,'ignoredCharacterCount':len(emoji)-len(ignored),'originalRecovered':False},'scope':'Known synthetic encodings; no universal encoding detector. UTF-8-sig stripping is specific to the first BOM. No Excel app executed.'} import sys,json from datetime import datetime,timezone result={'experiment':'utf8-bom-cp949-import','executedAt':datetime.now(timezone.utc).isoformat(),'runtime':sys.version.split()[0],'synthetic':True,**result} print(json.dumps(result,ensure_ascii=False,indent=2))