// Vse vhodne datoteke morajo imeti končnico ".txt" in v imenu ne smejo imeti pike! #include #include #include #include #include // samo za getch(), ki ga v končni verziji najbrž sploh ne bo // makroji za pretvorbo datotek #define MAX_PREBR_ZNAKOV 10000 // Predvidevamo, da nobena vrstica nima več znakov #define MAX_DOLZ_IMEN_DATOTEK 20 #define MAX_ZNAKOV_NASLOVA 200 #define MAX_ZNAKOV_RUBRIKE 20 #define OFFSET_NASLOVA 9 // število mest od začetka niza ""naslov"" do začetka dejanskega naslova #define OFFSET_RUBRIKE 10 #define OFFSET_CILJA 14 // makroji za komunikacijo #define NASLOV 'n' #define RUBRIKA 'r' #define CILJ 'c' #define MAX_DOLZ_VHODNEGA_NIZA 300 #define MAX_CRK_SLO_BESEDE 23 #define MIN_UJEMANJE 170 // najmanjša stopnja ujemanja, za katero še takoj ponudimo konkreten članek #define STEV_VELIKIH_UJEMANJ 4 // za 1 zmanjšano število najbolj ujemajočih se naslovov, ki jih hranimo v matriki "najvecjaUjemanja[][]" // funkcije za pretvorbo datotek int pretvoriDatotekoKazalo(char* imeDatoteke); int pretvoriDatotekoClanek(char* imeDatoteke); void zmeciVen(char* vrstica, char znak, char* medZnakoma, char* niz); // določene izbrise preskočimo, če podamo parametre: vrstica, '\0', NULL, NULL // funkcije in razredi za komunikacijo class kazalo { char naslov[MAX_ZNAKOV_NASLOVA], rubrika[MAX_ZNAKOV_RUBRIKE], cilj[MAX_DOLZ_IMEN_DATOTEK]; // ni mi jasno, zakaj po zagonu izvede neveljavno operacijo (op. sistem ga prekine), če je naslov deklariran kot public! bool ujemanjeRubrike; int stopnjaUjemanja; static int najvecjaUjemanja[2][STEV_VELIKIH_UJEMANJ + 1]; // prva vrstica: stopnje ujemanja, druga vrstica: indeksi pripadajočih primerkov razreda public: kazalo(); void vpisiPodatke(ifstream& tokKazalo); void dolociStopnjoUjemanja(char* vhodniNiz, unsigned int zaporednaStev); bool dolociUjemanjeRubrike(char* vhodniNiz); static int clanekNajvecjeUjemanje(int kateroPoVrsti, int minUjemanje); static void resetirajNajvecjaUjemanja(); char* vrniClana(char katerega); }; int kazalo::najvecjaUjemanja[2][STEV_VELIKIH_UJEMANJ + 1] = {{0,0,0,0,0}, {-1,-1,-1,-1,-1}}; int kazalo::clanekNajvecjeUjemanje(int kateroPoVrsti, int minUjemanje) { if(najvecjaUjemanja[0][kateroPoVrsti] >= minUjemanje) return najvecjaUjemanja[1][kateroPoVrsti]; else return -1; } void kazalo::resetirajNajvecjaUjemanja() { for(int i = 0; i <= STEV_VELIKIH_UJEMANJ; i++) { najvecjaUjemanja[0][i] = 0; najvecjaUjemanja[1][i] = -1; } } void narediVelikeCrke(char* niz); bool jeVelikaCrka(char crka); bool vsebujeDa(char* vhodniNiz); void izpisiClanek(char* cilj); void izlusciRubriko(char* vhodniNiz); int main() { char vhodniNiz[MAX_DOLZ_VHODNEGA_NIZA], zazelenaRubrika[MAX_ZNAKOV_RUBRIKE]; int stClankov, i, j, nakljucnaStev; stClankov = pretvoriDatotekoKazalo("zvpl.txt"); if(stClankov == -1) // datoteke "zvpl.txt" nismo našli exit(1); kazalo* clanekPodatki = new kazalo[stClankov]; ifstream tokKazalo("Rubrike in naslovi.txt"); for(i = 0; i < stClankov; i++) clanekPodatki[i].vpisiPodatke(tokKazalo); cout << "Pozdravljen na žvpl-ju! Sem tvoj vodnik po dogodkih. "; cout.flush(); // sicer se ne izpiše takoj do { cout << "Kakšen, oziroma kateri dogodek te zanima?" << endl; cout.flush(); gets(vhodniNiz); narediVelikeCrke(vhodniNiz); for(i = 0; i < stClankov; i++) clanekPodatki[i].dolociStopnjoUjemanja(vhodniNiz, i); // for(i = 0; i <= STEV_VELIKIH_UJEMANJ; i++) // cout << kazalo::clanekNajvecjeUjemanje(i, MIN_UJEMANJE) << " "; for(i = 0; i <= STEV_VELIKIH_UJEMANJ; i++) { // najprej ponujamo konkretne članke z velikim ujemanjem j = kazalo::clanekNajvecjeUjemanje(i, MIN_UJEMANJE); if(j != -1) { // če se kakšen članek dovolj ujema z vhodnim nizom if(!i) // če gremo prvič čez to for zanko (ponujamo članek z najboljšim ujemanjem) cout << endl << "Ali te zanima članek z naslovom: "; else { if(i == 1) // če gremo drugič čez for zanko (uporabnik nam je zavrnil samo en ponujeni članek) nakljucnaStev = rand(); else ++nakljucnaStev; // da izpišemo vedno malo drugačno vprašanje switch(nakljucnaStev % 3) { // ostanek po deljenju s številom case stavkov case 0: cout << "Ne? Kaj pa ta: "; break; case 1: cout << "Ali pa mogoče: "; break; case 2: cout << "Morda pa te zanima tale: "; } } cout << endl << clanekPodatki[j].vrniClana(NASLOV) << "?" << endl; cout.flush(); gets(vhodniNiz); if(vsebujeDa(vhodniNiz)) { izpisiClanek(clanekPodatki[j].vrniClana(CILJ)); i = STEV_VELIKIH_UJEMANJ + 2; // s tem dosežemo izstop iz for zanke in povemo, da smo pravi članek že izpisali } } else // nobeden članek se ne ujema dovolj z vhodnim nizom break; } if(i != STEV_VELIKIH_UJEMANJ + 3) { // pravega članka še nismo našli. Prištejemo 1 več zato, ker bedasta for zanka poveča i tudi po testnem pogoju "false" cout << "Zaenkrat ne najdem nobenih primernih člankov. Ali te zanima katera od rubrik: napoved, glasba, videospotnice, recenzija, ečlanki, ali ostali članki?" << endl; cout.flush(); gets(vhodniNiz); if(vsebujeDa(vhodniNiz)) { izlusciRubriko(vhodniNiz); if(strcmp(vhodniNiz, "NOBENA")) { // če niz "vhodniNiz" NI enak nizu "NOBENA" strcpy(zazelenaRubrika, vhodniNiz); cout << "Pod rubriko \"" << vhodniNiz << "\" so naslednji članki:" << endl; for(i = 0; i < stClankov; i++) if(clanekPodatki[i].dolociUjemanjeRubrike(vhodniNiz)) cout << clanekPodatki[i].vrniClana(NASLOV) << endl; cout << "Ali ti je kateri od teh člankov zanimiv?" << endl; cout.flush(); gets(vhodniNiz); if(vsebujeDa(vhodniNiz)) { // če uporabnik želi izbrati enega od naštetih člankov, ki so vsi v isti rubriki do { kazalo::resetirajNajvecjaUjemanja(); for(i = 0; i < stClankov; i++) if(clanekPodatki[i].dolociUjemanjeRubrike(zazelenaRubrika)) clanekPodatki[i].dolociStopnjoUjemanja(vhodniNiz, i); i = kazalo::clanekNajvecjeUjemanje(0, MIN_UJEMANJE); if(i != -1) izpisiClanek(clanekPodatki[i].vrniClana(CILJ)); else { cout << "Kateri od teh člankov te zanima?" << endl; gets(vhodniNiz); narediVelikeCrke(vhodniNiz); if(strstr(vhodniNiz, "NOBEN") || strstr(vhodniNiz, "NOBEDEN")) break; } }while(i == -1); // dokler ne najdemo in izpišemo iskanega članka } } } } cout << "Ali te zanima še kateri dogodek?" << endl; cout.flush(); gets(vhodniNiz); kazalo::resetirajNajvecjaUjemanja(); } while(vsebujeDa(vhodniNiz)); delete[] clanekPodatki; return 0; } int pretvoriDatotekoClanek(char* imeDatoteke) { // preformatira datoteko "imeDatoteke", nova datoteka ima pripono ".dat". Vpiše ime nove datoteke // v niz "imeDatoteke", zato je treba parameter podati kot kazalec in ne kot ime med dvema narekovajema int i, dolzImenaDatoteke = strlen(imeDatoteke); char vrstica[MAX_PREBR_ZNAKOV + 1]; ifstream tokOriginalna; tokOriginalna.open(imeDatoteke, ios::nocreate); if(!tokOriginalna) { // če ne uspemo odpreti originalne datoteke cerr << "Ustrezna datoteka z imenom " << imeDatoteke << ", manjka" << endl; return -1; } char* imeNove = new char[dolzImenaDatoteke + 1]; for (i = 0; i < dolzImenaDatoteke && imeDatoteke[i] != '.'; i++) imeNove[i] = imeDatoteke[i]; imeNove[i] = '.'; // končnico pretvorimo v ".dat" imeNove[i+1] = 'd'; imeNove[i+2] = 'a'; imeNove[i+3] = 't'; imeNove[i+4] = '\0'; ofstream tokNova(imeNove); for(i = 1; i <= 39; i++) tokOriginalna.getline(vrstica, MAX_PREBR_ZNAKOV); zmeciVen(vrstica, ' ', "<>", "ZVPL - "); // vsak naslov članka pri datotekah "clanekID=XXXX.txt" se začne z "ZVPL - " tokNova << vrstica << endl; for(i = 1; i <= 116; i++) tokOriginalna.getline(vrstica, MAX_PREBR_ZNAKOV); zmeciVen(vrstica, ' ', "<>", "ZVPL - "); // ven vržemo tab-e, ne presledke tokNova << vrstica << endl; for(i = 1; i <= 4; i++) { tokOriginalna.getline(vrstica, MAX_PREBR_ZNAKOV); if(strlen(vrstica) < 2) // če je vrstica prazna, ali če vsebuje samo tab break; tokOriginalna.getline(vrstica, MAX_PREBR_ZNAKOV); zmeciVen(vrstica, ' ', "<>", "ZVPL - "); tokNova << vrstica << endl; } tokNova.flush(); strcpy(imeDatoteke, imeNove); delete[] imeNove; return 0; } int pretvoriDatotekoKazalo(char* imeDatoteke) { int i, j = 0; char vrstica[MAX_PREBR_ZNAKOV + 1], naslov[MAX_ZNAKOV_NASLOVA], rubrika[MAX_ZNAKOV_RUBRIKE], cilj[MAX_DOLZ_IMEN_DATOTEK]; char* zacetekPodniza; ifstream tokOriginalna; tokOriginalna.open(imeDatoteke, ios::nocreate); if(!tokOriginalna) { // če ne uspemo odpreti originalne datoteke cerr << "Ustrezna datoteka z imenom " << imeDatoteke << ", manjka" << endl; return -1; } ofstream tokRubrikeInNaslovi("Rubrike in naslovi.txt"); while(tokOriginalna.good()) { // dokler ni ali EOF ali napake tokOriginalna.getline(vrstica, MAX_PREBR_ZNAKOV); zacetekPodniza = strstr(vrstica, "\"naslov\""); if(zacetekPodniza != NULL) { // če smo prebrali vrstico, ki vsebuje iskane podatke ++j; for(i = 0; *(zacetekPodniza + OFFSET_NASLOVA + i) != '<' && *(zacetekPodniza + OFFSET_NASLOVA + i) != '>'; i++) naslov[i] = *(zacetekPodniza + OFFSET_NASLOVA + i); naslov[i] = '\0'; zacetekPodniza = strstr(vrstica, "\"rubrika\""); for(i = 0; *(zacetekPodniza + OFFSET_RUBRIKE + i) != '<' && *(zacetekPodniza + OFFSET_RUBRIKE + i) != '>'; i++) rubrika[i] = *(zacetekPodniza + OFFSET_RUBRIKE + i); rubrika[i] = '\0'; zacetekPodniza = strstr(vrstica, "clanek.asp?ID="); for(i = 0; *(zacetekPodniza + OFFSET_CILJA + i) != '<' && *(zacetekPodniza + OFFSET_CILJA + i) != '>'; i++) cilj[i] = *(zacetekPodniza + OFFSET_CILJA + i); cilj[i] = '\0'; tokRubrikeInNaslovi << naslov << "|" << rubrika << "|" << cilj << endl; } } tokRubrikeInNaslovi.flush(); return j; // vrnemo število vseh člankov } void zmeciVen(char* vrstica, char znak, char* medZnakoma, char* niz) { char *zacetekSmeti, *konecSmeti, temp[MAX_PREBR_ZNAKOV]; while(medZnakoma != NULL) { // zmečemo ven vse med znakoma, ki ju vsebuje niz "medZnakoma", kar preskočimo, če podamo kot parameter NULL zacetekSmeti = strchr(vrstica, medZnakoma[0]); if(zacetekSmeti == NULL) break; konecSmeti = zacetekSmeti; do ++konecSmeti; while(*konecSmeti != medZnakoma[1]); do { ++konecSmeti; *zacetekSmeti = *konecSmeti; ++zacetekSmeti; } while(*konecSmeti != '\0'); } while(znak != '\0') { // zmečemo ven vse znake "znak", kar preskočimo, če je znak=='\0' zacetekSmeti = strchr(vrstica, znak); if(zacetekSmeti == NULL) break; do { ++zacetekSmeti; *(zacetekSmeti - 1) = *zacetekSmeti; } while(*zacetekSmeti != '\0'); } if(niz != NULL) { // če podamo kot parameter NULL, to preskočimo strcpy(temp, vrstica); // da vržemo ven še niz "niz", če se nahaja na začetku vrstice temp[strlen(niz)] = '\0'; if(!strcmp(niz, temp)) { zacetekSmeti = vrstica; konecSmeti = zacetekSmeti + strlen(temp) - 1; do { // vržemo ven niz "niz" ++konecSmeti; *zacetekSmeti = *konecSmeti; ++zacetekSmeti; } while(*konecSmeti != '\0'); } } } void narediVelikeCrke(char* niz) { char* sumnik; strupr(niz); while((sumnik = strchr(niz, 'č')) != NULL) *sumnik = 'Č'; while((sumnik = strchr(niz, 'ć')) != NULL) *sumnik = 'Ć'; while((sumnik = strchr(niz, 'đ')) != NULL) *sumnik = 'Đ'; while((sumnik = strchr(niz, 'š')) != NULL) *sumnik = 'Š'; while((sumnik = strchr(niz, 'ž')) != NULL) *sumnik = 'Ž'; } void kazalo::vpisiPodatke(ifstream& tokKazalo) { tokKazalo.getline(naslov, MAX_ZNAKOV_NASLOVA, '|'); tokKazalo.getline(rubrika, MAX_ZNAKOV_RUBRIKE, '|'); tokKazalo.getline(cilj, MAX_DOLZ_IMEN_DATOTEK); } void kazalo::dolociStopnjoUjemanja(char* vhodniNiz, unsigned int zaporednaStev) { // ujemanje niza "vhodniNiz" z naslovom tega članka // vhodniNiz naj ima velike črke, male bodo ignorirane // manjša kot je zaporednaStev, novejši je članek int i, j, k = 0; // i-ta beseda v nizu "naslov", j-ti znak v nizu "beseda", k-ti znak v nizu "naslov" char beseda[MAX_CRK_SLO_BESEDE]; char* kazZnak; stopnjaUjemanja = 0; for(i = 0; naslov[k] != '\0'; i++) { // IZLUŠČIMO KOREN i-TE BESEDE IZ NASLOVA: if(i != 0) ++k; // da spustimo presledek v nizu "naslov" for(j = 0; strchr(" -!?.,:;", naslov[k]) == NULL && naslov[k] != '\0'; j++, k++) // i-to besedo v nizu "naslov" skopiramo v niz "beseda" beseda[j] = naslov[k]; beseda[j] = '\0'; while(beseda[strlen(beseda) - 1] == '.' || beseda[strlen(beseda) - 1] == '!' || beseda[strlen(beseda) - 1] == '?') beseda[strlen(beseda) - 1] = '\0'; if(strlen(beseda) < 4) // besed, krajših od 4 črk, sploh ne upoštevamo continue; beseda[strlen(beseda) - 1] = '\0'; // zadnjo črko vzamemo ven, tako zajamemo tudi večino ostalih oblik (sklon) besede narediVelikeCrke(beseda); // POGLEDAMO UJEMANJE TEGA KORENA BESEDE Z VHODNIM NIZOM: kazZnak = strstr(vhodniNiz, beseda); if(kazZnak != NULL && (kazZnak == vhodniNiz || strchr(" -!?.,:;", *(kazZnak - 1)) != NULL)) { stopnjaUjemanja += 50; if(jeVelikaCrka(naslov[k - j])) { stopnjaUjemanja += 300; if(k - j != 0) // če ta beseda z veliko začetnico ni prva beseda v nizu "vhodniNiz" stopnjaUjemanja += 200; } } } if(strstr(vhodniNiz, " NOV") != NULL && zaporednaStev < 10) // če je, predvidevamo, da uporabnika zanima kaj novega stopnjaUjemanja += -20 * zaporednaStev + 200; // cout << stopnjaUjemanja << ", za naslov:" << naslov << endl; // POSODOBITEV NAJVEČJIH UJEMANJ: if(stopnjaUjemanja > najvecjaUjemanja[0][STEV_VELIKIH_UJEMANJ]) { najvecjaUjemanja[0][STEV_VELIKIH_UJEMANJ] = stopnjaUjemanja; najvecjaUjemanja[1][STEV_VELIKIH_UJEMANJ] = zaporednaStev; i = STEV_VELIKIH_UJEMANJ - 1; while(stopnjaUjemanja > najvecjaUjemanja[0][i] && i >= 0) { najvecjaUjemanja[0][i + 1] = najvecjaUjemanja[0][i]; najvecjaUjemanja[1][i + 1] = najvecjaUjemanja[1][i]; --i; } ++i; najvecjaUjemanja[0][i] = stopnjaUjemanja; najvecjaUjemanja[1][i] = zaporednaStev; } } bool kazalo::dolociUjemanjeRubrike(char* vhodniNiz) { // vhodniNiz naj ima velike črke, male bodo ignorirane int dolzRubrike = strlen(rubrika); char temp = rubrika[dolzRubrike]; rubrika[dolzRubrike - 1] = '\0'; // zadnjo črko vzamemo ven, tako zajamemo tudi večino ostalih oblik (sklon) besede v nizu "rubrika" if(strstr(vhodniNiz, rubrika) != NULL) ujemanjeRubrike = true; else ujemanjeRubrike = false; rubrika[dolzRubrike - 1] = temp; rubrika[dolzRubrike] = '\0'; // za vsak slučaj return ujemanjeRubrike; } kazalo::kazalo() { // s konstruktorjem določimo začetne vrednosti stopnjaUjemanja = -1; ujemanjeRubrike = false; } char* kazalo::vrniClana(char katerega) { switch(katerega) { case NASLOV: return naslov; case RUBRIKA: return rubrika; case CILJ: return cilj; default: return NULL; } } bool jeVelikaCrka(char crka) { if(crka >= 'A' && crka <= 'Z' || crka == 'Č' || crka == 'Ć' || crka == 'Đ' || crka == 'Š' || crka == 'Ž') return true; else return false; } bool vsebujeDa(char* vhodniNiz) { narediVelikeCrke(vhodniNiz); if(strstr(vhodniNiz, "DA") || strstr(vhodniNiz, "JA")) { if(!strstr(vhodniNiz, "NE")) return true; else { cout << "Nisem razumel. Odgovori z da ali ne." << endl; gets(vhodniNiz); return vsebujeDa(vhodniNiz); // rekurzivni klic dokler ne najdemo odgovora } } else { if(strstr(vhodniNiz, "NE")) return false; else { cout << "Nisem razumel. Odgovori z da ali ne." << endl; gets(vhodniNiz); return vsebujeDa(vhodniNiz); // rekurzivni klic dokler ne najdemo odgovora } } } void izpisiClanek(char* cilj) { char vrstica[MAX_PREBR_ZNAKOV + 1]; char imeDatoteke[MAX_DOLZ_IMEN_DATOTEK]; strcpy(imeDatoteke, "clanekID="); strcat(imeDatoteke, cilj); strcat(imeDatoteke, ".dat"); ifstream* tokDatoteka = new ifstream; (*tokDatoteka).open(imeDatoteke, ios::nocreate); if(!(*tokDatoteka)) { // če ne uspemo odpreti datoteke "clanekID=XXXX.dat" (pri tem so XXXX ustrezne številke) delete tokDatoteka; ifstream* tokDatoteka = new ifstream; imeDatoteke[strlen(imeDatoteke) - 4] = '\0'; strcat(imeDatoteke, ".txt"); if(pretvoriDatotekoClanek(imeDatoteke) == -1) // če datoteke s člankom (html) ni bilo mogoče odpreti exit(1); (*tokDatoteka).open(imeDatoteke, ios::nocreate); if(!(*tokDatoteka)) // če originalne datoteke nismo prej uspeli pretvoriti (nekaj je torej narobe) cerr << endl << "Problemi z odpiranjem datoteke s clankom!" << endl; } (*tokDatoteka).getline(vrstica, MAX_PREBR_ZNAKOV); // v prvi vrstici preformatirane datoteke je samo naslov članka while((*tokDatoteka).good()) { // v drugi pa vsebina članka (*tokDatoteka).getline(vrstica, MAX_PREBR_ZNAKOV); cout << vrstica << endl; } delete tokDatoteka; } void izlusciRubriko(char* vhodniNiz) { // izluščimo rubriko iz niza "vhodniNiz" in jo vpišemo v "vhodniNiz" int i = 0; bool rubrikeNismoNasli; do { narediVelikeCrke(vhodniNiz); rubrikeNismoNasli = false; if(strstr(vhodniNiz, "VIDEOSPOTNIC")) strcpy(vhodniNiz, "VIDEOSPOTNICE"); else if(strstr(vhodniNiz, "RECENZIJ")) strcpy(vhodniNiz, "RECENZIJA"); else if(strstr(vhodniNiz, "NAPOVED")) strcpy(vhodniNiz, "NAPOVED"); else if(strstr(vhodniNiz, "EČLANEK") || strstr(vhodniNiz, "EČLANK")) strcpy(vhodniNiz, "EČLANEK"); else if(strstr(vhodniNiz, "GLASB")) strcpy(vhodniNiz, "GLASBA"); else if(strstr(vhodniNiz, "ČLANEK") || strstr(vhodniNiz, "ČLANK")) strcpy(vhodniNiz, "ČLANEK"); else if(strstr(vhodniNiz, "NOBENA")) // če uporabnika ne zanima nobena od naštetih rubrik strcpy(vhodniNiz, "NOBENA"); else { // v nizu "vhodniNiz" ne najdemo nobene rubrike if(!i) // če smo prvič šli čez do-while zanko cout << "Katera rubrika te zanima?" << endl; else if(i == 1) cout << "Nisem razumel. Katera rubrika od prej nastetih te zanima?" << endl; else // če so precejšnje napake v razpoznavi govora, ali pa je uporabnik debil cout << "Ce te ne zanima nobena, reci nobena. Sicer pa izbiraj med: napoved, glasba, videospotnice, recenzija, eclanki, ali ostali clanki." << endl; cout.flush(); // sicer se ne izpiše takoj gets(vhodniNiz); ++i; rubrikeNismoNasli = true; } } while(rubrikeNismoNasli); }