Source file: /~heha/hs/transcode.zip/src/transcode.cpp

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shlwapi.h>
#include <shellapi.h>

#define elemof(x) (sizeof(x)/sizeof(*(x)))
#define T TEXT
#define nobreak
typedef const BYTE *LPCBYTE;

HANDLE stdin,stdout,stderr;
int icp,ocp;		// Eingabe- und Ausgabe-Codeseite
bool no_marker;		// kein Marker an Ausgabedatei (mittels vorangestelltem Fragezeichen)
HANDLE fin,fout;
int exitcode;		// 1 wenn Warnung aufgetreten
LPCTSTR DeleteOnError;	// Dateiname zum Löschen

#ifdef UNICODE
# define CommandLineToArgv CommandLineToArgvW
# define STR "%S"
#else
# define CommandLineToArgv CommandLineToArgvA
# define STR "%s"
static LPSTR*CommandLineToArgvA(LPCSTR CmdLine,int*argc) {
// Vereinfachte Version für begrenzte Argument-Anzahl
 struct _ret_t{
  LPSTR argv[10];
  CHAR buf[1];
 } *ret;
 LPSTR d;
 int i=0;
 bool inquote=false;
 bool instring=false;
 ret=(_ret_t*)LocalAlloc(LPTR,sizeof(*ret)+lstrlen(CmdLine));
 for (d=ret->buf;;) {
  CHAR c=*CmdLine++;
  switch (c) {
   case '"': {
    inquote=!inquote;
    if (!instring) {
     ret->argv[i++]=d;	// Stringanfang trotzdem setzen
     instring=true;
    }
   }break;

   case ' ':
   case '\t': if (inquote) goto def;

   case 0: if (instring) {
    *d++=0;		// Stringende setzen
    instring=false;
   }break;

   default: def:{
    if (!instring) {
     ret->argv[i++]=d;	// Stringanfang setzen
     instring=true;
    }
    *d++=c;
   }
  }
  if (!c) break;
 }
 if (argc) *argc=i;
 return ret->argv;
}
#endif

// Wandelt Zeilenenden von "\n" nach "\r\n"
// Liefert Zeiger auf Ende der Ergebnis-Zeichenkette
LPSTR Unix2DosA(LPCSTR s, LPSTR d, UINT l) {
 CHAR c;
 if (!l) return NULL;
 for(;;){
  c=*s++;
  if (!c) break;
  if (c=='\n') {
   if (!--l) break;
   *d++='\r';
  }
  if (!--l) break;
  *d++=c;
 };
 *d=0;	// Nullterminierung Windows-mäßig sicherstellen
 return d;
}

static void vfprintf(HANDLE f, LPCSTR p, va_list va) {
 CHAR buf1[1024];
 CHAR buf2[1024];
 if (!HIWORD(p)) {
  LoadStringA(0,(UINT)p,buf2,elemof(buf2));
  p=buf2;
 }
 wvnsprintfA(buf1,elemof(buf1),p,va);
 CharToOemA(buf1,buf2);
 Unix2DosA(buf2,buf1,elemof(buf1));
 DWORD bw;
 WriteFile(f,buf1,lstrlenA(buf1),&bw,NULL);
}

static void crlf(HANDLE f) {
 vfprintf(f,"\n",NULL);
}

static void _cdecl warn(PCSTR p, ...) {
 vfprintf(stderr,p,(va_list)(&p+1));
 crlf(stderr);
 exitcode=1;
}

static void _declspec(noreturn) _cdecl err(PCSTR p, ...) {
 vfprintf(stderr,p,(va_list)(&p+1));
 crlf(stderr);
 if (DeleteOnError) {
  CloseHandle(fout);
  DeleteFile(DeleteOnError);
 }
 ExitProcess(2);
}

static LPCBYTE MemChr(LPCBYTE s, DWORD len, BYTE c) {
 if (len) do{
  if (*s==c) return s;
  s++;
 }while(--len);
 return NULL;
}

// Länge in Bytes, geradzahlig
static void SwapBytes(PBYTE s, DWORD len) {
 if (len && (len&1)==0) do {
  BYTE t=*s;
  *s=s[1];
  ++*s++=t;
 }while (len-=2);
}

static void DecodeCodepage(LPCTSTR s, int&cp) {
 if (StrToIntEx(s,STIF_SUPPORT_HEX,&cp));
 else if (!lstrcmpi(s,T("utf-8"))) cp=CP_UTF8;
 else if (!lstrcmpi(s,T("utf8"))) cp=CP_UTF8;
 else if (!lstrcmpi(s,T("utf-7"))) cp=CP_UTF7;
 else if (!lstrcmpi(s,T("utf7"))) cp=CP_UTF7;
 else if (!lstrcmpi(s,T("ansi"))) cp=CP_ACP;
 else if (!lstrcmpi(s,T("mac"))) cp=CP_MACCP;
 else if (!lstrcmpi(s,T("oem"))) cp=CP_OEMCP;
 else if (!lstrcmpi(s,T("symbol"))) cp=CP_SYMBOL;
 else if (!lstrcmpi(s,T("utf-16"))) cp=1200;
 else if (!lstrcmpi(s,T("utf16"))) cp=1200;
 else if (!lstrcmpi(s,T("gb"))) cp=936;		// Vereinfachtes Chinesisch
 else if (!lstrcmpi(s,T("big5"))) cp=950;	// Traditionelles Chinesisch
 else if (!lstrcmpi(s,T("sjis"))) cp=932;	// Japanisch
 else if (!lstrcmpi(s,T("koi-8r"))) cp=20866;	// Russisch (weder DOS noch Windows)
 else if (!lstrcmpi(s,T("koi8r"))) cp=20866;	// Russisch (weder DOS noch Windows)
 else err("Unbekannte Kodierungsangabe \"" STR "\"!",s);
}

extern "C" void CALLBACK mainCRTStartup(void) {
 stdin=GetStdHandle(STD_INPUT_HANDLE);
 stdout=GetStdHandle(STD_OUTPUT_HANDLE);
 stderr=GetStdHandle(STD_ERROR_HANDLE);
 int argc;
 LPTSTR *argv=CommandLineToArgv(GetCommandLine(),&argc);
 icp=-1;	// Auto-Detect!
 if (argc>=2) {
  LPTSTR p=StrChr(argv[1],':');
  if (p) {
   *p=0;
   DecodeCodepage(argv[1],icp);
   argv[1]=p+1;
  }
  if (argv[1][0]=='?') {
   argv[1]++;
   no_marker=true;
  }
  DecodeCodepage(argv[1],ocp);
 }else err("Die Ausgabe-Codeseite muss als erstes Argument angegeben werden.\n"
	   "Möglich sind numerische Angaben (auch hex), \"utf-8\", \"utf-16\", \"?utf-8\", \"?utf-16\",\n"
	   "\"oem\", \"ansi\", \"utf-7\", \"mac\", \"symbol\", \"gb\", \"big5\", \"sjis\", \"koi-8r\"\n"
	   "Vorangestellte Kodeseite mit \":\" abgetrennt ist Eingabe-Kodeseite, sonst Auto-Detect.\n"
	   "Für eine komplette Liste aller Nummern durchsuche MSDN nach \"sjis\"!\n"
	   "Bis zu zwei weitere Argumente sind Ein- und Ausgabedateiname.\n"
	   "h#s, 05/08");
 if (argc>=3) {
  if (argc>=4 && !lstrcmpi(argv[2],argv[3]))
    fout=fin=CreateFile(argv[2],GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,0);
  else
    fin=CreateFile(argv[2],GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,0);
  if (fin==INVALID_HANDLE_VALUE) err("Kann Eingabedatei " STR " nicht öffnen, Fehlerkode %d!",argv[2],GetLastError());
 }else fin=stdin;
 if (argc>=4) {
  if (!fout) {
   fout=CreateFile(argv[3],GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,0);
   if (fout==INVALID_HANDLE_VALUE) err("Kann Ausgabedatei " STR " nicht anlegen, Fehlerkode %d!",argv[3],GetLastError());
   DeleteOnError=argv[3];	// folgendes err() löscht diese Datei
  }
 }else fout=stdout;
 if (argc>=5) warn("Zu viele Argumente (argc == %d)!",argc);
// Eingabedatei einlesen
 DWORD fsize=GetFileSize(fin,NULL);
 if (!fsize || fsize==INVALID_FILE_SIZE)
   err("Länge der Eingabedatei liefert %d (Eingabedatenströme gehen nicht, Fehlerkode %d)",fsize,GetLastError());
 LPBYTE pin=(LPBYTE)GlobalAlloc(GMEM_FIXED,fsize);
 if (!pin) err("Speicheranforderung von %u Bytes fehlgeschlagen!",fsize);
 DWORD br;
 ReadFile(fin,pin,fsize,&br,NULL);
 if (br!=fsize) err("Konnte nicht die volle Dateilänge (%u) lesen, nur %u Bytes!",fsize,br);
 if (fin!=stdin && fin!=fout) CloseHandle(fin);
 int skip=0;
// Autodetect der Eingabecodeseite sowie Entfernen von Windows-Notepad-Präfixen
 if (icp==-1) {
  if ((fsize&1)==0) {	// gerade Gesamt-Byte-Anzahl
   if (pin[0]==0xFF && pin[1]==0xFE) {	// Windows' Notepad-Präfix (Byte Order Mark)
    icp=1200;		// UTF-16 Little Endian
    skip=2;
    goto found;
   }
   if (pin[0]==0xFE && pin[1]==0xFF) {
    icp=1201;		// UTF-16 Big Endian
    skip=2;
    goto found;
   }
   LPCBYTE p=MemChr(pin,fsize,0);	// Null-Byte suchen
   if (p) {
    icp=(p-pin)&1?1200:1201;	// UTF-16, Endian je nach Position der ersten Null
    goto found;
   }
  }
  if (fsize>=3 && pin[0]==0xEF && pin[1]==0xBB && pin[2]==0xBF) {
   icp=CP_UTF8;
   skip=3;
   goto found;
  }
  if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,(LPCSTR)pin,fsize,NULL,0)) {	// probieren: UTF-8
   icp=CP_UTF8;
   goto found;
  }
  warn("Codeseite der Eingabedatei kann nicht erraten werden, \"ansi\" wird angenommen.");
  icp=CP_ACP;
 }else{
// nur Entfernen von Windows-Notepad-Präfixen (BOM)
  if (icp==CP_UTF8 && fsize>=3 && pin[0]==0xEF && pin[1]==0xBB && pin[2]==0xBF) skip=3;
  else if (fsize>=2 && (fsize&1)==0
  && (icp==1200 && pin[0]==0xFF && pin[1]==0xFE || icp==1201 && pin[0]==0xFE && pin[1]==0xFF)) skip=2;
 }
found:
 LPBYTE pin2=pin+skip;
 fsize-=skip;
 if (icp!=ocp) {	// gleiche Kodeseiten eigentlich nur zum Setzen/Entfernen von Präfixen
// Transkodierung in UCS-16, wenn Eingabedatei nicht in diesem Format
  switch (icp) {
   case 1200: break;				// nicht transkodieren
   case 1201: SwapBytes(pin2,fsize); break;	// Byte-Order tauschen
   default: {
    LPBYTE pin3=(LPBYTE)GlobalAlloc(GMEM_FIXED,fsize*2);	// bläht maximal Faktor 2 auf
    fsize=MultiByteToWideChar(icp,0,(LPCSTR)pin2,fsize,(LPWSTR)pin3,fsize)<<1;
    if (!fsize) err("Eingabetranskodiervorgang fehlgeschlagen, Fehlerkode %d!",GetLastError());
    GlobalFree(pin);
    pin=pin2=pin3;	// pin: zum Freigeben, pin2: zum Transkodieren
   }
  }
// Transkodierung zur Ziel-Kodeseitem wenn Ausgabedatei nicht in UTF-16
  switch (ocp) {
   case 1200: break;
   case 1201: SwapBytes(pin2,fsize); break;
   default: {
    DWORD allocsize=fsize+(fsize>>1);	// bläht maximal aufs 1,5fache auf
    LPBYTE pin3=(LPBYTE)GlobalAlloc(GMEM_FIXED,allocsize);
    fsize=WideCharToMultiByte(ocp,0,(LPCWSTR)pin2,fsize>>1,(LPSTR)pin3,allocsize,NULL,NULL);
    if (!fsize) err("Ausgabetranskodiervorgang fehlgeschlagen, Fehlerkode %d!",GetLastError());
    GlobalFree(pin);
    pin=pin2=pin3;
   }
  }
 }
// Datei-Ausgabe
 if (fout==fin) SetFilePointer(fout,0,NULL,FILE_BEGIN);
 DWORD bw;
 if (!no_marker) switch (ocp) {
  case CP_UTF8: WriteFile(fout,"\xEF\xBB\xBF",3,&bw,NULL); break;
  case 1200:    WriteFile(fout,"\xFF\xFE",2,&bw,NULL); break;
  case 1201:    WriteFile(fout,"\xFE\xFF",2,&bw,NULL); break;
 }
// DeleteOnError=NULL;	// Bei diesen Fehlern Dateibruchstück stehen lassen
 if (!WriteFile(fout,pin2,fsize,&bw,NULL) || bw!=fsize)
   err("Fehler beim Schreiben der Ausgabedatei! Platte voll?");
 if (fin==fout) WriteFile(fout,pin2,0,&bw,NULL);	// abschneiden
 if (fout!=stdout && !CloseHandle(fout))
   err("Fehler beim Schließen der Ausgabedatei! Platte voll?");
 ExitProcess(exitcode);
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded