Парсер Java с несколькими символами в виде кавычек или поддержкой разделителя строк

Существует ли какая-либо библиотека с открытым исходным кодом Java, которая поддерживает цитирование строки с помощью нескольких символов (т. Е. Строка с длиной › 1) для файла с разделителями?

Большинство парсеров с разделителями/CSV поддерживают один символ в качестве кавычки (например, или ' ). Но есть требование читать файл с разделителями, передавая многосимвольный символ в кавычки. Или мы можем назвать это строковым окружением или строковым разделителем. Один из вариантов использования: в Azure мы можем передавать данные из Azure Synapse Analytics в Gen2 через Poly base SQLs. При этом мы можем указать только два варианта формата файла: FIELD_TERMINATOR (разделитель полей) и STRING_DELIMITER (символ кавычки). Они не будут экранировать данные при записи в Gen2.

Если ваши данные в DW


Col1    Col2    Col3
1   Anneke  Preusig
2   Georgi  Facello

CREATE EXTERNAL FILE FORMAT file.CSV
WITH (FORMAT_TYPE = DELIMITEDTEXT,
      FORMAT_OPTIONS(
          FIELD_TERMINATOR = ',',
          STRING_DELIMITER = '|XYZ|',
);

Файл выглядит так

1,|XYZ|Anneke|XYZ|,|XYZ|Preusig|XYZ|
2,|XYZ|Georgi|XYZ|,|XYZ|Facello|XYZ|

Поэтому я ищу общую библиотеку Java для чтения этого файла. Исходные данные содержат все виды данных/символов, поэтому трудно идентифицировать один символ как символ кавычки, и при записи таких файлов не происходит экранирования.


person Shrikrishna Bhat    schedule 08.07.2020    source источник


Ответы (1)


Используя Java Streams API, следующее может работать, если вы можете гарантировать, что FIELD_TERMINATOR не используется ни в STRING_DELIMITER, ни в любом из ваших значений.

Path source = Paths.get("Your File");
char terminator = ',';
String delimiter = "|XYZ|";
String[][] parsed = Files.lines(source).map(l->
  Stream.of(l.split(""+terminator)).map(s->
    s.matches(delimiter+".*"+delimiter)?s.substring(delimiter.length(),s.length()-delimiter.length()):s
  ).toArray(String[]::new)
).toArray(String[][]::new);

Это решение разбивает каждую строку на свои поля и проверяет, окружено ли какое-либо заданное значение вашим разделителем, и соответственно удаляет его.

Редактировать:

Для многострочных данных я написал небольшой класс, который на основе Predicate<String[]> решает, завершена ли строка данных.

package com.example.parser;

import java.util.stream.*;
import java.nio.file.*;
import java.io.IOException;
import java.util.*;
import java.util.function.*;

public class FileIterator implements Iterator<String> {

  private String terminator;

  private Predicate<String[]> complete;

  private Iterator<String> source;

  private String[] buffer;
  private int pointer;

  public FileIterator(Iterator<String> source, String terminator, Predicate<String[]> complete) {
    this.source = source;
    this.terminator = terminator;
    this.complete = complete;
  }

  public static FileIterator from(Path path, String terminator, Predicate<String[]> complete) throws IOException {
    return new FileIterator(Files.lines(path).iterator(),terminator,complete);
  }

  public Stream<String> asStream() {
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(this, Spliterator.DISTINCT), false);
  }

  @Override
  public boolean hasNext() {
    return source.hasNext() || pointer < len(buffer);
  }

  @Override
  public String next() {
    if(pointer < len(buffer)) {
      return buffer[pointer++];
    } else {
      buffer = source.next().split(terminator);
      while(!complete.test(buffer)) {
        buffer = concat(buffer,source.next().split(terminator));
      }
      pointer = 1;
      return buffer[0];
    }
  }

  private static String[] concat(String[] b1, String[] b2) {
    if(b1 == null) return b2;
    if(b2 == null) return b1;
    String[] r = new String[b1.length+b2.length-1];
    for(int i = 0; i < r.length; i++) {
      if(i < b1.length-1) r[i] = b1[i];
      else if(i == b1.length-1) r[i] = b1[i] + '\n' + b2[0];
      else r[i] = b2[i-b1.length+1];
    }
    return r;
  }

  private static int len(String[] b) {
    return b == null ? 0 : b.length;
  }

}

Метод next() работает путем создания буфера элементов текущей строки, повторно запрашивая предикат о том, закончен он или нет, а затем очищая его при последовательных вызовах. Предполагаемый вариант использования выглядит следующим образом:

FileIterator.from(Paths.get("yourFile"),",",(s)->yourWayOfDeterminingWetherARowIsTerminated(s)).asStream();

Элементы, разделенные новой строкой, также соединяются с помощью символа \n. Однако этот метод не удаляет строковые кавычки, но это можно сделать аналогичным образом, как описано выше. Также элементы возвращаются по отдельности, но реализация может быть изменена, чтобы возвращать весь буфер, чтобы соответствовать приведенному выше примеру. Чтобы проверить, завершена ли ваша строка, я предлагаю посмотреть регулярное выражение, которое идентифицирует ваш незавершенный адрес.

  @Override
  public String[] next() {
    String[] buffer = source.next().split(terminator);
    while(!complete.test(buffer)) {
      buffer = concat(buffer,source.next().split(terminator));
    }
    return buffer;
  }
person Severin Nitsche    schedule 08.07.2020
comment
Спасибо за быстрый ответ. Это было полезно, но проблема теперь с новой строкой данных. Если у нас есть столбец с многострочными данными (например, адрес), это не сработает. - person Shrikrishna Bhat; 09.07.2020
comment
@ShrikrishnaBhat Я не знаю, как отформатированы ваши адреса, но я надеюсь, что редактирование поможет - person Severin Nitsche; 09.07.2020
comment
Спасибо Северин Ницше за всю помощь здесь и приводит. Возьму это отсюда и что лучше всего можно сделать - person Shrikrishna Bhat; 14.07.2020