Sostituzione o rimozione di una nuova riga con qualcos'altro ma solo tra virgolette singole o doppie utilizzando PHP su un file CSV

StackOverflow https://stackoverflow.com/questions/187076

  •  06-07-2019
  •  | 
  •  

Domanda

Ho un file CSV che contiene circa 200.000 - 300.000 record. La maggior parte dei record può essere separata e inserita in un database MySQL con un semplice

$line = explode("\n", $fileData);

e quindi i valori separati da

$lineValues = explode(',', $line);

e quindi inserito nel database utilizzando il tipo di dati appropriato, ovvero int, float, stringa, testo, ecc.

Tuttavia, alcuni dei record hanno una colonna di testo che include un \ n nella stringa. Che si interrompe quando si utilizza $ line = explode (" \ n " ;, $ fileData); metodo. Ogni riga di dati che deve essere inserita nel database ha circa 216 colonne. non tutte le righe hanno un record con un \ n nella stringa. Tuttavia, ogni volta che viene trovato un \ n nella riga viene racchiuso tra una coppia di virgolette singole (')

ogni riga è impostata nel seguente formato:

id,data,data,data,text,more data

Esempio:

1,0,0,0,'Hello World,0
2,0,0,0,'Hello
    World',0
3,0,0,0,'Hi',0
4,0,0,0,,0

Come puoi vedere dall'esempio, la maggior parte dei record può essere facilmente divisa con i metodi mostrati sopra. È il secondo record nell'esempio che causa il problema.

Le nuove righe sono solo \ n e il file non include affatto \ r nel file.

È stato utile?

Soluzione

Se i dati csv sono in un file, puoi semplicemente usare fgetcsv () come altri hanno sottolineato. fgetcsv gestisce correttamente le nuove linee incorporate.

Tuttavia, se i tuoi dati csv sono in una stringa (come $ fileData nel tuo esempio), il seguente metodo può essere utile poiché str_getcsv () funziona solo su una riga alla volta e non può dividere un intero file in record.

È possibile rilevare le nuove righe incorporate contando le virgolette in ciascuna riga. Se c'è un numero dispari di virgolette, hai una linea incompleta, quindi concatena questa linea con la seguente riga. Una volta che hai un numero pari di virgolette, hai un record completo.

Una volta che hai un record completo, dividilo tra virgolette (di nuovo usando explode ()). I campi con numeri dispari sono quotati (quindi le virgole incorporate non sono speciali), i campi con numeri pari non lo sono.

Esempio:

# Split file into physical lines (records may span lines)
$lines = explode("\n", $fileData);

# Re-assemble records
$records = array ();
$record = '';
$lineSep = '';
foreach ($lines as $line) {
  # Escape @ symbol so we can use it as a marker (as it does not conflict with
  # any special CSV character.)
  $line = str_replace('@', '@a', $line);

  # Escape commas as we don't yet know which ones are separators
  $line = str_replace(',', '@c', $line);

  # Escape quotes in a form that uses no special characters
  $line = str_replace("\\'", '@q', $line);
  $line = str_replace('\\', '@b', $line);

  $record .= $lineSep . $line;
  $lineSep = "\n";

  # Must have an even number of quotes in a complete record!
  if (substr_count($record, "'") % 2 == 0) {
    $records[] = $record;
    $record = '';
    $lineSep = '';
  }
}
if (strlen($record) > 0) {
  $records[] = $record;
}

$rows = array ();

foreach ($records as $record) {
  $chunks_in = explode("'", $record);
  $chunks_out = array ();

  # Decode escaped quotes/backslashes.
  # Decode field-separating commas (unless quoted)
  foreach ($chunks_in as $i => $chunk) {
    # Unescape quotes & backslashes
    $chunk = str_replace('@q', "'", $chunk);
    $chunk = str_replace('@b', '\\', $chunk);
    if ($i % 2 == 0) {
      # Unescape commas
      $chunk = str_replace('@c', ',', $chunk);
    }
    $chunks_out[] = $chunk;
  }

  # Join back together, discarding unescaped quotes
  $record = join('', $chunks_out);

  $chunks_in = explode(',', $record);
  $row = array ();
  foreach ($chunks_in as $chunk) {
    $chunk = str_replace('@c', ',', $chunk);
    $chunk = str_replace('@a', '@', $chunk);
    $row[] = $chunk;
  }
  $rows[] = $row;
}

Altri suggerimenti

L'altro consiglio qui è, ovviamente, valido, specialmente se si mira a scrivere il proprio parser CSV, tuttavia, se si desidera solo ottenere i dati, utilizzare fgetcsv () e non preoccuparti dei dettagli di implementazione.

che ne dici di scorrere manualmente i dati, dall'inizio alla fine, con un for-loop o due? È più lento di explode () , ma è più facile ottenere risultati coerenti e affidabili per quanto riguarda le virgolette.

Se scegli questo metodo, ricorda di prendere in considerazione le virgolette sfuggite.

Se ti venisse garantito che ogni nuova riga che inizia con un numero è una nuova riga valida (ovvero non nel mezzo di una descrizione testuale), puoi provare qualcosa di simile al seguente:

// Replace all new-line then id patterns with new-line 0+id
$line = preg_replace('/\n(\d)/',"\n0$1",$line);

// Split on new-line then id
$linevalues = preg_split("/\n\d/",$data);

Il primo passo identifica tutte le linee che hanno una nuova linea seguite da un valore numerico. Quindi antepone " 0 " a questo valore numerico. La seconda riga si divide dove trova una nuova riga e quindi un numero intero.

Lo " 0 " viene aggiunto all'inizio dell'id come preg_split rimuove i caratteri corrispondenti alle corrispondenze successive.

Come ho detto, funzionerà solo se sei sicuro che il testo che interrompe una riga non inizierà una nuova riga con un numero.

Usa fgetcsv e si occuperà di tutto ciò per te. A meno che non ci siano ragioni imperative per avere il tuo parser CSV.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top