Ho finito di sistemare il tutto, la funzione che ti posto vuole in ingresso un contenuto UTF-16 e ti butta fuori un contenuto UTF-8
Non supporta il formato BIG ENDIAN cosa che verifica da solo se c'è il BOM ... ovviamente se gli viene passato il BOM se ne accorge e lo salta
codice:
function Utf16ToUtf8($Utf16Sequence, $Offset = 0, $Length = -1)
{
// Verifica se la tabella ascii delle conversioni è stata già inizializzata
if (isset($GLOBALS['__AsciiTableCHR']) === false)
{
$GLOBALS['__AsciiTableCHR'] = array();
$GLOBALS['__AsciiTableORD'] = array();
for($i = 0; $i < 256; $i++)
{
$char = chr($i);
$GLOBALS['__AsciiTableCHR'][$char] = $i;
$GLOBALS['__AsciiTableORD'][$i] = $char;
}
}
// Inizializza la sequenza di uscita
$utf8Sequence = '';
// Acquisisce la lunghezza della sequenza
$utf16SequenceLength = strlen($Utf16Sequence);
// Inizializza l'indice dei byte
$byteIndex = 0;
// Verifica se è presente il BOM in formato Little Endian
if ($Utf16Sequence{0} == chr(0xFF) && $Utf16Sequence{1} == chr(0xFE))
{
$byteIndex = 2;
}
// Verifica se è presente il BOM in formato Big Endian
else if ($Utf16Sequence{0} == chr(0xFE) && $Utf16Sequence{1} == chr(0xFF))
{
die('Big Endian encoding not supported');
}
// Inizializza i counter
$offsetCounter = 0;
$lengthCounter = 0;
// Cicla la sequenza
for(; $byteIndex < $utf16SequenceLength; $byteIndex += 2)
{
// Acquisisce il valore numerico del primo e del secondo byte
$firstByte = $GLOBALS['__AsciiTableCHR'][$Utf16Sequence[$byteIndex]];
$secondByte = $GLOBALS['__AsciiTableCHR'][$Utf16Sequence[$byteIndex + 1]];
// Verifica se il byte corrente inizia con 110110xx
if (($firstByte & 0xD8) == 0xD8)
{
// Acquisisce il terzo byte
$thirdByte = $GLOBALS['__AsciiTableCHR'][$Utf16Sequence[$byteIndex + 2]];
// Verifica se la maschera DC da un risultato positivo
if (($thirdByte & 0xDC) == 0xDC)
{
die('Unsupported four bytes ssequence starting at offset ' . $byteIndex . '!');
/**
* UNSUPPORTED!
*
// Legge il quarto byte
$fourthByte = $GLOBALS['__AsciiTableCHR'][$Utf16Sequence[$byteIndex + 3]];
// Costruisce la sequenza UTF8
$utf8Sequence .= chr(0xF0 + ($firstByte & 0x03));
$utf8Sequence .= chr(0x80 + ($secondByte & 0x7C));
$utf8Sequence .= chr(0x80 + (($secondByte & 0x03) << 4) + (($thirdByte & 0x03) << 2) +
$fourthByte & 0xC0));
$utf8Sequence .= chr(0x80 + ($fourthByte & 0xFC));
// Dopo aver effettuato le operazioni fa un salto aggiuntivo di 2 byte
// nella sequenza
$byteIndex += 2;
// Passa alla sequenza successiva
continue;
*
**/
}
}
// Incrementa il counter dell'offset
$offsetCounter++;
// Verifica se deve leggere il carattere
if ($offsetCounter < $Offset)
{
// Passa al blocco successivo
continue;
}
// Se l'esecuzione arriva qui la sequenza è formata solo da 2 byte quindi verifica
// quale range è quello corretto per la codifica, in questo caso è il range ascii
// (ovvero 0xxxxxxx)
if ($secondByte == 0 && ($firstByte & 0x80) == 0)
{
$utf8Sequence .= $GLOBALS['__AsciiTableORD'][$firstByte & 0x7F];
}
// Verifica se questo è il range di caratteri che va da 0x000080 a 0x0007FF, ovvero se
// i primi 5 bit del primo byte sono pari a zero (110xxxxx 10xxxxxx)
else if (($secondByte & 0xF8) == 0)
{
$utf8Sequence .= $GLOBALS['__AsciiTableORD'][0xC0 + (($secondByte & 0x07) << 2) +
(($firstByte & 0xC0) >> 6)];
$utf8Sequence .= $GLOBALS['__AsciiTableORD'][0x80 + ($firstByte & 0x3F)];
}
// Questo è il range di caratteri che va da 0x000800 - 0x00FFFF (1110xxxx 10xxxxxx 10xxxxxx)
else
{
$utf8Sequence .= $GLOBALS['__AsciiTableORD'][0xE0 + (($secondByte & 0xF0) >> 4)];
$utf8Sequence .= $GLOBALS['__AsciiTableORD'][0x80 + (($secondByte & 0x0F) << 2) +
(($firstByte & 0xC0) >> 6)];
$utf8Sequence .= $GLOBALS['__AsciiTableORD'][0x80 + ($firstByte & 0x3F)];
}
// Incrementa il counter della lunghezza
$lengthCounter++;
// Verifica se ha letto tutti i caratteri che dovevano essere letti
if ($Length == $lengthCounter)
{
// Interrompe il ciclo di analisi
break;
}
}
// Restituisce la sequenza
return $utf8Sequence;
}
Non so a livello di performance com'è messa, ho fatto dei test su 100kb di dati e ci sta circa 0.1s (51870 caratteri da convertire per la precisione con gli ultimi test che ho fatto)
purtroppo fa un sacco di lavoro inutile perché php non mi fa applicare le maschere ad i singoli caratteri pur essendo per lui un singolo byte in memoria e quindi prima mi tocca convertirli in numero
Se si accelera quell'operazione e l'operazione inversa (ad esempio tramite una tabella) sicuramente ci sarà un recupero di velocità
UPDATE
Ho variato il codice in modo da fargli creare 2 tabelle di conversione da indice a carattere e viceversa ... cosi il tempo di esecuzione è sceso a 0.08 ... non c'è stato un cambiamento abbissale però se la conversione deve essere eseguita più volte ora è più veloce.
In aggiunta ho anche implementato il codice necessario per iniziare a leggere da un dato carattere ed il codice necessario per leggere N caratteri
mmm per sapere quanti caratteri sono presenti indicativamente puoi fare la lunghezza del file / 2 ... nel caso che è presente il BOM prima di dividere devi togliere 2 byte. Nel caso in qui ci sono sequenze da 4 byte ti sballa il calcolo