TemplateRender.php 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588
  1. <?php
  2. namespace App\Http\Api;
  3. use App\Http\Controllers\CorpusController;
  4. use App\Models\BookTitle as BookSeries;
  5. use App\Models\Channel;
  6. use App\Models\DhammaTerm;
  7. use App\Models\Discussion;
  8. use App\Models\PageNumber;
  9. use App\Models\PaliText;
  10. use App\Services\ArticleService;
  11. use App\Tools\Tools;
  12. use Illuminate\Support\Facades\Cache;
  13. use Illuminate\Support\Facades\Log;
  14. use Illuminate\Support\Str;
  15. class TemplateRender
  16. {
  17. protected $param = [];
  18. protected $mode = 'read';
  19. protected $channel_id = [];
  20. protected $debug = [];
  21. protected $format = 'react';
  22. protected $studioId = null;
  23. protected $lang = 'en';
  24. protected $langFamily = 'en';
  25. protected $glossaryKey = 'glossary';
  26. protected $channelInfo = [];
  27. protected $options = [
  28. 'mode' => 'read',
  29. 'channelType' => 'translation',
  30. 'contentType' => 'markdown',
  31. 'format' => 'react',
  32. 'debug' => [],
  33. 'studioId' => null,
  34. 'lang' => 'zh-Hans',
  35. 'footnote' => false,
  36. 'paragraph' => false,
  37. 'origin' => true,
  38. 'translation' => true,
  39. ];
  40. /**
  41. * Create a new command instance.
  42. * string $mode 'read' | 'edit'
  43. * string $format 'react' | 'text' | 'tex' | 'unity'
  44. *
  45. * @return void
  46. */
  47. public function __construct(array $param, $channelInfo, string $mode, string $format = 'react', ?string $studioId = null, $debug = [], $lang = 'zh-Hans')
  48. {
  49. $this->param = $param;
  50. foreach ($channelInfo as $channel) {
  51. if ($channel && isset($channel->uid)) {
  52. $this->channel_id[] = $channel->uid;
  53. } else {
  54. Log::error('template render init error invalid channel');
  55. }
  56. }
  57. $this->channelInfo = $channelInfo;
  58. $this->mode = $mode;
  59. $this->format = $format;
  60. $this->studioId = $studioId;
  61. $this->debug = $debug;
  62. $this->glossaryKey = 'glossary';
  63. if (count($this->channel_id) > 0) {
  64. $channelId = $this->channel_id[0];
  65. if (Str::isUuid($channelId)) {
  66. $lang = Channel::where('uid', $channelId)->value('lang');
  67. }
  68. }
  69. if (! empty($lang)) {
  70. $this->lang = $lang;
  71. $this->langFamily = explode('-', $lang)[0];
  72. }
  73. }
  74. public function options($options = [])
  75. {
  76. foreach ($options as $key => $value) {
  77. $this->options[$key] = $value;
  78. }
  79. }
  80. public function glossaryKey()
  81. {
  82. return $this->glossaryKey;
  83. }
  84. /**
  85. * TODO 设置默认语言。在渲染某些内容的时候需要语言信息
  86. */
  87. public function setLang($lang)
  88. {
  89. $this->lang = $lang;
  90. $this->langFamily = explode('-', $lang)[0];
  91. }
  92. private function info($message, $debug)
  93. {
  94. if (in_array($debug, $this->debug)) {
  95. Log::info($message);
  96. }
  97. }
  98. private function error($message, $debug)
  99. {
  100. if (in_array($debug, $this->debug)) {
  101. Log::error($message);
  102. }
  103. }
  104. public function render($tpl_name)
  105. {
  106. switch ($tpl_name) {
  107. case 'term':
  108. // 术语
  109. $result = $this->render_term();
  110. break;
  111. case 'note':
  112. $result = $this->render_note();
  113. break;
  114. case 'sent':
  115. $result = $this->render_sent();
  116. break;
  117. case 'quote':
  118. $result = $this->render_quote();
  119. break;
  120. case 'ql':
  121. $result = $this->render_quote_link();
  122. break;
  123. case 'exercise':
  124. $result = $this->render_exercise();
  125. break;
  126. case 'article':
  127. $result = $this->render_article();
  128. break;
  129. case 'nissaya':
  130. $result = $this->render_nissaya();
  131. break;
  132. case 'mermaid':
  133. $result = $this->render_mermaid();
  134. break;
  135. case 'qa':
  136. $result = $this->render_qa();
  137. break;
  138. case 'v':
  139. $result = $this->render_video();
  140. break;
  141. case 'g':
  142. $result = $this->render_grammar_lookup();
  143. break;
  144. case 'ref':
  145. $result = $this->render_ref();
  146. break;
  147. case 'dict-pref':
  148. $result = $this->render_dict_pref();
  149. break;
  150. case 'ai':
  151. $result = $this->render_ai();
  152. break;
  153. case 'para':
  154. $result = $this->render_para();
  155. break;
  156. case 'category':
  157. $result = $this->render_category();
  158. break;
  159. case 'quality':
  160. // 质量标签模版:{{quality|pending}}
  161. $result = $this->render_quality();
  162. break;
  163. default:
  164. if (mb_substr($tpl_name, 0, 4, 'UTF-8') === 'Tpl:') {
  165. $result = $this->render_tpl($tpl_name);
  166. } else {
  167. $result = [
  168. 'props' => base64_encode(\json_encode([])),
  169. 'html' => '',
  170. 'tag' => 'span',
  171. 'tpl' => 'unknown',
  172. ];
  173. }
  174. break;
  175. }
  176. return $result;
  177. }
  178. public function render_tpl($name)
  179. {
  180. $article = app(ArticleService::class)->getRawByTitle($name);
  181. $content = $article->content;
  182. if (count($this->param) > 0) {
  183. $m = new \Mustache_Engine([
  184. 'entity_flags' => ENT_QUOTES,
  185. 'escape' => function ($value) {
  186. return $value;
  187. },
  188. ]);
  189. $content = $m->render($content, $this->param);
  190. }
  191. $output = [];
  192. switch ($this->format) {
  193. case 'react':
  194. $output = [
  195. 'props' => base64_encode(\json_encode(['content' => $content])),
  196. 'html' => $content,
  197. 'tag' => 'span',
  198. 'tpl' => 'tpl',
  199. ];
  200. break;
  201. default:
  202. $output = $content;
  203. break;
  204. }
  205. return $output;
  206. }
  207. public function render_para()
  208. {
  209. $props = [];
  210. $props['id'] = $this->get_param($this->param, 'id', 1);
  211. $props['title'] = $this->get_param($this->param, 'title', 2);
  212. $props['style'] = $this->get_param($this->param, 'style', 3);
  213. $output = [];
  214. switch ($this->format) {
  215. case 'react':
  216. $output = [
  217. 'props' => base64_encode(\json_encode($props)),
  218. 'html' => $props['title'],
  219. 'tag' => 'span',
  220. 'tpl' => 'para',
  221. ];
  222. break;
  223. default:
  224. $output = $props['title'];
  225. break;
  226. }
  227. return $output;
  228. }
  229. public function render_category()
  230. {
  231. $props = [];
  232. $props['name'] = $this->get_param($this->param, 'name', 1);
  233. $output = [];
  234. switch ($this->format) {
  235. case 'react':
  236. $output = [
  237. 'props' => base64_encode(\json_encode($props)),
  238. 'html' => $props['name'],
  239. 'tag' => 'span',
  240. 'tpl' => 'para',
  241. ];
  242. break;
  243. default:
  244. $output = $props['name'];
  245. break;
  246. }
  247. return $output;
  248. }
  249. /**
  250. * 渲染质量标签模版:{{quality|pending}}
  251. *
  252. * 合法的取值只有四个:featured | standard | draft | pending
  253. * 非法取值统一回退为 pending,避免渲染出未知样式。
  254. */
  255. public function render_quality()
  256. {
  257. // 默认值为 pending,同时支持命名参数 {{quality|value=featured}}
  258. $value = $this->get_param($this->param, 'value', 1, 'pending');
  259. // 校验取值是否合法,不合法则回退到默认值
  260. $allowed = ['featured', 'standard', 'draft', 'pending'];
  261. if (! in_array($value, $allowed, true)) {
  262. $value = 'pending';
  263. }
  264. $props = ['value' => $value];
  265. $output = [];
  266. switch ($this->format) {
  267. case 'react':
  268. // 前端通过 props 解析,使用 antd Tag 按取值渲染不同颜色
  269. $output = [
  270. 'props' => base64_encode(\json_encode($props)),
  271. 'html' => $value,
  272. 'tag' => 'span',
  273. 'tpl' => 'quality',
  274. ];
  275. break;
  276. case 'html':
  277. // 静态 html 输出,class 包含固定前缀 tag-quality 和具体取值
  278. $output = "<span class=\"tag-quality {$value}\">{$value}</span>";
  279. break;
  280. default:
  281. // text / tex / simple 等纯文本格式直接输出取值
  282. $output = $value;
  283. break;
  284. }
  285. return $output;
  286. }
  287. public function getTermProps($word, $tag = null, $channel = null)
  288. {
  289. if ($channel && ! empty($channel)) {
  290. $channelId = $channel;
  291. } else {
  292. if (count($this->channel_id) > 0) {
  293. $channelId = $this->channel_id[0];
  294. } else {
  295. $channelId = null;
  296. }
  297. }
  298. if (count($this->channelInfo) === 0) {
  299. if (! empty($channel)) {
  300. $channelInfo = Channel::where('uid', $channel)->first();
  301. if (! $channelInfo) {
  302. unset($channelInfo);
  303. }
  304. }
  305. if (! isset($channelInfo)) {
  306. Log::warning('channel is null');
  307. $output = [
  308. 'word' => $word,
  309. 'innerHtml' => '',
  310. ];
  311. return $output;
  312. }
  313. } else {
  314. $channelInfo = $this->channelInfo[0];
  315. }
  316. if (Str::isUuid($channelId)) {
  317. $lang = Channel::where('uid', $channelId)->value('lang');
  318. if (! empty($lang)) {
  319. $langFamily = explode('-', $lang)[0];
  320. } else {
  321. $langFamily = 'zh';
  322. }
  323. $this->info("term:{$word} 先查属于这个channel 的", 'term');
  324. $this->info('channel id' . $channelId, 'term');
  325. $table = DhammaTerm::where('word', $word)
  326. ->where('channal', $channelId);
  327. if ($tag && ! empty($tag)) {
  328. $table = $table->where('tag', $tag);
  329. }
  330. $tplParam = $table->orderBy('updated_at', 'desc')
  331. ->first();
  332. $studioId = $channelInfo->owner_uid;
  333. } else {
  334. $tplParam = false;
  335. $lang = '';
  336. $langFamily = '';
  337. $studioId = $this->studioId;
  338. }
  339. if (! $tplParam) {
  340. if (Str::isUuid($studioId)) {
  341. /**
  342. * 没有,再查这个studio的
  343. * 按照语言过滤
  344. * 完全匹配的优先
  345. * 语族匹配也行
  346. */
  347. $this->info('没有-再查这个studio的', 'term');
  348. $table = DhammaTerm::where('word', $word);
  349. if (! empty($tag)) {
  350. $table = $table->where('tag', $tag);
  351. }
  352. $termsInStudio = $table->where('owner', $channelInfo->owner_uid)
  353. ->orderBy('updated_at', 'desc')
  354. ->get();
  355. if (count($termsInStudio) > 0) {
  356. $list = [];
  357. foreach ($termsInStudio as $key => $term) {
  358. if (empty($term->channal)) {
  359. if ($term->language === $lang) {
  360. $list[$term->guid] = 2;
  361. } elseif (strpos($term->language, $langFamily) !== false) {
  362. $list[$term->guid] = 1;
  363. }
  364. }
  365. }
  366. if (count($list) > 0) {
  367. arsort($list);
  368. foreach ($list as $key => $one) {
  369. foreach ($termsInStudio as $term) {
  370. if ($term->guid === $key) {
  371. $tplParam = $term;
  372. break;
  373. }
  374. }
  375. break;
  376. }
  377. }
  378. }
  379. }
  380. }
  381. if (! $tplParam) {
  382. $this->info('没有,再查社区', 'term');
  383. $community_channel = ChannelApi::getSysChannel('_community_term_zh-hans_');
  384. $table = DhammaTerm::where('word', $word);
  385. if (! empty($tag)) {
  386. $table = $table->where('tag', $tag);
  387. }
  388. $tplParam = $table->where('channal', $community_channel)
  389. ->first();
  390. if ($tplParam) {
  391. $isCommunity = true;
  392. } else {
  393. $this->info('查社区没有', 'term');
  394. }
  395. }
  396. $output = [
  397. 'word' => $word,
  398. 'parentChannelId' => $channelId,
  399. 'parentStudioId' => $channelInfo ? $channelInfo->owner_uid : null,
  400. ];
  401. $innerString = $output['word'];
  402. if ($tplParam) {
  403. $output['id'] = $tplParam->guid;
  404. $output['meaning'] = $tplParam->meaning;
  405. $output['channel'] = $tplParam->channal;
  406. if (! empty($tplParam->note)) {
  407. $mdRender = new MdRender(['format' => $this->format]);
  408. $output['note'] = $mdRender->convert($tplParam->note, $this->channel_id);
  409. }
  410. if (isset($isCommunity)) {
  411. $output['isCommunity'] = true;
  412. }
  413. $innerString = "{$output['meaning']}({$output['word']})";
  414. if (! empty($tplParam->other_meaning)) {
  415. $output['meaning2'] = $tplParam->other_meaning;
  416. }
  417. }
  418. $output['innerHtml'] = $innerString;
  419. return $output;
  420. }
  421. private function render_term()
  422. {
  423. $word = $this->get_param($this->param, 'word', 1);
  424. if (str_contains($word, '@')) {
  425. [$wordHead, $tag] = explode('@', $word);
  426. }
  427. if (str_contains($word, '#')) {
  428. [$wordHead, $display] = explode('#', $word);
  429. }
  430. $props = $this->getTermProps($wordHead ?? $word, $tag ?? '');
  431. if (isset($display)) {
  432. $props['meaning'] = $display;
  433. }
  434. $output = $props['word'];
  435. switch ($this->format) {
  436. case 'react':
  437. $output = [
  438. 'props' => base64_encode(\json_encode($props)),
  439. 'html' => $props['innerHtml'],
  440. 'tag' => 'span',
  441. 'tpl' => 'term',
  442. ];
  443. break;
  444. case 'unity':
  445. $output = [
  446. 'props' => base64_encode(\json_encode($props)),
  447. 'tpl' => 'term',
  448. ];
  449. break;
  450. case 'html':
  451. $no = isset($props['id']) ? '' : 'term_invalid';
  452. $id = isset($props['id']) ? $props['id'] : '';
  453. $output = '<span ';
  454. $output .= "class='term-ref {$no}' ";
  455. $output .= "data-id='{$id}' ";
  456. $output .= "data-term='{$props['word']}' ";
  457. $output .= '>';
  458. $output .= $props['meaning'] ?? $props['word'];
  459. $output .= '</span>';
  460. break;
  461. case 'markdown':
  462. if (isset($props['meaning'])) {
  463. $key = 'term-' . $props['word'];
  464. if (isset($GLOBALS[$key]) && $GLOBALS[$key] === 1) {
  465. $GLOBALS[$key]++;
  466. $output = $props['meaning'];
  467. } else {
  468. $GLOBALS[$key] = 1;
  469. $output = $props['meaning'] . '(' . $props['word'] . ')';
  470. }
  471. } else {
  472. $output = $props['word'];
  473. }
  474. // 如果有内容且第一次出现,显示为脚注
  475. if (! empty($props['note']) && $GLOBALS[$key] === 1) {
  476. if (isset($GLOBALS['note_sn'])) {
  477. $GLOBALS['note_sn']++;
  478. } else {
  479. $GLOBALS['note_sn'] = 1;
  480. $GLOBALS['note'] = [];
  481. }
  482. $content = $props['note'];
  483. $output .= '[^' . $GLOBALS['note_sn'] . ']';
  484. $GLOBALS['note'][] = [
  485. 'sn' => $GLOBALS['note_sn'],
  486. 'trigger' => '',
  487. 'content' => $content,
  488. ];
  489. }
  490. break;
  491. default:
  492. $output = $props['meaning'] ?? $props['word'];
  493. break;
  494. }
  495. return $output;
  496. }
  497. private function render_note()
  498. {
  499. $note = $this->get_param($this->param, 'text', 1);
  500. $trigger = $this->get_param($this->param, 'trigger', 2, '');
  501. $props = ['note' => $note];
  502. $innerString = '';
  503. if (! empty($trigger)) {
  504. $props['trigger'] = $trigger;
  505. $innerString = $props['trigger'];
  506. }
  507. if ($this->format === 'unity') {
  508. $props['note'] = MdRender::render(
  509. $props['note'],
  510. $this->channel_id,
  511. null,
  512. 'read',
  513. 'translation',
  514. 'markdown',
  515. 'unity'
  516. );
  517. }
  518. $output = $note;
  519. switch ($this->format) {
  520. case 'react':
  521. $output = [
  522. 'props' => base64_encode(\json_encode($props)),
  523. 'html' => $innerString,
  524. 'tag' => 'span',
  525. 'tpl' => 'note',
  526. ];
  527. break;
  528. case 'unity':
  529. $output = [
  530. 'props' => base64_encode(\json_encode($props)),
  531. 'tpl' => 'note',
  532. ];
  533. break;
  534. case 'html':
  535. if (isset($GLOBALS['note_sn'])) {
  536. $GLOBALS['note_sn']++;
  537. } else {
  538. $GLOBALS['note_sn'] = 1;
  539. $GLOBALS['note'] = [];
  540. }
  541. $noteContent = MdRender::render(
  542. $props['note'],
  543. $this->channel_id,
  544. null,
  545. 'read',
  546. 'translation',
  547. 'markdown',
  548. 'html'
  549. );
  550. $GLOBALS['note'][] = [
  551. 'sn' => $GLOBALS['note_sn'],
  552. 'trigger' => $trigger,
  553. 'content' => $noteContent,
  554. ];
  555. $link = "<a href='#footnote-" . $GLOBALS['note_sn'] . "' name='note-" . $GLOBALS['note_sn'] . "'>";
  556. if (empty($trigger)) {
  557. $output = $link . '<sup>[' . $GLOBALS['note_sn'] . ']</sup></a>';
  558. } else {
  559. $output = $link . $trigger . '</a>';
  560. }
  561. $output = "<label for=\"sn-{$GLOBALS['note_sn']}\"
  562. class=\"margin-toggle sidenote-number\" >{$trigger}</label>
  563. <input type=\"checkbox\" id=\"sn-{$GLOBALS['note_sn']}\"
  564. class=\"margin-toggle\"/>
  565. <span class=\"sidenote\">{$noteContent}</span>";
  566. break;
  567. case 'text':
  568. $output = $trigger;
  569. break;
  570. case 'tex':
  571. $output = $trigger;
  572. break;
  573. case 'simple':
  574. $output = '';
  575. break;
  576. case 'markdown':
  577. if (isset($GLOBALS['note_sn'])) {
  578. $GLOBALS['note_sn']++;
  579. } else {
  580. $GLOBALS['note_sn'] = 1;
  581. $GLOBALS['note'] = [];
  582. }
  583. $content = MdRender::render(
  584. $props['note'],
  585. $this->channel_id,
  586. null,
  587. 'read',
  588. 'translation',
  589. 'markdown',
  590. 'markdown'
  591. );
  592. $output = '[^' . $GLOBALS['note_sn'] . ']';
  593. $GLOBALS['note'][] = [
  594. 'sn' => $GLOBALS['note_sn'],
  595. 'trigger' => $trigger,
  596. 'content' => $content,
  597. ];
  598. // $output = '<footnote id="'.$GLOBALS['note_sn'].'">'.$content.'</footnote>';
  599. break;
  600. default:
  601. $output = '';
  602. break;
  603. }
  604. return $output;
  605. }
  606. private function render_nissaya()
  607. {
  608. $pali = $this->get_param($this->param, 'pali', 1);
  609. $meaning = $this->get_param($this->param, 'meaning', 2);
  610. $innerString = '';
  611. $props = [
  612. 'pali' => $pali,
  613. 'meaning' => explode('=', $meaning),
  614. 'lang' => $this->lang,
  615. ];
  616. switch ($this->format) {
  617. case 'react':
  618. $output = [
  619. 'props' => base64_encode(\json_encode($props)),
  620. 'html' => $innerString,
  621. 'tag' => 'span',
  622. 'tpl' => 'nissaya',
  623. ];
  624. break;
  625. case 'unity':
  626. $output = [
  627. 'props' => base64_encode(\json_encode($props)),
  628. 'tpl' => 'nissaya',
  629. ];
  630. break;
  631. case 'prompt':
  632. $output = Tools::MyToRm($pali) . ':' . end($props['meaning']);
  633. break;
  634. default:
  635. $output = $pali . '၊' . $meaning;
  636. break;
  637. }
  638. return $output;
  639. }
  640. private function render_exercise()
  641. {
  642. $id = $this->get_param($this->param, 'id', 1);
  643. $title = $this->get_param($this->param, 'title', 1);
  644. $props = [
  645. 'id' => $id,
  646. 'title' => $title,
  647. 'channel' => $this->channel_id[0],
  648. ];
  649. switch ($this->format) {
  650. case 'react':
  651. $output = [
  652. 'props' => base64_encode(\json_encode($props)),
  653. 'html' => '',
  654. 'tag' => 'span',
  655. 'tpl' => 'exercise',
  656. ];
  657. break;
  658. case 'unity':
  659. $output = [
  660. 'props' => base64_encode(\json_encode($props)),
  661. 'tpl' => 'exercise',
  662. ];
  663. break;
  664. case 'text':
  665. $output = $title;
  666. break;
  667. case 'tex':
  668. $output = $title;
  669. break;
  670. case 'simple':
  671. $output = $title;
  672. break;
  673. default:
  674. $output = '';
  675. break;
  676. }
  677. return $output;
  678. }
  679. private function render_article()
  680. {
  681. $type = $this->get_param($this->param, 'type', 1);
  682. $id = $this->get_param($this->param, 'id', 2);
  683. $title = $this->get_param($this->param, 'title', 3);
  684. $channel = $this->get_param($this->param, 'channel', 4);
  685. $style = $this->get_param($this->param, 'style', 5);
  686. $book = $this->get_param($this->param, 'book', 6);
  687. $paragraphs = $this->get_param($this->param, 'paragraphs', 7);
  688. $anthology = $this->get_param($this->param, 'anthology', 8);
  689. if ($type === 'chapter' && empty($id)) {
  690. $book = (int) $book;
  691. $paragraphs = (int) $paragraphs;
  692. $id = "{$book}-{$paragraphs}";
  693. }
  694. $props = [
  695. 'type' => $type,
  696. 'id' => $id,
  697. 'style' => $style,
  698. ];
  699. if (! empty($channel)) {
  700. $props['channel'] = $channel;
  701. }
  702. if (! empty($title)) {
  703. $props['title'] = $title;
  704. }
  705. if (! empty($book)) {
  706. $props['book'] = $book;
  707. }
  708. if (! empty($paragraphs)) {
  709. $props['paragraphs'] = $paragraphs;
  710. }
  711. if (! empty($anthology)) {
  712. $props['anthology'] = $anthology;
  713. }
  714. if (is_array($this->channel_id)) {
  715. $props['parentChannels'] = $this->channel_id;
  716. }
  717. switch ($this->format) {
  718. case 'react':
  719. $output = [
  720. 'props' => base64_encode(\json_encode($props)),
  721. 'html' => '',
  722. 'text' => $title,
  723. 'tag' => 'span',
  724. 'tpl' => 'article',
  725. ];
  726. break;
  727. case 'unity':
  728. $output = [
  729. 'props' => base64_encode(\json_encode($props)),
  730. 'tpl' => 'article',
  731. ];
  732. break;
  733. case 'text':
  734. $output = $title;
  735. break;
  736. case 'tex':
  737. $output = $title;
  738. break;
  739. case 'simple':
  740. $output = $title;
  741. break;
  742. default:
  743. $output = '';
  744. break;
  745. }
  746. return $output;
  747. }
  748. private function render_quote()
  749. {
  750. $paraId = $this->get_param($this->param, 'para', 1);
  751. $channelId = $this->channel_id[0];
  752. $props = Cache::remember(
  753. "/quote/{$channelId}/{$paraId}",
  754. config('mint.cache.expire'),
  755. function () use ($paraId, $channelId) {
  756. $para = \explode('-', $paraId);
  757. $output = [
  758. 'paraId' => $paraId,
  759. 'channel' => $channelId,
  760. 'innerString' => $paraId,
  761. ];
  762. if (count($para) < 2) {
  763. return $output;
  764. }
  765. $PaliText = PaliText::where('book', $para[0])
  766. ->where('paragraph', $para[1])
  767. ->select(['toc', 'path'])
  768. ->first();
  769. if ($PaliText) {
  770. $output['pali'] = $PaliText->toc;
  771. $output['paliPath'] = \json_decode((string) $PaliText->path, true);
  772. $output['innerString'] = $PaliText->toc;
  773. }
  774. return $output;
  775. }
  776. );
  777. switch ($this->format) {
  778. case 'react':
  779. $output = [
  780. 'props' => base64_encode(\json_encode($props)),
  781. 'html' => $props['innerString'],
  782. 'tag' => 'span',
  783. 'tpl' => 'quote',
  784. ];
  785. break;
  786. case 'unity':
  787. $output = [
  788. 'props' => base64_encode(\json_encode($props)),
  789. 'tpl' => 'quote',
  790. ];
  791. break;
  792. case 'text':
  793. $output = $props['innerString'];
  794. break;
  795. case 'tex':
  796. $output = $props['innerString'];
  797. break;
  798. case 'simple':
  799. $output = $props['innerString'];
  800. break;
  801. default:
  802. $output = $props['innerString'];
  803. break;
  804. }
  805. return $output;
  806. }
  807. private function render_quote_link()
  808. {
  809. $type = $this->get_param($this->param, 'type', 1);
  810. $title = $this->get_param($this->param, 'title', 6, '');
  811. $bookName = $this->get_param($this->param, 'bookname', 2, '');
  812. $volume = $this->get_param($this->param, 'volume', 3);
  813. $page = $this->get_param($this->param, 'page', 4, '');
  814. $style = $this->get_param($this->param, 'style', 5, 'modal');
  815. $book = $this->get_param($this->param, 'book', 7, false);
  816. $para = $this->get_param($this->param, 'para', 8, false);
  817. $props = [
  818. 'type' => $type,
  819. 'style' => $style,
  820. 'found' => true,
  821. ];
  822. if (! empty($bookName) && $volume !== '' && ! empty($page)) {
  823. $props['bookName'] = $bookName;
  824. $props['volume'] = (int) $volume;
  825. $props['page'] = $page;
  826. $props['found'] = true;
  827. } elseif ($book && $para) {
  828. /**
  829. * 没有指定书名,根据book para 查询
  830. */
  831. if ($type === 'c') {
  832. // 按照章节名称显示
  833. $path = PaliTextApi::getChapterPath($book, $para);
  834. if ($path) {
  835. $path = json_decode($path, true);
  836. }
  837. if ($path && is_array($path) && count($path) > 2) {
  838. $props['bookName'] = strtolower($path[0]['title']);
  839. $props['chapter'] = strtolower(end($path)['title']);
  840. $props['found'] = true;
  841. } else {
  842. $props['found'] = false;
  843. }
  844. } else {
  845. $pageInfo = $this->pageInfoByPara($type, $book, $para);
  846. if ($pageInfo['found']) {
  847. $props['bookName'] = $pageInfo['bookName'];
  848. $props['volume'] = $pageInfo['volume'];
  849. $props['page'] = $pageInfo['page'];
  850. $props['found'] = true;
  851. } else {
  852. $props['found'] = false;
  853. }
  854. }
  855. } elseif ($title) {
  856. // 没有书号用title查询
  857. // $tmpTitle = explode('။',$title);
  858. for ($i = mb_strlen($title, 'UTF-8'); $i > 0; $i--) {
  859. $mTitle = mb_substr($title, 0, $i);
  860. $has = array_search($mTitle, array_column(BookTitle::my(), 'title2'));
  861. if ($has !== false) {
  862. $tmpBookTitle = $mTitle;
  863. $tmpBookPage = mb_substr($title, $i);
  864. $tmpBookPage = $this->mb_trim($tmpBookPage, '၊။');
  865. break;
  866. }
  867. }
  868. if (isset($tmpBookTitle)) {
  869. // $tmpBookTitle = $tmpTitle[0];
  870. // $tmpBookPage = $tmpTitle[1];
  871. $tmpBookPage = (int) str_replace(
  872. ['၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉', '၀'],
  873. ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
  874. $tmpBookPage
  875. );
  876. $found_key = array_search($tmpBookTitle, array_column(BookTitle::my(), 'title2'));
  877. if ($found_key !== false) {
  878. $props['bookName'] = BookTitle::my()[$found_key]['bookname'];
  879. $props['volume'] = BookTitle::my()[$found_key]['volume'];
  880. $props['page'] = $tmpBookPage;
  881. if (! empty($props['bookName'])) {
  882. $found_title = array_search($props['bookName'], array_column(BookTitle::my(), 'bookname'));
  883. if ($found_title === false) {
  884. $props['found'] = false;
  885. }
  886. }
  887. } else {
  888. // 没找到,返回术语和页码
  889. $props['found'] = false;
  890. $props['bookName'] = $tmpBookTitle;
  891. $props['page'] = $tmpBookPage;
  892. $props['volume'] = 0;
  893. }
  894. }
  895. } else {
  896. $props['found'] = false;
  897. }
  898. if ($book && $para) {
  899. $props['book'] = $book;
  900. $props['para'] = $para;
  901. }
  902. if ($title) {
  903. $props['title'] = $title;
  904. }
  905. $text = '';
  906. if (isset($props['bookName'])) {
  907. $searchField = '';
  908. switch ($type) {
  909. case 'm':
  910. $searchField = 'm_title';
  911. break;
  912. case 'p':
  913. $searchField = 'p_title';
  914. break;
  915. }
  916. $found_title = array_search($props['bookName'], array_column(BookTitle::get(), $searchField));
  917. if ($found_title === false) {
  918. $props['found'] = false;
  919. }
  920. $term = $this->getTermProps($props['bookName'], ':quote:');
  921. $props['term'] = $term;
  922. if (isset($term['id'])) {
  923. $props['bookNameLocal'] = $term['meaning'];
  924. $text .= $term['meaning'];
  925. } else {
  926. $text .= $bookName;
  927. }
  928. }
  929. if (isset($props['volume']) && isset($props['page'])) {
  930. $text .= " {$volume}.{$page}";
  931. }
  932. switch ($this->format) {
  933. case 'react':
  934. $output = [
  935. 'props' => base64_encode(\json_encode($props)),
  936. 'html' => '',
  937. 'tag' => 'span',
  938. 'tpl' => 'quote-link',
  939. ];
  940. break;
  941. case 'unity':
  942. $output = [
  943. 'props' => base64_encode(\json_encode($props)),
  944. 'tpl' => 'quote-link',
  945. ];
  946. break;
  947. default:
  948. $output = $text;
  949. break;
  950. }
  951. return $output;
  952. }
  953. private function pageInfoByPara($type, $book, $para)
  954. {
  955. $output = [];
  956. $pageInfo = PageNumber::where('type', strtoupper($type))
  957. ->where('book', $book)
  958. ->where('paragraph', '<=', $para)
  959. ->orderBy('paragraph', 'desc')
  960. ->first();
  961. if ($pageInfo) {
  962. foreach (BookTitle::get() as $value) {
  963. if ($value['id'] === $pageInfo->pcd_book_id) {
  964. switch (strtoupper($type)) {
  965. case 'M':
  966. $key = 'm_title';
  967. break;
  968. case 'P':
  969. $key = 'p_title';
  970. break;
  971. case 'V':
  972. $key = 'v_title';
  973. break;
  974. default:
  975. $key = 'term';
  976. break;
  977. }
  978. $output['bookName'] = $value[$key];
  979. break;
  980. }
  981. }
  982. $output['volume'] = $pageInfo->volume;
  983. $output['page'] = $pageInfo->page;
  984. $output['found'] = true;
  985. } else {
  986. $output['found'] = false;
  987. }
  988. return $output;
  989. }
  990. private function render_sent()
  991. {
  992. $sid = $this->get_param($this->param, 'id', 1);
  993. $channel = $this->get_param($this->param, 'channel', 2);
  994. $show = $this->get_param($this->param, 'text', 2, 'both');
  995. $mode = $this->get_param($this->param, 'mode', 3, null);
  996. if (! empty($channel)) {
  997. $channels = explode(',', $channel);
  998. } else {
  999. $channels = $this->channel_id;
  1000. }
  1001. $sentInfo = explode('@', trim($sid));
  1002. $sentId = $sentInfo[0];
  1003. if (isset($sentInfo[1])) {
  1004. $channels = [$sentInfo[1]];
  1005. }
  1006. $Sent = new CorpusController;
  1007. $currMode = $mode ?? $this->mode;
  1008. $props = $Sent->getSentTpl(
  1009. $sentId,
  1010. $channels,
  1011. $currMode,
  1012. true,
  1013. $this->format
  1014. );
  1015. if (! $props) {
  1016. $props['error'] = '句子模版渲染错误。句子参数个数不符。应该是四个。';
  1017. Log::error('句子模版渲染错误。句子参数个数不符。应该是四个。');
  1018. }
  1019. if ($currMode === 'read') {
  1020. $tpl = 'sentread';
  1021. } else {
  1022. $tpl = 'sentedit';
  1023. }
  1024. if (is_array($props)) {
  1025. $props['show'] = $show;
  1026. }
  1027. // 输出引用
  1028. $arrSid = explode('-', $sid);
  1029. $bookPara = array_slice($arrSid, 0, 2);
  1030. if (! isset($GLOBALS['ref_sent'])) {
  1031. $GLOBALS['ref_sent'] = [];
  1032. }
  1033. $GLOBALS['ref_sent'][] = $bookPara;
  1034. switch ($this->format) {
  1035. case 'react':
  1036. $output = [
  1037. 'props' => base64_encode(\json_encode($props)),
  1038. 'html' => '',
  1039. 'tag' => 'span',
  1040. 'tpl' => $tpl,
  1041. ];
  1042. break;
  1043. case 'unity':
  1044. $output = [
  1045. 'props' => base64_encode(\json_encode($props)),
  1046. 'tpl' => $tpl,
  1047. ];
  1048. break;
  1049. case 'text':
  1050. $output = '';
  1051. if (isset($props['origin']) && is_array($props['origin'])) {
  1052. foreach ($props['origin'] as $key => $value) {
  1053. $output .= $value['html'];
  1054. }
  1055. }
  1056. if (isset($props['translation']) && is_array($props['translation'])) {
  1057. foreach ($props['translation'] as $key => $value) {
  1058. $output .= $value['html'];
  1059. }
  1060. }
  1061. break;
  1062. case 'prompt':
  1063. $output = '';
  1064. if ($show === 'both' || $show === 'origin') {
  1065. if (isset($props['origin']) && is_array($props['origin'])) {
  1066. foreach ($props['origin'] as $key => $value) {
  1067. $output .= $value['html'];
  1068. }
  1069. }
  1070. }
  1071. if ($show === 'both' || $show === 'translation') {
  1072. if (isset($props['translation']) && is_array($props['translation'])) {
  1073. foreach ($props['translation'] as $key => $value) {
  1074. $output .= $value['html'];
  1075. }
  1076. }
  1077. }
  1078. break;
  1079. case 'html':
  1080. $output = '';
  1081. $output .= '<span class="sentence">';
  1082. if ($show === 'both' || $show === 'origin') {
  1083. if (isset($props['origin']) && is_array($props['origin'])) {
  1084. foreach ($props['origin'] as $key => $value) {
  1085. $output .= '<span class="origin">' . $value['html'] . '</span>';
  1086. }
  1087. }
  1088. }
  1089. if ($show === 'both' || $show === 'translation') {
  1090. if (isset($props['translation']) && is_array($props['translation'])) {
  1091. foreach ($props['translation'] as $key => $value) {
  1092. $output .= '<span class="translation">' . $value['html'] . '</span>';
  1093. }
  1094. }
  1095. }
  1096. $output .= '</span>';
  1097. break;
  1098. case 'tex':
  1099. $output = '';
  1100. if (isset($props['translation']) && is_array($props['translation'])) {
  1101. foreach ($props['translation'] as $key => $value) {
  1102. $output .= $value['html'];
  1103. }
  1104. }
  1105. break;
  1106. case 'simple':
  1107. $output = '';
  1108. if ($show === 'both' || $show === 'origin') {
  1109. if (empty($output)) {
  1110. if (
  1111. isset($props['origin']) &&
  1112. is_array($props['origin']) &&
  1113. count($props['origin']) > 0
  1114. ) {
  1115. foreach ($props['origin'] as $key => $value) {
  1116. $output .= trim($value['html']);
  1117. }
  1118. }
  1119. }
  1120. }
  1121. if ($show === 'both' || $show === 'translation') {
  1122. if (
  1123. isset($props['translation']) &&
  1124. is_array($props['translation']) &&
  1125. count($props['translation']) > 0
  1126. ) {
  1127. foreach ($props['translation'] as $key => $value) {
  1128. $output .= trim($value['html']);
  1129. }
  1130. }
  1131. }
  1132. break;
  1133. case 'markdown':
  1134. $output = '';
  1135. if ($show === 'both' || $show === 'origin') {
  1136. if (
  1137. $this->options['origin'] === true ||
  1138. $this->options['origin'] === 'true'
  1139. ) {
  1140. if (isset($props['origin']) && is_array($props['origin'])) {
  1141. foreach ($props['origin'] as $key => $value) {
  1142. $output .= trim($value['html']);
  1143. }
  1144. }
  1145. }
  1146. }
  1147. if ($show === 'both' || $show === 'translation') {
  1148. if (
  1149. $this->options['translation'] === true ||
  1150. $this->options['translation'] === 'true'
  1151. ) {
  1152. if (
  1153. isset($props['translation']) &&
  1154. is_array($props['translation']) &&
  1155. count($props['translation']) > 0
  1156. ) {
  1157. foreach ($props['translation'] as $key => $value) {
  1158. $output .= trim($value['html']);
  1159. }
  1160. } else {
  1161. if ($show === 'translation') {
  1162. // 无译文用原文代替
  1163. if (isset($props['origin']) && is_array($props['origin'])) {
  1164. foreach ($props['origin'] as $key => $value) {
  1165. $output .= trim($value['html']);
  1166. }
  1167. }
  1168. }
  1169. }
  1170. }
  1171. }
  1172. break;
  1173. default:
  1174. $output = '';
  1175. break;
  1176. }
  1177. return $output;
  1178. }
  1179. private function render_mermaid()
  1180. {
  1181. $text = json_decode(base64_decode($this->get_param($this->param, 'text', 1)));
  1182. $props = ['text' => implode("\n", $text)];
  1183. switch ($this->format) {
  1184. case 'react':
  1185. $output = [
  1186. 'props' => base64_encode(\json_encode($props)),
  1187. 'html' => 'mermaid',
  1188. 'tag' => 'div',
  1189. 'tpl' => 'mermaid',
  1190. ];
  1191. break;
  1192. case 'unity':
  1193. $output = [
  1194. 'props' => base64_encode(\json_encode($props)),
  1195. 'tpl' => 'mermaid',
  1196. ];
  1197. break;
  1198. case 'text':
  1199. $output = 'mermaid';
  1200. break;
  1201. case 'tex':
  1202. $output = 'mermaid';
  1203. break;
  1204. case 'simple':
  1205. $output = 'mermaid';
  1206. break;
  1207. default:
  1208. $output = 'mermaid';
  1209. break;
  1210. }
  1211. return $output;
  1212. }
  1213. private function render_qa()
  1214. {
  1215. $id = $this->get_param($this->param, 'id', 1);
  1216. $style = $this->get_param($this->param, 'style', 2);
  1217. $props = [
  1218. 'type' => 'qa',
  1219. 'id' => $id,
  1220. 'title' => '',
  1221. 'style' => $style,
  1222. ];
  1223. $qa = Discussion::where('id', $id)->first();
  1224. if ($qa) {
  1225. $props['title'] = $qa->title;
  1226. $props['resId'] = $qa->res_id;
  1227. $props['resType'] = $qa->res_type;
  1228. }
  1229. switch ($this->format) {
  1230. case 'react':
  1231. $output = [
  1232. 'props' => base64_encode(\json_encode($props)),
  1233. 'html' => '',
  1234. 'text' => $props['title'],
  1235. 'tag' => 'div',
  1236. 'tpl' => 'qa',
  1237. ];
  1238. break;
  1239. case 'unity':
  1240. $output = [
  1241. 'props' => base64_encode(\json_encode($props)),
  1242. 'tpl' => 'qa',
  1243. ];
  1244. break;
  1245. default:
  1246. $output = $props['title'];
  1247. break;
  1248. }
  1249. return $output;
  1250. }
  1251. private function render_grammar_lookup()
  1252. {
  1253. $word = $this->get_param($this->param, 'word', 1);
  1254. $props = ['word' => $word];
  1255. $localTermChannel = ChannelApi::getSysChannel(
  1256. '_System_Grammar_Term_' . strtolower($this->lang) . '_',
  1257. '_System_Grammar_Term_en_'
  1258. );
  1259. $term = $this->getTermProps($word, null, $localTermChannel);
  1260. $props['term'] = $term;
  1261. switch ($this->format) {
  1262. case 'react':
  1263. $output = [
  1264. 'props' => base64_encode(\json_encode($props)),
  1265. 'html' => '',
  1266. 'text' => $props['word'],
  1267. 'tag' => 'span',
  1268. 'tpl' => 'grammar',
  1269. ];
  1270. break;
  1271. case 'unity':
  1272. $output = [
  1273. 'props' => base64_encode(\json_encode($props)),
  1274. 'tpl' => 'grammar',
  1275. ];
  1276. break;
  1277. default:
  1278. $output = $props['word'];
  1279. break;
  1280. }
  1281. return $output;
  1282. }
  1283. private function render_video()
  1284. {
  1285. $url = $this->get_param($this->param, 'url', 1);
  1286. $style = $this->get_param($this->param, 'style', 2, 'modal');
  1287. $title = $this->get_param($this->param, 'title', 3);
  1288. $props = [
  1289. 'url' => $url,
  1290. 'title' => $title,
  1291. 'style' => $style,
  1292. ];
  1293. switch ($this->format) {
  1294. case 'react':
  1295. $output = [
  1296. 'props' => base64_encode(\json_encode($props)),
  1297. 'html' => '',
  1298. 'text' => $props['title'],
  1299. 'tag' => 'span',
  1300. 'tpl' => 'video',
  1301. ];
  1302. break;
  1303. case 'unity':
  1304. $output = [
  1305. 'props' => base64_encode(\json_encode($props)),
  1306. 'tpl' => 'video',
  1307. ];
  1308. break;
  1309. default:
  1310. $output = $props['title'];
  1311. break;
  1312. }
  1313. return $output;
  1314. }
  1315. // 论文后面的参考资料
  1316. private function render_ref()
  1317. {
  1318. $references = [];
  1319. $counter = 0;
  1320. if (isset($GLOBALS['ref_sent'])) {
  1321. $hasBooks = [];
  1322. $book_titles = BookSeries::select(['book', 'paragraph', 'title', 'sn'])
  1323. ->orderBy('sn', 'DESC')->get();
  1324. $bTitles = [];
  1325. foreach ($book_titles as $key => $book) {
  1326. $bTitles[] = [
  1327. 'book' => $book->book,
  1328. 'paragraph' => $book->paragraph,
  1329. 'title' => $book->title,
  1330. ];
  1331. }
  1332. foreach ($GLOBALS['ref_sent'] as $key => $ref) {
  1333. $books = array_filter($bTitles, function ($value) use ($ref) {
  1334. return $value['book'] === (int) $ref[0];
  1335. });
  1336. if (count($books) > 0) {
  1337. foreach ($books as $key => $book) {
  1338. if ($book['paragraph'] < (int) $ref[1]) {
  1339. if (! isset($hasBooks[$book['title']])) {
  1340. $hasBooks[$book['title']] = 1;
  1341. $counter++;
  1342. $references[] = [
  1343. 'sn' => $counter,
  1344. 'title' => $book['title'],
  1345. 'copyright' => 'CSCD V4 VRI 2008',
  1346. ];
  1347. }
  1348. }
  1349. }
  1350. }
  1351. }
  1352. }
  1353. $props = [
  1354. 'pali' => $references,
  1355. ];
  1356. switch ($this->format) {
  1357. case 'react':
  1358. $output = [
  1359. 'props' => base64_encode(\json_encode($props)),
  1360. 'html' => '',
  1361. 'tag' => 'div',
  1362. 'tpl' => 'reference',
  1363. ];
  1364. break;
  1365. case 'unity':
  1366. $output = [
  1367. 'props' => base64_encode(\json_encode($props)),
  1368. 'tpl' => 'reference',
  1369. ];
  1370. break;
  1371. case 'markdown':
  1372. $output = '';
  1373. foreach ($references as $key => $reference) {
  1374. $output .= '[' . $reference['sn'] . '] **' . ucfirst($reference['title']) . '** ';
  1375. $output .= $reference['copyright'] . "\n\n";
  1376. }
  1377. break;
  1378. default:
  1379. $output = '';
  1380. foreach ($references as $key => $reference) {
  1381. $output .= '[' . $reference['sn'] . '] ' . ucfirst($reference['title']) . ' ';
  1382. $output .= $reference['copyright'] . "\n";
  1383. }
  1384. break;
  1385. }
  1386. return $output;
  1387. }
  1388. private function render_dict_pref()
  1389. {
  1390. $currPage = $this->get_param($this->param, 'page', 1, 1);
  1391. $pageSize = $this->get_param($this->param, 'size', 2, 100);
  1392. $props = [
  1393. 'currPage' => $currPage,
  1394. 'pageSize' => $pageSize,
  1395. ];
  1396. switch ($this->format) {
  1397. case 'react':
  1398. $output = [
  1399. 'props' => base64_encode(\json_encode($props)),
  1400. 'html' => '',
  1401. 'text' => '',
  1402. 'tag' => 'div',
  1403. 'tpl' => 'dict-pref',
  1404. ];
  1405. break;
  1406. case 'unity':
  1407. $output = [
  1408. 'props' => base64_encode(\json_encode($props)),
  1409. 'tpl' => 'dict-pref',
  1410. ];
  1411. break;
  1412. default:
  1413. $output = 'dict-pref';
  1414. break;
  1415. }
  1416. return $output;
  1417. }
  1418. private function render_ai()
  1419. {
  1420. $model = $this->get_param($this->param, 'model', 1, 1);
  1421. $props = [
  1422. 'model' => $model,
  1423. ];
  1424. switch ($this->format) {
  1425. case 'react':
  1426. $output = [
  1427. 'props' => base64_encode(\json_encode($props)),
  1428. 'html' => '',
  1429. 'text' => '',
  1430. 'tag' => 'div',
  1431. 'tpl' => 'ai',
  1432. ];
  1433. break;
  1434. case 'unity':
  1435. $output = [
  1436. 'props' => base64_encode(\json_encode($props)),
  1437. 'tpl' => 'ai',
  1438. ];
  1439. break;
  1440. case 'text':
  1441. $output = 'ai';
  1442. break;
  1443. case 'prompt':
  1444. $output = '';
  1445. break;
  1446. default:
  1447. $output = 'ai';
  1448. break;
  1449. }
  1450. return $output;
  1451. }
  1452. private function get_param(array $param, string $name, int $id, ?string $default = '')
  1453. {
  1454. if (isset($param[$name])) {
  1455. return trim($param[$name]);
  1456. } elseif (isset($param["{$id}"])) {
  1457. return trim($param["{$id}"]);
  1458. } else {
  1459. return $default;
  1460. }
  1461. }
  1462. private function mb_trim($str, string $character_mask = ' ', $charset = 'UTF-8')
  1463. {
  1464. $start = 0;
  1465. $end = mb_strlen($str, $charset) - 1;
  1466. $chars = preg_split('//u', $character_mask, -1, PREG_SPLIT_NO_EMPTY);
  1467. while ($start <= $end && in_array(mb_substr($str, $start, 1, $charset), $chars)) {
  1468. $start++;
  1469. }
  1470. while ($end >= $start && in_array(mb_substr($str, $end, 1, $charset), $chars)) {
  1471. $end--;
  1472. }
  1473. return mb_substr($str, $start, $end - $start + 1, $charset);
  1474. }
  1475. }