Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.
Avatar
Martin Štefánik:26.10.2018 0:17

Ahoj, naskytl se mi problém při tvorbě textového editoru. Mám za úkol, aby si program bral jako vstup (stdin) příkazy z textového souboru (které budu mít v programu zadány/definovány). A upravovaný text bude opět v textovém souboru. Zápis, výpis u souborů chápu, ale tady nějak nedokážu pobrat jak propojit tyto dva soubory, aby soubor s textem reagoval na každý jeden příkaz (definované v programu) v souboru s příkazy.

Stdout je obrazovka, ne "upravovaný" soubor.

Zkusil jsem: Zapsat obojí stejně, jako soubor ke čtení mi přijde zvláštní, protože od každého souboru očekávám něco jiného. Ke čtení se určitě zapíše soubor s textem k úpravě, ale na soubor s příkazy zatím nemohu přijít.

 
Odpovědět
26.10.2018 0:17
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Martin Štefánik
DarkCoder:26.10.2018 13:50

Velice pěkná a praktická úloha. Zejména ta část, v níž se přebírá a zpracovává načtený příkaz. Propojovacím můstkem mezi soubory je tvá aplikace. Stejně tak aplikace je tím co má reagovat na příkaz, nikoli soubor.

Oba soubory nactes pro čtení. Natáhnes obsah souboru určeného k úpravě do paměti. Postupně načítas příkazy ze souboru, které zpracovávas dle toho co mají delat. Výsledky příkazů pak můžeš ale i nemusíš opisovat na obrazovku. Nakonec z paměti vypíše upravený text na obrazovku.

Příkazy načítas postupně dokud nejsou předchozí vyhodnoceny. Nactes celý řádek popřípadě celý blok do pameti. Provedes vyhodnocení korektnosti příkazu a pokud je úspěšná, příkaz provedes a nactes nový, dokud není konec souboru nebo nenactes ukončovací instrukci.

Uvědom si že příkaz je složena konstrukce a tudíž ho musíš rozfázovat a jeho části přiřadit tam kde mají být.

Nahoru Odpovědět
26.10.2018 13:50
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Martin Štefánik:27.10.2018 14:23

Zatím se mi povedlo nějak začít, přikládám zdroják a dotazy co stále nemohu pochopit.

  1. jak propojit (ošetřit) maximální délky souborů (znaků) s definovaným polem
  2. jestli je zápis pro práci se soubory udělán správně. Head chci vypisovat, momentálně se vypíše celý, až přidám definované funkce, bude upravený.
  3. Jak si ty funkce definovat? Definovat je mimo main? Nejsem si pak jistý správnou editací textu, jak tyto funkce zakomponovat do vypisovaného (editovaného( textu.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*int c;

void vymaz_radek(char )
{
    }
}
*/
int main(int argc, char *argv[])
{
    argv[0]="vstup.txt";

    int c, i=0;

char prikaz[100]; //max pocet prikazu - jeden na radek

char radek[1000]; // max pocet znaku na radek

FILE *fr;
if((fr = fopen("head.txt", "r")) == NULL){ //pojistka spusteni souboru
    printf("Soubor head.txt se nepodarilo otevrit.\n");
    return;
}

while((c=getc(fr)) != EOF){ //vypisovani znaku z head.txt
        putchar(c);
        i++;
        if(i>radek){
            return;
        }
}
/*
if((fr=fopen("vstup.txt", "r")) == NULL){
    printf("Soubor vstup.txt se nepodarilo otevrit.\n");
    return;
}
while((c=getc(fr)) == EOF){
    i++;
    if(i>prikaz){
        return;
    }
}
*/

 //otevreni prikazu

//vymaz_radek(radek);


    return 0;
}
 
Nahoru Odpovědět
27.10.2018 14:23
Avatar
Martin Štefánik:27.10.2018 19:35

Tak momentálně mám problém v tom, že nevím jak poskládat ty soubory do sebe. V podstatě si musím vytvořit dva cykly vložené do sebe. V prvním While budu načítat soubor s textem >>

while(fgets (radek, sizeof(radek), fr) != NULL){

A pod to má spadat druhý while, kde si mám načíst příkazy, ovšem nemám tušení jak. Jakmile sem se je snažil načíst, tak mi v podstatě ukončí první cyklus.

Pod tyto dva whily budou spadat ostatní definované funkce, kdy se snažím reagovat na určité písmeno v souboru s příkazy (např. d = vymaž řádek).

 
Nahoru Odpovědět
27.10.2018 19:35
Avatar
Odpovídá na Martin Štefánik
Matúš Olejník:27.10.2018 22:32

Ahoj, čo povieš na takýto nejaký "template"? Je to narýchlo napísané tak to môžme prípade nejako dokončiť.
instructions.txt môže vyzerať takto napr.

print
append
print
save
fwanfkja
exit

a kód

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define INSTRUCTIONS_FILE "instructions.txt"
#define TEXT_FILE "text.txt"

#define INST_PRINT 1
#define INST_APPEND 2
#define INST_SAVE 3
#define INST_EXIT 0
#define INST_ERROR -1

int getInstruction(char *line);
void printFile(FILE *file);
void append(FILE *file);
void saveFile(FILE *file);
void exitProgram(FILE *instrctionFile, FILE *textFile);
int continueOrExit(char *instruction);


int getInstruction(char *line) {
    if (line[strlen(line) - 1] == '\n') {
        line[strlen(line) - 1] = '\0';
    }

    if (strcmp(line, "print") == 0) {
        return INST_PRINT;
    }
    if (strcmp(line, "append") == 0) {
        return INST_APPEND;
    }
    if (strcmp(line, "save") == 0) {
        return INST_SAVE;
    }
    if (strcmp(line, "exit") == 0) {
        return INST_EXIT;
    }

    return INST_ERROR;
}

void printFile(FILE *file) {
    char buffer[256];

    rewind(file);

    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%s", buffer);
    }
}

void append(FILE *file) {

}

void saveFile(FILE *file) {

}

void exitProgram(FILE *instrctionFile, FILE *textFile) {
    //todo kontrola
    fclose(instrctionFile);
    fclose(textFile);
}

int continueOrExit(char *instruction) {
    char answer;

    printf("Pre instrukciu %s nie je definovana ziadna akcia\n", instruction);
    printf("Prajete si pokracovat vo vykonavani dalsich instrukcii? [A/N]: ");

    scanf(" %c", &answer);

    return tolower(answer) == 'a' ? 1 : 0;
}

int main() {
    FILE *instructionsFile, *textFile;
    char lineBuffer[256];
    int stop = 0;

    //todo kontrola
    instructionsFile = fopen(INSTRUCTIONS_FILE, "r");
    textFile = fopen(TEXT_FILE, "r");

    while (!stop) {
        if (fgets(lineBuffer, sizeof(lineBuffer), instructionsFile) == NULL) {
            printf("Nenacitala sa ziadna dalsia instrukcia\n");
            break;
        }
        int instruction = getInstruction(buffer);
        switch (instruction) {
            case INST_PRINT:
                printFile(textFile);
                break;
            case INST_APPEND:
                append(instructionsFile);
                break;
            case INST_SAVE:
                saveFile(textFile);
                break;
            case INST_EXIT:
                exitProgram(instructionsFile, textFile);
                stop = 1;
                break;
            case INST_ERROR:
                if (continueOrExit(lineBuffer) == 0) {
                    exitProgram(instructionsFile, textFile);
                    stop = 1;
                }
                break;
        }
    }

    getchar();
    getchar();

    return 0;
}
Nahoru Odpovědět
27.10.2018 22:32
/* I am not sure why this works but it fixes the problem */
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Martin Štefánik
DarkCoder:28.10.2018 2:14

Odpovědi k dotazům a pár připomínek k přiloženému úryvku zdrojového kódu:

  1. Ošetření velikosti pole vzhledem k velikosti pole řešíš pouze u souboru obsahující text k editaci. Aby se Ti obsah souboru do pole vešel a zároveň velikost pole nebyla zbytečně předimenzována, je třeba pole vytvořit dynamicky nikoli staticky. Následující body představují veškeré úkony spojené se souborem obsahující text k editaci.

Otevření souboru pro čtení (ideálně binární mód, ve kterém nedochází k transformaci znaku CR a LF pro urční velikosti souboru. A dále využití efektivního načtení celého bloku pomocí funkce fread().
Zjištění délky souboru.
Dynamická alokace paměti (vytvoření pole pro uchování celého obsahu souboru) o velikosti délky souboru + 1 (null znak).
Načtení celého obsahu souboru pomocí fread() do pole jeho začátek je dán ukazatelem vytvořeným v přechozím bodu.
Uzavření souboru.

Po těchto úkonech máš veškerý obsah souboru uložen v paměti a dále už se souborem nepracuješ. Na rozdíl od souboru obsahující instrukce který používáš po celou dobu programu dokud nedošla instrukce pro ukončení nebo nebyl načten konec souboru.

  1. Ze souboru na obrazovku nic nevypisuješ. Veškerý výpis na obrazovku se děje při zpracování instrukcí a při výpisu obsahu dynamicky alokovaného pole na konci programu. Výpis obsahu souboru znak po znaku je pomalé, využívej výhody bufferování. Dále namísto EOF používej funkce feof() a ferror() které Ti přesně určí jaká událost nastala. EOF může představovat jak chybu tak konec souboru. Při testování úspěšného otevření souboru při neúspěchu používej funkci exit() s návratovou hodnotou, definovanou ve stdlib.h, nikoli return bez vrácené hodnoty.
  2. Velmi pěkně, jak definovat instrukce, zde prezentoval Matúš Olejník. Instrukce však budou parametrizované. Ty si pak musíš vytvořit v programu instrukční prototypy (jméno instrukce, druh instrukce, počet parametrů, typy parametrů, atd). a načtenou instrukci otestovat. Můžeš dostat tři výsledky - instrukce je správná, nebo CompileError a nebo RunTimeError. Dále pak ze seznamu instrukcí volat danou instrukci a přiřadit ji správně parametry. Po zpracování instrukce načíst ze souboru s instrukcemi další instrukci. Tento bod je na celé aplikaci to nejnáročnější, ale zároveň nejzajímavější.

Nepřepisuj hodnoty zadané na příkazovém řádku. Pracuj s hodnotami uloženými v daném indexu argv. Aplikace bude spouštěna se třemi argumenty (jméno aplikace, název souboru s textem, název souboru s instrukcemi).

Např: TextEdit.exe input.txt commands.txt

Příkazy ze soubory všechny naráz nenačítáš, ale pro zvýšení efektivity můžeš. To můžeš vytvořit pomocí dynamicky alokovaného pole řetězců na které před spuštěním první instrukce můžeš aplikovat test kompilace. RuntimeError však v tento okamžik neodchytíš.

Pro představu, jak mohou vypadat parametrizované instrukce:

REVERSE ALL - otočí obsah celého pole
REVERSE 3 10 - otočí obsah pole na intervalu
REPLACE a b - nahradí znak a znakem b
SWAP 2 10 - prohodí druhé s desátým znakem
EXIT - ukončí čtení instrukcí a vypíše aktuální obsah
REMOVE a - vymaže znak a
ADD * test - přidá slovo "test" za každou hvězdičku
PRINT COUNT a 10 30 - vypíše počet znaků a v daném intervalu
atd.

Nahoru Odpovědět
28.10.2018 2:14
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovídá na Matúš Olejník
Martin Štefánik:28.10.2018 20:03

Moc děkuji za návrh. Ovšem na mě jsou tam složité funkce a docela mi dává zabrat vše v tom kódu pochopit.

Konkrétně třeba tuhle část. Strlen a strcmp jsem si vyhledal, ale nevím jestli jsem o něco moudrejší.

int getInstruction(char *line) {
    if (line[strlen(line) - 1] == '\n') {
        line[strlen(line) - 1] = '\0';
    }

    if (strcmp(line, "print") == 0) {
        return INST_PRINT;
    }
    if (strcmp(line, "append") == 0) {
        return INST_APPEND;
    }
    if (strcmp(line, "save") == 0) {
        return INST_SAVE;
    }
    if (strcmp(line, "exit") == 0) {
        return INST_EXIT;
    }

    return INST_ERROR;
}

Potom tam máš "předpřipravené" funkce na definování. Přičemž funkci print sem pochopil, že již máš hotovou. Akorát tam nedokážu pochopit jak ji budeš volat v tom cyklu.

case INST_PRINT:
                (textFile);
                break;

A ještě se chci zeptat, co znamená

while (!stop)

To je to samé jako...?

while(!= NULL)

Každopádně moc děkuji, dost to pomohlo :).

 
Nahoru Odpovědět
28.10.2018 20:03
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Martin Štefánik
DarkCoder:28.10.2018 21:19

Funkce fgets() načítá řetězec znaků. Při načítání může docházet v případě, že načítaný řetězec je kratší než velikost pole, přidávání znaku nového řádku. Podmínka zjišťuje, zda-li se tam znak nového řádku vyskytuje a pokud ano, nahrazuje ho null znakem, který představuje konec řetězce. To je důvod, proč tam tato podmínka je.

Další podmínky představují to, jaká instrukce byla načtena. Porovnání řetězce s modelem instrukce. Každá instrukce je definována pod určitou konstantou (instrukce preprocesoru #define). Toto číslo je pak funkcí vráceno. Následný switch obsahuje všechny možné instrukce. Zde už jsou instrukce volány a to na základě vrácené konstanty.

while(!stop) představuje hlavní smyčku programu. Pokud je stop pravdivá, dojde k jeho ukončení. Změna hodnoty stop na pravdivou může docházet při chybové instrukci nebo když je volána instrukce pro ukončení. Není to stejné jako != NULL. Je to proměnná která řídí hlavní smyčku. !stop bude pravdivá, když stop bude nula, tedy nezachycení instrukce pro ukončení, ať už při chybě nebo při ukončovací instrukci.

Nahoru Odpovědět
28.10.2018 21:19
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Martin Štefánik:30.10.2018 10:05

A proč to v momentální situaci neustále vypisuje "Pre instrukciu %s nie je definovana ziadna akcia"? I přes to, že si ji definuji, konkrétně pro printf již definovaná je.

 
Nahoru Odpovědět
30.10.2018 10:05
Avatar
Odpovídá na Martin Štefánik
Matúš Olejník:30.10.2018 10:35

No, zo súboru máš spracovávať nejaké tebou dané inštrukcie či uz zložitejšie ako napísal DarkCoder alebo jednoduchšie ako som uviedol ja (print, append, atď) a implementoval ich v kóde.

V metóde getInstruction porovnávam inštrukciu ktorá príde zo súboru s mojimi implementovanými a ak sa niektoré zhodujú tak sa vráti číslo 0, 1, 2 alebo 3 a inak sa vrati -1. Takže ak máš v súbore na niektorom riadku napísané napr. print tak funkcia getInstruction vráti číslo 1 a v maine sa zavolá metóda na výpis súboru. Ak však máš v súbore napr. reverse tak to sa nezhoduje s ničím čo v getInstruction kontrolujem tak sa vráti hodnota -1 a program vypíše že pre takú inštrukciu nie je nič definované

Editováno 30.10.2018 10:35
Nahoru Odpovědět
30.10.2018 10:35
/* I am not sure why this works but it fixes the problem */
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Martin Štefánik
DarkCoder:30.10.2018 11:14

Chyba je ve volání funkce getInstruction(), respektive ve jménu argumentu.

Řádek:

int instruction = getInstruction(buffer);

Nahraď tímto:

instruction = getInstruction(lineBuffer);

Proměnnou instruction deklaruj na začátku programu v místě kde se deklarují všechny ostatní proměnné. Pokud používáš starší překladač byla by deklarace proměnné v tomto místě chybná. Od C11 je to však v pořádku.

Nahoru Odpovědět
30.10.2018 11:14
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovídá na Matúš Olejník
Martin Štefánik:30.10.2018 12:03

No a jak bych zapsal například funkci pro tisknutí jednoho řádku? Tedy místo printfile, pouze tisknout současný řádek na kterém se onen pokyn nachází ->> na prvním řádku souboru s textem je něco napsáno, na prvním řádku souboru s pokyny je pokyn k tisknutí aktuálního řádku. Poté následují další příkazy, například druhý řádek odhození o 5 znaků do prava, smazání prvního znaku řádku atd...

 
Nahoru Odpovědět
30.10.2018 12:03
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Martin Štefánik
DarkCoder:30.10.2018 13:52

Nelehká instrukce. Každý úspěšně otevřený soubor má svůj ukazatel nastaven na začátek. Takže pokud by pokyn pro tisk aktuálního řádku byl jako první instrukce, pak by si tisknul znaky až dokud nenarazis na znak nového řádku. Ale instrukce musí fungovat pro jakýkoli řádek. Zjištění aktuálního řádku vychází z předchozí instrukce. Dost instrukci může končit na konci souboru, když například zjišťujeme počet konkrétního znaků v souboru. Což nemusí být zrovna to co se očekává. Instrukce pro tisk aktuálního řádku vyžaduje zjištění aktuální pozice v souboru. To lze zjistit pomocí funkce ftell(). Pak pomocí fseek() najít předchozí znak nového řádku nebo začátek souboru a číst znaky od tohoto místa dokud se nedojde k znaků nového řádku či konce souboru. Největší problém této instrukce je získání aktuální pozice v souboru na základě předchozí instrukce.

Nahoru Odpovědět
30.10.2018 13:52
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Martin Štefánik
DarkCoder:30.10.2018 13:59

To ale lze použít pokud bys pracoval se souborem. Z důvodu efektivity je ale dobré celý soubor načíst do paměti. Tedy odpadá jakákoli práce se souborovým ukazatelem. Je tedy třeba si vytvořit svůj vlastní ukazatel který budeš aktualizovat po každé instrukci. Jen tak budeš moci vytvořit a pracovat s instrukcemi, které jsou závislé na pozici v souboru.

Nahoru Odpovědět
30.10.2018 13:59
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 14 zpráv z 14.