TemplateRender.php 48 KB

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