CorpusController.php 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Api\ChannelApi;
  4. use App\Http\Api\MdRender;
  5. use App\Http\Api\StudioApi;
  6. use App\Http\Api\SuggestionApi;
  7. use App\Http\Api\UserApi;
  8. use App\Http\Resources\TocResource;
  9. use App\Models\Channel;
  10. use App\Models\CustomBook;
  11. use App\Models\Discussion;
  12. use App\Models\PaliSentence;
  13. use App\Models\PaliText;
  14. use App\Models\Sentence;
  15. use App\Models\SentSimIndex;
  16. use App\Models\Wbw;
  17. use App\Models\WbwBlock;
  18. use App\Services\AuthService;
  19. use App\Services\PaliContentService;
  20. use Illuminate\Http\Request;
  21. use Illuminate\Http\Response;
  22. use Illuminate\Support\Arr;
  23. use Illuminate\Support\Facades\Cache;
  24. use Illuminate\Support\Facades\Log;
  25. use Illuminate\Support\Facades\Redis;
  26. use Illuminate\Support\Str;
  27. class CorpusController extends Controller
  28. {
  29. protected $result = [
  30. 'uid' => '',
  31. 'title' => '',
  32. 'path' => [],
  33. 'sub_title' => '',
  34. 'summary' => '',
  35. 'content' => '',
  36. 'content_type' => 'html',
  37. 'toc' => [],
  38. 'status' => 30,
  39. 'lang' => '',
  40. 'created_at' => '',
  41. 'updated_at' => '',
  42. ];
  43. protected $wbwChannels = [];
  44. // 句子需要查询的列
  45. protected $selectCol = [
  46. 'uid',
  47. 'book_id',
  48. 'paragraph',
  49. 'word_start',
  50. 'word_end',
  51. 'channel_uid',
  52. 'content',
  53. 'content_type',
  54. 'editor_uid',
  55. 'acceptor_uid',
  56. 'pr_edit_at',
  57. 'fork_at',
  58. 'create_time',
  59. 'modify_time',
  60. 'created_at',
  61. 'updated_at',
  62. ];
  63. protected $userUuid = null;
  64. protected $debug = [];
  65. public function __construct() {}
  66. /**
  67. * Display a listing of the resource.
  68. *
  69. * @return Response
  70. */
  71. public function index(Request $request)
  72. {
  73. //
  74. switch ($request->get('view')) {
  75. case 'para':
  76. return $this->showPara($request);
  77. break;
  78. default:
  79. // code...
  80. break;
  81. }
  82. }
  83. /**
  84. * Store a newly created resource in storage.
  85. *
  86. * @return Response
  87. */
  88. public function store(Request $request)
  89. {
  90. //
  91. }
  92. /**
  93. * Display the specified resource.
  94. *
  95. * @return Response
  96. */
  97. public function show(Sentence $sentence)
  98. {
  99. //
  100. }
  101. public function getSentTpl(
  102. string $id,
  103. array $inputChannels,
  104. $mode = 'edit',
  105. $onlyProps = false,
  106. $format = 'react'
  107. ) {
  108. $sent = [];
  109. $channels = $inputChannels;
  110. $sentId = \explode('-', $id);
  111. if (count($sentId) !== 4) {
  112. return false;
  113. }
  114. $bookId = (int) $sentId[0];
  115. if ($bookId < 1000) {
  116. if ($mode === 'read') {
  117. $originalChannelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
  118. } else {
  119. $originalChannelId = ChannelApi::getSysChannel('_System_Wbw_VRI_');
  120. }
  121. } else {
  122. $originalChannelId = CustomBook::where('book_id', $bookId)->value('channel_id');
  123. }
  124. if (isset($originalChannelId) && $originalChannelId) {
  125. array_push($channels, $originalChannelId);
  126. }
  127. $record = Sentence::select($this->selectCol)
  128. ->where('book_id', $sentId[0])
  129. ->where('paragraph', $sentId[1])
  130. ->where('word_start', (int) $sentId[2])
  131. ->where('word_end', (int) $sentId[3])
  132. ->whereIn('channel_uid', $channels)
  133. ->get();
  134. $channelIndex = $this->getChannelIndex($channels);
  135. if (isset($toSentFormat)) {
  136. foreach ($toSentFormat as $key => $org) {
  137. $record[] = $org;
  138. }
  139. }
  140. // 获取wbw channel
  141. // 目前默认的 wbw channel 是第一个translation channel
  142. // TODO: channel 不存在时目前只是静默跳过(句子会渲染为空)。
  143. // 以后改为抛出异常,由外层(render_sent / 控制器)捕获,
  144. // 把出错信息 message 传给前端提示用户 channel 无效。
  145. foreach ($channels as $channel) {
  146. // code...
  147. if (
  148. isset($channelIndex[$channel]) &&
  149. $channelIndex[$channel]->type === 'translation'
  150. ) {
  151. $this->wbwChannels[] = $channel;
  152. break;
  153. }
  154. }
  155. return $this->makeContent($record, $mode, $channelIndex, [], $onlyProps, false, $format);
  156. }
  157. /**
  158. * Display the specified resource.
  159. *
  160. * @return Response
  161. */
  162. public function showSent(Request $request, string $id)
  163. {
  164. $user = AuthService::current($request);
  165. if ($user) {
  166. $this->userUuid = $user['user_uid'];
  167. }
  168. $channels = \explode('_', $request->get('channels'));
  169. $this->result['uid'] = '';
  170. $this->result['title'] = '';
  171. $this->result['subtitle'] = '';
  172. $this->result['summary'] = '';
  173. $this->result['lang'] = '';
  174. $this->result['status'] = 30;
  175. $this->result['content'] = $this->getSentTpl($id, $channels, $request->get('mode', 'edit'));
  176. return $this->ok($this->result);
  177. }
  178. /**
  179. * 获取某句子的全部译文
  180. *
  181. * @return Response
  182. */
  183. public function showSentences(Request $request, string $type, string $id)
  184. {
  185. $user = AuthService::current($request);
  186. if ($user) {
  187. $this->userUuid = $user['user_uid'];
  188. }
  189. $param = \explode('_', $id);
  190. $sentId = \explode('-', $param[0]);
  191. $channels = [];
  192. // 获取channel类型
  193. $sentChannel = Sentence::select('channel_uid')
  194. ->where('book_id', $sentId[0])
  195. ->where('paragraph', $sentId[1])
  196. ->where('word_start', $sentId[2])
  197. ->where('word_end', $sentId[3])
  198. ->get();
  199. foreach ($sentChannel as $key => $value) {
  200. // code...
  201. $channels[] = $value->channel_uid;
  202. }
  203. $channelInfo = Channel::whereIn('uid', $channels)->select(['uid', 'type', 'lang', 'name'])->get();
  204. $indexChannel = [];
  205. $channels = [];
  206. foreach ($channelInfo as $key => $value) {
  207. // code...
  208. if ($value->type === $type) {
  209. $indexChannel[$value->uid] = $value;
  210. $channels[] = $value->uid;
  211. }
  212. }
  213. // 获取句子数据
  214. $record = Sentence::select($this->selectCol)
  215. ->where('book_id', $sentId[0])
  216. ->where('paragraph', $sentId[1])
  217. ->where('word_start', $sentId[2])
  218. ->where('word_end', $sentId[3])
  219. ->whereIn('channel_uid', $channels)
  220. ->orderBy('paragraph')
  221. ->orderBy('word_start')
  222. ->get();
  223. if (count($record) === 0) {
  224. return $this->error('no data');
  225. }
  226. $this->result['uid'] = '';
  227. $this->result['title'] = '';
  228. $this->result['subtitle'] = '';
  229. $this->result['summary'] = '';
  230. $this->result['lang'] = '';
  231. $this->result['status'] = 30;
  232. $this->result['content'] = $this->makeContent($record, 'read', $indexChannel);
  233. // TODO 检查一下这个read为什么要写死
  234. return $this->ok($this->result);
  235. }
  236. /**
  237. * Store a newly created resource in storage.
  238. *
  239. * @param string $id
  240. * @param string $mode
  241. * @return Response
  242. */
  243. public function showPara(Request $request)
  244. {
  245. if ($request->has('debug')) {
  246. $this->debug = explode(',', $request->get('debug'));
  247. }
  248. $user = AuthService::current($request);
  249. if ($user) {
  250. $this->userUuid = $user['user_uid'];
  251. }
  252. //
  253. $channels = [];
  254. if ($request->has('channels')) {
  255. if (strpos($request->get('channels'), ',') === false) {
  256. $getChannel = explode('_', $request->get('channels'));
  257. } else {
  258. $getChannel = explode(',', $request->get('channels'));
  259. }
  260. foreach ($getChannel as $channel) {
  261. if (Str::isUuid($channel)) {
  262. $channels[] = $channel;
  263. }
  264. }
  265. }
  266. // 系统原文channel必须追加在末尾,makeContentObj 依赖此约定剔除它
  267. if ($request->get('mode') === 'edit') {
  268. // 翻译模式加载json格式原文
  269. $channels[] = ChannelApi::getSysChannel('_System_Wbw_VRI_');
  270. } else {
  271. // 阅读模式加载html格式原文
  272. $channels[] = ChannelApi::getSysChannel('_System_Pali_VRI_');
  273. }
  274. $para = explode(',', $request->get('par'));
  275. // 段落所在章节
  276. $parent = PaliText::where('book', $request->get('book'))
  277. ->where('paragraph', $para[0])->first();
  278. $chapter = PaliText::where('book', $request->get('book'))
  279. ->where('paragraph', $parent->parent)->first();
  280. if ($chapter) {
  281. if (empty($chapter->toc)) {
  282. $this->result['title'] = 'unknown';
  283. } else {
  284. $this->result['title'] = $chapter->toc;
  285. $this->result['sub_title'] = $chapter->toc;
  286. $this->result['path'] = json_decode($parent->path);
  287. }
  288. }
  289. $paraFrom = $para[0];
  290. $paraTo = end($para);
  291. $indexedHeading = [];
  292. // 获取channel索引表
  293. $tranChannels = [];
  294. $channelInfo = Channel::whereIn('uid', $channels)
  295. ->select(['uid', 'type', 'lang', 'name'])->get();
  296. foreach ($channelInfo as $key => $value) {
  297. // code...
  298. if ($value->type === 'translation') {
  299. $tranChannels[] = $value->uid;
  300. }
  301. }
  302. $indexChannel = [];
  303. $indexChannel = $this->getChannelIndex($channels);
  304. // 获取wbw channel
  305. // 目前默认的 wbw channel 是第一个translation channel
  306. foreach ($channels as $key => $value) {
  307. // code...
  308. if (
  309. isset($indexChannel[$value]) &&
  310. $indexChannel[$value]->type === 'translation'
  311. ) {
  312. $this->wbwChannels[] = $value;
  313. break;
  314. }
  315. }
  316. // 章节译文标题
  317. $title = Sentence::select($this->selectCol)
  318. ->where('book_id', $parent->book)
  319. ->where('paragraph', $parent->parent)
  320. ->whereIn('channel_uid', $tranChannels)
  321. ->first();
  322. if ($title) {
  323. $this->result['title'] = MdRender::render($title->content, [$title->channel_uid]);
  324. }
  325. /**
  326. * 获取句子数据
  327. */
  328. $record = Sentence::select($this->selectCol)
  329. ->where('book_id', $request->get('book'))
  330. ->whereIn('paragraph', $para)
  331. ->whereIn('channel_uid', $channels)
  332. ->orderBy('paragraph')
  333. ->orderBy('word_start')
  334. ->get();
  335. if (count($record) === 0) {
  336. $this->result['content'] = '<span>No Data</span>';
  337. } else {
  338. $paliService = app(PaliContentService::class);
  339. $this->result['content'] = json_encode(
  340. $paliService->makeContentObj($record, $request->get('mode', 'read'), $indexChannel),
  341. JSON_UNESCAPED_UNICODE
  342. );
  343. $this->result['content_type'] = 'json';
  344. }
  345. return $this->ok($this->result);
  346. }
  347. /**
  348. * Store a newly created resource in storage.
  349. *
  350. * @return Response
  351. */
  352. public function showChapter(Request $request, string $id)
  353. {
  354. if ($request->has('debug')) {
  355. $this->debug = explode(',', $request->get('debug'));
  356. }
  357. $user = AuthService::current($request);
  358. if ($user) {
  359. $this->userUuid = $user['user_uid'];
  360. }
  361. //
  362. $sentId = \explode('-', $id);
  363. $channels = [];
  364. if ($request->has('channels')) {
  365. if (strpos($request->get('channels'), ',') === false) {
  366. $_channels = explode('_', $request->get('channels'));
  367. } else {
  368. $_channels = explode(',', $request->get('channels'));
  369. }
  370. foreach ($_channels as $key => $channel) {
  371. if (Str::isUuid($channel)) {
  372. $channels[] = $channel;
  373. }
  374. }
  375. }
  376. $mode = $request->get('mode', 'read');
  377. if ($mode === 'read') {
  378. // 阅读模式加载html格式原文
  379. $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
  380. } else {
  381. // 翻译模式加载json格式原文
  382. $channelId = ChannelApi::getSysChannel('_System_Wbw_VRI_');
  383. }
  384. if ($channelId !== false) {
  385. $channels[] = $channelId;
  386. }
  387. $chapter = PaliText::where('book', $sentId[0])->where('paragraph', $sentId[1])->first();
  388. if (! $chapter) {
  389. return $this->error('no data');
  390. }
  391. $paraFrom = $sentId[1];
  392. $paraTo = $sentId[1] + $chapter->chapter_len - 1;
  393. if (empty($chapter->toc)) {
  394. $this->result['title'] = 'unknown';
  395. } else {
  396. $this->result['title'] = $chapter->toc;
  397. $this->result['sub_title'] = $chapter->toc;
  398. $this->result['path'] = json_decode($chapter->path);
  399. }
  400. // 获取标题
  401. $heading = PaliText::select(['book', 'paragraph', 'level'])
  402. ->where('book', $sentId[0])
  403. ->whereBetween('paragraph', [$paraFrom, $paraTo])
  404. ->where('level', '<', 8)
  405. ->get();
  406. // 将标题段落转成索引数组 以便输出标题层级
  407. $indexedHeading = [];
  408. foreach ($heading as $key => $value) {
  409. // code...
  410. $indexedHeading["{$value->book}-{$value->paragraph}"] = $value->level;
  411. }
  412. // 获取channel索引表
  413. $tranChannels = [];
  414. $channelInfo = Channel::whereIn('uid', $channels)
  415. ->select(['uid', 'type', 'lang', 'name'])->get();
  416. foreach ($channelInfo as $key => $value) {
  417. // code...
  418. if ($value->type === 'translation') {
  419. $tranChannels[] = $value->uid;
  420. }
  421. }
  422. $indexChannel = [];
  423. $indexChannel = $this->getChannelIndex($channels);
  424. // 获取wbw channel
  425. // 目前默认的 wbw channel 是第一个translation channel
  426. // TODO 处理不存在的channel id
  427. foreach ($channels as $key => $value) {
  428. // code...
  429. if (
  430. isset($indexChannel[$value]) &&
  431. $indexChannel[$value]->type === 'translation'
  432. ) {
  433. $this->wbwChannels[] = $value;
  434. break;
  435. }
  436. }
  437. $title = Sentence::select($this->selectCol)
  438. ->where('book_id', $sentId[0])
  439. ->where('paragraph', $sentId[1])
  440. ->whereIn('channel_uid', $tranChannels)
  441. ->first();
  442. if ($title) {
  443. $this->result['title'] = MdRender::render($title->content, [$title->channel_uid]);
  444. $mdRender = new MdRender(['format' => 'simple']);
  445. $this->result['title_text'] = $mdRender->convert($title->content, [$title->channel_uid]);
  446. }
  447. /**
  448. * 获取句子数据
  449. * 算法:
  450. * 1. 如果标题和下一级第一个标题之间有段落。只输出这些段落和子目录
  451. * 2. 如果标题和下一级第一个标题之间没有间隔 且 chapter 长度大于10000个字符 且有子目录,只输出子目录
  452. * 3. 如果二者都不是,lazy load
  453. */
  454. // 1. 计算 标题和下一级第一个标题之间 是否有间隔
  455. $nextChapter = PaliText::where('book', $sentId[0])
  456. ->where('paragraph', '>', $sentId[1])
  457. ->where('level', '<', 8)
  458. ->orderBy('paragraph')
  459. ->value('paragraph');
  460. $between = $nextChapter - $sentId[1];
  461. // 查找子目录
  462. $chapterLen = $chapter->chapter_len;
  463. $toc = PaliText::where('book', $sentId[0])
  464. ->whereBetween('paragraph', [$paraFrom + 1, $paraFrom + $chapterLen - 1])
  465. ->where('level', '<', 8)
  466. ->orderBy('paragraph')
  467. ->select(['book', 'paragraph', 'level', 'toc'])
  468. ->get();
  469. $maxLen = 3000;
  470. if ($between > 1) {
  471. // 有间隔
  472. $paraTo = $nextChapter - 1;
  473. } else {
  474. if ($chapter->chapter_strlen > $maxLen) {
  475. if (count($toc) > 0) {
  476. // 有子目录只输出标题和目录
  477. $paraTo = $paraFrom;
  478. } else {
  479. // 没有子目录 全部输出
  480. }
  481. } else {
  482. // 章节小。全部输出 不输出子目录
  483. $toc = [];
  484. }
  485. }
  486. $pFrom = $request->get('from', $paraFrom);
  487. $pTo = $request->get('to', $paraTo);
  488. // 根据句子的长度找到这次应该加载的段落
  489. $paliText = PaliText::select(['paragraph', 'lenght'])
  490. ->where('book', $sentId[0])
  491. ->whereBetween('paragraph', [$pFrom, $pTo])
  492. ->orderBy('paragraph')
  493. ->get();
  494. $sumLen = 0;
  495. $currTo = $pTo;
  496. foreach ($paliText as $para) {
  497. $sumLen += $para->lenght;
  498. if ($sumLen > $maxLen) {
  499. $currTo = $para->paragraph;
  500. break;
  501. }
  502. }
  503. $record = Sentence::select($this->selectCol)
  504. ->where('book_id', $sentId[0])
  505. ->whereBetween('paragraph', [$pFrom, $currTo])
  506. ->whereIn('channel_uid', $channels)
  507. ->orderBy('paragraph')
  508. ->orderBy('word_start')
  509. ->get();
  510. if (count($record) === 0) {
  511. return $this->error('no data');
  512. }
  513. $this->result['content'] = $this->makeContent($record, $mode, $indexChannel, $indexedHeading, false, true);
  514. if (! $request->has('from')) {
  515. // 第一次才显示toc
  516. $this->result['toc'] = TocResource::collection($toc);
  517. }
  518. if ($currTo < $pTo) {
  519. $this->result['from'] = $currTo + 1;
  520. $this->result['to'] = $pTo;
  521. $this->result['paraId'] = $id;
  522. $this->result['channels'] = $request->get('channels');
  523. $this->result['mode'] = $request->get('mode');
  524. }
  525. return $this->ok($this->result);
  526. }
  527. private function getChannelIndex($channels, $type = null)
  528. {
  529. // 获取channel索引表
  530. $channelInfo = Channel::whereIn('uid', $channels)
  531. ->select(['uid', 'type', 'name', 'lang', 'owner_uid'])
  532. ->get();
  533. $indexChannel = [];
  534. foreach ($channels as $key => $channelId) {
  535. $channelInfo = Channel::where('uid', $channelId)
  536. ->select(['uid', 'type', 'name', 'lang', 'owner_uid'])->first();
  537. if (! $channelInfo) {
  538. Log::error('no channel id'.$channelId);
  539. continue;
  540. }
  541. if ($type !== null && $channelInfo->type !== $type) {
  542. continue;
  543. }
  544. $indexChannel[$channelId] = $channelInfo;
  545. $indexChannel[$channelId]->studio = StudioApi::getById($channelInfo->owner_uid);
  546. }
  547. return $indexChannel;
  548. }
  549. /**
  550. * 根据句子库数据生成文章内容
  551. * $record 句子数据
  552. * $mode read | edit | wbw
  553. * $indexChannel channel索引
  554. * $indexedHeading 标题索引 用于给段落加标题标签 <h1> ect.
  555. */
  556. private function makeContent($record, $mode, $indexChannel, $indexedHeading = [], $onlyProps = false, $paraMark = false, $format = 'react')
  557. {
  558. $content = [];
  559. $lastSent = '0-0';
  560. $sentCount = 0;
  561. $sent = [];
  562. $sent['origin'] = [];
  563. $sent['translation'] = [];
  564. $sent['commentaries'] = [];
  565. // 获取句子编号列表
  566. $sentList = [];
  567. foreach ($record as $key => $value) {
  568. $currSentId = "{$value->book_id}-{$value->paragraph}-{$value->word_start}-{$value->word_end}";
  569. $sentList[$currSentId] = [$value->book_id, $value->paragraph, $value->word_start, $value->word_end];
  570. $value->sid = "{$currSentId}_{$value->channel_uid}";
  571. }
  572. $channelsId = [];
  573. foreach ($indexChannel as $channelId => $info) {
  574. $channelsId[] = $channelId;
  575. }
  576. array_pop($channelsId);
  577. // 遍历列表查找每个句子的所有channel的数据,并填充
  578. $currPara = '';
  579. foreach ($sentList as $currSentId => $arrSentId) {
  580. $para = $arrSentId[0].'-'.$arrSentId[1];
  581. if ($currPara !== $para) {
  582. $currPara = $para;
  583. // 输出段落标记
  584. if ($paraMark) {
  585. $sentInPara = [];
  586. foreach ($sentList as $sentId => $sentParam) {
  587. if (
  588. $sentParam[0] === $arrSentId[0] &&
  589. $sentParam[1] === $arrSentId[1]
  590. ) {
  591. $sentInPara[] = $sentId;
  592. }
  593. }
  594. // 输出段落起始
  595. if (! empty($currPara)) {
  596. $content[] = '</MdTpl>';
  597. }
  598. $markProps = base64_encode(\json_encode([
  599. 'book' => $arrSentId[0],
  600. 'para' => $arrSentId[1],
  601. 'channels' => $channelsId,
  602. 'sentences' => $sentInPara,
  603. 'mode' => $mode,
  604. ]));
  605. $content[] = "<MdTpl tpl='para-shell' props='{$markProps}' >";
  606. }
  607. }
  608. $sent = $this->newSent($arrSentId[0], $arrSentId[1], $arrSentId[2], $arrSentId[3]);
  609. foreach ($indexChannel as $channelId => $info) {
  610. // code...
  611. $sid = "{$currSentId}_{$channelId}";
  612. if (isset($info->studio)) {
  613. $studioInfo = $info->studio;
  614. } else {
  615. $studioInfo = null;
  616. }
  617. $newSent = [
  618. 'content' => '',
  619. 'html' => '',
  620. 'book' => $arrSentId[0],
  621. 'para' => $arrSentId[1],
  622. 'wordStart' => $arrSentId[2],
  623. 'wordEnd' => $arrSentId[3],
  624. 'channel' => [
  625. 'name' => $info->name,
  626. 'type' => $info->type,
  627. 'id' => $info->uid,
  628. 'lang' => $info->lang,
  629. ],
  630. 'studio' => $studioInfo,
  631. 'updateAt' => '',
  632. 'suggestionCount' => SuggestionApi::getCountBySent($arrSentId[0], $arrSentId[1], $arrSentId[2], $arrSentId[3], $channelId),
  633. ];
  634. $row = Arr::first($record, function ($value, $key) use ($sid) {
  635. return $value->sid === $sid;
  636. });
  637. if ($row) {
  638. $newSent['id'] = $row->uid;
  639. $newSent['content'] = $row->content;
  640. $newSent['contentType'] = $row->content_type;
  641. $newSent['html'] = '';
  642. $newSent['editor'] = UserApi::getByUuid($row->editor_uid);
  643. /**
  644. * TODO 刷库改数据
  645. * 旧版api没有更新updated_at所以造成旧版的数据updated_at数据比modify_time 要晚
  646. */
  647. $newSent['forkAt'] = $row->fork_at; //
  648. $newSent['updateAt'] = $row->updated_at; //
  649. $newSent['updateAt'] = date('Y-m-d H:i:s.', $row->modify_time / 1000).($row->modify_time % 1000).' UTC';
  650. $newSent['createdAt'] = $row->created_at;
  651. if ($mode !== 'read') {
  652. if (isset($row->acceptor_uid) && ! empty($row->acceptor_uid)) {
  653. $newSent['acceptor'] = UserApi::getByUuid($row->acceptor_uid);
  654. $newSent['prEditAt'] = $row->pr_edit_at;
  655. }
  656. }
  657. switch ($info->type) {
  658. case 'wbw':
  659. case 'original':
  660. //
  661. // 在编辑模式下。
  662. // 如果是原文,查看是否有逐词解析数据,
  663. // 有的话优先显示。
  664. // 阅读模式直接显示html原文
  665. // 传过来的数据一定有一个原文channel
  666. //
  667. if ($mode === 'read') {
  668. $newSent['content'] = '';
  669. $newSent['html'] = MdRender::render(
  670. $row->content,
  671. [$row->channel_uid],
  672. null,
  673. $mode,
  674. 'translation',
  675. $row->content_type,
  676. $format
  677. );
  678. } else {
  679. if ($row->content_type === 'json') {
  680. $newSent['channel']['type'] = 'wbw';
  681. if (isset($this->wbwChannels[0])) {
  682. $newSent['channel']['name'] = $indexChannel[$this->wbwChannels[0]]->name;
  683. $newSent['channel']['lang'] = $indexChannel[$this->wbwChannels[0]]->lang;
  684. $newSent['channel']['id'] = $this->wbwChannels[0];
  685. // 存在一个translation channel
  686. // 尝试查找逐词解析数据。找到,替换现有数据
  687. $wbwData = $this->getWbw(
  688. $arrSentId[0],
  689. $arrSentId[1],
  690. $arrSentId[2],
  691. $arrSentId[3],
  692. $this->wbwChannels[0]
  693. );
  694. if ($wbwData) {
  695. $newSent['content'] = $wbwData;
  696. $newSent['contentType'] = 'json';
  697. $newSent['html'] = '';
  698. $newSent['studio'] = $indexChannel[$this->wbwChannels[0]]->studio;
  699. }
  700. }
  701. } else {
  702. $newSent['content'] = $row->content;
  703. $newSent['html'] = MdRender::render(
  704. $row->content,
  705. [$row->channel_uid],
  706. null,
  707. $mode,
  708. 'translation',
  709. $row->content_type,
  710. $format
  711. );
  712. }
  713. }
  714. break;
  715. case 'nissaya':
  716. $newSent['html'] = Cache::remember(
  717. "/sent/{$channelId}/{$currSentId}/{$format}",
  718. config('mint.cache.expire'),
  719. function () use ($row, $mode, $format) {
  720. return MdRender::render(
  721. $row->content,
  722. [$row->channel_uid],
  723. null,
  724. $mode,
  725. 'nissaya',
  726. $row->content_type,
  727. $format
  728. );
  729. }
  730. );
  731. break;
  732. case 'commentary':
  733. $options = [
  734. 'debug' => $this->debug,
  735. 'format' => $format,
  736. 'mode' => $mode,
  737. 'channelType' => 'translation',
  738. 'contentType' => $row->content_type,
  739. ];
  740. $mdRender = new MdRender($options);
  741. $newSent['html'] = $mdRender->convert($row->content, $channelsId);
  742. break;
  743. default:
  744. $options = [
  745. 'debug' => $this->debug,
  746. 'format' => $format,
  747. 'mode' => $mode,
  748. 'channelType' => 'translation',
  749. 'contentType' => $row->content_type,
  750. ];
  751. $mdRender = new MdRender($options);
  752. $newSent['html'] = $mdRender->convert($row->content, [$row->channel_uid]);
  753. // Log::debug('md render', ['content' => $row->content, 'options' => $options, 'render' => $newSent['html']]);
  754. break;
  755. }
  756. }
  757. switch ($info->type) {
  758. case 'wbw':
  759. case 'original':
  760. array_push($sent['origin'], $newSent);
  761. break;
  762. case 'commentary':
  763. array_push($sent['commentaries'], $newSent);
  764. break;
  765. default:
  766. array_push($sent['translation'], $newSent);
  767. break;
  768. }
  769. }
  770. if ($onlyProps) {
  771. return $sent;
  772. }
  773. $content = $this->pushSent($content, $sent, 0, $mode);
  774. }
  775. if ($paraMark) {
  776. $content[] = '</MdTpl>';
  777. }
  778. $output = \implode('', $content);
  779. return "<div>{$output}</div>";
  780. }
  781. public function getWbw($book, $para, $start, $end, $channel)
  782. {
  783. /**
  784. * 非阅读模式下。原文使用逐词解析数据。
  785. * 优先加载第一个translation channel 如果没有。加载默认逐词解析。
  786. */
  787. // 获取逐词解析数据
  788. $wbwBlock = WbwBlock::where('channel_uid', $channel)
  789. ->where('book_id', $book)
  790. ->where('paragraph', $para)
  791. ->select('uid')
  792. ->first();
  793. if (! $wbwBlock) {
  794. return false;
  795. }
  796. // 找到逐词解析数据
  797. $wbwData = Wbw::where('block_uid', $wbwBlock->uid)
  798. ->whereBetween('wid', [$start, $end])
  799. ->select(['book_id', 'paragraph', 'wid', 'data', 'uid', 'editor_id', 'created_at', 'updated_at'])
  800. ->orderBy('wid')
  801. ->get();
  802. $wbwContent = [];
  803. foreach ($wbwData as $wbwrow) {
  804. $wbw = str_replace('&nbsp;', ' ', $wbwrow->data);
  805. $wbw = str_replace('<br>', ' ', $wbw);
  806. $xmlString = '<root>'.$wbw.'</root>';
  807. try {
  808. $xmlWord = simplexml_load_string($xmlString);
  809. $wordsList = $xmlWord->xpath('//word');
  810. } catch (\Exception $e) {
  811. Log::error('corpus getWbw', ['error' => $e, 'data' => $xmlString]);
  812. return false;
  813. }
  814. foreach ($wordsList as $word) {
  815. $case = \str_replace(['#', '.'], ['$', ''], $word->case->__toString());
  816. $case = \str_replace('$$', '$', $case);
  817. $case = trim($case);
  818. $case = trim($case, '$');
  819. $wbwId = explode('-', $word->id->__toString());
  820. $wbwData = [
  821. 'uid' => $wbwrow->uid,
  822. 'book' => $wbwrow->book_id,
  823. 'para' => $wbwrow->paragraph,
  824. 'sn' => array_slice($wbwId, 2),
  825. 'word' => ['value' => $word->pali->__toString(), 'status' => 0],
  826. 'real' => ['value' => $word->real->__toString(), 'status' => 0],
  827. 'meaning' => ['value' => $word->mean->__toString(), 'status' => 0],
  828. 'type' => ['value' => $word->type->__toString(), 'status' => 0],
  829. 'grammar' => ['value' => $word->gramma->__toString(), 'status' => 0],
  830. 'case' => ['value' => $word->case->__toString(), 'status' => 0],
  831. 'parent' => ['value' => $word->parent->__toString(), 'status' => 0],
  832. 'style' => ['value' => $word->style->__toString(), 'status' => 0],
  833. 'factors' => ['value' => $word->org->__toString(), 'status' => 0],
  834. 'factorMeaning' => ['value' => $word->om->__toString(), 'status' => 0],
  835. 'confidence' => $word->cf->__toString(),
  836. 'created_at' => $wbwrow->created_at,
  837. 'updated_at' => $wbwrow->updated_at,
  838. 'hasComment' => Discussion::where('res_id', $wbwrow->uid)->exists(),
  839. ];
  840. if (isset($word->parent2)) {
  841. $wbwData['parent2']['value'] = $word->parent2->__toString();
  842. if (isset($word->parent2['status'])) {
  843. $wbwData['parent2']['status'] = (int) $word->parent2['status'];
  844. } else {
  845. $wbwData['parent2']['status'] = 0;
  846. }
  847. }
  848. if (isset($word->pg)) {
  849. $wbwData['grammar2']['value'] = $word->pg->__toString();
  850. if (isset($word->pg['status'])) {
  851. $wbwData['grammar2']['status'] = (int) $word->pg['status'];
  852. } else {
  853. $wbwData['grammar2']['status'] = 0;
  854. }
  855. }
  856. if (isset($word->rela)) {
  857. $wbwData['relation']['value'] = $word->rela->__toString();
  858. if (isset($word->rela['status'])) {
  859. $wbwData['relation']['status'] = (int) $word->rela['status'];
  860. } else {
  861. $wbwData['relation']['status'] = 7;
  862. }
  863. }
  864. if (isset($word->bmt)) {
  865. $wbwData['bookMarkText']['value'] = $word->bmt->__toString();
  866. if (isset($word->bmt['status'])) {
  867. $wbwData['bookMarkText']['status'] = (int) $word->bmt['status'];
  868. } else {
  869. $wbwData['bookMarkText']['status'] = 7;
  870. }
  871. }
  872. if (isset($word->bmc)) {
  873. $wbwData['bookMarkColor']['value'] = $word->bmc->__toString();
  874. if (isset($word->bmc['status'])) {
  875. $wbwData['bookMarkColor']['status'] = (int) $word->bmc['status'];
  876. } else {
  877. $wbwData['bookMarkColor']['status'] = 7;
  878. }
  879. }
  880. if (isset($word->note)) {
  881. $wbwData['note']['value'] = $word->note->__toString();
  882. if (isset($word->note['status'])) {
  883. $wbwData['note']['status'] = (int) $word->note['status'];
  884. } else {
  885. $wbwData['note']['status'] = 7;
  886. }
  887. }
  888. if (isset($word->cf)) {
  889. $wbwData['confidence'] = (float) $word->cf->__toString();
  890. }
  891. if (isset($word->attachments)) {
  892. $wbwData['attachments'] = json_decode($word->attachments->__toString());
  893. }
  894. if (isset($word->pali['status'])) {
  895. $wbwData['word']['status'] = (int) $word->pali['status'];
  896. }
  897. if (isset($word->real['status'])) {
  898. $wbwData['real']['status'] = (int) $word->real['status'];
  899. }
  900. if (isset($word->mean['status'])) {
  901. $wbwData['meaning']['status'] = (int) $word->mean['status'];
  902. }
  903. if (isset($word->type['status'])) {
  904. $wbwData['type']['status'] = (int) $word->type['status'];
  905. }
  906. if (isset($word->gramma['status'])) {
  907. $wbwData['grammar']['status'] = (int) $word->gramma['status'];
  908. }
  909. if (isset($word->case['status'])) {
  910. $wbwData['case']['status'] = (int) $word->case['status'];
  911. }
  912. if (isset($word->parent['status'])) {
  913. $wbwData['parent']['status'] = (int) $word->parent['status'];
  914. }
  915. if (isset($word->org['status'])) {
  916. $wbwData['factors']['status'] = (int) $word->org['status'];
  917. }
  918. if (isset($word->om['status'])) {
  919. $wbwData['factorMeaning']['status'] = (int) $word->om['status'];
  920. }
  921. $wbwContent[] = $wbwData;
  922. }
  923. }
  924. if (count($wbwContent) === 0) {
  925. return false;
  926. }
  927. return \json_encode($wbwContent, JSON_UNESCAPED_UNICODE);
  928. }
  929. /**
  930. * 将句子放进结果列表
  931. */
  932. private function pushSent($result, $sent, $level = 0, $mode = 'read')
  933. {
  934. $sent['mode'] = $mode;
  935. $sentProps = base64_encode(\json_encode($sent));
  936. if ($mode === 'read') {
  937. $sentWidget = "<MdTpl tpl='sentread' props='{$sentProps}' ></MdTpl>";
  938. } else {
  939. $sentWidget = "<MdTpl tpl='sentedit' props='{$sentProps}' ></MdTpl>";
  940. }
  941. // 增加标题的html标记
  942. if ($level > 0) {
  943. $sentWidget = "<h{$level}>".$sentWidget."</h{$level}>";
  944. }
  945. array_push($result, $sentWidget);
  946. return $result;
  947. }
  948. private function newSent($book, $para, $word_start, $word_end)
  949. {
  950. $sent = [
  951. 'id' => "{$book}-{$para}-{$word_start}-{$word_end}",
  952. 'book' => $book,
  953. 'para' => $para,
  954. 'wordStart' => $word_start,
  955. 'wordEnd' => $word_end,
  956. 'origin' => [],
  957. 'translation' => [],
  958. 'commentaries' => [],
  959. ];
  960. if ($book < 1000) {
  961. // 生成channel 数量列表
  962. $sentId = "{$book}-{$para}-{$word_start}-{$word_end}";
  963. $channelCount = CorpusController::_sentCanReadCount($book, $para, $word_start, $word_end, $this->userUuid);
  964. $path = json_decode(PaliText::where('book', $book)->where('paragraph', $para)->value('path'), true);
  965. $sent['path'] = [];
  966. foreach ($path as $key => $value) {
  967. // code...
  968. $value['paliTitle'] = $value['title'];
  969. $sent['path'][] = $value;
  970. }
  971. $sent['tranNum'] = $channelCount['tranNum'];
  972. $sent['nissayaNum'] = $channelCount['nissayaNum'];
  973. $sent['commNum'] = $channelCount['commNum'];
  974. $sent['originNum'] = $channelCount['originNum'];
  975. $sent['simNum'] = $channelCount['simNum'];
  976. }
  977. return $sent;
  978. }
  979. public static function _sentCanReadCount($book, $para, $start, $end, $userUuid = null)
  980. {
  981. $keyCanRead = '/channel/can-read/';
  982. if ($userUuid) {
  983. $keyCanRead .= $userUuid;
  984. } else {
  985. $keyCanRead .= 'guest';
  986. }
  987. $channelCanRead = Cache::remember(
  988. $keyCanRead,
  989. config('mint.cache.expire'),
  990. function () use ($userUuid) {
  991. return ChannelApi::getCanReadByUser($userUuid);
  992. }
  993. );
  994. $channels = Sentence::where('book_id', $book)
  995. ->where('paragraph', $para)
  996. ->where('word_start', $start)
  997. ->where('word_end', $end)
  998. ->where('strlen', '<>', 0)
  999. ->whereIn('channel_uid', $channelCanRead)
  1000. ->select('channel_uid')
  1001. ->groupBy('channel_uid')
  1002. ->get();
  1003. $channelList = [];
  1004. foreach ($channels as $key => $value) {
  1005. // code...
  1006. if (Str::isUuid($value->channel_uid)) {
  1007. $channelList[] = $value->channel_uid;
  1008. }
  1009. }
  1010. $simId = PaliSentence::where('book', $book)
  1011. ->where('paragraph', $para)
  1012. ->where('word_begin', $start)
  1013. ->where('word_end', $end)
  1014. ->value('id');
  1015. if ($simId) {
  1016. $output['simNum'] = SentSimIndex::where('sent_id', $simId)->value('count');
  1017. } else {
  1018. $output['simNum'] = 0;
  1019. }
  1020. $channelInfo = Channel::whereIn('uid', $channelList)->select('type')->get();
  1021. $output['tranNum'] = 0;
  1022. $output['nissayaNum'] = 0;
  1023. $output['commNum'] = 0;
  1024. $output['originNum'] = 0;
  1025. foreach ($channelInfo as $key => $value) {
  1026. // code...
  1027. switch ($value->type) {
  1028. case 'translation':
  1029. $output['tranNum']++;
  1030. break;
  1031. case 'nissaya':
  1032. $output['nissayaNum']++;
  1033. break;
  1034. case 'commentary':
  1035. $output['commNum']++;
  1036. break;
  1037. case 'original':
  1038. $output['originNum']++;
  1039. break;
  1040. }
  1041. }
  1042. return $output;
  1043. }
  1044. /**
  1045. * 获取某个句子的相关资源的句子数量
  1046. */
  1047. public static function sentCanReadCount($book, $para, $start, $end, $userUuid = null)
  1048. {
  1049. $sentId = "{$book}-{$para}-{$start}-{$end}";
  1050. $hKey = "/sentence/res-count/{$sentId}/";
  1051. if ($userUuid) {
  1052. $key = $userUuid;
  1053. } else {
  1054. $key = 'guest';
  1055. }
  1056. if (Redis::hExists($hKey, $key)) {
  1057. return json_decode(Redis::hGet($hKey, $key), true);
  1058. } else {
  1059. $channelCount = CorpusController::_sentCanReadCount($book, $para, $start, $end, $userUuid);
  1060. Redis::hSet($hKey, $key, json_encode($channelCount));
  1061. return $channelCount;
  1062. }
  1063. }
  1064. private function markdownRender($input) {}
  1065. /**
  1066. * Update the specified resource in storage.
  1067. *
  1068. * @return Response
  1069. */
  1070. public function update(Request $request, Sentence $sentence)
  1071. {
  1072. //
  1073. }
  1074. /**
  1075. * Remove the specified resource from storage.
  1076. *
  1077. * @return Response
  1078. */
  1079. public function destroy(Sentence $sentence)
  1080. {
  1081. //
  1082. }
  1083. }