|
@@ -641,7 +641,10 @@ HTML;
|
|
|
$zip_file->close();
|
|
$zip_file->close();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function echoSubmissionContent($submission, $requirement) {
|
|
|
|
|
|
|
+function echoSubmissionContent($submission, $requirement, $options = array()) {
|
|
|
|
|
+ static $uoj_submission_content_seq = 0;
|
|
|
|
|
+ $collapsible = isset($options['collapsible']) && $options['collapsible'];
|
|
|
|
|
+
|
|
|
$zip_file = new ZipArchive();
|
|
$zip_file = new ZipArchive();
|
|
|
$submission_content = json_decode($submission['content'], true);
|
|
$submission_content = json_decode($submission['content'], true);
|
|
|
$zip_file->open(UOJContext::storagePath().$submission_content['file_name']);
|
|
$zip_file->open(UOJContext::storagePath().$submission_content['file_name']);
|
|
@@ -686,7 +689,31 @@ function echoSubmissionContent($submission, $requirement) {
|
|
|
echo '<h4 class="card-title">'.$req['name'].'</h4>';
|
|
echo '<h4 class="card-title">'.$req['name'].'</h4>';
|
|
|
echo '</div>';
|
|
echo '</div>';
|
|
|
echo '<div class="card-body">';
|
|
echo '<div class="card-body">';
|
|
|
- echo '<pre><code class="'.$sh_class.'">'.$file_content."\n".'</code></pre>';
|
|
|
|
|
|
|
+ if ($collapsible) {
|
|
|
|
|
+ $uoj_submission_content_seq++;
|
|
|
|
|
+ $fold_id = 'uoj-code-'.$submission['id'].'-'.$uoj_submission_content_seq;
|
|
|
|
|
+ echo '<button type="button" class="btn btn-sm btn-outline-secondary uoj-code-toggle" id="', $fold_id, '_toggle" aria-haspopup="dialog">', '查看源代码', '</button>';
|
|
|
|
|
+ echo '<div class="uoj-modal" id="', $fold_id, '_modal" hidden>';
|
|
|
|
|
+ echo '<div class="uoj-modal-backdrop" data-uoj-modal-close></div>';
|
|
|
|
|
+ echo '<div class="uoj-modal-window" role="dialog" aria-modal="true" aria-label="', '源代码', '">';
|
|
|
|
|
+ echo '<button type="button" class="uoj-modal-close" data-uoj-modal-close aria-label="', '关闭', '">×</button>';
|
|
|
|
|
+ echo '<div class="uoj-modal-body">';
|
|
|
|
|
+ echo '<div class="text-muted uoj-code-meta">'.$footer_text.'</div>';
|
|
|
|
|
+ echo '<pre><code class="'.$sh_class.'">'.$file_content."\n".'</code></pre>';
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ echoUOJModalHelper();
|
|
|
|
|
+ echo '<script type="text/javascript">';
|
|
|
|
|
+ echo '(function(){var b=document.getElementById(', json_encode($fold_id.'_toggle'), '),m=document.getElementById(', json_encode($fold_id.'_modal'), ');';
|
|
|
|
|
+ echo 'if(!b||!m||!window.uojModalInit){return;}';
|
|
|
|
|
+ echo 'var api=window.uojModalInit(m);';
|
|
|
|
|
+ echo 'b.addEventListener("click",function(){api.open();});';
|
|
|
|
|
+ echo '})();';
|
|
|
|
|
+ echo '</script>';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ echo '<pre><code class="'.$sh_class.'">'.$file_content."\n".'</code></pre>';
|
|
|
|
|
+ }
|
|
|
echo '</div>';
|
|
echo '</div>';
|
|
|
echo '<div class="card-footer">'.$footer_text.'</div>';
|
|
echo '<div class="card-footer">'.$footer_text.'</div>';
|
|
|
echo '</div>';
|
|
echo '</div>';
|
|
@@ -710,14 +737,90 @@ function echoSubmissionContent($submission, $requirement) {
|
|
|
$zip_file->close();
|
|
$zip_file->close();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Shared vanilla-JS modal helper. Emitted at most once per request, and it is
|
|
|
|
|
+// also guarded in JS, so both the test-detail modal and the source-code modal
|
|
|
|
|
+// can use it independently (a page with only one of them still works).
|
|
|
|
|
+function echoUOJModalHelper() {
|
|
|
|
|
+ static $emitted = false;
|
|
|
|
|
+ if ($emitted) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ $emitted = true;
|
|
|
|
|
+ echo <<<'UOJMODAL'
|
|
|
|
|
+<script type="text/javascript">
|
|
|
|
|
+if (typeof window.uojModalInit !== 'function') {
|
|
|
|
|
+ window.uojModalInit = function (modal) {
|
|
|
|
|
+ var win = modal.querySelector('.uoj-modal-window');
|
|
|
|
|
+ var api = {
|
|
|
|
|
+ lastFocus: null,
|
|
|
|
|
+ body: modal.querySelector('.uoj-modal-body'),
|
|
|
|
|
+ open: function () {
|
|
|
|
|
+ if (!modal.hidden) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (window.uojModalCurrent && window.uojModalCurrent !== api) {
|
|
|
|
|
+ window.uojModalCurrent.close();
|
|
|
|
|
+ }
|
|
|
|
|
+ api.lastFocus = document.activeElement;
|
|
|
|
|
+ modal.hidden = false;
|
|
|
|
|
+ document.body.classList.add('uoj-modal-open');
|
|
|
|
|
+ window.uojModalCurrent = api;
|
|
|
|
|
+ var btn = modal.querySelector('.uoj-modal-close');
|
|
|
|
|
+ if (btn && btn.focus) {
|
|
|
|
|
+ btn.focus();
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ close: function () {
|
|
|
|
|
+ if (modal.hidden) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ modal.hidden = true;
|
|
|
|
|
+ if (window.uojModalCurrent === api) {
|
|
|
|
|
+ window.uojModalCurrent = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!window.uojModalCurrent) {
|
|
|
|
|
+ document.body.classList.remove('uoj-modal-open');
|
|
|
|
|
+ }
|
|
|
|
|
+ if (api.lastFocus && api.lastFocus.focus) {
|
|
|
|
|
+ api.lastFocus.focus();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ modal.addEventListener('click', function (e) {
|
|
|
|
|
+ if (e.target === modal || (e.target.getAttribute && e.target.getAttribute('data-uoj-modal-close') !== null)) {
|
|
|
|
|
+ api.close();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ document.addEventListener('keydown', function (e) {
|
|
|
|
|
+ if ((e.key === 'Escape' || e.keyCode === 27) && !modal.hidden) {
|
|
|
|
|
+ api.close();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ return api;
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+UOJMODAL;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
|
|
|
class JudgementDetailsPrinter {
|
|
class JudgementDetailsPrinter {
|
|
|
private $name;
|
|
private $name;
|
|
|
private $styler;
|
|
private $styler;
|
|
|
private $dom;
|
|
private $dom;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
private $subtask_num;
|
|
private $subtask_num;
|
|
|
|
|
|
|
|
|
|
+ private $prefix;
|
|
|
|
|
+ private $blocks = array();
|
|
|
|
|
+ private $pending_tests = array();
|
|
|
|
|
+ private $bodies = array();
|
|
|
|
|
+ private $body_seq = 0;
|
|
|
|
|
+ private $rect_seq = 0;
|
|
|
|
|
+ private $auto_select_id = null;
|
|
|
|
|
+
|
|
|
|
|
+ private static $instance_seq = 0;
|
|
|
|
|
+
|
|
|
private function _print_c($node) {
|
|
private function _print_c($node) {
|
|
|
foreach ($node->childNodes as $child) {
|
|
foreach ($node->childNodes as $child) {
|
|
|
if ($child->nodeName == '#text') {
|
|
if ($child->nodeName == '#text') {
|
|
@@ -732,205 +835,287 @@ class JudgementDetailsPrinter {
|
|
|
echo "<pre>\n";
|
|
echo "<pre>\n";
|
|
|
$this->_print_c($node);
|
|
$this->_print_c($node);
|
|
|
echo "\n</pre>";
|
|
echo "\n</pre>";
|
|
|
- } elseif ($node->nodeName == 'tests') {
|
|
|
|
|
- echo '<div id="', $this->name, '_details_accordion">';
|
|
|
|
|
- if ($this->styler->show_small_tip) {
|
|
|
|
|
- echo '<div class="text-right text-muted">', '小提示:点击横条可展开更详细的信息', '</div>';
|
|
|
|
|
- } elseif ($this->styler->ioi_contest_is_running) {
|
|
|
|
|
- echo '<div class="text-right text-muted">', 'IOI 赛制比赛中不支持显示详细信息', '</div>';
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ } elseif ($node->nodeName == 'in') {
|
|
|
|
|
+ echo "<h4>标准输入:</h4><pre>\n";
|
|
|
$this->_print_c($node);
|
|
$this->_print_c($node);
|
|
|
- echo '</div>';
|
|
|
|
|
- } elseif ($node->nodeName == 'subtask') {
|
|
|
|
|
- $subtask_num = $node->getAttribute('num');
|
|
|
|
|
- $subtask_score = $node->getAttribute('score');
|
|
|
|
|
- $subtask_info = $node->getAttribute('info');
|
|
|
|
|
-
|
|
|
|
|
- echo '<div class="card ', $this->styler->getTestInfoClass($subtask_info), ' mb-3">';
|
|
|
|
|
-
|
|
|
|
|
- $accordion_parent = "{$this->name}_details_accordion";
|
|
|
|
|
- $accordion_collapse = "{$accordion_parent}_collapse_subtask_{$subtask_num}";
|
|
|
|
|
- $accordion_collapse_accordion = "{$accordion_collapse}_accordion";
|
|
|
|
|
- echo '<div class="card-header" data-toggle="collapse" data-parent="#', $accordion_parent, '" data-target="#', $accordion_collapse, '">';
|
|
|
|
|
-
|
|
|
|
|
- echo '<div class="row">';
|
|
|
|
|
- echo '<div class="col-sm-4">';
|
|
|
|
|
- echo '<h3 class="card-title">', 'Subtask #', $subtask_num, ': ', '</h3>';
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
-
|
|
|
|
|
- if ($this->styler->show_score) {
|
|
|
|
|
- echo '<div class="col-sm-4">';
|
|
|
|
|
- echo '得分:', $subtask_score;
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- echo '<div class="col-sm-4">';
|
|
|
|
|
- echo htmlspecialchars($subtask_info);
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- } else {
|
|
|
|
|
- echo '<div class="col-sm-8">';
|
|
|
|
|
- echo htmlspecialchars($subtask_info);
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
|
|
+ echo "\n</pre>";
|
|
|
|
|
+ } elseif ($node->nodeName == 'out') {
|
|
|
|
|
+ echo "<h4>标准输出:</h4><pre>\n";
|
|
|
|
|
+ $this->_print_c($node);
|
|
|
|
|
+ echo "\n</pre>";
|
|
|
|
|
+ } elseif ($node->nodeName == 'res') {
|
|
|
|
|
+ echo "<h4>检验结果:</h4><pre>\n";
|
|
|
|
|
+ $this->_print_c($node);
|
|
|
|
|
+ echo "\n</pre>";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ echo '<', $node->nodeName;
|
|
|
|
|
+ foreach ($node->attributes as $attr) {
|
|
|
|
|
+ echo ' ', $attr->name, '="', htmlspecialchars($attr->value), '"';
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
-
|
|
|
|
|
- echo '<div id="', $accordion_collapse, '" class="card-collapse collapse">';
|
|
|
|
|
- echo '<div class="card-body">';
|
|
|
|
|
-
|
|
|
|
|
- echo '<div id="', $accordion_collapse_accordion, '">';
|
|
|
|
|
- $this->subtask_num = $subtask_num;
|
|
|
|
|
|
|
+ echo '>';
|
|
|
$this->_print_c($node);
|
|
$this->_print_c($node);
|
|
|
- $this->subtask_num = null;
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
|
|
+ echo '</', $node->nodeName, '>';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- } elseif ($node->nodeName == 'test') {
|
|
|
|
|
- $test_info = $node->getAttribute('info');
|
|
|
|
|
- $test_num = $node->getAttribute('num');
|
|
|
|
|
- $test_score = $node->getAttribute('score');
|
|
|
|
|
- $test_time = $node->getAttribute('time');
|
|
|
|
|
- $test_memory = $node->getAttribute('memory');
|
|
|
|
|
-
|
|
|
|
|
- echo '<div class="card ', $this->styler->getTestInfoClass($test_info), ' mb-3">';
|
|
|
|
|
-
|
|
|
|
|
- $accordion_parent = "{$this->name}_details_accordion";
|
|
|
|
|
- if ($this->subtask_num != null) {
|
|
|
|
|
- $accordion_parent .= "_collapse_subtask_{$this->subtask_num}_accordion";
|
|
|
|
|
|
|
+ private function getVerdictAbbr($info) {
|
|
|
|
|
+ $info = trim($info);
|
|
|
|
|
+ $map = array(
|
|
|
|
|
+ 'Accepted' => 'AC',
|
|
|
|
|
+ 'Extra Test Passed' => 'AC',
|
|
|
|
|
+ 'Success' => 'AC',
|
|
|
|
|
+ 'Acceptable Answer' => 'AC',
|
|
|
|
|
+ 'Wrong Answer' => 'WA',
|
|
|
|
|
+ 'Time Limit Exceeded' => 'TLE',
|
|
|
|
|
+ 'Memory Limit Exceeded' => 'MLE',
|
|
|
|
|
+ 'Runtime Error' => 'RE',
|
|
|
|
|
+ 'Output Limit Exceeded' => 'OLE',
|
|
|
|
|
+ 'Dangerous Syscalls' => 'DGS',
|
|
|
|
|
+ 'Judgement Failed' => 'JGF',
|
|
|
|
|
+ 'No Comment' => '?',
|
|
|
|
|
+ );
|
|
|
|
|
+ if ($info === '') {
|
|
|
|
|
+ return '?';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isset($map[$info])) {
|
|
|
|
|
+ return $map[$info];
|
|
|
|
|
+ }
|
|
|
|
|
+ $words = preg_split('/\s+/', $info, -1, PREG_SPLIT_NO_EMPTY);
|
|
|
|
|
+ if (count($words) > 1) {
|
|
|
|
|
+ $abbr = '';
|
|
|
|
|
+ foreach ($words as $word) {
|
|
|
|
|
+ $abbr .= strtoupper(substr($word, 0, 1));
|
|
|
|
|
+ if (strlen($abbr) >= 4) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- $accordion_collapse = "{$accordion_parent}_collapse_test_{$test_num}";
|
|
|
|
|
- if (!$this->styler->shouldFadeDetails($test_info)) {
|
|
|
|
|
- echo '<div class="card-header" data-toggle="collapse" data-parent="#', $accordion_parent, '" data-target="#', $accordion_collapse, '">';
|
|
|
|
|
- } else {
|
|
|
|
|
- echo '<div class="card-header">';
|
|
|
|
|
|
|
+ if ($abbr !== '') {
|
|
|
|
|
+ return substr($abbr, 0, 4);
|
|
|
}
|
|
}
|
|
|
- echo '<div class="row">';
|
|
|
|
|
- echo '<div class="col-sm-4">';
|
|
|
|
|
- if ($test_num > 0) {
|
|
|
|
|
- echo '<h4 class="card-title">', 'Test #', $test_num, ': ', '</h4>';
|
|
|
|
|
- } else {
|
|
|
|
|
- echo '<h4 class="card-title">', 'Extra Test:', '</h4>';
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ if (function_exists('mb_substr')) {
|
|
|
|
|
+ return strtoupper(mb_substr($info, 0, 4, 'UTF-8'));
|
|
|
|
|
+ }
|
|
|
|
|
+ return strtoupper(substr($info, 0, 4));
|
|
|
|
|
+ }
|
|
|
|
|
+ private function getVerdictClass($info) {
|
|
|
|
|
+ $info = trim($info);
|
|
|
|
|
+ if ($info == 'Accepted' || $info == 'Extra Test Passed' || $info == 'Success' || $info == 'Acceptable Answer') {
|
|
|
|
|
+ return 'uoj-test-rect-ac';
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($info == 'Wrong Answer') {
|
|
|
|
|
+ return 'uoj-test-rect-wa';
|
|
|
|
|
+ }
|
|
|
|
|
+ return 'uoj-test-rect-other';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function collectBlocks($node) {
|
|
|
|
|
+ foreach ($node->childNodes as $child) {
|
|
|
|
|
+ if ($child->nodeType != XML_ELEMENT_NODE) {
|
|
|
|
|
+ continue;
|
|
|
}
|
|
}
|
|
|
- echo '</div>';
|
|
|
|
|
-
|
|
|
|
|
- if ($this->styler->show_score) {
|
|
|
|
|
- echo '<div class="col-sm-4">';
|
|
|
|
|
- echo '得分:', $test_score;
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- echo '<div class="col-sm-4">';
|
|
|
|
|
- echo htmlspecialchars($test_info);
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
|
|
+ if ($child->nodeName == 'subtask') {
|
|
|
|
|
+ $this->flushPendingTests();
|
|
|
|
|
+ $this->blocks[] = array('type' => 'subtask', 'node' => $child);
|
|
|
|
|
+ } elseif ($child->nodeName == 'test' || $child->nodeName == 'custom-test') {
|
|
|
|
|
+ $this->pending_tests[] = $child;
|
|
|
|
|
+ } elseif ($child->nodeName == 'error') {
|
|
|
|
|
+ $this->flushPendingTests();
|
|
|
|
|
+ $this->blocks[] = array('type' => 'error', 'node' => $child);
|
|
|
} else {
|
|
} else {
|
|
|
- echo '<div class="col-sm-8">';
|
|
|
|
|
- echo htmlspecialchars($test_info);
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
|
|
+ $this->collectBlocks($child);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- echo '<div class="col-sm-4">';
|
|
|
|
|
- if ($test_time >= 0) {
|
|
|
|
|
- echo '时间:', $test_time, 'ms';
|
|
|
|
|
- }
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ private function flushPendingTests() {
|
|
|
|
|
+ if (count($this->pending_tests)) {
|
|
|
|
|
+ $this->blocks[] = array('type' => 'tests', 'nodes' => $this->pending_tests);
|
|
|
|
|
+ $this->pending_tests = array();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ private function collectRoot($node) {
|
|
|
|
|
+ if ($node->nodeName == 'subtask') {
|
|
|
|
|
+ $this->blocks[] = array('type' => 'subtask', 'node' => $node);
|
|
|
|
|
+ } elseif ($node->nodeName == 'test' || $node->nodeName == 'custom-test') {
|
|
|
|
|
+ $this->pending_tests[] = $node;
|
|
|
|
|
+ } elseif ($node->nodeName == 'error') {
|
|
|
|
|
+ $this->blocks[] = array('type' => 'error', 'node' => $node);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $this->collectBlocks($node);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- echo '<div class="col-sm-4">';
|
|
|
|
|
- if ($test_memory >= 0) {
|
|
|
|
|
- echo '内存:', $test_memory, 'kb';
|
|
|
|
|
- }
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
|
|
+ private function renderRect($node) {
|
|
|
|
|
+ $info = trim($node->getAttribute('info'));
|
|
|
|
|
+ $num = $node->getAttribute('num');
|
|
|
|
|
+ $time = $node->getAttribute('time');
|
|
|
|
|
+ $memory = $node->getAttribute('memory');
|
|
|
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
|
|
+ $this->rect_seq++;
|
|
|
|
|
|
|
|
- if (!$this->styler->shouldFadeDetails($test_info)) {
|
|
|
|
|
- $accordion_collapse_class = 'card-collapse collapse';
|
|
|
|
|
- if ($this->styler->collapse_in) {
|
|
|
|
|
- $accordion_collapse_class .= ' in';
|
|
|
|
|
- }
|
|
|
|
|
- echo '<div id="', $accordion_collapse, '" class="', $accordion_collapse_class, '">';
|
|
|
|
|
- echo '<div class="card-body">';
|
|
|
|
|
|
|
+ if ($num !== '' && is_numeric($num)) {
|
|
|
|
|
+ $num_text = ((int)$num > 0) ? ('#' . (int)$num) : 'Extra';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $num_text = '#' . $this->rect_seq;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- $this->_print_c($node);
|
|
|
|
|
|
|
+ // The verdict abbreviation is always the label. UOJ stores a per-test
|
|
|
|
|
+ // point value in score="..." (e.g. 10 on every test of an ACM problem),
|
|
|
|
|
+ // so it is NOT a partial-credit signal and must never replace the
|
|
|
|
|
+ // verdict (see the subtask header for the per-subtask score instead).
|
|
|
|
|
+ $label = $this->getVerdictAbbr($info);
|
|
|
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ $meta = array();
|
|
|
|
|
+ if ($time !== '' && is_numeric($time) && $time >= 0) {
|
|
|
|
|
+ $meta[] = $time . 'ms';
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($memory !== '' && is_numeric($memory) && $memory >= 0) {
|
|
|
|
|
+ $meta[] = $memory . 'kb';
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- } elseif ($node->nodeName == 'custom-test') {
|
|
|
|
|
- $test_info = $node->getAttribute('info');
|
|
|
|
|
- $test_time = $node->getAttribute('time');
|
|
|
|
|
- $test_memory = $node->getAttribute('memory');
|
|
|
|
|
|
|
+ $class = $this->getVerdictClass($info);
|
|
|
|
|
+ $title = (strlen($num_text) && $num_text[0] === '#') ? trim($num_text . ' ' . $info) : $info;
|
|
|
|
|
|
|
|
- echo '<div class="card ', $this->styler->getTestInfoClass($test_info), ' mb-3">';
|
|
|
|
|
-
|
|
|
|
|
- $accordion_parent = "{$this->name}_details_accordion";
|
|
|
|
|
- $accordion_collapse = "{$accordion_parent}_collapse_custom_test";
|
|
|
|
|
- if (!$this->styler->shouldFadeDetails($test_info)) {
|
|
|
|
|
- echo '<div class="card-header" data-toggle="collapse" data-parent="#', $accordion_parent, '" data-target="#', $accordion_collapse, '">';
|
|
|
|
|
- } else {
|
|
|
|
|
- echo '<div class="card-header">';
|
|
|
|
|
- }
|
|
|
|
|
- echo '<div class="row">';
|
|
|
|
|
- echo '<div class="col-sm-2">';
|
|
|
|
|
- echo '<h4 class="card-title">', '自定义测试:', '</h4>';
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
-
|
|
|
|
|
- echo '<div class="col-sm-4">';
|
|
|
|
|
- echo htmlspecialchars($test_info);
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
-
|
|
|
|
|
- echo '<div class="col-sm-3">';
|
|
|
|
|
- if ($test_time >= 0) {
|
|
|
|
|
- echo '时间:', $test_time, 'ms';
|
|
|
|
|
|
|
+ if ($this->styler->shouldFadeDetails($info)) {
|
|
|
|
|
+ echo '<div class="uoj-test-rect ', $class, ' is-disabled" title="', htmlspecialchars($title), '">';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $body_id = $this->prefix . '_body_' . (++$this->body_seq);
|
|
|
|
|
+ $this->bodies[$body_id] = $node;
|
|
|
|
|
+ $auto_attr = '';
|
|
|
|
|
+ if ($this->styler->collapse_in && $this->auto_select_id === null) {
|
|
|
|
|
+ $this->auto_select_id = $body_id;
|
|
|
|
|
+ $auto_attr = ' data-auto="1"';
|
|
|
}
|
|
}
|
|
|
- echo '</div>';
|
|
|
|
|
|
|
+ echo '<div class="uoj-test-rect ', $class, '" role="button" tabindex="0" data-target="', $body_id, '"', $auto_attr, ' title="', htmlspecialchars($title), '">';
|
|
|
|
|
+ }
|
|
|
|
|
+ echo '<span class="uoj-test-rect-index">', htmlspecialchars($num_text), '</span>';
|
|
|
|
|
+ echo '<span class="uoj-test-rect-verdict">', htmlspecialchars($label), '</span>';
|
|
|
|
|
+ foreach ($meta as $meta_part) {
|
|
|
|
|
+ echo '<span class="uoj-test-rect-meta">', htmlspecialchars($meta_part), '</span>';
|
|
|
|
|
+ }
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- echo '<div class="col-sm-3">';
|
|
|
|
|
- if ($test_memory >= 0) {
|
|
|
|
|
- echo '空间:', $test_memory, 'kb';
|
|
|
|
|
|
|
+ private function renderSubtask($node) {
|
|
|
|
|
+ $num = $node->getAttribute('num');
|
|
|
|
|
+ $score = $node->getAttribute('score');
|
|
|
|
|
+ $tests = array();
|
|
|
|
|
+ $sum = 0;
|
|
|
|
|
+ $has_test_score = false;
|
|
|
|
|
+ foreach ($node->childNodes as $child) {
|
|
|
|
|
+ if ($child->nodeType != XML_ELEMENT_NODE) {
|
|
|
|
|
+ continue;
|
|
|
}
|
|
}
|
|
|
- echo '</div>';
|
|
|
|
|
-
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
-
|
|
|
|
|
- if (!$this->styler->shouldFadeDetails($test_info)) {
|
|
|
|
|
- $accordion_collapse_class = 'card-collapse collapse';
|
|
|
|
|
- if ($this->styler->collapse_in) {
|
|
|
|
|
- $accordion_collapse_class .= ' in';
|
|
|
|
|
- }
|
|
|
|
|
- echo '<div id="', $accordion_collapse, '" class="', $accordion_collapse_class, '">';
|
|
|
|
|
- echo '<div class="card-body">';
|
|
|
|
|
-
|
|
|
|
|
- $this->_print_c($node);
|
|
|
|
|
-
|
|
|
|
|
- echo '</div>';
|
|
|
|
|
|
|
+ if ($child->nodeName != 'test' && $child->nodeName != 'custom-test') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $tests[] = $child;
|
|
|
|
|
+ $test_score = $child->getAttribute('score');
|
|
|
|
|
+ if ($test_score !== '' && is_numeric($test_score)) {
|
|
|
|
|
+ $sum += $test_score + 0;
|
|
|
|
|
+ $has_test_score = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ echo '<div class="uoj-test-group uoj-test-group-subtask">';
|
|
|
|
|
+ echo '<div class="uoj-test-subtask-head">Subtask #', htmlspecialchars($num);
|
|
|
|
|
+ if ($this->styler->show_score) {
|
|
|
|
|
+ if ($score !== '') {
|
|
|
|
|
+ echo ' · ', htmlspecialchars($score);
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($has_test_score) {
|
|
|
|
|
+ echo ' / ', htmlspecialchars(rtrim(rtrim(sprintf('%.2f', $sum), '0'), '.'));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ echo '<div class="uoj-test-rects">';
|
|
|
|
|
+ foreach ($tests as $test) {
|
|
|
|
|
+ $this->renderRect($test);
|
|
|
|
|
+ }
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function renderGrid() {
|
|
|
|
|
+ echo '<div class="uoj-details-wrap" id="', $this->prefix, '_wrap">';
|
|
|
|
|
+ // Only once per page: a two-phase submission (pretest + final test) runs
|
|
|
|
|
+ // this printer twice, and showing the same tip above both grids was
|
|
|
|
|
+ // confusing. static so it spans every instance in this request.
|
|
|
|
|
+ static $tip_shown = false;
|
|
|
|
|
+ if (!$tip_shown && $this->styler->show_small_tip) {
|
|
|
|
|
+ $tip_shown = true;
|
|
|
|
|
+ echo '<div class="text-right text-muted uoj-details-tip">', '小提示:点击方块,在弹出的窗口中查看该测试点的详细信息', '</div>';
|
|
|
|
|
+ } elseif (!$tip_shown && !empty($this->styler->ioi_contest_is_running)) {
|
|
|
|
|
+ $tip_shown = true;
|
|
|
|
|
+ echo '<div class="text-right text-muted uoj-details-tip">', 'IOI 赛制比赛中不支持显示详细信息', '</div>';
|
|
|
|
|
+ }
|
|
|
|
|
+ echo '<div class="uoj-test-grid">';
|
|
|
|
|
+ foreach ($this->blocks as $block) {
|
|
|
|
|
+ if ($block['type'] == 'error') {
|
|
|
|
|
+ echo '<div class="uoj-details-error">';
|
|
|
|
|
+ $this->_print($block['node']);
|
|
|
echo '</div>';
|
|
echo '</div>';
|
|
|
-
|
|
|
|
|
|
|
+ } elseif ($block['type'] == 'subtask') {
|
|
|
|
|
+ $this->renderSubtask($block['node']);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ echo '<div class="uoj-test-group">';
|
|
|
|
|
+ foreach ($block['nodes'] as $test) {
|
|
|
|
|
+ $this->renderRect($test);
|
|
|
|
|
+ }
|
|
|
echo '</div>';
|
|
echo '</div>';
|
|
|
}
|
|
}
|
|
|
- } elseif ($node->nodeName == 'in') {
|
|
|
|
|
- echo "<h4>标准输入:</h4><pre>\n";
|
|
|
|
|
- $this->_print_c($node);
|
|
|
|
|
- echo "\n</pre>";
|
|
|
|
|
- } elseif ($node->nodeName == 'out') {
|
|
|
|
|
- echo "<h4>标准输出:</h4><pre>\n";
|
|
|
|
|
- $this->_print_c($node);
|
|
|
|
|
- echo "\n</pre>";
|
|
|
|
|
- } elseif ($node->nodeName == 'res') {
|
|
|
|
|
- echo "<h4>检验结果:</h4><pre>\n";
|
|
|
|
|
- $this->_print_c($node);
|
|
|
|
|
- echo "\n</pre>";
|
|
|
|
|
- } else {
|
|
|
|
|
- echo '<', $node->nodeName;
|
|
|
|
|
- foreach ($node->attributes as $attr) {
|
|
|
|
|
- echo ' ', $attr->name, '="', htmlspecialchars($attr->value), '"';
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ if (count($this->bodies)) {
|
|
|
|
|
+ echo '<div class="uoj-modal" id="', $this->prefix, '_modal" hidden>';
|
|
|
|
|
+ echo '<div class="uoj-modal-backdrop" data-uoj-modal-close></div>';
|
|
|
|
|
+ echo '<div class="uoj-modal-window" role="dialog" aria-modal="true" aria-label="', '测试点详细信息', '">';
|
|
|
|
|
+ echo '<button type="button" class="uoj-modal-close" data-uoj-modal-close aria-label="', '关闭', '">×</button>';
|
|
|
|
|
+ echo '<div class="uoj-modal-body" id="', $this->prefix, '_modal_body">';
|
|
|
|
|
+ foreach ($this->bodies as $body_id => $test) {
|
|
|
|
|
+ echo '<div class="uoj-test-body" id="', $body_id, '" hidden>';
|
|
|
|
|
+ $this->_print_c($test);
|
|
|
|
|
+ echo '</div>';
|
|
|
}
|
|
}
|
|
|
- echo '>';
|
|
|
|
|
- $this->_print_c($node);
|
|
|
|
|
- echo '</', $node->nodeName, '>';
|
|
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ echo '</div>';
|
|
|
}
|
|
}
|
|
|
|
|
+ echo '</div>';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function renderScript() {
|
|
|
|
|
+ if (!count($this->bodies)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ echoUOJModalHelper();
|
|
|
|
|
+ $wrap = $this->prefix . '_wrap';
|
|
|
|
|
+ $modal = $this->prefix . '_modal';
|
|
|
|
|
+ echo '<script type="text/javascript">';
|
|
|
|
|
+ echo '(function(){';
|
|
|
|
|
+ echo 'var wrap=document.getElementById(', json_encode($wrap), ');';
|
|
|
|
|
+ echo 'var modal=document.getElementById(', json_encode($modal), ');';
|
|
|
|
|
+ echo 'if(!wrap||!modal||!window.uojModalInit){return;}';
|
|
|
|
|
+ echo 'var api=window.uojModalInit(modal);';
|
|
|
|
|
+ echo 'var body=api.body;';
|
|
|
|
|
+ echo 'var rects=wrap.querySelectorAll(".uoj-test-rect[data-target]");';
|
|
|
|
|
+ echo 'function show(rect){';
|
|
|
|
|
+ echo 'var target=document.getElementById(rect.getAttribute("data-target"));';
|
|
|
|
|
+ echo 'if(!target){return;}';
|
|
|
|
|
+ echo 'var bodies=body.querySelectorAll(".uoj-test-body");';
|
|
|
|
|
+ echo 'for(var i=0;i<bodies.length;i++){bodies[i].hidden=true;}';
|
|
|
|
|
+ echo 'target.hidden=false;';
|
|
|
|
|
+ echo 'for(var i=0;i<rects.length;i++){';
|
|
|
|
|
+ echo 'if(rects[i]===rect){rects[i].classList.add("is-selected");}else{rects[i].classList.remove("is-selected");}';
|
|
|
|
|
+ echo '}';
|
|
|
|
|
+ echo '}';
|
|
|
|
|
+ echo 'for(var i=0;i<rects.length;i++){(function(rect){';
|
|
|
|
|
+ echo 'rect.addEventListener("click",function(){show(rect);api.open();});';
|
|
|
|
|
+ echo 'rect.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();show(rect);api.open();}});';
|
|
|
|
|
+ echo '})(rects[i]);}';
|
|
|
|
|
+ echo 'var auto=wrap.querySelector(".uoj-test-rect[data-auto=\"1\"]");';
|
|
|
|
|
+ echo 'if(auto){show(auto);api.open();}';
|
|
|
|
|
+ echo '})();';
|
|
|
|
|
+ echo '</script>';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function __construct($details, $styler, $name) {
|
|
public function __construct($details, $styler, $name) {
|
|
@@ -945,7 +1130,16 @@ class JudgementDetailsPrinter {
|
|
|
}
|
|
}
|
|
|
public function printHTML() {
|
|
public function printHTML() {
|
|
|
$this->subtask_num = null;
|
|
$this->subtask_num = null;
|
|
|
- $this->_print($this->dom->documentElement);
|
|
|
|
|
|
|
+ $this->prefix = $this->name . '_' . (++self::$instance_seq);
|
|
|
|
|
+ $root = $this->dom->documentElement;
|
|
|
|
|
+ if ($root->nodeName == 'error') {
|
|
|
|
|
+ $this->_print($root);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ $this->collectRoot($root);
|
|
|
|
|
+ $this->flushPendingTests();
|
|
|
|
|
+ $this->renderGrid();
|
|
|
|
|
+ $this->renderScript();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|