Parsování souboru s podobným obsahem jako JSON – C / C++ – Fórum – Programujte.com
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Parsování souboru s podobným obsahem jako JSON – C / C++ – Fórum – Programujte.comParsování souboru s podobným obsahem jako JSON – C / C++ – Fórum – Programujte.com

 

richard.zavodny0
Návštěvník
9. 6. 2018   #1
-
0
-

Ahoj, potřebuju naparsovat soubor s podobným obsahem jako JSON a přistupovat k němu určitým způsobem.

Příklad souboru:
 

zavod = {
    misto = "Opava",
    datum = "19.10.2017",
    skoly = {
        {
            nazev = "ZŠ Englišova",
            body = "0",
            soutezici = {
                {
                    jmeno = "Petr Novák",
                    pohlavi = "m",
                    rocnik = "2005",
                    vysledky = {
                        beh60 = "8.5",
                        beh1500 = "05:43,2",
                        dalka = "421",
                        micek = "42.23",
                        splh = "5.6"
                    }
                }
            }
        }
    }
}

Chtěl bych k němu přistupovat následujícím způsobem.
 

NĚJAKÝ TYP bla = parseFile(std::string filePath);

bla.zavod.misto;
bla.zavod.datum;
bla.zavod.skoly[0].nazev;
bla.zavod.skoly[0].body;
bla.zavod.skoly[0].soutezici[0].jmeno;
bla.zavod.skoly[0].soutezici[0].pohlavi;
bla.zavod.skoly[0].soutezici[0].rocnik;
bla.zavod.skoly[0].soutezici[0].vysledky.beh60;
bla.zavod.skoly[0].soutezici[0].vysledky.beh1500;
bla.zavod.skoly[0].soutezici[0].vysledky.dalka;
bla.zavod.skoly[0].soutezici[0].vysledky.micek;
bla.zavod.skoly[0].soutezici[0].vysledky.splh;

První problém je samotné parsování, tady nevím, za který konec to vzít. Rozkouskovat to podle nějakého znaku (",", "}") nejde, protože mi vznikne mnoho nesmyslných částí.

Další problém je přístup k již naparsovaným hodnotám. Nejsem si úplně jistý, zda-li je to výše uvedeným způsobem možné.

Děkuji moc. :)

Nahlásit jako SPAM
IP: 89.24.189.–
Programátor, Kóder, Grafický designér, Kritik, zastánce Open-Source a Linuxu.
KIIV
~ Moderátor
+43
God of flame
9. 6. 2018   #2
-
0
-

Rozparsovat to pujde, pristup by ale byl spise neco jako  bla["zavod"]["skoly"][0]["soutezici"]...

To co bys chtel, to by muselo byt proste zkompilovane podle datoveho schematu a i parsovani by bylo pomerne striktni.

Parsovani se dela pomoci "tokenizace", jako ze to rozdelis na jednotlive tokeny typu  "{" "}" "," klic, hodnota  a tak

Nahlásit jako SPAM
IP: 178.41.247.–
Program vždy dělá to co naprogramujete, ne to co chcete...
richard.zavodny0
Návštěvník
9. 6. 2018   #3
-
0
-

#2 KIIV
Jo, tak tady ten přístup k těm datům mi nevadí. :) Stále je to dostatečně přehledné. Nějaký pseudokód pro tu tokenizaci by nebyl? A bla bude asi vektor ne?

Nahlásit jako SPAM
IP: 89.24.189.–
Programátor, Kóder, Grafický designér, Kritik, zastánce Open-Source a Linuxu.
KIIV
~ Moderátor
+43
God of flame
9. 6. 2018   #4
-
0
-

Bla bude spis mapa. Vektor bude ten seznam skol a studentu.

Na parsovani bude potreba nejaky stavovy automat. Neco jako jestli cekas uz na zacatku { nebo znaky (alias klic). Muzes se inspirovat Jsoncpp libkou. Tam se ale vzdy zacina objektem - { }  pak seznamy jsou v [ ... ] a tak dale.

Nahlásit jako SPAM
IP: 178.41.247.–
Program vždy dělá to co naprogramujete, ne to co chcete...
Kit+15
Guru
9. 6. 2018   #5
-
0
-

#4 KIIV
Zkus si konvertovat do JSONu třeba číslo 42. Dostaneš dvoubajtový string "42".

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:d46a:...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
KIIV
~ Moderátor
+43
God of flame
9. 6. 2018   #6
-
0
-

#5 Kit
zrovna v jsoncpp se cislo 42 zapise jako cislo. Kdyz mi nekdo poslal "42", tak uz na to ani nefungovala metoda asInt(), protoze je to prece retezec :)

Nahlásit jako SPAM
IP: 178.41.247.–
Program vždy dělá to co naprogramujete, ne to co chcete...
Kit+15
Guru
9. 6. 2018   #7
-
0
-

#6 KIIV
Chtěl jsem tím sdělit, že JSON nemusí začínat znakem "{" nebo "[". Když kóduješ string "42", tak je výstup čtyřbajtový - včetně uvozovek.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:d46a:...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
MilanL+1
Grafoman
11. 6. 2018   #8
-
0
-

nebylo by lepší zkusit si vytvořit "uzlování" - Node Tree a naparsovat do toho?

Myslím, že by se tam pak dali případně ošetřit i konverze typů podle nějaké šablony.

Nahlásit jako SPAM
IP: 91.139.9.–
Staon0
Návštěvník
1. 9. 2018   #9
-
0
-

#1 richard.zavodny
Za teorií parsování textových souborů je velmi hezká matematika. Nicméně, zkusím v kostce to popsat i bez ní.

Textové parsery typicky mají dvě nebo tři úrovně:

  1. lexikální. To je ona zmiňovaná tokenizace. Ve vašem případě by tokeny asi byly: identifikátor (to jsou ty názvy položek bez uvozovek), řetězec (texty v uvozovkách), rovnítkolevá_závorka, pravá_závorka, čárka.
  2. Syntaktická. Tato úroveň definuje strukturu textu. Ve vašem případě říká, že soubor se skládá ze seznamu závodů, každý závod obsahuje datum, místo konání a seznam škol, které se ho účastní. Každá škola má název a počet bodů a seznam soutěžících žáků, atd.
  3. Sémantická. Tato úroveň už pracuje s konkrétními významy a vy ji už asi nepotřebujete. V případě reálného programovacího jazyka například pracuje s datovými typy (můžete napsat syntakticky správný výraz, který ale sémanticky selže, protože např. sčítáte číslo a strukturu).

Tokenizace se typicky dělá pomocí konečných automatů. Je to jednoduchá, ale většinou dost nezajímavá práce, např.: 

Token ziskejToken() {
  Stav stav(INIT);
  while(true) {
    int znak(nactiDalsiZnak());
    switch(stav) {
      /* -- pocatecni stav */
      case INIT:
        /* -- konec vstupu */
        if(znak == EOF) return TokenEof;

        /* -- znak je pismeno, prejdi do stavu parsovani slova */
        if(isalpha(znak)) stav = WORD;

        /* -- znak je cislice, prejdi do stavu parsovani cisla */
        if(isnum(znak)) stav = NUM;

        /* -- znak je neco jineho, ignorujeme ho azustavame
              ve stejnem stavu */
        break;

      /* -- parsujeme slovo */
      case WORD:
        /* -- dosli jsme na konec slova? */
        if(znak == EOF || !isalpha(znak)) {
          /* -- ano jsme na konci slova. Nacteny znak
                vratime zpet do vstupu, protoze muze byt
                soucasti nasledujiciho tokenu. */
	  vratZnakZpetDoVstupu(znak);
          return TokenWord;
        }
        /* -- nejsme na konci slova, zustavame ve stejnem stavu */
        break;

      /* -- parsujeme cislo */
      case NUM:
        /* -- dosli jsme na konec cisla? */
        if(znak == EOF || !isnum(znak)) {
          /* -- ano jsme na konci cisla. Nacteny znak
                vratime zpet do vstupu, protoze muze byt
                soucasti nasledujiciho tokenu. */
	  vratZnakZpetDoVstupu(znak);
          return TokenNum;
        }
        /* -- nejsme na konci cisla, zustavame ve stejnem stavu */
        break;
    }
  }
}

Předchozí pseudokód tokenizuje slova a čísla z nějakého textu. Má tři stavy: init, word a num. Pokud v initu padne na písmenko, přejde do stavu word a v něm setrvá tak dlouho, dokud mu chodí písmenka. Podobně pokud padne na číslici, přejde do stavu num a v něm setrvá tak dlouho, dokud mu chodí číslice. Skutečný kód by byl podstatně složitější, protože také obvykle chceme vědět, jaké skutečné slovo nebo číslo jsme přečetli, takže je potřeba si načtené znaky někam schovávat. Ale doufám, že princip je z příkladu jasný.

Konečné automaty se dají zakódovat i jinak, než pomocí switch a if, ale tohle je asi nejčastější způsob, pokud se dělají ručně. Také ale existují jejich generátory - v případě lexikální analýzy se asi nejčastěji používá GNU flex. Ty obvykle používají nějaké tabulky.

Syntaktická analýza už pracuje se streamem tokenů. Existuje pro ni řada algoritmů. Nejjednodušší, a pro váš účel postačující, je asi něco, co se nazývá rekurzivní sestup (ve skutečnosti je to implementace algoritmu označovaného jako LL(1)). Ve vašem případě by vypadal nějak takhle (zjednodušil jsem si práci a neřeším tu čárky mezi hodnotami): 

void parsujSoubor() {
  while(true) {
    Token token(dejNahled());

    /* -- konec vstupu */
    if(token == TokenEof) return;

    /* -- ocekavame zavod */
    if(token == TokenIdent && token.value == "zavod") {
      srovnej(TokenIdent);
      srovnej(TokenEqual);
      srovnej(TokenLeftBrace);
      parsujZavod();
      srovnej(TokenRightBrace);
      continue;
    }

    /* -- cokoliv jineho na vstupu je chyba */
    skonciSChybou();
  }
}

void parsujZavod() {
  parsujHodnotu("misto");
  parsujHodnotu("datum");

  Token token(dejNahled());
  if(token != TokenIdent || token.value != "skoly") {
    skonciSChybou();
  }
  srovnej(TokenIdent);
  srovnej(TokenEqual);
  srovnej(TokenLeftBrace);
  while(true) {
    token = dejNahled();

    /* -- pokud mam uzaviraci zavorku, seznam skol konci */
    if(token == TokenRightBrace) {
      /* -- konec seznamu skol */
      srovnej(TokenRightBrace);
      break;
    }

    /* -- oteviraci zavorka znamena novou skolu */
    if(token == TokenLeftBrace) {
      /* -- Oteviraci zavorku se nesnazime pozrat pomoci
         srovnej(). To si udela parsujSkolu() samo. */
      parsujSkolu();
    }

    /* -- cokoliv jineho je chyba */
    skonciSChybou();
  }
}

void parsujHodnotu(string jmeno) {
  Token token(dejNahled());
  if(token != TokenIdent || token.value != jmeno) {
    skonciSChybou();
  }
  srovnej(TokenIdent);
  srovnej(TokenEqual);
  srovnej(TokenString);
}

Pro algoritmus potřebujete náhled jednoho tokenu - tzn. možnost přečíst token ze vstupu, aniž by se z něho odebral. To je používané tam, kde se parser potřebuje rozhodnout, kudy dál. Funkce parsujSoubor tak např. rozlišuje korektní ukončení vstupu, situaci, kdy má začít parsovat závod, anebo chybovou situaci.

Další důležitá operace je srovnání. To odebere token ze vstupu a případně ukončí parser s chybou, pokud token neodpovídá tomu, který dostal jako parametr.

Struktura dokumentu je pak v kódu pěkně vidět. Soubor se skládá ze seznamu závodů (cyklus tak dlouho, dokud dostávám závody). Závod se skládá z místa, data a seznamu škol (cyklus tak dlouho, dokud nedostanu uzavírací závorku).

I psaní syntaktického analyzátoru je poměrně nezajímavá a mechanická práce. Proto i zde je možné použít generátor. Nejznámější je asi GNU Bison. Nicméně ten používá výrazně komplikovanější algoritmus, než jsem popsal výše, takže jeho použití je už poněkud vyšší dívčí. Ale existují i jiné.

 
Nahlásit jako SPAM
IP: 94.112.135.–
Zjistit počet nových příspěvků

Přidej příspěvek

Toto téma je starší jak čtvrt roku – přidej svůj příspěvek jen tehdy, máš-li k tématu opravdu co říct!

Ano, opravdu chci reagovat → zobrazí formulář pro přidání příspěvku

×Vložení zdrojáku

×Vložení obrázku

Vložit URL obrázku Vybrat obrázek na disku
Vlož URL adresu obrázku:
Klikni a vyber obrázek z počítače:

×Vložení videa

Aktuálně jsou podporována videa ze serverů YouTube, Vimeo a Dailymotion.
×
 
Podporujeme Gravatara.
Zadej URL adresu Avatara (40 x 40 px) nebo emailovou adresu pro použití Gravatara.
Email nikam neukládáme, po získání Gravatara je zahozen.
-
Pravidla pro psaní příspěvků, používej diakritiku. ENTER pro nový odstavec, SHIFT + ENTER pro nový řádek.
Sledovat nové příspěvky (pouze pro přihlášené)
Sleduj vlákno a v případě přidání nového příspěvku o tom budeš vědět mezi prvními.
Reaguješ na příspěvek:

Uživatelé prohlížející si toto vlákno

Uživatelé on-line: 0 registrovaných, 123 hostů

Podobná vlákna

Parsování souboru — založil oxidián

Parsování souboru — založil cniry

úprava JSON souboru — založil Dale

Parsování php souboru — založil Konycz

Moderátoři diskuze

 

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý