Extracting cyrillic terms/keywords from text in php - php

I'm trying to build keywords for my webpage and I want that keywords to be extracted from text.
I have that function
function extractCommonWords($string){
$stopWords = array('и', 'или');
$string = preg_replace('/ss+/i', '', $string);
$string = trim($string);
$string = preg_replace('/[^a-zA-Z0-9 -]/', '', $string);
$string = strtolower($string);
preg_match_all('/\b.*?\b/i', $string, $matchWords);
$matchWords = $matchWords[0];
foreach ( $matchWords as $key=>$item ) {
if ( $item == '' || in_array(strtolower($item), $stopWords) || strlen($item) <= 3 ) {
unset($matchWords[$key]);
}
}
$wordCountArr = array();
if ( is_array($matchWords) ) {
foreach ( $matchWords as $key => $val ) {
$val = strtolower($val);
if ( isset($wordCountArr[$val]) ) {
$wordCountArr[$val]++;
} else {
$wordCountArr[$val] = 1;
}
}
}
arsort($wordCountArr);
$wordCountArr = array_slice($wordCountArr, 0, 10);
return $wordCountArr;
}
Here is what I try:
$text = "Текст кирилица";
$words = extractCommonWords($text);
echo implode(',', array_keys($words));
The problem is dosen`t work with cyrillic letters. How to fix that ?

Cyrillic letters are multi-byte characters. You'll need to use multi-byte character function of PHP.
For regular expressions, you'll need to add the /u modifier to make them unicode compliant.
See also Are the PHP preg_functions multibyte safe?

Your pattern to replace will also remove all cyrillic characters, because a-z will not match them.
Add this to the character-class to keep the cyrillic characters:
\p{Cyrillic}
...and use the modifiier u like suggested by GolezTrol.
$string = preg_replace('/[^\p{Cyrillic} a-zA-Z0-9 -]/u', '', $string);
If you only like to extract cyrillic words, you don't need to replace anything, just use this to match the words:
preg_match_all('/\b(\p{Cyrillic}+)\b/u', $string, $matchWords);

Related

preg_replace remove all unwanted characters

i want to block or remove all unwanted characters from from my site
characters like ᾄҭᾄ
or нєℓℓσ
ĤĔĹĹŐ
etc..
my code now is
class badWordsC
{
public function check($text)
{
$badwords = 'com|net|org|info|.name|.biz|.me|.tv|.tel|.mobi|.asia|.uk|.eu|.us|.in|.tk|.cc|.ws|.bz|.mn|.co|.tw|.vn|.es|.pw|.club|.ca|.cn|.email|.photography|.photos|.tips|.solutions|.center|.gallery|.kitchen|.land|.technology|.today|.academy|.computer|.shoes|.careers|.domains|.coffee|.link|.guru|.estate|.company|.bike|.clothing|.holdings|.plumbing|.singles|.ventures|.camera|.equipment|.graphics|.lighting|.construction|.contractors|.directory|.diamonds|.enterprises|.voyage|.recipes|.gift|.site|.ly|.gq|.cf|.ga|.ml|.tk|in|rb2';
$badwords .= 'type|ingoogle';
$badwords = explode('|', $badwords);
$goodwords = 'youtube.com|prntscr.com|az545221.vo.msecnd.net';
$goodwords .= 'wink|crying|fingerscrossed|blushing|wondering|inlove|evilgrin|yawning|puking|in';
$goodwords = explode('|', $goodwords);
$text = str_replace($goodwords, '', $text);
$text = trim(preg_replace('/\s\s+/', '', $text));
$text = preg_replace('/\P{L}+/u', '', $text);
foreach ($badwords as $word)
{
if (strpos($text, $word) !== false || strpos($text, strtoupper($word)) !== false)
{
return false;
}
}
$text = preg_replace("/[a-zA-Z0-9]/", '', $text);
$text = preg_replace(array('/)/','/(/','/;/','/-/','/+/','/لأ/','/لإ/','/لا/','/إ/','/أ/', '/ا/', '/ض/', '/ص/', '/ث/', '/ق/', '/ف/', '/غ/', '/ع/', '/ه/', '/خ/', '/ح/', '/ج/', '/د/', '/ش/', '/س/', '/ي/', '/ب/', '/ل/', '/ت/', '/ن/', '/م/', '/ك/', '/ط/', '/ئ/', '/ء/', '/ؤ/', '/ر/', '/ى/', '/ة/', '/و/', '/ز/', '/ظ/', '/ذ/', '/ـ/'), '', $text);
if($text != '')
{
return false;
}
return true;
}
}
its working but not bloking or removing characters like н Ĕ Ő
any idea ?
The u modifier you will need to use you also need to expand your character class to include the non-ascii characters.
I'd use:
/[[:alnum:]]/u
Regex Demo: https://regex101.com/r/iS1yZ2/2
That is a posix bracket, you can see more of those here, www.regular-expressions.info/posixbrackets.html.
Also in your second expression the + needs to be escaped (or put in a character class, there are some symbols putting in a character won't fix -, ], ^) because that is a quantifier. There is a PHP function that will escape special characters, preg_quote.

Create keywords from text string - encoding issue for cyrillic

I was searching for a code that can automatically create keywords from text string. The code below works fine with latin characters but if I try to use it with russian(cyrillic) - the result is just commas with white spaces in between.
I am not sure what the problem is, but I believe it has to do with the encoding. I have tried to encode the string with this
$string = preg_replace('/\s\s+/i', '', mb_strtolower(utf8_encode($string)));
but no luck. The result is the same.
Also, I included only russian words in the $stopwords var
$stopWords = array('у','ни','на','да','и','нет','были','уже','лет','в','что','их','до','сих','пор','из','но','бы','чтобы','вы','была','было','мы','к','от','с','так','как','не','есть','ее','нам','для','вас','о','них','без');
Here is the original code
function extractCommonWords($string){
$stopWords = array('i','a','about','an','and','are','as','at','be','by','com','de','en','for','from','how','in','is','it','la','of','on','or','that','the','this','to','was','what','when','where','who','will','with','und','the','www');
$string = preg_replace('/\s\s+/i', '', $string); // replace whitespace
$string = trim($string); // trim the string
$string = preg_replace('/[^a-zA-Z0-9 -]/', '', $string); // only take alphanumerical characters, but keep the spaces and dashes too…
$string = strtolower($string); // make it lowercase
preg_match_all('/\b.*?\b/i', $string, $matchWords);
$matchWords = $matchWords[0];
foreach ( $matchWords as $key=>$item ) {
if ( $item == '' || in_array(strtolower($item), $stopWords) || strlen($item) <= 3 ) {
unset($matchWords[$key]);
}
}
$wordCountArr = array();
if ( is_array($matchWords) ) {
foreach ( $matchWords as $key => $val ) {
$val = strtolower($val);
if ( isset($wordCountArr[$val]) ) {
$wordCountArr[$val]++;
} else {
$wordCountArr[$val] = 1;
}
}
}
arsort($wordCountArr);
$wordCountArr = array_slice($wordCountArr, 0, 10);
return $wordCountArr;}
Any help would be appreciated.
Cyrillic characters are matched by the following regex and removed from the string:
$string = preg_replace('/[^a-zA-Z0-9 -]/', '', $string);
To avoid it, match word characters with \w. But you need to use the u (PCRE_UTF8) modifier for \w and friends to match letters from other scripts (including Cyrillic).
$string = preg_replace('/[^\w -]+/u', '', $string);
One more thing, the regex /\b.*?\b/i is wrong for several reasons. But if you think about it, it could match the same boundary twice.
Anyway, I believe you don't need to remove unwanted characters form your string. You're only trying to extract words. You could simply use 1 regex to match them.
Regex to match words (with Unicode Scripts, including Cyrillic)
preg_match_all('/\w+/u', $string, $matchWords);
Code
function extractCommonWords($string){
$stopWords = array('i','a','about','an','and','are','as','at','be','by','com','de','en','for','from','how','in','is','it','la','of','on','or','that','the','this','to','was','what','when','where','who','will','with','und','the','www');
$string = strtolower($string);
preg_match_all('/\w+/u', $string, $matchWords);
$matchWords = $matchWords[0];
foreach ( $matchWords as $key=>$item ) {
if ( $item == '' || in_array(strtolower($item), $stopWords) || strlen($item) <= 3 ) {
unset($matchWords[$key]);
}
}
$wordCountArr = array();
if ( is_array($matchWords) ) {
foreach ( $matchWords as $key => $val ) {
$val = mb_strtolower($val);
if ( isset($wordCountArr[$val]) ) {
$wordCountArr[$val]++;
} else {
$wordCountArr[$val] = 1;
}
}
}
arsort($wordCountArr);
$wordCountArr = array_slice($wordCountArr, 0, 10);
return $wordCountArr;
}
//for testing purposes
$sc = 'Hello there нам для!';
$result = extractCommonWords($sc);
print_r($result);
*Disclaimer: I didn't even read the rest of the code. I'm simply pointing out what was wrong
rextester demo

how to do a preg_replace on a string in php?

i have some simple code that does a preg match:
$bad_words = array('dic', 'tit', 'fuc',); //for this example i replaced the bad words
for($i = 0; $i < sizeof($bad_words); $i++)
{
if(preg_match("/$bad_words[$i]/", $str, $matches))
{
$rep = str_pad('', strlen($bad_words[$i]), '*');
$str = str_replace($bad_words[$i], $rep, $str);
}
}
echo $str;
So, if $str was "dic" the result will be '*' and so on.
Now there is a small problem if $str == f.u.c. The solution might be to use:
$pattern = '~f(.*)u(.*)c(.*)~i';
$replacement = '***';
$foo = preg_replace($pattern, $replacement, $str);
In this case i will get ***, in any case. My issue is putting all this code together.
I've tried:
$pattern = '~f(.*)u(.*)c(.*)~i';
$replacement = 'fuc';
$fuc = preg_replace($pattern, $replacement, $str);
$bad_words = array('dic', 'tit', $fuc,);
for($i = 0; $i < sizeof($bad_words); $i++)
{
if(preg_match("/$bad_words[$i]/", $str, $matches))
{
$rep = str_pad('', strlen($bad_words[$i]), '*');
$str = str_replace($bad_words[$i], $rep, $str);
}
}
echo $str;
The idea is that $fuc becomes fuc then I place it in the array then the array does its jobs, but this doesn't seem to work.
First of all, you can do all of the bad word replacements with one (dynamically generated) regex, like this:
$bad_words = array('dic', 'tit', 'fuc',);
$str = preg_replace_callback("/\b(?:" . implode( '|', $bad_words) . ")\b/",
function( $match) {
return str_repeat( '*', strlen( $match[0]));
}, $str);
Now, you have the problem of people adding periods in between the word, which you can search for with another regex and replace them as well. However, you must keep in mind that . matches any character in a regex, and must be escaped (with preg_quote() or a backslash).
$bad_words = array_map( function( $el) {
return implode( '\.', str_split( $el));
}, $bad_words);
This will create a $bad_words array similar to:
array(
'd\.i\.c',
't\.i\.t',
'f\.u\.c'
)
Now, you can use this new $bad_words array just like the above one to replace these obfuscated ones.
Hint: You can make this array_map() call "better" in the sense that it can be smarter to catch more obfuscations. For example, if you wanted to catch a bad word separated with either a period or a whitespace character or a comma, you can do:
$bad_words = array_map( function( $el) {
return implode( '(?:\.|\s|,)', str_split( $el));
}, $bad_words);
Now if you make that obfuscation group optional, you'll catch a lot more bad words:
$bad_words = array_map( function( $el) {
return implode( '(?:\.|\s|,)?', str_split( $el));
}, $bad_words);
Now, bad words should match:
f.u.c
f,u.c
f u c
fu c
f.uc
And many more.

Check if any array values are present at the end of a string

I am trying to test if a string made up of multiple words and has any values from an array at the end of it. The following is what I have so far. I am stuck on how to check if the string is longer than the array value being tested and that it is present at the end of the string.
$words = trim(preg_replace('/\s+/',' ', $string));
$words = explode(' ', $words);
$words = count($words);
if ($words > 2) {
// Check if $string ends with any of the following
$test_array = array();
$test_array[0] = 'Wizard';
$test_array[1] = 'Wizard?';
$test_array[2] = '/Wizard';
$test_array[4] = '/Wizard?';
// Stuck here
if ($string is longer than $test_array and $test_array is found at the end of the string) {
Do stuff;
}
}
By end of string do you mean the very last word? You could use preg_match
preg_match('~/?Wizard\??$~', $string, $matches);
echo "<pre>".print_r($matches, true)."</pre>";
I think you want something like this:
if (preg_match('/\/?Wizard\??$/', $string)) { // ...
If it has to be an arbitrary array (and not the one containing the 'wizard' strings you provided in your question), you could construct the regex dynamically:
$words = array('wizard', 'test');
foreach ($words as &$word) {
$word = preg_quote($word, '/');
}
$regex = '/(' . implode('|', $words) . ')$/';
if (preg_match($regex, $string)) { // ends with 'wizard' or 'test'
Is this what you want (no guarantee for correctness, couldn't test)?
foreach( $test_array as $testString ) {
$searchLength = strlen( $testString );
$sourceLength = strlen( $string );
if( $sourceLength <= $searchLength && substr( $string, $sourceLength - $searchLength ) == $testString ) {
// ...
}
}
I wonder if some regular expression wouldn't make more sense here.

Uppercase the first character of each word in a string except 'and', 'to', etc

How can I make upper-case the first character of each word in a string accept a couple of words which I don't want to transform them, like - and, to, etc?
For instance, I want this - ucwords('art and design') to output the string below,
'Art and Design'
is it possible to be like - strip_tags($text, '<p><a>') which we allow and in the string?
or I should use something else? please advise!
thanks.
None of these are really UTF8 friendly, so here's one that works flawlessly (so far)
function titleCase($string, $delimiters = array(" ", "-", ".", "'", "O'", "Mc"), $exceptions = array("and", "to", "of", "das", "dos", "I", "II", "III", "IV", "V", "VI"))
{
/*
* Exceptions in lower case are words you don't want converted
* Exceptions all in upper case are any words you don't want converted to title case
* but should be converted to upper case, e.g.:
* king henry viii or king henry Viii should be King Henry VIII
*/
$string = mb_convert_case($string, MB_CASE_TITLE, "UTF-8");
foreach ($delimiters as $dlnr => $delimiter) {
$words = explode($delimiter, $string);
$newwords = array();
foreach ($words as $wordnr => $word) {
if (in_array(mb_strtoupper($word, "UTF-8"), $exceptions)) {
// check exceptions list for any words that should be in upper case
$word = mb_strtoupper($word, "UTF-8");
} elseif (in_array(mb_strtolower($word, "UTF-8"), $exceptions)) {
// check exceptions list for any words that should be in upper case
$word = mb_strtolower($word, "UTF-8");
} elseif (!in_array($word, $exceptions)) {
// convert to uppercase (non-utf8 only)
$word = ucfirst($word);
}
array_push($newwords, $word);
}
$string = join($delimiter, $newwords);
}//foreach
return $string;
}
Usage:
$s = 'SÃO JOÃO DOS SANTOS';
$v = titleCase($s); // 'São João dos Santos'
since we all love regexps, an alternative, that also works with interpunction (unlike the explode(" ",...) solution)
$newString = preg_replace_callback("/[a-zA-Z]+/",'ucfirst_some',$string);
function ucfirst_some($match)
{
$exclude = array('and','not');
if ( in_array(strtolower($match[0]),$exclude) ) return $match[0];
return ucfirst($match[0]);
}
edit added strtolower(), or "Not" would remain "Not".
How about this ?
$string = str_replace(' And ', ' and ', ucwords($string));
You will have to use ucfirst and loop through every word, checking e.g. an array of exceptions for each one.
Something like the following:
$exclude = array('and', 'not');
$words = explode(' ', $string);
foreach($words as $key => $word) {
if(in_array($word, $exclude)) {
continue;
}
$words[$key] = ucfirst($word);
}
$newString = implode(' ', $words);
I know it is a few years after the question, but I was looking for an answer to the insuring proper English in the titles of a CMS I am programming and wrote a light weight function from the ideas on this page so I thought I would share it:
function makeTitle($title){
$str = ucwords($title);
$exclude = 'a,an,the,for,and,nor,but,or,yet,so,such,as,at,around,by,after,along,for,from,of,on,to,with,without';
$excluded = explode(",",$exclude);
foreach($excluded as $noCap){$str = str_replace(ucwords($noCap),strtolower($noCap),$str);}
return ucfirst($str);
}
The excluded list was found at:
http://www.superheronation.com/2011/08/16/words-that-should-not-be-capitalized-in-titles/
USAGE: makeTitle($title);

Categories