Skip to content

Commit 3a10bc6

Browse files
committed
Allow sequence of arrays for problem types
See: https://www.kattis.com/problem-package-format/spec/2025-09.html#type
1 parent a275414 commit 3a10bc6

3 files changed

Lines changed: 126 additions & 3 deletions

File tree

webapp/src/Entity/Problem.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,10 @@ public function setTypesAsString(array $types): Problem
331331
return $this;
332332
}
333333

334-
public function getTypesAsString(): string
334+
/**
335+
* @return list<string>
336+
*/
337+
public function getTypesAsStringArray(): array
335338
{
336339
$typeConstants = $this->getTypes();
337340
$typeStrings = [];
@@ -341,7 +344,12 @@ public function getTypesAsString(): string
341344
}
342345
$typeStrings[] = $this->typesToString[$type];
343346
}
344-
return implode(', ', $typeStrings);
347+
return $typeStrings;
348+
}
349+
350+
public function getTypesAsString(): string
351+
{
352+
return implode(', ', $this->getTypesAsStringArray());
345353
}
346354

347355
/**

webapp/src/Service/ImportProblemService.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,27 @@ public static function parseTestCaseGroupMeta(string $fileContent, string $name,
12051205
return $testcaseGroup;
12061206
}
12071207

1208+
/**
1209+
* Allow to import the types both as arrays and as strings. Due
1210+
* to the recursion to make parsing of the strings easier this now
1211+
* also works for arrays inside arrays.
1212+
*
1213+
* @param string|array<mixed, mixed> $input
1214+
* @return string[]
1215+
*/
1216+
private static function parseTypes(string|array $input): array
1217+
{
1218+
$final = [];
1219+
if (is_array($input)) {
1220+
foreach ($input as $possibleType) {
1221+
$final = array_merge($final, self::parseTypes($possibleType));
1222+
}
1223+
} else {
1224+
$final = array_merge($final, preg_split("/[\s,;]+/", $input));
1225+
}
1226+
return $final;
1227+
}
1228+
12081229
/**
12091230
* Returns true iff the yaml could be parsed correctly.
12101231
*
@@ -1241,7 +1262,7 @@ public static function parseYaml(bool|string $problemYaml, array &$messages, str
12411262

12421263
$validationMode = 'default';
12431264
if (isset($yamlData['type'])) {
1244-
$types = explode(' ', $yamlData['type']);
1265+
$types = self::parseTypes($yamlData['type']);
12451266
// Validation happens later when we set the properties.
12461267
$yamlProblemProperties['typesAsString'] = $types;
12471268
if (in_array('interactive', $types)) {

webapp/tests/Unit/Service/ImportProblemServiceTest.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,53 @@ public function testTypesYamlTest(): void
135135
}
136136
}
137137

138+
#[DataProvider('provideAlternativeTypeNotations')]
139+
public function testTypesStringWithAlternativeChars(string $separator): void
140+
{
141+
$expectedTypes = ['pass-fail', 'interactive'];
142+
$typesAsString = implode($separator, $expectedTypes);
143+
$specVersion = 'draft';
144+
$yaml = <<<YAML
145+
name: test
146+
problem_format_version: $specVersion
147+
type: $typesAsString
148+
YAML;
149+
150+
$messages = [];
151+
$validationMode = 'xxx';
152+
$problem = new Problem();
153+
154+
$ret = ImportProblemService::parseYaml($yaml, $messages, $validationMode, PropertyAccess::createPropertyAccessor(), $problem);
155+
$messageString = var_export($messages, true);
156+
$this->assertTrue($ret, 'Parsing failed for type: ' . $typesAsString . ', messages: ' . $messageString);
157+
$problemTypes = $problem->getTypesAsStringArray();
158+
$this->assertEquals(
159+
$expectedTypes, $problemTypes,
160+
'Found: "' . implode(' ', $problemTypes) . '" vs Expected: "' . implode(' ', $expectedTypes) . '"'
161+
);
162+
}
163+
164+
/**
165+
* @param string[] $expectedTypes
166+
*/
167+
#[DataProvider('provideAlternativeArrayNotations')]
168+
public function testTypesSequenceStrings(string $yaml, array $expectedTypes): void
169+
{
170+
$messages = [];
171+
$validationMode = 'xxx';
172+
$problem = new Problem();
173+
$typesAsString = implode(', ', $expectedTypes);
174+
175+
$ret = ImportProblemService::parseYaml($yaml, $messages, $validationMode, PropertyAccess::createPropertyAccessor(), $problem);
176+
$messageString = var_export($messages, true);
177+
$this->assertTrue($ret, 'Parsing failed for type: ' . $typesAsString . ', messages: ' . $messageString);
178+
$problemTypes = $problem->getTypesAsStringArray();
179+
$this->assertEquals(
180+
$expectedTypes, $problemTypes,
181+
'Found: "' . implode(' ', $problemTypes) . '" vs Expected: "' . implode(' ', $expectedTypes) . '"'
182+
);
183+
}
184+
138185
public function testUnknownProblemType(): void
139186
{
140187
$yaml = <<<YAML
@@ -796,4 +843,51 @@ public static function problemSpecVersionProvider(): Generator
796843
yield ['icpc-legacy'];
797844
yield ['2025-09'];
798845
}
846+
847+
848+
public static function provideAlternativeTypeNotations(): Generator
849+
{
850+
yield ["\t"];
851+
yield [", "];
852+
yield ["; "];
853+
}
854+
855+
public static function provideAlternativeArrayNotations(): Generator
856+
{
857+
$specVersion = 'draft';
858+
$yamlBasic = <<<YAML
859+
name: test
860+
problem_format_version: $specVersion
861+
YAML;
862+
$simpleArray = <<<YAML
863+
$yamlBasic
864+
type:
865+
- pass-fail
866+
YAML;
867+
$mappedArray = <<<YAML
868+
$yamlBasic
869+
type:
870+
pass-fail: pass-fail
871+
YAML;
872+
$oneLineArray = <<<YAML
873+
$yamlBasic
874+
type: [pass-fail]
875+
YAML;
876+
$malformedArray = <<<YAML
877+
$yamlBasic
878+
type: [pass-fail, [multi-pass, interactive]]
879+
YAML;
880+
$combinedStringArray = <<<YAML
881+
$yamlBasic
882+
type:
883+
- pass-fail
884+
- multi-pass, interactive
885+
YAML;
886+
887+
foreach ([$simpleArray, $mappedArray, $oneLineArray] as $yamlFile) {
888+
yield [$yamlFile, ['pass-fail']];
889+
}
890+
yield [$malformedArray, ['pass-fail', 'multi-pass', 'interactive']];
891+
yield [$combinedStringArray, ['pass-fail', 'multi-pass', 'interactive']];
892+
}
799893
}

0 commit comments

Comments
 (0)