From 64ec991ec86efb4fc6290b57a4db0f71f6a69931 Mon Sep 17 00:00:00 2001 From: 100yo Date: Sat, 25 Nov 2023 00:32:11 +0200 Subject: [PATCH] Add Lab 07 assignment --- 07-io-streams-and-files/lab/README.md | 270 ++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 07-io-streams-and-files/lab/README.md diff --git a/07-io-streams-and-files/lab/README.md b/07-io-streams-and-files/lab/README.md new file mode 100644 index 00000000..df36aab0 --- /dev/null +++ b/07-io-streams-and-files/lab/README.md @@ -0,0 +1,270 @@ +# CSV Processor :page_with_curl: + +Задачата тази седмица е да се имплементира прост converter на CSV таблици към Markdown такива. +Идеята е да се прочете един [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) файл, да се конвертира към Markdown таблица и впоследствие да се запише в [Markdown](https://en.wikipedia.org/wiki/Markdown) файл (файл с разширение .md). +За целта, CSV файлът трябва да отговаря на следните изисквания: +- Първият ред от файла съдържа имената на колоните от таблицата. +- Вторият ред е разделителен и съдържа информация за подравняването. +- Всеки следващ ред трябва да съдържа точно толкова на брой стойности, разделени от даден разделител, колкото са посочените колони в първия ред. + +### Формат на генерираната таблица + + Важно : Спазвайте стриктно инструкциите за форматиране на таблицата. Един символ невнимание може да доведе до +огромно количество фейлващи тестове. Интервал не означава произволен символ за whitespace! + +- Всеки ред започва и завършва с права черта `|` +- Преди всяка права черта (без първата) има **поне** един интервал. +- След всяка права черта (без последната) има **точно** един интервал. +- Броят символи на най-дългия елемент на колона ще приемаме за maxLength на колоната. За улеснение +приемаме, че maxLength е минимум 3. +- Тогава всяка една колона има следния формат `|\s.......\s|` където `\s` е интервал, а броят на . е равен на maxLength. +- Ако данните в една колона са по-кратки от maxLength, допълваме интервали, а за втори ред `-` +- Вторият ред се състои от същия на брой колони, но са запълнени с тирета. Броят на тиретата отговаря на дължината на най-дългата данна от съответната колона. +- Вторият ред носи информация за подравняване на всяка една от колоните - ляво, дясно, централно. За да подравним даден ред, слагаме +символа ':' на страната, на която искаме да е подреден, а за централно и от двете. Символът заема мястото на едно тире `-`. Пример : + - `| :---------- |` за подравняване вляво + - `| ----------: |` за подравняване вдясно + - `| :---------: |` за подравняване в центъра + - `| ----------- |` за подравняване по подразбиране +- Първият ред съдържа имената на колоните +- Всеки следващ ред съответства на ред данни от оригиналния файл, форматирани по описания начин +- Краят на таблицата е обработеният последен ред от CSV файла. **НЯМА** допълнителен празен ред след него. CSV файлът също не завършва с празен ред. +- Hint: Когато парсвате редовете по зададен разделител, обърнете внимание, че [```String.split```](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html#split(java.lang.String)) функцията приема не низ, +а регулярен израз за разделител. Съответно, някои символи имат специално значение и трябва да бъдат escape-нати `\\.`. +[*ТУК*](https://www.threesl.com/blog/special-characters-regular-expressions-escape/) можете да намерите списък на всички такива символи. За добро ескейпване, можем да смятаме, че в началото на +разделителя слагаме стринга `"\\Q"`, а в края му - `"\\E"`. Тези два низа означават, че всичко, написано между тях, се интерпретира +като текст, а не като regex. + +Пример за правилно форматиране: + +CSV: +``` +hdr,testheader,z +testcolumn,b,c +``` + +Markdown: +``` +| hdr | testheader | z | +| ---------: | :--------- | :-: | +| testcolumn | b | c | +``` + +*Пояснение: Всяка колона започва със задължителен интервал. За първата колона, maxLength е равно на броя символи в testcolumn - 10. +Искаме всеки запис от тази колона да има точно 10 + 2 (интервал в начало и край) символа. Следователно, за да запазим подравняването, добавяме 10 - 3 (дължината на hdr) интервала към него + 1 задължителен, който има преди всяка права черта. За втория ред добавяме +низ, състоящ се от тирета и двуеточия, по начина описан по-горе. Неговият размер трябва да е равен на maxLength.* +

+*Пояснение 2: В третата колона се вижда изискването за минимален брой символи(3).* + +### CSVProcessor + +В пакета `bg.sofia.uni.fmi.mjt.csvprocessor` създайте публичен клас `CsvProcessor`, който има конструктори + +```java +public CsvProcessor() { + this(new Table()); +} + +public CsvProcessor(Table table) { + this.table = table; +} +``` + +и имплементира интерфейса `CsvProcessorAPI`: + +```java +package bg.sofia.uni.fmi.mjt.csvprocessor; + +import bg.sofia.uni.fmi.mjt.csvprocessor.exceptions.CsvDataNotCorrectException; +import bg.sofia.uni.fmi.mjt.csvprocessor.table.printer.ColumnAlignment; + +import java.io.Reader; +import java.io.Writer; + +public interface CsvProcessorAPI { + + /** + * Reads a CSV data from Reader + * @param reader the Reader from which the CSV will be read + * @param delimiter the delimeter used to split the CSV (such as ,.- and so on) + * @throws CsvDataNotCorrectException if the CSV data is in wrong format + */ + void readCsv(Reader reader, String delimiter) throws CsvDataNotCorrectException; + + /** + * Writes the content of the table to the provided Writer + * @param writer - the Writer to which the table will be written + */ + void writeTable(Writer writer, ColumnAlignment... alignments); + +} +``` + +### Table + +Прочетените данни се съхраняват в таблици, състоящи се от редове и колони. +В пакета `bg.sofia.uni.fmi.mjt.csvprocessor.table` създайте публичен клас `BaseTable`, който имплементира следния интерфейс: + +```java +package bg.sofia.uni.fmi.mjt.csvprocessor.table; + +import bg.sofia.uni.fmi.mjt.csvprocessor.exceptions.CsvDataNotCorrectException; + +import java.util.Collection; + +public interface Table { + + /** + * Adds data to the table. If the table doesn't have any columns, + * the data parameter is interpreted as the table headers. + * If the table contains columns, then the data parameter is interpreted as a row of data. + * @param data - the data to be added + * @throws CsvDataNotCorrectException - if the data is in incorrect format - if the count of + * the provided data parts is less or more than the number of columns in the table + * @throws IllegalArgumentException if data is null + */ + void addData(String[] data) throws CsvDataNotCorrectException; + + /** + * Returns unmodifiable collection of the names of all columns + */ + Collection getColumnNames(); + + /** + * Returns unmodifiable collection of all strings in a specific column + * @param column - the column for which the data will be returned + * @throws IllegalArgumentException is column is null or blank or there is no corresponding column with that name + * in the table + */ + Collection getColumnData(String column); + + /** + * Returns the total count of all rows. + */ + int getRowsCount(); + +} +``` + +В пакетa `bg.sofia.uni.fmi.mjt.csvprocessor.table.column` създайте публичен клас `BaseColumn`, чрез който се моделират колоните на таблиците. Той трябва да имплементира интерфейса `Column`: + +```java +package bg.sofia.uni.fmi.mjt.csvprocessor.table.column; + +import java.util.Collection; + +public interface Column { + + /** + * Adds new data string to the column + * @param data - the string to be added + * @throws IllegalArgumentException if data is null or blank + */ + void addData(String data); + + /** + * Returns an unmodifiable collection of all data stored in the column + */ + Collection getData(); +} +``` + +и да има следните конструктори: + +```java + public BaseColumn() { + this(new HashSet<>()); + } + + public BaseColumn(Set values) { + this.values = values; + } +``` + +### TablePrinter + +За да е точно представянето на таблицата, създайте и клас `MarkdownTablePrinter`, +който се грижи за правилното форматиране на таблицата преди принтиране. +Той имплементира интерфейса `TablePrinter`: + +```java +package bg.sofia.uni.fmi.mjt.csvprocessor.table.printer; + +import bg.sofia.uni.fmi.mjt.csvprocessor.table.Table; + +import java.util.Collection; + +public interface TablePrinter { + + /** + * Returns unmodifiable collection of strings, each one representing a formatted single row that needs to be printed + * @param table - the table to be printed + * @param alignments - the applied alignments for columns; if the number of given alignments is + * smaller than the number of columns, the remaining columns have NOALIGNMENT. If it's more, ignore the extra ones. + */ + Collection printTable(Table table, ColumnAlignment... alignments); + +} +``` + +Подреждането на таблицата се моделира от enum-а `ColumnAlignment` + +```java +package bg.sofia.uni.fmi.mjt.csvprocessor.table.printer; + +public enum ColumnAlignment { + LEFT(1), + + CENTER(2), + + RIGHT(1), + + NOALIGNMENT(0); + + private final int alignmentCharactersCount; + + ColumnAlignment(int count) { + this.alignmentCharactersCount = count; + } + + public int getAlignmentCharactersCount() { + return alignmentCharactersCount; + } +} +``` + +### Тестване + +Валидирайте решението си чрез unit тестове. + +### Пакети + +Спазвайте имената на пакетите на всички по-горе описани типове, тъй като в противен случай, решението ви няма да може да бъде автоматично тествано. + +``` +src +└── bg.sofia.uni.fmi.mjt.csvprocessor + ├── CsvProcessor.java + ├── CsvProcessorAPI.java + ├── exceptions + │ └── CsvDataNotCorrectException.java + └── table + ├── BaseTable.java + ├── Table.java + ├── column + │ ├── BaseColumn.java + │ ├── Column.java + │ └── (...) + └── printer + ├── ColumnAlignment.java + ├── MarkdownTablePrinter.java + ├── TablePrinter.java + └── (...) +test +└── bg.sofia.uni.fmi.mjt.csvprocessor + └── (...) +``` + +### :warning: Забележки + +- Не променяйте по никакъв начин интерфейсите, дадени в условието.