Repository: kmyk/online-judge-tools
Pull request #724 (2020-04-14 01:33:36)

入出力の検出
https://atcoder.jp/contests/ttpc2015/tasks/ttpc2015_i
この問題のサンプルのダウンロードに失敗します。preとh3の間にpが存在するのが問題なので、find_previous_siblingにexpected_tag_nameを入れればよいと思います。get_headerでのチェックも不要となります。


Patch:
diff --git a/onlinejudge/service/atcoder.py b/onlinejudge/service/atcoder.py
index 6d90625..434a419 100644
--- a/onlinejudge/service/atcoder.py
+++ b/onlinejudge/service/atcoder.py
@@ -642,7 +642,10 @@ class AtCoderProblemDetailedData(AtCoderProblemData):
     def _find_sample_tags(cls, soup: bs4.BeautifulSoup) -> Iterator[Tuple[bs4.Tag, bs4.Tag]]:
         expected_strings = ('入力例', '出力例', 'Sample Input', 'Sample Output')
 
-        def get_header(tag, expected_tag_name):
+        def get_header(tag, expected_tag_name, ignore_tag_names):
+            assert expected_tag_name not in ignore_tag_names
+            while tag and tag.name in ignore_tag_names:
+                tag = tag.find_previous_sibling()
             if tag and tag.name == expected_tag_name and tag.string and any(s in tag.string for s in expected_strings):
                 return tag
             return None
@@ -650,36 +653,40 @@ class AtCoderProblemDetailedData(AtCoderProblemData):
         for pre in soup.find(id='task-statement').find_all('pre'):
             log.debug('pre tag: %s', str(pre))
 
-            # the standard format: #task-statement h3+pre
-            # used by AtCoder's JavaScript, sometimes used with .prettyprint
-            # example: https://atcoder.jp/contests/abc114/tasks/abc114_d
-            # NOTE: The AtCoder's JavaScript (at https://atcoder.jp/public/js/contest.js?v=201911110917 version) supports:
-            #     -   "#task-statement h3+pre" format for Copy buttons of <h3> and <pre> tags
-            #     -   "pre.prettyprint" format for Copy buttons of <pre> tags
-            h3 = get_header(tag=pre.find_previous_sibling(), expected_tag_name='h3')
-            if h3:
-                yield (pre, h3)
-                continue
+            # a very old format: #task-statement p+pre.literal-block
+            # between <p> and <pre class="literal-block">, ignore text and <br>
+            # entirely unsupported by AtCoder's JavaScript
+            # NOTE: h3+p+pre.literal-block can be regarded as the standard format, so FIRSTLY check a very old format.
+            # example: https://atcoder.jp/contests/utpc2011/tasks/utpc2011_1
+            if 'literal-block' in pre.attrs.get('class', []):
+                p = get_header(tag=pre.find_previous_sibling(), expected_tag_name='p', ignore_tag_names=('br',))
+                if p:
+                    yield (pre, p)
+                    continue
 
             # a old format: #task-statement h3+section>pre:first-child
+            # between <h3> and <section>, ignore text, <br> and <p>
             # partially supported by AtCoder's JavaScript
             # 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 pre.find_previous_sibling() is None and pre.parent.name == 'section':
-                h3 = get_header(tag=pre.parent.find_previous_sibling(), expected_tag_name='h3')
+                h3 = get_header(tag=pre.parent.find_previous_sibling(), expected_tag_name='h3', ignore_tag_names=('br', 'p'))
                 if h3:
                     yield (pre, h3)
                     continue
 
-            # a very old format: #task-statement p+pre.literal-block
-            # entirely unsupported by AtCoder's JavaScript
-            # example: https://atcoder.jp/contests/utpc2011/tasks/utpc2011_1
-            if 'literal-block' in pre.attrs.get('class', []):
-                p = get_header(tag=pre.find_previous_sibling(), expected_tag_name='p')
-                if p:
-                    yield (pre, p)
-                    continue
+            # the standard format: #task-statement h3+pre
+            # between <h3> and <pre>, ignore text, <br> and <p>
+            # used by AtCoder's JavaScript, sometimes used with .prettyprint
+            # example: https://atcoder.jp/contests/abc114/tasks/abc114_d
+            # NOTE: The AtCoder's JavaScript (at https://atcoder.jp/public/js/contest.js?v=201911110917 version) supports:
+            #     -   "#task-statement h3+pre" format for Copy buttons of <h3> and <pre> tags
+            #     -   "pre.prettyprint" format for Copy buttons of <pre> tags
+            h3 = get_header(tag=pre.find_previous_sibling(), expected_tag_name='h3', ignore_tag_names=('br', 'p'))
+            if h3:
+                yield (pre, h3)
+                continue
 
     @classmethod
     def _parse_sample_cases(cls, soup: bs4.BeautifulSoup) -> List[onlinejudge.type.TestCase]:


Test patch:
diff --git a/tests/service_atcoder.py b/tests/service_atcoder.py
index 30edaed..66470ca 100644
--- a/tests/service_atcoder.py
+++ b/tests/service_atcoder.py
@@ -495,6 +495,11 @@ class AtCoderProblemDataTest(unittest.TestCase):
             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'),
         ])
 
+    def test_download_sample_cases_ttpc2015_inserted_p_tag(self):
+        self.assertEqual(AtCoderProblem.from_url('https://atcoder.jp/contests/ttpc2015/tasks/ttpc2015_i').download_sample_cases(), [
+            TestCase(name='sample-1', input_name='入力例1', input_data=b'5\n4 2 3 1 5\n', output_name='出力例1', output_data=b'3\n2 4\n1 2\n2 4\n'),
+        ])
+
 
 class AtCoderProblemGetInputFormatTest(unittest.TestCase):
     def test_normal(self):