Given a source text like
nin2 hao3 ma
(which is a typical way to write ASCII Pinyin, without proper accentuated characters)
and given a (UTF8) conversion table like
a1;ā
e1;ē
i1;ī
o1;ō
u1;ū
ü1;ǖ
A1;Ā
E1;Ē
...
how would I convert the source text into
nín hǎo ma
?
For what it's worth I'm using PHP, and this might be a regex I'm looking into?
Ollie's algorithm was a nice start, but it didn't apply the marks correctly. For example, qiao1 became qīāō. This one is correct and complete. You can easily see how the replacement rules are defined.
It does the whole thing for tone 5 as well, although it doesn't affect the output, except for deleting the number. I left it in, in case you want to do something with tone 5.
The algorithm works as follows:
The word and tone are provided in $match[1] and [2]
A star is added behind the letter that should get the accent mark
A letter with a star is replaced by that letter with the correct tone mark.
Example:
qiao => (iao becomes ia*o) => qia*o => qiǎo
This strategy, and the use of strtr (which prioritizes longer replacements), makes sure that this won't happen:
qiao1 => qīāō
function pinyin_addaccents($string) {
# Find words with a number behind them, and replace with callback fn.
return preg_replace_callback(
'~([a-zA-ZüÜ]+)(\d)~',
'pinyin_addaccents_cb',
$string);
}
# Helper callback
function pinyin_addaccents_cb($match) {
static $accentmap = null;
if( $accentmap === null ) {
# Where to place the accent marks
$stars =
'a* e* i* o* u* ü* '.
'A* E* I* O* U* Ü* '.
'a*i a*o e*i ia* ia*o ie* io* iu* '.
'A*I A*O E*I IA* IA*O IE* IO* IU* '.
'o*u ua* ua*i ue* ui* uo* üe* '.
'O*U UA* UA*I UE* UI* UO* ÜE*';
$nostars = str_replace('*', '', $stars);
# Build an array like Array('a' => 'a*') and store statically
$accentmap = array_combine(explode(' ',$nostars), explode(' ', $stars));
unset($stars, $nostars);
}
static $vowels =
Array('a*','e*','i*','o*','u*','ü*','A*','E*','I*','O*','U*','Ü*');
static $pinyin = Array(
1 => Array('ā','ē','ī','ō','ū','ǖ','Ā','Ē','Ī','Ō','Ū','Ǖ'),
2 => Array('á','é','í','ó','ú','ǘ','Á','É','Í','Ó','Ú','Ǘ'),
3 => Array('ǎ','ě','ǐ','ǒ','ǔ','ǚ','Ǎ','Ě','Ǐ','Ǒ','Ǔ','Ǚ'),
4 => Array('à','è','ì','ò','ù','ǜ','À','È','Ì','Ò','Ù','Ǜ'),
5 => Array('a','e','i','o','u','ü','A','E','I','O','U','Ü')
);
list(,$word,$tone) = $match;
# Add star to vowelcluster
$word = strtr($word, $accentmap);
# Replace starred letter with accented
$word = str_replace($vowels, $pinyin[$tone], $word);
return $word;
}
<?php
$in = 'nin2 hao3 ma';
$out = 'nín hǎo ma';
function replacer($match) {
static $trTable = array(
1 => array(
'a' => 'ā',
'e' => 'ē',
'i' => 'ī',
'o' => 'ō',
'u' => 'ū',
'ü' => 'ǖ',
'A' => 'Ā',
'E' => 'Ē'),
2 => array('i' => 'í'),
3 => array('a' => 'ǎ')
);
list(, $word, $i) = $match;
return str_replace(
array_keys($trTable[$i]),
array_values($trTable[$i]),
$word); }
// Outputs: bool(true)
var_dump(preg_replace_callback('~(\w+)(\d+)~', 'replacer', $in) === $out);
For a .NET solution try Pinyin4j.NET
Features
Convert Chinese (both Simplified and Traditional) to most popular pinyin systems. Supporting pinyin system are listed below.
Hanyu Pinyin 汉语拼音
Tongyong Pinyin 通用拼音
Wade-Giles 威妥玛拼音
MPS2 (Mandarin Phonetic Symbols II) 国语注音符号第二式
Yale Romanization 耶鲁罗马化拼音
Gwoyeu Romatzyh国语国语罗马化拼音
To add a javascript solution:
This code places Tonemarks according to the official algorithm for placing one,
see wikipedia.
Hope that helps some of you, suggestions and improvements wellcome!
var ACCENTED = {
'1': {'a': '\u0101', 'e': '\u0113', 'i': '\u012B', 'o': '\u014D', 'u': '\u016B', 'ü': '\u01D6'},
'2': {'a': '\u00E1', 'e': '\u00E9', 'i': '\u00ED', 'o': '\u00F3', 'u': '\u00FA', 'ü': '\u01D8'},
'3': {'a': '\u01CE', 'e': '\u011B', 'i': '\u01D0', 'o': '\u01D2', 'u': '\u01D4', 'ü': '\u01DA'},
'4': {'a': '\u00E0', 'e': '\u00E8', 'i': '\u00EC', 'o': '\u00F2', 'u': '\u00F9', 'ü': '\u01DC'},
'5': {'a': 'a', 'e': 'e', 'i': 'i', 'o': 'o', 'u': 'u', 'ü': 'ü'}
};
function getPos (token) {
if (token.length === 1){
// only one letter, nothing to differentiate
return 0;
}
var precedence = ['a', 'e', 'o'];
for (i=0; i<precedence.length; i += 1){
var pos = token.indexOf(precedence[i]);
// checking a before o, will take care of ao automatically
if (pos >= 0){
return pos;
}
}
var u = token.indexOf('u');
var i = token.indexOf('i');
if (i < u){
// -iu OR u-only case, accent goes to u
return u;
} else {
// -ui OR i-only case, accent goes to i
return i;
}
// the only vowel left is ü
var ü = token.indexOf('ü');
if (ü >= 0){
return ü;
}
}
//and finally:
// we asume the input to be valid PinYin, therefore no security checks....
function placeTone(numbered_PinYin){
var ToneIndex = numbered_PinYin.charAt(numbered_PinYin.length -1);
var accentpos = getPos(numbered_PinYin);
var accented_Char = ACCENTED[ToneIndex][numbered_PinYin.charAt(accentpos)];
var accented_PinYin = "";
if (accentpos === 0){
// minus one to trimm the number off
accented_PinYin = accented_Char + numbered_PinYin.substr(1, numbered_PinYin.length-1);
} else {
var before = numbered_PinYin.substr(0, accentpos);
var after = numbered_PinYin.substring(accentpos+1, numbered_PinYin.length-1);
accented_PinYin = before + accented_Char + after;
}
return accented_PinYin;
}
console.log(placeTone('han4 zi4'));
VB Macro (Libre)Office : Convert pinyin tone numbers to accents
Hopefully the algorithm is correct accordingly to pinyin rules specially for i and u.
sub replaceNumberByTones
call PinyinTonesNumber("a([a-z]*[a-z]*)0", "a$1")
call PinyinTonesNumber("a([a-z]*[a-z]*)1", "a$1")
call PinyinTonesNumber("a([a-z]*[a-z]*)2", "á$1")
call PinyinTonesNumber("a([a-z]*[a-z]*)3", "a$1")
call PinyinTonesNumber("a([a-z]*[a-z]*)4", "à$1")
call PinyinTonesNumber("o([a-z]*[a-z]*)0", "o$1")
call PinyinTonesNumber("o([a-z]*[a-z]*)1", "o$1")
call PinyinTonesNumber("o([a-z]*[a-z]*)2", "ó$1")
call PinyinTonesNumber("o([a-z]*[a-z]*)3", "o$1")
call PinyinTonesNumber("o([a-z]*[a-z]*)4", "ò$1")
call PinyinTonesNumber("e([a-z]*[a-z]*)0", "e$1")
call PinyinTonesNumber("e([a-z]*[a-z]*)1", "e$1")
call PinyinTonesNumber("e([a-z]*[a-z]*)2", "é$1")
call PinyinTonesNumber("e([a-z]*[a-z]*)3", "e$1")
call PinyinTonesNumber("e([a-z]*[a-z]*)4", "è$1")
call PinyinTonesNumber("u([a-hj-z]*[a-hj-z]*)0", "u$1")
call PinyinTonesNumber("u([a-hj-z]*[a-hj-z]*)1", "u$1")
call PinyinTonesNumber("u([a-hj-z]*[a-hj-z]*)2", "ú$1")
call PinyinTonesNumber("u([a-hj-z]*[a-hj-z]*)3", "u$1")
call PinyinTonesNumber("u([a-hj-z]*[a-hj-z]*)4", "ù$1")
call PinyinTonesNumber("i([a-z]*[a-z]*)0", "i$1")
call PinyinTonesNumber("i([a-z]*[a-z]*)1", "i$1")
call PinyinTonesNumber("i([a-z]*[a-z]*)2", "í$1")
call PinyinTonesNumber("i([a-z]*[a-z]*)3", "i$1")
call PinyinTonesNumber("i([a-z]*[a-z]*)4", "ì$1")
End sub
sub PinyinTonesNumber(expression, replacement)
rem ----------------------------------------------------------------------
rem define variables
dim document as object
dim dispatcher as object
rem ----------------------------------------------------------------------
rem get access to the document
document = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
rem ----------------------------------------------------------------------
dim args1(18) as new com.sun.star.beans.PropertyValue
args1(0).Name = "SearchItem.StyleFamily"
args1(0).Value = 2
args1(1).Name = "SearchItem.CellType"
args1(1).Value = 0
args1(2).Name = "SearchItem.RowDirection"
args1(2).Value = true
args1(3).Name = "SearchItem.AllTables"
args1(3).Value = false
args1(4).Name = "SearchItem.Backward"
args1(4).Value = false
args1(5).Name = "SearchItem.Pattern"
args1(5).Value = false
args1(6).Name = "SearchItem.Content"
args1(6).Value = false
args1(7).Name = "SearchItem.AsianOptions"
args1(7).Value = false
args1(8).Name = "SearchItem.AlgorithmType"
args1(8).Value = 1
args1(9).Name = "SearchItem.SearchFlags"
args1(9).Value = 65536
args1(10).Name = "SearchItem.SearchString"
args1(10).Value = expression
args1(11).Name = "SearchItem.ReplaceString"
args1(11).Value = replacement
args1(12).Name = "SearchItem.Locale"
args1(12).Value = 255
args1(13).Name = "SearchItem.ChangedChars"
args1(13).Value = 2
args1(14).Name = "SearchItem.DeletedChars"
args1(14).Value = 2
args1(15).Name = "SearchItem.InsertedChars"
args1(15).Value = 2
args1(16).Name = "SearchItem.TransliterateFlags"
args1(16).Value = 1280
args1(17).Name = "SearchItem.Command"
args1(17).Value = 3
args1(18).Name = "Quiet"
args1(18).Value = true
dispatcher.executeDispatch(document, ".uno:ExecuteSearch", "", 0, args1())
end sub
Hope this helps someone
François
Related
i am trying to make a youtube video downloader with php it works fine, but if the youtube video id has a character like - or _ it shows an error undefined index url,it seems like php isn't seeing this characters e,g if the youtube video id is N65RvNkZFGE it will work but if its something like wXhTHyIgQ_U it wont work cause of the underscore here's the code:
// Check whether the url is valid
if(!empty($youtubeURL) && !filter_var($youtubeURL, FILTER_VALIDATE_URL) === false){
// Get the downloader object
$downloader = $handler->getDownloader($youtubeURL);
// Set the url
$downloader->setUrl($youtubeURL);
// Validate the youtube video url
if($downloader->hasVideo()){
// Get the video download link info
$videoDownloadLink = $downloader->getVideoDownloadLink();
$videoTitle = $videoDownloadLink[0]['title'];
$videoQuality = $videoDownloadLink[0]['qualityLabel'];
$videoFormat = $videoDownloadLink[0]['format'];
$videoFileName = strtolower(str_replace(' ', '_', $videoTitle)).'.'.$videoFormat;
$downloadURL = $videoDownloadLink[0]['url'];
$fileName = preg_replace('/[^A-Za-z0-9.\_\-]/', '', basename($videoFileName));
the YouTubeDownloader.class.php file which holds the setUrl() function, the get extractVideoId() here's the code:
public function setUrl($url){
$this->video_url = $url;
}
private function extractVideoId($video_url){
//parse the url
$parsed_url = parse_url($video_url);
if($parsed_url["path"] == "youtube.com/watch"){
$this->video_url = "https://www.".$video_url;
}elseif($parsed_url["path"] == "www.youtube.com/watch"){
$this->video_url = "https://".$video_url;
}
if(isset($parsed_url["query"])){
$query_string = $parsed_url["query"];
//parse the string separated by '&' to array
parse_str($query_string, $query_arr);
if(isset($query_arr["v"])){
return $query_arr["v"];
}
}
}
It has nothing to do with "_" or "-". "n85KukOXc0A" doesn't work either. Some videos don't return the "url" field, but "cipher". It's an attempt from YouTube to obfuscate the URL.
Your $videoDownloadLink, for the video ID "wXhTHyIgQ_U", looks like:
array (
0 =>
array (
'itag' => 18,
'bitrate' => 568627,
'width' => 640,
'height' => 360,
'lastModified' => '1575010363774854',
'contentLength' => '16085704',
'quality' => 'medium',
'qualityLabel' => '360p',
'projectionType' => 'RECTANGULAR',
'averageBitrate' => 568472,
'audioQuality' => 'AUDIO_QUALITY_LOW',
'approxDurationMs' => '226371',
'audioSampleRate' => '44100',
'audioChannels' => 2,
'cipher' => 's=__L8kZ2zTIc_OfmovvG91jyFU3WN4QTERuPCxA7rHfbHICEhCrCQkmqPth6pmfw5wmrIPOT_ijWceGCWdCeK-lVYXgIARwMGkhKDv&url=https%3A%2F%2Fr4---sn-hpa7kn7s.googlevideo.com%2Fvideoplayback%3Fexpire%3D1583898090%26ei%3DigloXtGYD4bngAeu8YXQCg%26ip%3D2a00%253Aee2%253A1200%253Ae400%253A8c11%253A6897%253A2e00%253Abef0%26id%3Do-AAcaOp-0syooPWmAUuzOfm6gHGPWYCiDlfa-RNdIP34W%26itag%3D18%26source%3Dyoutube%26requiressl%3Dyes%26mm%3D31%252C26%26mn%3Dsn-hpa7kn7s%252Csn-nv47lnly%26ms%3Dau%252Conr%26mv%3Dm%26mvi%3D3%26pl%3D32%26gcr%3Dsi%26initcwndbps%3D1023750%26vprv%3D1%26mime%3Dvideo%252Fmp4%26gir%3Dyes%26clen%3D16085704%26ratebypass%3Dyes%26dur%3D226.371%26lmt%3D1575010363774854%26mt%3D1583876448%26fvip%3D4%26fexp%3D23842630%26c%3DWEB%26txp%3D5531432%26sparams%3Dexpire%252Cei%252Cip%252Cid%252Citag%252Csource%252Crequiressl%252Cgcr%252Cvprv%252Cmime%252Cgir%252Cclen%252Cratebypass%252Cdur%252Clmt%26lsparams%3Dmm%252Cmn%252Cms%252Cmv%252Cmvi%252Cpl%252Cinitcwndbps%26lsig%3DABSNjpQwRQIgBvV2KI0zNTv-7PsmdoRnpyNBvxeMRJIHSlKjfScxihcCIQDlHa5A-1cGAVReyssZ4YkH2nV2rdN1fel6_-Bkv7CAjA%253D%253D&sp=sig',
'title' => 'Post Malone - Circles',
'mime' => 'video/mp4',
'format' => 'mp4',
),
)
As you see, there is no "url" field, but there is a "cipher" field.
If we decode it with parse_str($videoDownloadLink[0]['cipher'], $cipher) we get:
array (
's' => '__L8kZ2zTIc_OfmovvG91jyFU3WN4QTERuPCxA7rHfbHICEhCrCQkmqPth6pmfw5wmrIPOT_ijWceGCWdCeK-lVYXgIARwMGkhKDv',
'url' => 'https://r4---sn-hpa7kn7s.googlevideo.com/videoplayback?expire=1583898090&ei=igloXtGYD4bngAeu8YXQCg&ip=2a00%3Aee2%3A1200%3Ae400%3A8c11%3A6897%3A2e00%3Abef0&id=o-AAcaOp-0syooPWmAUuzOfm6gHGPWYCiDlfa-RNdIP34W&itag=18&source=youtube&requiressl=yes&mm=31%2C26&mn=sn-hpa7kn7s%2Csn-nv47lnly&ms=au%2Conr&mv=m&mvi=3&pl=32&gcr=si&initcwndbps=1023750&vprv=1&mime=video%2Fmp4&gir=yes&clen=16085704&ratebypass=yes&dur=226.371&lmt=1575010363774854&mt=1583876448&fvip=4&fexp=23842630&c=WEB&txp=5531432&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cgcr%2Cvprv%2Cmime%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&lsparams=mm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=ABSNjpQwRQIgBvV2KI0zNTv-7PsmdoRnpyNBvxeMRJIHSlKjfScxihcCIQDlHa5A-1cGAVReyssZ4YkH2nV2rdN1fel6_-Bkv7CAjA%3D%3D',
'sp' => 'sig',
)
You need to properly scramble the "s" field value and add it to the URL as the field named with the "sp" field value.
The way it needs to be scrambled changes regularly. The current way from https://www.youtube.com/yts/jsbin/player_ias-vfle4a9aa/en_US/base.js is:
var Ps = function(a) {
a = a.split("");
Os.Dw(a, 1);
Os.hZ(a, 21);
Os.An(a, 24);
Os.hZ(a, 34);
Os.hZ(a, 18);
Os.hZ(a, 63);
return a.join("")
};
var Os = {
Dw: function(a, b) {
a.splice(0, b)
},
An: function(a) {
a.reverse()
},
hZ: function(a, b) {
var c = a[0];
a[0] = a[b % a.length];
a[b % a.length] = c
}
};
Which translates into PHP as:
function scramble($a) {
$a = str_split($a);
scr_splice($a, 1);
scr_swap($a, 21);
scr_reverse($a, 24);
scr_swap($a, 34);
scr_swap($a, 18);
scr_swap($a, 63);
return implode('', $a);
}
function scr_reverse(&$a) {
$a = array_reverse($a);
}
function scr_splice(&$a, $b) {
array_splice($a, 0, $b);
}
function scr_swap(&$a, $b) {
$c = $a[0];
$a[0] = $a[$b % count($a)];
$a[$b % count($a)] = $c;
}
In your code, you need to check which type of URL you got and get the proper URL.
if (isset($videoDownloadLink[0]['url'])) {
$downloadURL = $videoDownloadLink[0]['url'];
}
else if (isset($videoDownloadLink[0]['cipher'])) {
parse_str($videoDownloadLink[0]['cipher'], $cipher);
$downloadURL = $cipher['url']."&".$cipher["sp"]."=".scramble($cipher["s"]);
}
else {
die('Error getting YouTube URL!');
}
Note:
This will only work until YouTube changes the way it's scrambled again.
In both Postman and jQuery, I'm getting responses in the form
{"key1": "value1", "key2": "value2"}null
That trailing null is messing with anything that tries to parse it client-side and I can't figure out where it's coming from. If I error_log the encoded JSON before echoing it, there's no trailing null, so I assume that it's a string terminator, but I didn't think that PHP used null-terminated strings. How do I get rid of these nulls?
The object being encoded and returned:
public function jsonSerialize()
{
return [
'internal_id' => $this->internal_id, //int
'friendly_name' => $this->friendly_name, //string
'external_id' => $this->external_id, //string
'picture' => $this->picture //string
];
}
The actual return statement is just echo(json_encode($retval));
Once a PHP file has executed, you have to exit manually or return instead of echoing, otherwise it'll return NULL implicitly and mess everything up. Lesson learned.
Not the most elegant probably... but this SAVED me:
function removeTrailingNulls(__str){
var sanitized = __str;
var lastCharIndex = sanitized.length - 1;
var lastChar = sanitized[lastCharIndex];
var lastCharCode = lastChar.charCodeAt(0);
var isWeirdNullSpace = lastCharCode === 0;
console.log('checking last char (' + lastChar + ') code: ' + lastCharCode + '...null space end?' + isWeirdNullSpace);
var loopCount = 0;
while(isWeirdNullSpace){
sanitized = sanitized.substring(0, sanitized.length-1);
lastChar = sanitized[sanitized.length-1];
lastCharCode = lastChar.charCodeAt(0);
isWeirdNullSpace = lastCharCode === 0;
loopCount++;
if(loopCount>100) break; // prevent infinite loops just in case.
}
return String(sanitized);
}
I created a PHP script that checks the HTTP_ACCEPT_LANGUAGE and loads the website using the appropriate language from the 1st two characters:
$http_lang = substr($_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2);
switch ($http_lang) {
case 'en':
$SESSION->conf['language'] = 'english';
break;
case 'es':
$SESSION->conf['language'] = 'spanish';
break;
default:
$SESSION->conf['language'] = $PREFS->conf['languages'][$SESSION->conf['language_id']];
}
If I change the language to Spanish in Firefox the website loads in Spanish fine. However I have had several reports that people in Colombia see the website in english.
Details:
"es-co" LCID = 9226 Spanish(Colombia)
Anyone have any ideas as to why this is happening? I thought this was the best way to check what language users support.
A more contemporary method would be to use http_negotiate_language():
$map = array("en" => "english", "es" => "spanish");
$conf_language= $map[ http_negotiate_language(array_keys($map)) ];
If you don't have the http extension installed (and not the intl one as well), there is yet another workaround in the comments (user-note #86787 (Nov 2008; by Anonymous)):
<?php
/*
determine which language out of an available set the user prefers most
$available_languages array with language-tag-strings (must be lowercase) that are available
$http_accept_language a HTTP_ACCEPT_LANGUAGE string (read from $_SERVER['HTTP_ACCEPT_LANGUAGE'] if left out)
*/
function prefered_language ($available_languages,$http_accept_language="auto") {
// if $http_accept_language was left out, read it from the HTTP-Header
if ($http_accept_language == "auto") $http_accept_language = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
// standard for HTTP_ACCEPT_LANGUAGE is defined under
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
// pattern to find is therefore something like this:
// 1#( language-range [ ";" "q" "=" qvalue ] )
// where:
// language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
// qvalue = ( "0" [ "." 0*3DIGIT ] )
// | ( "1" [ "." 0*3("0") ] )
preg_match_all("/([[:alpha:]]{1,8})(-([[:alpha:]|-]{1,8}))?" .
"(\s*;\s*q\s*=\s*(1\.0{0,3}|0\.\d{0,3}))?\s*(,|$)/i",
$http_accept_language, $hits, PREG_SET_ORDER);
// default language (in case of no hits) is the first in the array
$bestlang = $available_languages[0];
$bestqval = 0;
foreach ($hits as $arr) {
// read data from the array of this hit
$langprefix = strtolower ($arr[1]);
if (!empty($arr[3])) {
$langrange = strtolower ($arr[3]);
$language = $langprefix . "-" . $langrange;
}
else $language = $langprefix;
$qvalue = 1.0;
if (!empty($arr[5])) $qvalue = floatval($arr[5]);
// find q-maximal language
if (in_array($language,$available_languages) && ($qvalue > $bestqval)) {
$bestlang = $language;
$bestqval = $qvalue;
}
// if no direct hit, try the prefix only but decrease q-value by 10% (as http_negotiate_language does)
else if (in_array($langprefix,$available_languages) && (($qvalue*0.9) > $bestqval)) {
$bestlang = $langprefix;
$bestqval = $qvalue*0.9;
}
}
return $bestlang;
}
?>
I used the regex from #GabrielAnderson and devised this function which behaves according to RFC 2616 (when no quality value is given to a language, it defaults to 1).
When several languages share the same quality value, the most specific are given priority over the less specific ones. (this behaviour is not part of the RFC which provides no recommendation for this specific case)
function Get_Client_Prefered_Language ($getSortedList = false, $acceptedLanguages = false)
{
if (empty($acceptedLanguages))
$acceptedLanguages = $_SERVER["HTTP_ACCEPT_LANGUAGE"];
// regex inspired from #GabrielAnderson on http://stackoverflow.com/questions/6038236/http-accept-language
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})*)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $acceptedLanguages, $lang_parse);
$langs = $lang_parse[1];
$ranks = $lang_parse[4];
// (create an associative array 'language' => 'preference')
$lang2pref = array();
for($i=0; $i<count($langs); $i++)
$lang2pref[$langs[$i]] = (float) (!empty($ranks[$i]) ? $ranks[$i] : 1);
// (comparison function for uksort)
$cmpLangs = function ($a, $b) use ($lang2pref) {
if ($lang2pref[$a] > $lang2pref[$b])
return -1;
elseif ($lang2pref[$a] < $lang2pref[$b])
return 1;
elseif (strlen($a) > strlen($b))
return -1;
elseif (strlen($a) < strlen($b))
return 1;
else
return 0;
};
// sort the languages by prefered language and by the most specific region
uksort($lang2pref, $cmpLangs);
if ($getSortedList)
return $lang2pref;
// return the first value's key
reset($lang2pref);
return key($lang2pref);
}
Example:
print_r(Get_Client_Prefered_Language(true, 'en,en-US,en-AU;q=0.8,fr;q=0.6,en-GB;q=0.4'));
Outputs:
Array
(
[en-US] => 1
[en] => 1
[en-AU] => 0.8
[fr] => 0.6
[en-GB] => 0.4
)
As you can notice, 'en-US' appears in first position despite the fact that 'en' was first in the given string.
So you could use this function and just replace your first line of code by:
$http_lang = substr(Get_Client_Prefered_Language(),0,2);
Do you know if this is happening for all visitors to your site from Colombia? Users are usually free to alter the language settings of their browsers — or to have them altered for them by whoever is in charge of the computer. As zerkms recommends, try logging IP addresses and their headers.
If you have the intl extension installed you can use Locale::lookup and Locale::acceptFromHttp to get a best-fit choice of language from the users browser settings and a list of what translations you have available.
Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']); # e.g. "en_US"
In the end I went with this solution:
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
if (count($lang_parse[1])){
$langs = array_combine($lang_parse[1], $lang_parse[4]);
foreach ($langs as $lang => $val){
if ($val === '') $langs[$lang] = 1;
}
arsort($langs, SORT_NUMERIC);
}
foreach ($langs as $lang => $val){
if (strpos($lang,'en')===0){
$language = 'english';
break;
} else if (strpos($lang,'es')===0){
$language = 'spanish';
}
}
}
I would like to thank AJ for the links. Also thanks to all that replied.
I will use full locale code to refer language, because like zh-TW and zh-CN is 2 different language.
function httpAcceptLanguage($httpAcceptLanguage = null)
{
if ($httpAcceptLanguage == null) {
$httpAcceptLanguage = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
}
$languages = explode(',', $httpAcceptLanguage);
$result = array();
foreach ($languages as $language) {
$lang = explode(';q=', $language);
// $lang == [language, weight], default weight = 1
$result[$lang[0]] = isset($lang[1]) ? floatval($lang[1]) : 1;
}
arsort($result);
return $result;
}
// zh-TW,en-US;q=0.7,en;q=0.3
echo $_SERVER['HTTP_ACCEPT_LANGUAGE'];
/*
Array
(
[zh-TW] => 1
[en-US] => 0.7
[en] => 0.3
)
*/
print_r(httpAcceptLanguage());
if you want to store languages in array, i do this:
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', 'pt-br,pt;q=0.8,en-us;q=0.5,en,en-uk;q=0.3', $lang_parse);
$langs = $lang_parse[1];
$rank = $lang_parse[4];
for($i=0; $i<count($langs); $i++){
if ($rank[$i] == NULL) $rank[$i] = $rank[$i+1];
}
this output an array to languages e other with values
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', 'pt-br,pt;q=0.8,en-us;q=0.5,en,en-uk;q=0.3', $lang_parse);
$langs = $lang_parse[1];
$rank = $lang_parse[4];
$lang = array();
for($i=0; $i<count($langs); $i++){
$lang[$langs[$i]] = ($rank[$i] == NULL) ? $rank[$i+1] : $rank[$i];
}
this output an array like this:
Array
(
[pt-br] => 0.8
[pt] => 0.8
[en-us] => 0.5
[en] => 0.3
[en-uk] => 0.3
)
I put my trust in the skilled programmers who work for PHP and think ahead.
Here is my version of a label for the Google translator drop down.
function gethttplanguage(){
$langs = array(
'en',// default
'it',
'dn',
'fr',
'es'
);
$questions = array(
"en" => "If you wish to see this site in another language click here",
"it" => "Se vuole vedere questo sito in italiano clicca qui",
"dn" => "Hvis du ønsker at se denne hjemmeside i danske klik her",
"fr" => "Si vous voulez visualiser ce site en français, cliquez ici",
"es" => "Si quieres ver este sitio en español haga clic aquí"
);
$result = array();
http_negotiate_language($langs, &$result);
return $questions[key($result)];
}
I have to convert .DBF and .FPT files from Visual FoxPro to MySQL. Right now my script works for .DBF files, it opens and reads them with dbase_open() and dbase_get_record_with_names() and then executes the MySQL INSERT commands.
However, some fields of these .DBF files are of type MEMO and therefore stored in a separate files ending in .FPT. How do I read this file?
I have found the specifications of this filetype in MSDN, but I don't know how I can read this file byte-wise with PHP (also, I would really prefer a simplier solution).
Any ideas?
Alright, I have carefully studied the MSDN specifications of DBF and FPT file structures and the outcome is a beautiful PHP class which can open a DBF and (optional) an FPT memo file at the same time. This class will give you record after record and thereby fetch any memos from the memo file - if opened.
class Prodigy_DBF {
private $Filename, $DB_Type, $DB_Update, $DB_Records, $DB_FirstData, $DB_RecordLength, $DB_Flags, $DB_CodePageMark, $DB_Fields, $FileHandle, $FileOpened;
private $Memo_Handle, $Memo_Opened, $Memo_BlockSize;
private function Initialize() {
if($this->FileOpened) {
fclose($this->FileHandle);
}
if($this->Memo_Opened) {
fclose($this->Memo_Handle);
}
$this->FileOpened = false;
$this->FileHandle = NULL;
$this->Filename = NULL;
$this->DB_Type = NULL;
$this->DB_Update = NULL;
$this->DB_Records = NULL;
$this->DB_FirstData = NULL;
$this->DB_RecordLength = NULL;
$this->DB_CodePageMark = NULL;
$this->DB_Flags = NULL;
$this->DB_Fields = array();
$this->Memo_Handle = NULL;
$this->Memo_Opened = false;
$this->Memo_BlockSize = NULL;
}
public function __construct($Filename, $MemoFilename = NULL) {
$this->Prodigy_DBF($Filename, $MemoFilename);
}
public function Prodigy_DBF($Filename, $MemoFilename = NULL) {
$this->Initialize();
$this->OpenDatabase($Filename, $MemoFilename);
}
public function OpenDatabase($Filename, $MemoFilename = NULL) {
$Return = false;
$this->Initialize();
$this->FileHandle = fopen($Filename, "r");
if($this->FileHandle) {
// DB Open, reading headers
$this->DB_Type = dechex(ord(fread($this->FileHandle, 1)));
$LUPD = fread($this->FileHandle, 3);
$this->DB_Update = ord($LUPD[0])."/".ord($LUPD[1])."/".ord($LUPD[2]);
$Rec = unpack("V", fread($this->FileHandle, 4));
$this->DB_Records = $Rec[1];
$Pos = fread($this->FileHandle, 2);
$this->DB_FirstData = (ord($Pos[0]) + ord($Pos[1]) * 256);
$Len = fread($this->FileHandle, 2);
$this->DB_RecordLength = (ord($Len[0]) + ord($Len[1]) * 256);
fseek($this->FileHandle, 28); // Ignoring "reserved" bytes, jumping to table flags
$this->DB_Flags = dechex(ord(fread($this->FileHandle, 1)));
$this->DB_CodePageMark = ord(fread($this->FileHandle, 1));
fseek($this->FileHandle, 2, SEEK_CUR); // Ignoring next 2 "reserved" bytes
// Now reading field captions and attributes
while(!feof($this->FileHandle)) {
// Checking for end of header
if(ord(fread($this->FileHandle, 1)) == 13) {
break; // End of header!
} else {
// Go back
fseek($this->FileHandle, -1, SEEK_CUR);
}
$Field["Name"] = trim(fread($this->FileHandle, 11));
$Field["Type"] = fread($this->FileHandle, 1);
fseek($this->FileHandle, 4, SEEK_CUR); // Skipping attribute "displacement"
$Field["Size"] = ord(fread($this->FileHandle, 1));
fseek($this->FileHandle, 15, SEEK_CUR); // Skipping any remaining attributes
$this->DB_Fields[] = $Field;
}
// Setting file pointer to the first record
fseek($this->FileHandle, $this->DB_FirstData);
$this->FileOpened = true;
// Open memo file, if exists
if(!empty($MemoFilename) and file_exists($MemoFilename) and preg_match("%^(.+).fpt$%i", $MemoFilename)) {
$this->Memo_Handle = fopen($MemoFilename, "r");
if($this->Memo_Handle) {
$this->Memo_Opened = true;
// Getting block size
fseek($this->Memo_Handle, 6);
$Data = unpack("n", fread($this->Memo_Handle, 2));
$this->Memo_BlockSize = $Data[1];
}
}
}
return $Return;
}
public function GetNextRecord($FieldCaptions = false) {
$Return = NULL;
$Record = array();
if(!$this->FileOpened) {
$Return = false;
} elseif(feof($this->FileHandle)) {
$Return = NULL;
} else {
// File open and not EOF
fseek($this->FileHandle, 1, SEEK_CUR); // Ignoring DELETE flag
foreach($this->DB_Fields as $Field) {
$RawData = fread($this->FileHandle, $Field["Size"]);
// Checking for memo reference
if($Field["Type"] == "M" and $Field["Size"] == 4 and !empty($RawData)) {
// Binary Memo reference
$Memo_BO = unpack("V", $RawData);
if($this->Memo_Opened and $Memo_BO != 0) {
fseek($this->Memo_Handle, $Memo_BO[1] * $this->Memo_BlockSize);
$Type = unpack("N", fread($this->Memo_Handle, 4));
if($Type[1] == "1") {
$Len = unpack("N", fread($this->Memo_Handle, 4));
$Value = trim(fread($this->Memo_Handle, $Len[1]));
} else {
// Pictures will not be shown
$Value = "{BINARY_PICTURE}";
}
} else {
$Value = "{NO_MEMO_FILE_OPEN}";
}
} else {
$Value = trim($RawData);
}
if($FieldCaptions) {
$Record[$Field["Name"]] = $Value;
} else {
$Record[] = $Value;
}
}
$Return = $Record;
}
return $Return;
}
function __destruct() {
// Cleanly close any open files before destruction
$this->Initialize();
}
}
The class can be used like this:
$Test = new Prodigy_DBF("customer.DBF", "customer.FPT");
while(($Record = $Test->GetNextRecord(true)) and !empty($Record)) {
print_r($Record);
}
It might not be an almighty perfect class, but it works for me. Feel free to use this code, but note that the class is VERY tolerant - it doesn't care if fread() and fseek() return true or anything else - so you might want to improve it a bit before using.
Also note that there are many private variables like number of records, recordsize etc. which are not used at the moment.
I replaced this code:
fseek($this->Memo_Handle, (trim($RawData) * $this->Memo_BlockSize)+8);
$Value = trim(fread($this->Memo_Handle, $this->Memo_BlockSize));
with this code:
fseek($this->Memo_Handle, (trim($RawData) * $this->Memo_BlockSize)+4);
$Len = unpack("N", fread($this->Memo_Handle, 4));
$Value = trim(fread($this->Memo_Handle, $Len[1] ));
this helped for me
Although not PHP, VFP is 1-based references and I think PHP is zero-based references so you'll have to decypher and adjust accordingly, but this works and hopefully you'll be able
to post your version of this portion when finished.
FILETOSTR() in VFP will open a file, and read the entire content into
a single memory variable as a character string -- all escape keys, high byte characters, etc, intact. You'll probably need to rely on an FOPEN(), FSEEK(), FCLOSE(), etc.
MemoTest.FPT was my sample memo table/file
fpt1 = FILETOSTR( "MEMOTEST.FPT" )
First, you'll have to detect the MEMO BLOCK SIZE used when the file was created. Typically this would be 64 BYTES, but per the link you had in your post.
The header positions 6-7 identify the size (VFP, positions 7 and 8). The first byte is the high-order
nBlockSize = ASC( SUBSTR( fpt1, 7, 1 )) * 256 + ASC( SUBSTR( fpt1, 8, 1 ))
Now, at your individual records. Wherever in your DBF structure has the memo FIELD (and you can have many per single record structure) there will be 4 bytes. In THE RECORD field, it identifies the "block" in the memo file where the content is stored.
MemoBytes = 4 bytes at your identified field location. These will be stored as ASCII from 0-255. This field is stored with the FIRST byte as low-order and the 4th byte as 256^3 = 16777216. The first "Block" ever used will be starting in position offset of 512 per the memo .fpt file spec that the header takes up positions 0-511.
So, if your first memo field has a content of "8000" where the 8 is the actual 0x08, not number "8" which is 0x38, and the zeros are 0x00.
YourMemoField = "8000" (actually use ascii, but for readability showing hex expected value)
First Byte is ASCII value * 1 ( 256 ^ 0 )
Second Byte is ASCII value * 256 (256 ^ 1)
Third Byte is ASCII value * 65536 (256 ^ 2)
Fourth Byte is ASCII value * 16777216 (256 ^ 3)
nMemoBlock = byte1 + ( byte2 * 256 ) + ( byte3 * 65536 ) + ( byte4 * 16777216 )
Now, you'll need to FSEEK() to the
FSEEK( handle, nMemoBlock * nBlockSize +1 )
for the first byte of the block you are looking for. This will point to the BLOCK header. In this case, per the spec, the first 4 bytes identify the Block SIGNATURE, the second 4 bytes is the length of the content. For these two, the bytes are stored with HIGH-BYTE first.
From your FSEEK(), its REVERSE of the nMemoBlock above with the high-byte. The "Byte1-4" here are from your FSEEK() position
nSignature = ( byte1 * 16777216 ) + ( byte2 * 65536 ) + ( byte3 * 256 ) + byte4
nMemoLength = ( byte5 * 16777216 ) + ( byte6 * 65536 ) + ( byte7 * 256 ) + byte8
Now, FSEEK() to the 9th byte (1st actual character of the data AFTER the 8 bytes of the header you just read for signature and memo length). This is the beginning of your data.
Now, read the rest of the content...
FSEEK() +9 characters to new position
cFinalMemoData = FREAD( handle, nMemoLength )
I know this isn't perfect, nor PHP script, but its enough of pseudo-code on hOW things are stored and hopefully gets you WELL on your way.
Again, PLEASE take into consideration as you are stepping through your debug process to ensure 0 or 1 offset basis. To help simplify and test this, I created a simple .DBF with 2 fields... a character field and a memo field, added a few records and some basic content to confirm all content, positions, etc.
I think it's unlikely there are FoxPro libraries in PHP.
You may have to code it from scratch. For byte-wise reading, meet fopen() fread() and colleagues.
Edit: There seems to be a Visual FoxPro ODBC driver. You may be able to connect to a FoxPro database through PDO and that connector. How the chances of success are, and how much work it would be, I don't know.
The FPT file contains memo data. In the DBF you have columns of type memo, and the information in this column is a pointer to the entry in the FPT file.
If you are querying the data from the table you only have to reference the memo column to get the data. You do not need to parse the data out of the FPT file separately. The OLE DB driver (or the ODBC driver if your files are VFP 6 or earlier) should just give you this information.
There is a tool that will automatically migrate your Visual FoxPro data to MySQL. You might want to check it out to see if you can save some time:
Go to http://leafe.com/dls/vfp
and search for "Stru2MySQL_2" for the tool to migrate the structures of the data and "VFP2MySQL Data Upload program" for tools to help with the migration.
Rick Schummer VFP MVP
You also might want to check the PHP dbase libraries.They work quite well with DBF files.
<?
class Prodigy_DBF {
private $Filename, $DB_Type, $DB_Update, $DB_Records, $DB_FirstData, $DB_RecordLength, $DB_Flags, $DB_CodePageMark, $DB_Fields, $FileHandle, $FileOpened;
private $Memo_Handle, $Memo_Opened, $Memo_BlockSize;
private function Initialize() {
if($this->FileOpened) {
fclose($this->FileHandle);
}
if($this->Memo_Opened) {
fclose($this->Memo_Handle);
}
$this->FileOpened = false;
$this->FileHandle = NULL;
$this->Filename = NULL;
$this->DB_Type = NULL;
$this->DB_Update = NULL;
$this->DB_Records = NULL;
$this->DB_FirstData = NULL;
$this->DB_RecordLength = NULL;
$this->DB_CodePageMark = NULL;
$this->DB_Flags = NULL;
$this->DB_Fields = array();
$this->Memo_Handle = NULL;
$this->Memo_Opened = false;
$this->Memo_BlockSize = NULL;
}
public function __construct($Filename, $MemoFilename = NULL) {
$this->Prodigy_DBF($Filename, $MemoFilename);
}
public function Prodigy_DBF($Filename, $MemoFilename = NULL) {
$this->Initialize();
$this->OpenDatabase($Filename, $MemoFilename);
}
public function OpenDatabase($Filename, $MemoFilename = NULL) {
$Return = false;
$this->Initialize();
$this->FileHandle = fopen($Filename, "r");
if($this->FileHandle) {
// DB Open, reading headers
$this->DB_Type = dechex(ord(fread($this->FileHandle, 1)));
$LUPD = fread($this->FileHandle, 3);
$this->DB_Update = ord($LUPD[0])."/".ord($LUPD[1])."/".ord($LUPD[2]);
$Rec = unpack("V", fread($this->FileHandle, 4));
$this->DB_Records = $Rec[1];
$Pos = fread($this->FileHandle, 2);
$this->DB_FirstData = (ord($Pos[0]) + ord($Pos[1]) * 256);
$Len = fread($this->FileHandle, 2);
$this->DB_RecordLength = (ord($Len[0]) + ord($Len[1]) * 256);
fseek($this->FileHandle, 28); // Ignoring "reserved" bytes, jumping to table flags
$this->DB_Flags = dechex(ord(fread($this->FileHandle, 1)));
$this->DB_CodePageMark = ord(fread($this->FileHandle, 1));
fseek($this->FileHandle, 2, SEEK_CUR); // Ignoring next 2 "reserved" bytes
// Now reading field captions and attributes
while(!feof($this->FileHandle)) {
// Checking for end of header
if(ord(fread($this->FileHandle, 1)) == 13) {
break; // End of header!
} else {
// Go back
fseek($this->FileHandle, -1, SEEK_CUR);
}
$Field["Name"] = trim(fread($this->FileHandle, 11));
$Field["Type"] = fread($this->FileHandle, 1);
fseek($this->FileHandle, 4, SEEK_CUR); // Skipping attribute "displacement"
$Field["Size"] = ord(fread($this->FileHandle, 1));
fseek($this->FileHandle, 15, SEEK_CUR); // Skipping any remaining attributes
$this->DB_Fields[] = $Field;
}
// Setting file pointer to the first record
fseek($this->FileHandle, $this->DB_FirstData);
$this->FileOpened = true;
// Open memo file, if exists
if(!empty($MemoFilename) and file_exists($MemoFilename) and preg_match("%^(.+).fpt$%i", $MemoFilename)) {
$this->Memo_Handle = fopen($MemoFilename, "r");
if($this->Memo_Handle) {
$this->Memo_Opened = true;
// Getting block size
fseek($this->Memo_Handle, 6);
$Data = unpack("n", fread($this->Memo_Handle, 2));
$this->Memo_BlockSize = $Data[1];
}
}
}
return $Return;
}
public function GetNextRecord($FieldCaptions = false) {
$Return = NULL;
$Record = array();
if(!$this->FileOpened) {
$Return = false;
} elseif(feof($this->FileHandle)) {
$Return = NULL;
} else {
// File open and not EOF
fseek($this->FileHandle, 1, SEEK_CUR); // Ignoring DELETE flag
foreach($this->DB_Fields as $Field) {
$RawData = fread($this->FileHandle, $Field["Size"]);
// Checking for memo reference
if($Field["Type"] == "M" and $Field["Size"] == 4 and !empty($RawData)) {
// Binary Memo reference
$Memo_BO = unpack("V", $RawData);
if($this->Memo_Opened and $Memo_BO != 0) {
fseek($this->Memo_Handle, $Memo_BO[1] * $this->Memo_BlockSize);
$Type = unpack("N", fread($this->Memo_Handle, 4));
if($Type[1] == "1") {
$Len = unpack("N", fread($this->Memo_Handle, 4));
$Value = trim(fread($this->Memo_Handle, $Len[1]));
} else {
// Pictures will not be shown
$Value = "{BINARY_PICTURE}";
}
} else {
$Value = "{NO_MEMO_FILE_OPEN}";
}
} else {
if($Field["Type"] == "M"){
if(trim($RawData) > 0) {
fseek($this->Memo_Handle, (trim($RawData) * $this->Memo_BlockSize)+8);
$Value = trim(fread($this->Memo_Handle, $this->Memo_BlockSize));
}
}else{
$Value = trim($RawData);
}
}
if($FieldCaptions) {
$Record[$Field["Name"]] = $Value;
} else {
$Record[] = $Value;
}
}
$Return = $Record;
}
return $Return;
}
function __destruct() {
// Cleanly close any open files before destruction
$this->Initialize();
}
}
?>
How can I re-factor the following code to make it more concise and more maintainable?
if ($row['vocation'] == 1) $vocation = "sorcerer";
if ($row['vocation'] == 2) $vocation = "druid";
if ($row['vocation'] == 3) $vocation = "paladin";
if ($row['vocation'] == 4) $vocation = "knight";
if ($row['vocation'] == 5) $vocation = "master sorcerer";
if ($row['vocation'] == 6) $vocation = "elder druid";
if ($row['vocation'] == 7) $vocation = "royal paladin";
if ($row['vocation'] == 8) $vocation = "elite knight";
else $vocation = "none";
I would recommend using an array, like this:
static $vocations = array(
1 => 'sorceror',
2 => 'druid',
3 => 'paladin',
4 => 'knight',
5 => 'master sorceror',
6 => 'elder druid',
7 => 'royal paladin',
8 => 'elite knight',
);
$vocation =
isset($vocations[$row['vocation']]) ? $vocations[$row['vocation']] : 'none';
Here's an example of used a switch to do this:
switch ($row['vocation']) {
case 1:
$vocation = "sorcerer";
break;
case 2:
$vocation = etc..
default:
$vocation = "none";
}
This is a common thing for many languages like C, Java, and C# and many others as well.
Here is another suggestion:
<?php
class Action
{
const TYPE__ADD = 0;
const TYPE__VIEW = 1;
const TYPE__EDIT = 2;
const TYPE__PATCH = 3;
const TYPE__DELETE = 4;
const TYPE__MAP = [
self::TYPE__ADD => 'add',
self::TYPE__VIEW => 'access',
self::TYPE__EDIT => 'edit',
self::TYPE__PATCH => 'patch',
self::TYPE__DELETE => 'delete'
];
protected $type;
public function setType(int $type)
{
if (!isset(self::TYPE__MAP[$this->type])) throw new \Exception(sprintf('Invalid type. Possible options are: %s.', implode(',', self::TYPE__MAP)));
$this->type = $type;
}
public function getType(): int
{
return $this->type;
}
public function getTypeStr(): string
{
return self::TYPE__MAP[$this->type];
}
}
// Test
$action = new Action();
$action->setType(Action::TYPE__PATCH);
echo 'Action type is: '.$action->getTypeStr().', and its representative int value is: '.$action->getType();
Following piece might be a little better. 8 elements are OK, but what if the list was containing 1000.
$list = array("sorcerer", "druid", ...);
$vocation = "none";
if($row['vocation'] <= count($list)){
$vocation = $list[$row['vocation'] - 1];
}
I'd use the suggestion with array and I'd use constants to represent the integer values like this:
define('VOCATION_SORCEROR', 1);
define('VOCATION_DRUID', 2);
define('VOCATION_PALADIN', 3);
$vocations = array(
VOCATION_SORCEROR => 'sorceror',
VOCATION_DRUID => 'druid',
VOCATION_PALADIN => 'paladin'
);
Start your project out right, use const now to represent those numeric constants and save yourself some headaches down the line. (in addition to using switch/case as others have suggested)