- 将原本分散在 `parse_diff_side_by_side` 与 `parse_pipe_diff_table` 的两套解析逻辑合并,新增统一的 `parse_one_diff_line` 工具函数 - `parse_one_diff_line` 同时识别 `|a|b`、`a | b`、`a<b` / `a>b` 等多种 diff 输出格式,便于后续扩展 - 简化 `parse_diff_blocks` 中 full mode / simple mode 的内联解析流程,消除代码重复 - 优化 `raw_error` 换行符归一化处理,兼容 `\r\n` 与单独 `\r` 结尾的情况
381 lines
12 KiB
PHP
381 lines
12 KiB
PHP
<?php
|
||
$cache_time = 10;
|
||
$OJ_CACHE_SHARE = false;
|
||
|
||
require_once('./include/cache_start.php');
|
||
require_once('./include/db_info.inc.php');
|
||
require_once('./include/setlang.php');
|
||
|
||
$view_title = "Welcome To Online Judge";
|
||
|
||
if(!isset($_SESSION[$OJ_NAME.'_'.'user_id'])){
|
||
header("location:loginpage.php");
|
||
exit(0);
|
||
}
|
||
|
||
require_once("./include/const.inc.php");
|
||
|
||
if(!isset($_GET['sid'])){
|
||
echo "No such code!\n";
|
||
require_once("oj-footer.php");
|
||
exit(0);
|
||
}
|
||
|
||
function is_valid($str2){
|
||
global $_SESSION,$OJ_NAME,$OJ_FRIENDLY_LEVEL;
|
||
if(isset($_SESSION[$OJ_NAME.'_'.'source_browser'])) return true;
|
||
if($OJ_FRIENDLY_LEVEL>3) return true;
|
||
// 如果希望能让任何人都查看对比和RE,放开行首注释,并设定$OJ_SHOW_DIFF=true;
|
||
// return true;
|
||
|
||
$n = strlen($str2);
|
||
$str = str_split($str2);
|
||
$m = 1;
|
||
for($i=0; $i<$n; $i++){
|
||
if(is_numeric($str[$i]))
|
||
$m++;
|
||
}
|
||
return $n/$m>3;
|
||
}
|
||
|
||
if(!isset($_SESSION[$OJ_NAME.'_'.'user_id'])){
|
||
$view_errors = $MSG_WARNING_ACCESS_DENIED ;
|
||
require("template/".$OJ_TEMPLATE."/error.php");
|
||
exit(0);
|
||
}
|
||
|
||
$ok = false;
|
||
$id = strval(intval($_GET['sid']));
|
||
|
||
$sql = "SELECT * FROM `solution` WHERE `solution_id`=?";
|
||
$result = pdo_query($sql,$id);
|
||
$row = $result[0];
|
||
$lang = $row['language'];
|
||
$contest_id = intval($row['contest_id']);
|
||
$isRE = $row['result']==10;
|
||
$isAC = $row['result']==4 ;
|
||
$mark=$row['pass_rate']*100;
|
||
if($isAC) $mark=100;
|
||
|
||
if((isset($_SESSION[$OJ_NAME.'_'.'user_id']) && $row && ($row['user_id']==$_SESSION[$OJ_NAME.'_'.'user_id']))||isset($_SESSION[$OJ_NAME.'_'.'source_browser']))
|
||
{
|
||
$ok = true;
|
||
}
|
||
$spj=pdo_query("select spj from problem where problem_id=?",$row['problem_id']);
|
||
if(!empty($spj)&&$spj[0][0]==2 && $OJ_HIDE_RIGHT_ANSWER && !isset($_SESSION[$OJ_NAME.'_'.'source_browser']) ){
|
||
$view_errors = "<h1>$MSG_MARK:$mark</h1><br>";
|
||
$ok = false;
|
||
}
|
||
/**
|
||
* 把 N 个相同测试点的 RE 折叠为 "1.in~5.in: Aborted (共 5 个测试点)"
|
||
*/
|
||
function dedup_runtimeinfo($text, &$dedup_count) {
|
||
$lines = explode("\n", $text);
|
||
$buckets = array(); // reason => [testcase, ...]
|
||
$other = array();
|
||
foreach ($lines as $line) {
|
||
$line = trim($line);
|
||
if ($line === "") continue;
|
||
// 形如 "data/1001/1.in:Aborted" 或 "1.in: Segmentation fault"
|
||
if (preg_match('/^(?:data\/\d+\/)?([^\s:]+(?:[.:][^\s:]+)*?):\s*(.+)$/', $line, $m)) {
|
||
$tc = $m[1];
|
||
$reason = trim($m[2]);
|
||
$buckets[$reason][] = $tc;
|
||
} else {
|
||
$other[] = $line;
|
||
}
|
||
}
|
||
$out = array();
|
||
foreach ($other as $o) $out[] = $o;
|
||
foreach ($buckets as $reason => $tcs) {
|
||
$count = count($tcs);
|
||
if ($count == 1) {
|
||
$out[] = $tcs[0] . ": " . $reason;
|
||
} else {
|
||
$first = $tcs[0];
|
||
$last = $tcs[$count - 1];
|
||
$range = ($first === $last) ? $first : ($first . "~" . $last);
|
||
$out[] = $range . ": " . $reason . " (共 " . $count . " 个测试点)";
|
||
$dedup_count += ($count - 1);
|
||
}
|
||
}
|
||
return implode("\n", $out);
|
||
}
|
||
|
||
/**
|
||
* 把 runtimeinfo.error 解析为结构化的 diff blocks
|
||
* 返回 [['name' => 'test1.out', 'expected' => [...], 'yours' => [...], 'full_mode' => bool], ...]
|
||
* 解析失败返回空数组
|
||
*/
|
||
function parse_diff_blocks($text) {
|
||
$blocks = array();
|
||
if (empty($text)) return $blocks;
|
||
|
||
// 先按 "\n\n" 切分(simple mode 每个测试点之间有空行)
|
||
$chunks = preg_split('/\n\s*\n/', $text);
|
||
foreach ($chunks as $chunk) {
|
||
$chunk = trim($chunk);
|
||
if ($chunk === '') continue;
|
||
|
||
// 格式 1: 整段就是一个 =====[name]===== 块(包含表头+数据+结尾 ====)
|
||
// 形如 "========[test1.out]========\nExpected | Yours\nFail | Fall\n=============================="
|
||
if (preg_match('/^=+\[([^\]]+)\]\=+(.+?)\n=+\s*$/s', $chunk, $m)) {
|
||
$name = trim($m[1]);
|
||
$body = $m[2];
|
||
// body 形如 "\nExpected | Yours\nFail | Fall"
|
||
// 去掉首尾的换行符
|
||
$body = trim($body, "\n\r");
|
||
$lines_in_body = explode("\n", $body);
|
||
$expected = array();
|
||
$yours = array();
|
||
$found_header = false;
|
||
foreach ($lines_in_body as $bl) {
|
||
$bl = rtrim($bl);
|
||
if ($bl === '') continue;
|
||
// 跳过表头
|
||
if (!$found_header && preg_match('/^Expected.*\|.*Yours/i', $bl)) {
|
||
$found_header = true;
|
||
continue;
|
||
}
|
||
if (!$found_header && (preg_match('/^\|?--\s*\|/', $bl) || trim($bl) === '--')) {
|
||
continue;
|
||
}
|
||
$parsed = parse_one_diff_line($bl);
|
||
if ($parsed !== null) {
|
||
$expected[] = $parsed[0];
|
||
$yours[] = $parsed[1];
|
||
$found_header = true;
|
||
} else {
|
||
$expected[] = $bl;
|
||
$yours[] = '';
|
||
$found_header = true;
|
||
}
|
||
}
|
||
if (!empty($expected)) {
|
||
$blocks[] = array(
|
||
'name' => $name,
|
||
'expected' => $expected,
|
||
'yours' => $yours,
|
||
'full_mode' => true,
|
||
);
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// 格式 2: simple mode "test1.out\n--\n|Expected|Yours\n|--|--\n|row1|row2"
|
||
if (preg_match('/^([^\n=|]+)\n--\n([\s\S]+)$/', $chunk, $m)) {
|
||
$name = trim($m[1]);
|
||
$body = $m[2];
|
||
$lines_in_body = explode("\n", $body);
|
||
$expected = array();
|
||
$yours = array();
|
||
$found_header = false;
|
||
foreach ($lines_in_body as $bl) {
|
||
$bl = rtrim($bl);
|
||
if ($bl === '') continue;
|
||
if (!$found_header && preg_match('/Expected.*\|.*Yours/i', $bl)) {
|
||
$found_header = true;
|
||
continue;
|
||
}
|
||
if (!$found_header && (preg_match('/^\|?--\s*\|/', $bl) || trim($bl) === '--')) {
|
||
continue;
|
||
}
|
||
$parsed = parse_one_diff_line($bl);
|
||
if ($parsed !== null) {
|
||
$expected[] = $parsed[0];
|
||
$yours[] = $parsed[1];
|
||
$found_header = true;
|
||
} else {
|
||
$expected[] = $bl;
|
||
$yours[] = '';
|
||
$found_header = true;
|
||
}
|
||
}
|
||
if (!empty($expected)) {
|
||
$blocks[] = array(
|
||
'name' => $name,
|
||
'expected' => $expected,
|
||
'yours' => $yours,
|
||
'full_mode' => false,
|
||
);
|
||
}
|
||
}
|
||
}
|
||
return $blocks;
|
||
}
|
||
|
||
/**
|
||
* 解析单行 "expected | yours" 格式
|
||
* 支持 "|a|b"、"a | b"、"a|b" 等
|
||
* 返回 [left, right] 或 null(无法解析)
|
||
*/
|
||
function parse_one_diff_line($line) {
|
||
$line = rtrim($line);
|
||
if ($line === '') return null;
|
||
|
||
// 格式 A: "|a|b" (首尾带 |)
|
||
if ($line[0] === '|') {
|
||
$rest = substr($line, 1);
|
||
$pos = strpos($rest, '|');
|
||
if ($pos !== false) {
|
||
return array(substr($rest, 0, $pos), substr($rest, $pos + 1));
|
||
}
|
||
}
|
||
|
||
// 格式 B: "a | b" 或 "a | b"(带空格的 markdown 风格)
|
||
// 注意:可能行内有空格,所以用 " | " 或 " | " 等宽松匹配
|
||
if (preg_match('/^(.*?)\s+\|\s+(.*)$/', $line, $m)) {
|
||
// 确保左右两边都非空
|
||
$left = trim($m[1]);
|
||
$right = trim($m[2]);
|
||
if ($left !== '' || $right !== '') {
|
||
return array($left, $right);
|
||
}
|
||
}
|
||
|
||
// 格式 C: "a<b" 或 "a>b"(diff -y 风格)
|
||
if (preg_match('/^(.*?)\s+([<>])\s+(.*)$/', $line, $m)) {
|
||
return array($m[1], $m[3]);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
$view_reinfo = "";
|
||
$view_reinfo_raw = ""; // 原始(管理员/有权限者看)
|
||
$view_reinfo_summary = ""; // 折叠后(普通用户看)
|
||
$view_reinfo_dedup_count = 0; // 折叠掉的测试点数
|
||
$verdict_color_class = "blue"; // semantic ui color: green/red/orange/yellow/grey
|
||
$result_icon = "info circle";
|
||
$verdict_tip = "";
|
||
|
||
if($ok){
|
||
|
||
if($row['user_id']!=$_SESSION[$OJ_NAME.'_'.'user_id']){
|
||
$view_mail_link= "<a href='mail.php?to_user=".htmlentities($row['user_id'],ENT_QUOTES,"UTF-8")."&title=$MSG_SUBMIT $id'>Mail the auther</a>";
|
||
}
|
||
|
||
$sql = "SELECT `error` FROM `runtimeinfo` WHERE `solution_id`=?";
|
||
$rt = pdo_query($sql,$id);
|
||
$raw_error = "";
|
||
if(isset($rt[0])){
|
||
$raw_error = $rt[0]['error'];
|
||
// 统一换行符:先替换 \r\n,再替换单独的 \r
|
||
$raw_error = str_replace(array("\r\n", "\r"), "\n", $raw_error);
|
||
}
|
||
|
||
// === 阶段 1:基于 solution.result 给"主解释"(永远非空)===
|
||
$result_code = intval($row['result']);
|
||
$is_admin_session = isset($_SESSION[$OJ_NAME."_administrator"]);
|
||
$is_source_browser = isset($_SESSION[$OJ_NAME.'_'.'source_browser']);
|
||
|
||
// 颜色 + 图标
|
||
switch ($result_code) {
|
||
case 4: $verdict_color_class = "green"; $result_icon = "check circle"; break;
|
||
case 5: // PE
|
||
case 6: $verdict_color_class = "red"; $result_icon = "times circle"; break;
|
||
case 7: // TLE
|
||
case 8: // MLE
|
||
case 9: $verdict_color_class = "yellow";$result_icon = "hourglass end"; break;
|
||
case 10: $verdict_color_class = "red"; $result_icon = "bug"; break;
|
||
case 11: $verdict_color_class = "grey"; $result_icon = "code"; break;
|
||
case 13: $verdict_color_class = "blue"; $result_icon = "play circle"; break;
|
||
default: $verdict_color_class = "blue"; $result_icon = "info circle";
|
||
}
|
||
|
||
// 友好提示:仅保留标题性文字,具体修复建议交给 AI 点评
|
||
switch ($result_code) {
|
||
case 4: // AC
|
||
$verdict_tip = "本次提交通过了所有测试点";
|
||
break;
|
||
case 5: // PE
|
||
$verdict_tip = "输出与预期基本一致但格式不符(Presentation Error)";
|
||
break;
|
||
case 6: // WA
|
||
$verdict_tip = "输出与预期不符(Wrong Answer)";
|
||
break;
|
||
case 7: // TLE
|
||
$verdict_tip = $MSG_HINT_TLE;
|
||
break;
|
||
case 8: // MLE
|
||
$verdict_tip = $MSG_HINT_MLE;
|
||
break;
|
||
case 9: // OLE
|
||
$verdict_tip = $MSG_HINT_OLE;
|
||
break;
|
||
case 10: // RE
|
||
$verdict_tip = "程序运行中发生错误(Runtime Error)";
|
||
break;
|
||
case 11: // CE
|
||
$verdict_tip = "编译错误(Compile Error)。请查看编译信息页。";
|
||
break;
|
||
case 13: // TR (test run)
|
||
$verdict_tip = "测试运行完成(Test Run)";
|
||
break;
|
||
default:
|
||
$verdict_tip = "正在评判,请稍候刷新";
|
||
}
|
||
|
||
// === 阶段 3:原始错误处理(syscall 提示 vs 占位符修复)===
|
||
$view_reinfo_raw = $raw_error;
|
||
if (trim($raw_error) === "") {
|
||
// TLE/MLE/OLE 时 runtimeinfo 通常为空
|
||
$view_reinfo_summary = "";
|
||
} else {
|
||
// 含 "PASS" 不再显示 "error3" 占位
|
||
// 含 "php" 不再显示 "error2" 占位(用 generic 提示)
|
||
if (strpos($raw_error, "php") !== false) {
|
||
$view_reinfo_summary = $MSG_RUNTIME_GENERIC_ERROR;
|
||
$view_reinfo_raw = $raw_error; // 管理员仍能看
|
||
}
|
||
// 含 "judge/" 区分:是否是 syscall 错误
|
||
elseif (strpos($raw_error, "judge/") !== false
|
||
&& strpos($raw_error, "CALLID") === false
|
||
&& strpos($raw_error, "Forbidden system call") === false) {
|
||
$view_reinfo_summary = "潜在的数组或指针越界,请检查代码。";
|
||
}
|
||
// 默认:原文 + 去重
|
||
else {
|
||
$view_reinfo_summary = $raw_error;
|
||
// 多测试点去重:按 "infile: error" 模式分组
|
||
$view_reinfo_summary = dedup_runtimeinfo($view_reinfo_summary, $view_reinfo_dedup_count);
|
||
}
|
||
}
|
||
|
||
$view_reinfo = $view_reinfo_summary;
|
||
|
||
// 尝试解析为结构化的 diff 数据(用于 WA/PE/AC 时的对比展示)
|
||
$diff_blocks = parse_diff_blocks($raw_error);
|
||
|
||
// 管理员或 source_browser 永远看完整原文
|
||
$show_raw = $is_admin_session || $is_source_browser;
|
||
if ($show_raw) {
|
||
$view_reinfo = $view_reinfo_raw;
|
||
} else {
|
||
$view_reinfo = $view_reinfo_summary;
|
||
}
|
||
}
|
||
|
||
// 不是本人的提交,且不是 source_browser
|
||
else{
|
||
if($spj[0][0]!=2) $view_errors = $MSG_WARNING_ACCESS_DENIED;
|
||
require("template/".$OJ_TEMPLATE."/error.php");
|
||
exit(0);
|
||
}
|
||
|
||
/////////////////////////Template
|
||
|
||
if(!isset($_SESSION[$OJ_NAME.'_'.'source_browser']) && $OJ_SHOW_DIFF==false){
|
||
$view_errors = $MSG_WARNING_ACCESS_DENIED;
|
||
require("template/".$OJ_TEMPLATE."/error.php");
|
||
exit(0);
|
||
}
|
||
else{
|
||
require("template/".$OJ_TEMPLATE."/reinfo.php");
|
||
}
|
||
/////////////////////////Common foot
|
||
if(file_exists('./include/cache_end.php')){
|
||
require_once('./include/cache_end.php');
|
||
}
|
||
?>
|