341 lines
12 KiB
PHP
341 lines
12 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
class Latex extends Base
|
||
{
|
||
public function __construct(\think\Request $request = null)
|
||
{
|
||
parent::__construct($request);
|
||
}
|
||
|
||
public function generateLatexPdf()
|
||
{
|
||
$data = $this->request->post();
|
||
$rule = new Validate([
|
||
'p_article_id' => 'require|number'
|
||
]);
|
||
|
||
if (!$rule->check($data)) {
|
||
return jsonError($rule->getError());
|
||
}
|
||
|
||
try {
|
||
// 获取文章信息
|
||
$p_info = $this->production_article_obj->where('p_article_id', $data['p_article_id'])->find();
|
||
if (!$p_info) {
|
||
return jsonError('文章实例不存在');
|
||
}
|
||
|
||
// 获取作者信息
|
||
$authors = $this->production_article_author_obj->where('p_article_id', $data['p_article_id'])->where('state', 0)->select();
|
||
|
||
// 获取机构信息
|
||
$organs = $this->production_article_organ_obj->where('p_article_id', $data['p_article_id'])->where('state', 0)->select();
|
||
|
||
// 获取正文内容
|
||
$main_contents = $this->production_article_main_obj->where('p_article_id', $data['p_article_id'])->where('state', 0)->order('p_main_id')->select();
|
||
|
||
// 获取参考文献
|
||
$references = $this->production_article_refer_obj->where('p_article_id', $data['p_article_id'])->where('state', 0)->order('index')->select();
|
||
|
||
// 构建LaTeX内容
|
||
$latex_content = $this->buildLatexContent($p_info, $authors, $organs, $main_contents, $references);
|
||
|
||
// 创建临时目录
|
||
$temp_dir = ROOT_PATH . 'public' . DS . 'temp_latex' . DS;
|
||
if (!is_dir($temp_dir)) {
|
||
mkdir($temp_dir, 0755, true);
|
||
}
|
||
|
||
// 生成唯一的文件名
|
||
$filename = 'article_' . $p_info['p_article_id'] . '_' . time();
|
||
$tex_file = $temp_dir . $filename . '.tex';
|
||
$pdf_file = $temp_dir . $filename . '.pdf';
|
||
|
||
// 写入LaTeX文件
|
||
file_put_contents($tex_file, $latex_content);
|
||
|
||
// 执行LaTeX编译命令生成PDF
|
||
$command = sprintf(
|
||
'cd %s && pdflatex -interaction=nonstopmode -output-directory=%s %s',
|
||
escapeshellarg($temp_dir),
|
||
escapeshellarg($temp_dir),
|
||
escapeshellarg($tex_file)
|
||
);
|
||
|
||
// 执行命令
|
||
$output = [];
|
||
$return_var = 0;
|
||
exec($command, $output, $return_var);
|
||
|
||
// 检查PDF是否生成成功
|
||
if (!file_exists($pdf_file) || filesize($pdf_file) == 0) {
|
||
return jsonError('PDF生成失败,请检查LaTeX内容或系统环境');
|
||
}
|
||
|
||
// 移动PDF到正式目录
|
||
$pdf_dir = ROOT_PATH . 'public' . DS . 'latex_pdfs' . DS;
|
||
if (!is_dir($pdf_dir)) {
|
||
mkdir($pdf_dir, 0755, true);
|
||
}
|
||
|
||
$final_pdf_path = $pdf_dir . $filename . '.pdf';
|
||
rename($pdf_file, $final_pdf_path);
|
||
|
||
// 清理临时文件
|
||
if (file_exists($tex_file)) {
|
||
unlink($tex_file);
|
||
}
|
||
|
||
// 清理辅助文件(.aux, .log等)
|
||
$aux_file = $temp_dir . $filename . '.aux';
|
||
$log_file = $temp_dir . $filename . '.log';
|
||
if (file_exists($aux_file)) unlink($aux_file);
|
||
if (file_exists($log_file)) unlink($log_file);
|
||
|
||
// 返回PDF路径
|
||
$relative_path = 'latex_pdfs/' . $filename . '.pdf';
|
||
return jsonSuccess([
|
||
'pdf_url' => $relative_path,
|
||
'message' => 'PDF生成成功'
|
||
]);
|
||
|
||
} catch (\Exception $e) {
|
||
return jsonError('生成过程中发生错误: ' . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 构建LaTeX文档内容
|
||
* @param $p_info
|
||
* @param $authors
|
||
* @param $organs
|
||
* @param $main_contents
|
||
* @param $references
|
||
* @return string
|
||
*/
|
||
private function buildLatexContent($p_info, $authors, $organs, $main_contents, $references)
|
||
{
|
||
// 构建机构映射
|
||
$organ_map = [];
|
||
foreach ($organs as $index => $organ) {
|
||
$organ_map[$organ['p_article_organ_id']] = $index + 1;
|
||
}
|
||
|
||
// LaTeX文档开始
|
||
$latex = '\documentclass[twocolumn]{article}' . "\n";
|
||
$latex .= '\usepackage[utf8]{inputenc}' . "\n";
|
||
$latex .= '\usepackage[T1]{fontenc}' . "\n";
|
||
$latex .= '\usepackage{geometry}' . "\n";
|
||
$latex .= '\usepackage{graphicx}' . "\n";
|
||
$latex .= '\usepackage{amsmath}' . "\n";
|
||
$latex .= '\usepackage{amsfonts}' . "\n";
|
||
$latex .= '\usepackage{amssymb}' . "\n";
|
||
$latex .= '\usepackage{setspace}' . "\n";
|
||
$latex .= '\usepackage{titlesec}' . "\n";
|
||
$latex .= '\usepackage{hyperref}' . "\n";
|
||
$latex .= '\usepackage{caption}' . "\n";
|
||
$latex .= '\usepackage{multicol}' . "\n";
|
||
$latex .= '\usepackage{abstract}' . "\n";
|
||
|
||
$latex .= '\geometry{left=1.5cm,right=1.5cm,top=2.5cm,bottom=2.5cm}' . "\n";
|
||
$latex .= '\setstretch{1.5}' . "\n";
|
||
|
||
$latex .= '\title{' . $this->escapeLatexText($p_info['title']) . '}' . "\n";
|
||
|
||
// 处理作者和机构
|
||
if (!empty($authors)) {
|
||
$author_text = '';
|
||
$affiliations = [];
|
||
|
||
foreach ($authors as $author) {
|
||
if ($author_text !== '') {
|
||
$author_text .= ', ';
|
||
}
|
||
$author_text .= $this->escapeLatexText($author['first_name'] . ' ' . $author['last_name']);
|
||
|
||
// 获取作者的机构
|
||
$author_organs = $this->production_article_author_to_organ_obj
|
||
->where('p_article_author_id', $author['p_article_author_id'])
|
||
->where('state', 0)
|
||
->select();
|
||
|
||
foreach ($author_organs as $ao) {
|
||
if (isset($organ_map[$ao['p_article_organ_id']])) {
|
||
$author_text .= '$^{' . $organ_map[$ao['p_article_organ_id']] . '}$';
|
||
}
|
||
}
|
||
|
||
if ($author['is_report'] == 1) {
|
||
$author_text .= '$^{*}$';
|
||
}
|
||
}
|
||
|
||
$latex .= '\author{' . $author_text . "\n";
|
||
|
||
// 添加机构信息
|
||
foreach ($organs as $index => $organ) {
|
||
$latex .= '\\\\$' . ($index + 1) . '$ ' . $this->escapeLatexText($organ['organ_name']) . "\n";
|
||
}
|
||
if (!empty($authors)) {
|
||
// 查找通讯作者
|
||
$corresponding_authors = array_filter($authors, function($author) {
|
||
return $author['is_report'] == 1;
|
||
});
|
||
|
||
if (!empty($corresponding_authors)) {
|
||
$corr_author = reset($corresponding_authors);
|
||
$latex .= '\\\\$*$ Corresponding author: ' .
|
||
$this->escapeLatexText($corr_author['email']) . "\n";
|
||
}
|
||
}
|
||
|
||
$latex .= '}' . "\n";
|
||
}
|
||
|
||
$latex .= '\date{}' . "\n";
|
||
$latex .= '\begin{document}' . "\n";
|
||
$latex .= '\maketitle' . "\n";
|
||
|
||
// 摘要
|
||
if (!empty($p_info['abstract'])) {
|
||
$latex .= '\begin{abstract}' . "\n";
|
||
$latex .= $this->escapeLatexText($p_info['abstract']) . "\n";
|
||
$latex .= '\end{abstract}' . "\n";
|
||
}
|
||
|
||
// 关键词
|
||
if (!empty($p_info['keywords'])) {
|
||
$latex .= '\textbf{Keywords:} ' . $this->escapeLatexText($p_info['keywords']) . "\n";
|
||
$latex .= '\sectionbreak' . "\n";
|
||
}
|
||
|
||
// 正文内容
|
||
$in_section = false;
|
||
$section_count = 0;
|
||
|
||
foreach ($main_contents as $content) {
|
||
$text = trim($content['content']);
|
||
|
||
// 跳过空内容
|
||
if (empty($text)) {
|
||
continue;
|
||
}
|
||
|
||
// 检查是否是标题
|
||
$clean_text = strip_tags($text);
|
||
$lower_text = strtolower($clean_text);
|
||
|
||
// 判断是否为章节标题
|
||
if (stripos($lower_text, 'introduction') === 0 ||
|
||
stripos($lower_text, 'methods') === 0 ||
|
||
stripos($lower_text, 'results') === 0 ||
|
||
stripos($lower_text, 'discussion') === 0 ||
|
||
stripos($lower_text, 'conclusion') === 0 ||
|
||
stripos($lower_text, 'references') === 0) {
|
||
|
||
// 结束前一节
|
||
if ($in_section) {
|
||
$latex .= "\n" . '\sectionbreak' . "\n";
|
||
}
|
||
|
||
// 开始新节
|
||
$section_count++;
|
||
$section_title = $this->escapeLatexText($clean_text);
|
||
$latex .= '\section{' . $section_title . '}' . "\n";
|
||
$in_section = true;
|
||
}
|
||
// 检查是否是图片或表格占位符
|
||
else if (preg_match('/<img[^>]*imageId=[\'"]([^\'"]+)[\'"][^>]*>/i', $text, $img_matches)) {
|
||
$image_id = $img_matches[1];
|
||
// 这里可以添加图片处理逻辑
|
||
$latex .= '% 图片插入位置 (ID: ' . $image_id . ')' . "\n";
|
||
$latex .= '\begin{figure}[htbp]' . "\n";
|
||
$latex .= '\centering' . "\n";
|
||
$latex .= '\includegraphics[width=0.8\textwidth]{image_' . $image_id . '}' . "\n";
|
||
$latex .= '\caption{图片说明}' . "\n";
|
||
$latex .= '\end{figure}' . "\n";
|
||
}
|
||
else if (preg_match('/<table[^>]*tableId=[\'"]([^\'"]+)[\'"][^>]*>/i', $text, $table_matches)) {
|
||
$table_id = $table_matches[1];
|
||
// 这里可以添加表格处理逻辑
|
||
$latex .= '% 表格插入位置 (ID: ' . $table_id . ')' . "\n";
|
||
$latex .= '\begin{table}[htbp]' . "\n";
|
||
$latex .= '\centering' . "\n";
|
||
$latex .= '\caption{表格说明}' . "\n";
|
||
$latex .= '\begin{tabular}{|c|c|c|}' . "\n";
|
||
$latex .= '\hline' . "\n";
|
||
$latex .= '列1 & 列2 & 列3 \\\\' . "\n";
|
||
$latex .= '\hline' . "\n";
|
||
$latex .= '% 表格数据' . "\n";
|
||
$latex .= '\hline' . "\n";
|
||
$latex .= '\end{tabular}' . "\n";
|
||
$latex .= '\end{table}' . "\n";
|
||
}
|
||
|
||
|
||
|
||
// 普通段落文本
|
||
else {
|
||
// 移除HTML标签并转义特殊字符
|
||
$plain_text = $this->escapeLatexText(strip_tags($text));
|
||
$latex .= $plain_text . "\n\n";
|
||
}
|
||
}
|
||
|
||
// 参考文献
|
||
if (!empty($references)) {
|
||
$latex .= '\section*{References}' . "\n";
|
||
$latex .= '\begin{thebibliography}{99}' . "\n";
|
||
|
||
foreach ($references as $index => $ref) {
|
||
$ref_text = isset($ref['refer_frag']) ? $ref['refer_frag'] : $ref['refer_content'];
|
||
$latex .= '\bibitem{} ' . $this->escapeLatexText($ref_text) . "\n";
|
||
}
|
||
|
||
$latex .= '\end{thebibliography}' . "\n";
|
||
}
|
||
|
||
$latex .= '\end{document}' . "\n";
|
||
|
||
return $latex;
|
||
}
|
||
|
||
/**
|
||
* 转义LaTeX特殊字符
|
||
* @param string $text
|
||
* @return string
|
||
*/
|
||
private function escapeLatexText($text)
|
||
{
|
||
// 移除HTML标签
|
||
$text = strip_tags($text);
|
||
|
||
// 转义LaTeX特殊字符
|
||
$escape_chars = [
|
||
'\\' => '\\textbackslash{}',
|
||
'&' => '\\&',
|
||
'%' => '\\%',
|
||
'$' => '\\$',
|
||
'#' => '\\#',
|
||
'_' => '\\_',
|
||
'{' => '\\{',
|
||
'}' => '\\}',
|
||
'~' => '\\textasciitilde{}',
|
||
'^' => '\\textasciicircum{}',
|
||
'"' => '\\"{}',
|
||
'\'' => '\\\'{}',
|
||
'<' => '\\textless{}',
|
||
'>' => '\\textgreater{}',
|
||
];
|
||
|
||
foreach ($escape_chars as $char => $replacement) {
|
||
$text = str_replace($char, $replacement, $text);
|
||
}
|
||
|
||
return $text;
|
||
}
|
||
|
||
|
||
} |