Strings::matchAll/substring – UTF-8

Kcko
Člen | 465
+
0
-

EDIT:
Řešení: https://stackoverflow.com/…5329/1519236 (délka řetezce vs bajty)
Ale stejně mě zajímá jak to řešíte vy?

===
Ahoj,

potřebuji najít pozici slova v textu (a pak ukázat kousek textu před a po, resp víc slov hledám, ale pro demonstraci, stačí 1 slovo).

Mám tedy nějaká text, zkusím vyhledat třeba slovo „Richter“ (potřebuji aby to umělo s diakritikou i bez), takže jsem použil regexp a zjištuju kde je zase problém.

  • funkce strpos samozřejmě nefunguje dobře (díky multi-byte kódování ukáže jinou pozici než by měla)
  • funkce mb_strpos se zachová správně (kdo by to byl čekal ;-))
  • preg_match_all se chová stejně „hloupě“ jako strpos
  • našel jsem v komentářích starou funkci upravenou o „MB“ chování a ta funguje dobře (jako mb_strpos) více na php.net (https://www.php.net/…atch-all.php#…)
  • vyzkoušel jsem si matchAll z balíčku Strings a funguje stejně špatně jako preg_match_All

Zde dump https://bit.ly/3E4zxZA jako důkaz.

Použití: ( ve $wordsJoin mám [rřRŘ][iíIÍ][cčCČ][hH][tťTŤ][eéEÉ][rřRŘ] )
\Tracy\Debugger::barDump(Strings::matchAll($editedString, '~'.$wordsJoin.'~iu', PREG_OFFSET_CAPTURE), 'matchAll');

Jako je mi jasné, že to je díky MB a že např. písmeno „Ř“ zabírá víc než 1 znak (víc bajtů), ale pak mi prostě Strings::substring nefunguje korektně.

Můžu tam nechat tu upravnenou funkci mb_preg_match_all … ale rád bych to vyřešil jen s Nette.

Editoval Kcko (22. 10. 2021 20:23)

David Grudl
Nette Core | 8082
+
0
-

Je to dokumentované chovani https://doc.nette.org/…tils/strings#…

Milo
Nette Core | 1283
+
0
-

Mohlo by to jít s obyčejným preg_match_all() s unicode escape sekvencí \p{L} a modifikátorem u.

Kcko
Člen | 465
+
0
-

Milo napsal(a):

Mohlo by to jít s obyčejným preg_match_all() s unicode escape sekvencí \p{L} a modifikátorem u.

modifikátor u tam mám, a jak mám zapsat konkretní výčet zmaků s \p{L} která má představovat Unicode znak?

Tohle je můj pattern
[rřRŘ][iíIÍ][cčCČ][hH][tťTŤ][eéEÉ][rřRŘ]

Editoval Kcko (23. 10. 2021 14:12)

David Grudl
Nette Core | 8082
+
0
-

Strings::lenght(substr($s, 0, $offset)) by mělo pro převod fungovat

Kcko
Člen | 465
+
0
-

David Grudl napsal(a):

Strings::lenght(substr($s, 0, $offset)) by mělo pro převod fungovat

Ano, to funguje, je to ekvivalent toho řešení ze SO co jsem házel.
Já nejsem nějak znalý kódování a problémů s tím spojením, pro někoho kdo je, by tohle problém asi nebyl, ale mě to chování preg_match_all a mb_* funkcí překvapilo.

Nešlo by to nějak; nejaký parametrem znormalizovat, aby to bud vždy bralo bajty nebo znaky, nebo si to nějakým dalším parametrem uvnitř samo „srovnalo“?

Milo
Nette Core | 1283
+
+1
-

@Kcko Myslel jsem to jako řešení celého problému: „Chci najít slovo a část před a za“. Že by ti ty části před a za rovnou vrátil regulár a nemusel bys je potom z toho textu vytahovat. Například takhle pro slovo pozici:

/(^|(?:\p{L}+\P{L}+){1,3})(p[oó][zž][ií][cč][ií])($|(?:\P{L}+\p{L}+){1,3})/uigm

Tři části:

  • začátek, nebo jeden až tři slova před
  • slovo pozici s, nebo bez diakritiky
  • konec, nebo jeden až tři slova za

https://regex101.com/r/37Fy2i/1

Je to zjednodušené, například slovo = pouze písmena.

Kcko
Člen | 465
+
0
-

Milo napsal(a):

@Kcko Myslel jsem to jako řešení celého problému: „Chci najít slovo a část před a za“. Že by ti ty části před a za rovnou vrátil regulár a nemusel bys je potom z toho textu vytahovat. Například takhle pro slovo pozici:

/(^|(?:\p{L}+\P{L}+){1,3})(p[oó][zž][ií][cč][ií])($|(?:\P{L}+\p{L}+){1,3})/uigm

Tři části:

  • začátek, nebo jeden až tři slova před
  • slovo pozici s, nebo bez diakritiky
  • konec, nebo jeden až tři slova za

https://regex101.com/r/37Fy2i/1

Je to zjednodušené, například slovo = pouze písmena.

Díky, vypadá to zajímavě. Já v tom mám trošku hlubší logiku, ten text, ze kterého uřezávám, může mít třeba 2000 znaků to slovo tam může být víckrát (což ani není problém), ale můžu napsat třeba jarní pozice 2021, což jsou 3 slova a 2021 může být až na konci a jarni pozice někde na začátku a ja z toho substringem a vlastní logikou, ten text uřezávám tak, aby to dávalo vždy cca stejný počet znaků, aby ve výsledku nebyl jeden blok s 2000 znaky a podruhé s 300 znaky.

Ale mrknu na to ještě, vyzkouším a třeba to upravím, díky za rozšíření obzoru.

Milo
Nette Core | 1283
+
0
-

Zní to trochu, jako bys implementovat full-text search :o)

Kdybys stál o to pročíst, co vše to „správně“ obnáší, tak na https://www.postgresql.org/…tsearch.html je hezký ucelený přehled.

Kcko
Člen | 465
+
+1
-

Milo napsal(a):

Zní to trochu, jako bys implementovat full-text search :o)

Kdybys stál o to pročíst, co vše to „správně“ obnáší, tak na https://www.postgresql.org/…tsearch.html je hezký ucelený přehled.

No v podstatě to tak je :-).
Měl jsem na výběr, bud tam vrazit

  • ElasticSearch (který neumím ještě zcela dobře)
  • Nechat tam tupý LIKE
  • Použít MySQL FullText, který funguje relativně dobře.

Vybral jsem si poslední možnost s tím, že jsem chtěl na výpisech vyhledávání, ještě hezky obarvit hledané slovo či slova a poskytnout pro lepší přehled výsek textu s danou frází, což se mi povedlo (tohle je samozřejmě věc už PHP, s MySQL fulltextem to už nemá nic společného).

Způsobem, kterým jsem k tomu došel, nepovažuji za zcela optimální a relativně komplikovaný :-), proto jsem ještě hledal něco smysluplnějšího viz moje předchozí dotazy (ale tady se už jedná jen o ty pozice slov v textu viz opět předchozí dotazy).
V podstatě mě jako člověka, co kódování a znaky nikdy moc neřešil právě překvapil ten problém znak vs bajt.

Jinak to funguje docela hezky :-)
(Když bude zájem, poslu do PM URL na vyzkoušení, ale předpokládám, že máš svojí práce dost ;-)).

PS. Vyhledávání není až zas tak důležitá část webu, je to obecní web, jsou tam důležitejší sekce, jen jsem to chtěl mít lepší než předchozí autor :-), což se určitě povedlo.

Díky Milo!

Editoval Kcko (25. 10. 2021 11:09)

Milo
Nette Core | 1283
+
0
-

Rozumím.

Trochu si tu zapropaguju… Kdybys použil PostgreSQL, stačily by ti 4 věci:

  • nahrát do PostgreSQL český slovník
  • přidat tabulce autogenerovaný sloupec s text-search vektorem (bez triggeru)
  • při hledání použít proceduru ts_headline(), která ti výsledek rovnou obarví
  • něco pro zábavu, protože bys to měl tak rychle, že bys nevěděl co s časem ;o)
Kcko
Člen | 465
+
0
-

Milo napsal(a):

Rozumím.

Trochu si tu zapropaguju… Kdybys použil PostgreSQL, stačily by ti 4 věci:

  • nahrát do PostgreSQL český slovník
  • přidat tabulce autogenerovaný sloupec s text-search vektorem (bez triggeru)
  • při hledání použít proceduru ts_headline(), která ti výsledek rovnou obarví
  • něco pro zábavu, protože bys to měl tak rychle, že bys nevěděl co s časem ;o)

:-D

OK někdy nabuduce. Hele CMSko máme na Nette 3, používáme MySQL, takže PostgreSQL nelze (neumím jej a upravovat CMS kvůli tomuhle nedává smysl).

Btw data ze starého (ještě současného webu) jsem převáděl z PostgreSQL do MySQL, málem jsem se u toho rozbrečel (naštěstí tam nejsou procedury, triggery), takže jsem si s tím nakonec poradil :o) (poloruční práce, pár regexpů kvůli odlišené vyexportované syntaxi, ale dal sem to).

:) dík