Skip to content

Commit 203ab41

Browse files
committed
[PORT-157] Work on Markdown parser
1 parent ae7dc05 commit 203ab41

15 files changed

+422
-64
lines changed

api/composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
"require": {
77
"php": ">=8.1",
88
"ext-ctype": "*",
9+
"ext-dom": "*",
910
"ext-iconv": "*",
1011
"api-platform/core": "^3.0",
1112
"doctrine/annotations": "^1.0",
1213
"doctrine/doctrine-bundle": "^2.7",
1314
"doctrine/doctrine-migrations-bundle": "^3.2",
1415
"doctrine/orm": "^2.13",
16+
"erusev/parsedown": "^1.7",
1517
"nelmio/cors-bundle": "^2.2",
1618
"phpdocumentor/reflection-docblock": "^5.3",
1719
"phpstan/phpdoc-parser": "^1.8",
@@ -29,7 +31,8 @@
2931
"symfony/serializer": "6.1.*",
3032
"symfony/twig-bundle": "6.1.*",
3133
"symfony/validator": "6.1.*",
32-
"symfony/yaml": "6.1.*"
34+
"symfony/yaml": "6.1.*",
35+
"ext-libxml": "*"
3336
},
3437
"require-dev": {
3538
"doctrine/doctrine-fixtures-bundle": "^3.4",

api/composer.lock

Lines changed: 52 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/src/Markdown/FetcherInterface.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<?php
22
namespace App\Markdown;
33

4-
interface FetcherInterface {
4+
interface FetcherInterface
5+
{
6+
/**
7+
* @param string $source
8+
* @return string[]
9+
*/
510
public function fetch(string $source): array;
611
}
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
<?php
22
namespace App\Markdown;
33

4-
interface GeneratorInterface {
4+
interface GeneratorInterface
5+
{
6+
/**
7+
* @param string $source
8+
* @return array
9+
*/
510
public function generate(string $source): array;
11+
12+
/**
13+
* @param string[] $filePaths
14+
* @return array
15+
*/
616
public function process(array $filePaths): array;
717
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace App\Markdown\Model;
4+
5+
interface ModelInterface
6+
{
7+
public function getId(): int;
8+
public function getFilePath(): string;
9+
}

api/src/Markdown/Model/Question.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace App\Markdown\Model;
4+
5+
class Question implements ModelInterface
6+
{
7+
8+
public function __construct(
9+
private readonly int $id,
10+
private readonly string $filePath,
11+
private readonly array $content,
12+
private readonly array $possibleAnswers,
13+
private readonly array $correctAnswer)
14+
{
15+
}
16+
17+
/**
18+
* @return int
19+
*/
20+
public function getId(): int
21+
{
22+
return $this->id;
23+
}
24+
25+
/**
26+
* @return array
27+
*/
28+
public function getContent(): array
29+
{
30+
return $this->content;
31+
}
32+
33+
/**
34+
* @return array
35+
*/
36+
public function getPossibleAnswers(): array
37+
{
38+
return $this->possibleAnswers;
39+
}
40+
41+
/**
42+
* @return array
43+
*/
44+
public function getCorrectAnswer(): array
45+
{
46+
return $this->correctAnswer;
47+
}
48+
49+
/**
50+
* @return string
51+
*/
52+
public function getFilePath(): string
53+
{
54+
return $this->filePath;
55+
}
56+
57+
58+
}

api/src/Markdown/Model/Quiz.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace App\Markdown\Model;
4+
5+
class Quiz
6+
{
7+
8+
public function __construct(
9+
private readonly int $id,
10+
private readonly string $name,
11+
private readonly string $filePath
12+
)
13+
{
14+
}
15+
16+
/**
17+
* @return int
18+
*/
19+
public function getId(): int
20+
{
21+
return $this->id;
22+
}
23+
24+
/**
25+
* @return string
26+
*/
27+
public function getName(): string
28+
{
29+
return $this->name;
30+
}
31+
32+
/**
33+
* @return string
34+
*/
35+
public function getFilePath()
36+
{
37+
return $this->filePath;
38+
}
39+
40+
41+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
namespace App\Markdown\Parser;
4+
5+
use DOMDocument;
6+
use DOMNode;
7+
8+
class QuestionParser
9+
{
10+
private array $question = [];
11+
private array $possibleAnswers = [];
12+
private array $correctAnswer = [];
13+
14+
private bool $foundPossibleAnswers = false;
15+
private bool $foundCorrectAnswer = false;
16+
17+
public function __construct(private readonly string $document)
18+
{
19+
20+
}
21+
22+
/**
23+
* @return void
24+
*/
25+
public function extract(): void
26+
{
27+
$domDocument = new DOMDocument();
28+
libxml_use_internal_errors(TRUE);
29+
$domDocument->loadHTML($this->document);
30+
libxml_get_errors();
31+
32+
$this->process($domDocument);
33+
34+
}
35+
36+
public function process(DOMNode $domNode): void
37+
{
38+
foreach ($domNode->childNodes as $node) {
39+
if ($node->nodeName === '#text') {
40+
continue;
41+
}
42+
43+
if ($node->nodeName === 'h2' && $node->nodeValue === 'Possible answers') {
44+
$this->foundPossibleAnswers = true;
45+
}
46+
47+
if ($node->nodeName === 'details') {
48+
$this->foundCorrectAnswer = true;
49+
}
50+
51+
// Get the question
52+
if (!$this->foundPossibleAnswers && !$this->foundCorrectAnswer) {
53+
54+
if ($node->nodeName !== 'body' && $node->nodeName !== 'html') {
55+
$this->question[] = $node;
56+
}
57+
}
58+
59+
// Get the possible answers
60+
if ($this->foundPossibleAnswers && !$this->foundCorrectAnswer) {
61+
$this->possibleAnswers[] = $node;
62+
}
63+
64+
// Get the answer
65+
if ($this->foundCorrectAnswer) {
66+
$this->correctAnswer[] = $node;
67+
}
68+
69+
70+
if ($node->hasChildNodes()) {
71+
$this->process($node);
72+
}
73+
}
74+
}
75+
76+
public function getQuestionNodes(): array
77+
{
78+
return $this->question;
79+
}
80+
81+
public function getPossibleAnswerNodes(): array
82+
{
83+
return $this->possibleAnswers;
84+
}
85+
86+
public function getCorrectAnswerNodes(): array
87+
{
88+
return $this->correctAnswer;
89+
}
90+
}

api/src/Markdown/QuestionFetcher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class QuestionFetcher implements FetcherInterface
99
* Fetch all the question filenames from within a given quiz directory (source)
1010
*
1111
* @param string $source
12-
* @return array
12+
* @return string[]
1313
*/
1414
public function fetch(string $source): array
1515
{
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace App\Markdown;
4+
5+
use App\Markdown\Model\Question;
6+
7+
8+
class QuestionGenerator implements GeneratorInterface
9+
{
10+
public function __construct(private readonly FetcherInterface $fetcher)
11+
{
12+
}
13+
14+
/**
15+
* @param string $source
16+
* @return array{int, array{id: int, name: string, file_path: string}}
17+
*/
18+
public function generate(string $source): array
19+
{
20+
$filePaths = $this->fetcher->fetch($source);
21+
return $this->process($filePaths);
22+
}
23+
24+
/**
25+
* @param string[] $filePaths
26+
* @return array{int, array{id: int, name: string, file_path: string}}
27+
*/
28+
public function process(array $filePaths): array
29+
{
30+
$dataSets = [];
31+
foreach ($filePaths as $filePath) {
32+
$id = $this->generateIDFromFilePath($filePath);
33+
$content = [];
34+
$possibleAnswers = [];
35+
$correctAnswers = [];
36+
$question = new Question($id, $filePath, $content, $possibleAnswers, $correctAnswers);
37+
38+
$dataSets[] = $question;
39+
}
40+
return $dataSets;
41+
}
42+
}

0 commit comments

Comments
 (0)