Repository: kmyk/online-judge-tools
Pull request #628 (2019-12-03 13:26:43)

update a pattern to list samples in AtCoder
-   fix https://github.com/kmyk/online-judge-tools/issues/625
-   replace `h3+section>pre.prettyprint` with `h3+section>pre:first-child`

単に `.prettyprint` の指定を消すと https://github.com/kmyk/online-judge-tools/issues/625#issuecomment-560944228 の問題があります。なので `:first-child` の制約で置き換えました。

kawacchu: [https://atcoder.jp/contests/s8pc-4/tasks/s8pc_4_d](https://atcoder.jp/contests/s8pc-4/tasks/s8pc_4_d) の`SampleParseError`が発生しなくなりました。

#620 はHTMLが壊れていることから生じる`false-positive`への対処でした。
当該問題のHTMLをみたところ、今回のPRによって、
- HTMLが壊れていない箇所 -> `a old format`
- HTMLが壊れている箇所(`<section>タグ抜け`) -> `the standard format`

でパースされ、結果的にユーザに期待されたサンプルを収集しているように見受けられます。
手元で試していないので、確認お願いします。

kmyk: テストの修正をしました。なぜか偶然動いてるだけという状態なのでなんだかこわいですが、これをわざわざエラーにするのも面倒なのでとりあえずこのままにしておきます

Patch:
diff --git a/onlinejudge/service/atcoder.py b/onlinejudge/service/atcoder.py
index ce53444..e53c4ae 100644
--- a/onlinejudge/service/atcoder.py
+++ b/onlinejudge/service/atcoder.py
@@ -656,11 +656,12 @@ class AtCoderProblemDetailedData(AtCoderProblemData):
                 yield (pre, h3)
                 continue
 
-            # a old format: #task-statement h3+section>pre.prettyprint
+            # a old format: #task-statement h3+section>pre:first-child
             # partially supported by AtCoder's JavaScript
-            # NOTE: The relaxed format "#task-statement h3+section>pre" may cause false-positive.
+            # NOTE: The relaxed format "#task-statement h3+section>pre" may cause false-positive. e.g. https://atcoder.jp/contests/abc003/tasks/abc003_4
+            # NOTE: The format "h3+section>pre.prettyprint" sometimes cause false-negative. e.g. https://atcoder.jp/contests/tdpc/tasks/tdpc_fibonacci
             # example: https://atcoder.jp/contests/abc003/tasks/abc003_4
-            if 'prettyprint' in pre.attrs.get('class', []) and pre.parent.name == 'section':
+            if pre.find_previous_sibling() is None and pre.parent.name == 'section':
                 h3 = get_header(pre, tag=pre.parent.find_previous_sibling(), expected_tag_name='h3')
                 if h3:
                     yield (pre, h3)


Test patch:
diff --git a/tests/command_download_atcoder.py b/tests/command_download_atcoder.py
index 945720f..31f1de9 100644
--- a/tests/command_download_atcoder.py
+++ b/tests/command_download_atcoder.py
@@ -174,8 +174,3 @@ class DownloadAtCoderTest(unittest.TestCase):
     def test_call_download_413(self):
         # This task is not supported.
         self.snippet_call_download_raises(SampleParseError, 'https://chokudai001.contest.atcoder.jp/tasks/chokudai_001_a')
-
-    def test_call_download_atcoder_s8pc_4_d(self):
-        # HTML is broken and impossible to recognize samples
-        # see https://github.com/kmyk/online-judge-tools/issues/615
-        self.snippet_call_download_raises(SampleParseError, 'https://atcoder.jp/contests/s8pc-4/tasks/s8pc_4_d')
diff --git a/tests/service_atcoder.py b/tests/service_atcoder.py
index 70dd275..30edaed 100644
--- a/tests/service_atcoder.py
+++ b/tests/service_atcoder.py
@@ -477,6 +477,24 @@ class AtCoderProblemDataTest(unittest.TestCase):
         self.assertEqual(data.score, None)
         self.assertEqual(data.time_limit_msec, 5 * 1000)
 
+    def test_download_sample_cases_pre_without_prettyprint_insection(self):
+        # see: https://github.com/kmyk/online-judge-tools/issues/625
+        self.assertEqual(AtCoderProblem.from_url('https://atcoder.jp/contests/tdpc/tasks/tdpc_fibonacci').download_sample_cases(), [
+            TestCase(name='sample-1', input_name='Sample Input 1', input_data=b'2 10\n', output_name='Sample Output 1', output_data=b'55\n'),
+            TestCase(name='sample-2', input_name='Sample Input 2', input_data=b'3 10\n', output_name='Sample Output 2', output_data=b'105\n'),
+        ])
+
+    def test_download_sample_cases_s8pc_broken_html(self):
+        # see: https://github.com/kmyk/online-judge-tools/issues/615
+        self.assertEqual(AtCoderProblem.from_url('https://atcoder.jp/contests/s8pc-4/tasks/s8pc_4_d').download_sample_cases(), [
+            TestCase(name='sample-1', input_name='Sample Input 1', input_data=b'4\n1 2\n2 3\n2 4\n', output_name='Sample Output 1', output_data=b'2.0\n1.0\n2.0\n2.0\n'),
+            TestCase(name='sample-2', input_name='Sample Input 2', input_data=b'4\n1 2\n2 4\n4 3\n', output_name='Sample Output 2', output_data=b'3.0\n1.5\n3.0\n1.5\n'),
+            TestCase(name='sample-3', input_name='Sample Input 3', input_data=b'5\n1 2\n2 3\n3 4\n4 5\n', output_name='Sample Output 3', output_data=b'4.0\n2.0\n2.0\n2.0\n4.0\n'),
+            TestCase(name='sample-4', input_name='Sample Input 4', input_data=b'7\n1 2\n1 3\n2 4\n2 5\n3 6\n3 7\n', output_name='Sample Output 4', output_data=b'2.000000000000\n1.666666666667\n1.666666666667\n3.000000000000\n3.000000000000\n3.000000000000\n3.000000000000\n'),
+            TestCase(name='sample-5', input_name='Sample Input 5', input_data=b'12\n1 2\n2 3\n2 4\n4 5\n5 6\n5 7\n6 8\n8 9\n2 10\n10 11\n11 12\n', output_name='Sample Output 5', output_data=b'3.666666666667\n2.250000000000\n3.666666666667\n2.833333333333\n2.555555555556\n2.666666666667\n4.333333333333\n2.666666666667\n5.333333333333\n2.500000000000\n2.500000000000\n5.000000000000\n'),
+            TestCase(name='sample-6', input_name='Sample Input 6', input_data=b'2\n1 2\n', output_name='Sample Output 6', output_data=b'1.0\n1.0\n'),
+        ])
+
 
 class AtCoderProblemGetInputFormatTest(unittest.TestCase):
     def test_normal(self):