Files
hustoj/web/reinfo.php
klarkxy 9b13c95e8a ♻️ refactor(reinfo): 重构 diff 解析逻辑并支持多格式
- 将原本分散在 `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` 结尾的情况
2026-07-08 21:24:08 +08:00

381 lines
12 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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');
}
?>