A Python parser for MediaWiki wikicode https://mwparserfromhell.readthedocs.io/
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 

644 rindas
23 KiB

  1. # Copyright (C) 2012-2025 Ben Kurtovic <ben.kurtovic@gmail.com>
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a copy
  4. # of this software and associated documentation files (the "Software"), to deal
  5. # in the Software without restriction, including without limitation the rights
  6. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. # copies of the Software, and to permit persons to whom the Software is
  8. # furnished to do so, subject to the following conditions:
  9. #
  10. # The above copyright notice and this permission notice shall be included in
  11. # all copies or substantial portions of the Software.
  12. #
  13. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19. # SOFTWARE.
  20. """
  21. Tests for the Wikicode class, which manages a list of nodes.
  22. """
  23. from __future__ import annotations
  24. import pickle
  25. import re
  26. from functools import partial
  27. from types import GeneratorType
  28. from typing import cast
  29. import pytest
  30. from mwparserfromhell import parse
  31. from mwparserfromhell.nodes import Argument, Heading, Template, Text
  32. from mwparserfromhell.smart_list import SmartList
  33. from mwparserfromhell.wikicode import Wikicode
  34. from .conftest import wrap, wraptext
  35. def test_str():
  36. """test Wikicode.__str__()"""
  37. code1 = parse("foobar")
  38. code2 = parse("Have a {{template}} and a [[page|link]]")
  39. assert "foobar" == str(code1)
  40. assert "Have a {{template}} and a [[page|link]]" == str(code2)
  41. def test_nodes():
  42. """test getter/setter for the nodes attribute"""
  43. code = parse("Have a {{template}}")
  44. assert ["Have a ", "{{template}}"] == code.nodes
  45. L1 = SmartList([Text("foobar"), Template(wraptext("abc"))])
  46. L2 = [Text("barfoo"), Template(wraptext("cba"))]
  47. L3 = "abc{{def}}"
  48. code.nodes = L1
  49. assert L1 is code.nodes
  50. code.nodes = L2
  51. assert L2 is code.nodes
  52. code.nodes = L3
  53. assert ["abc", "{{def}}"] == code.nodes
  54. with pytest.raises(ValueError):
  55. code.__setattr__("nodes", object)
  56. @pytest.mark.parametrize("protocol", range(pickle.HIGHEST_PROTOCOL + 1))
  57. def test_pickling(protocol: int):
  58. """test Wikicode objects can be pickled"""
  59. code = parse("Have a {{template}} and a [[page|link]]")
  60. enc = pickle.dumps(code, protocol=protocol)
  61. assert pickle.loads(enc) == code
  62. def test_get():
  63. """test Wikicode.get()"""
  64. code = parse("Have a {{template}} and a [[page|link]]")
  65. assert code.nodes[0] is code.get(0)
  66. assert code.nodes[2] is code.get(2)
  67. with pytest.raises(IndexError):
  68. code.get(4)
  69. def test_set():
  70. """test Wikicode.set()"""
  71. code = parse("Have a {{template}} and a [[page|link]]")
  72. code.set(1, "{{{argument}}}")
  73. assert "Have a {{{argument}}} and a [[page|link]]" == code
  74. assert isinstance(code.get(1), Argument)
  75. code.set(2, None)
  76. assert "Have a {{{argument}}}[[page|link]]" == code
  77. code.set(-3, "This is an ")
  78. assert "This is an {{{argument}}}[[page|link]]" == code
  79. with pytest.raises(ValueError):
  80. code.set(1, "foo {{bar}}")
  81. with pytest.raises(IndexError):
  82. code.set(3, "{{baz}}")
  83. with pytest.raises(IndexError):
  84. code.set(-4, "{{baz}}")
  85. def test_contains():
  86. """test Wikicode.contains()"""
  87. code = parse("Here is {{aaa|{{bbb|xyz{{ccc}}}}}} and a [[page|link]]")
  88. tmpl1, tmpl2, tmpl3 = code.filter_templates()
  89. tmpl4 = parse("{{ccc}}").filter_templates()[0]
  90. assert code.contains(tmpl1) is True
  91. assert code.contains(tmpl3) is True
  92. assert code.contains(tmpl4) is False
  93. assert code.contains(str(tmpl4)) is True
  94. assert code.contains(tmpl2.params[0].value) is True
  95. def test_index():
  96. """test Wikicode.index()"""
  97. code = parse("Have a {{template}} and a [[page|link]]")
  98. assert 0 == code.index("Have a ")
  99. assert 3 == code.index("[[page|link]]")
  100. assert 1 == code.index(code.get(1))
  101. with pytest.raises(ValueError):
  102. code.index("foo")
  103. code = parse("{{foo}}{{bar|{{baz}}}}")
  104. assert 1 == code.index("{{bar|{{baz}}}}")
  105. assert 1 == code.index("{{baz}}", recursive=True)
  106. assert 1 == code.index(cast(Template, code.get(1)).get(1).value, recursive=True)
  107. with pytest.raises(ValueError):
  108. code.index("{{baz}}", recursive=False)
  109. with pytest.raises(ValueError):
  110. code.index(cast(Template, code.get(1)).get(1).value, recursive=False)
  111. def test_get_ancestors_parent():
  112. """test Wikicode.get_ancestors() and Wikicode.get_parent()"""
  113. code = parse("{{a|{{b|{{d|{{e}}{{f}}}}{{g}}}}}}{{c}}")
  114. tmpl = code.filter_templates(matches=lambda n: n.name == "f")[0]
  115. parent1 = code.filter_templates(matches=lambda n: n.name == "d")[0]
  116. parent2 = code.filter_templates(matches=lambda n: n.name == "b")[0]
  117. parent3 = code.filter_templates(matches=lambda n: n.name == "a")[0]
  118. fake = parse("{{f}}").get(0)
  119. assert [parent3, parent2, parent1] == code.get_ancestors(tmpl)
  120. assert parent1 is code.get_parent(tmpl)
  121. assert [] == code.get_ancestors(parent3)
  122. assert None is code.get_parent(parent3)
  123. with pytest.raises(ValueError):
  124. code.get_ancestors(fake)
  125. with pytest.raises(ValueError):
  126. code.get_parent(fake)
  127. def test_insert():
  128. """test Wikicode.insert()"""
  129. code = parse("Have a {{template}} and a [[page|link]]")
  130. code.insert(1, "{{{argument}}}")
  131. assert "Have a {{{argument}}}{{template}} and a [[page|link]]" == code
  132. assert isinstance(code.get(1), Argument)
  133. code.insert(2, None)
  134. assert "Have a {{{argument}}}{{template}} and a [[page|link]]" == code
  135. code.insert(-3, Text("foo"))
  136. assert "Have a {{{argument}}}foo{{template}} and a [[page|link]]" == code
  137. code2 = parse("{{foo}}{{bar}}{{baz}}")
  138. code2.insert(1, "abc{{def}}ghi[[jk]]")
  139. assert "{{foo}}abc{{def}}ghi[[jk]]{{bar}}{{baz}}" == code2
  140. assert [
  141. "{{foo}}",
  142. "abc",
  143. "{{def}}",
  144. "ghi",
  145. "[[jk]]",
  146. "{{bar}}",
  147. "{{baz}}",
  148. ] == code2.nodes
  149. code3 = parse("{{foo}}bar")
  150. code3.insert(1000, "[[baz]]")
  151. code3.insert(-1000, "derp")
  152. assert "derp{{foo}}bar[[baz]]" == code3
  153. def _test_search(meth, expected):
  154. """Base test for insert_before(), insert_after(), and replace()."""
  155. code = parse("{{a}}{{b}}{{c}}{{d}}{{e}}")
  156. func = partial(meth, code)
  157. func("{{b}}", "x", recursive=True)
  158. func("{{d}}", "[[y]]", recursive=False)
  159. func(code.get(2), "z")
  160. assert expected[0] == code
  161. with pytest.raises(ValueError):
  162. func("{{r}}", "n", recursive=True)
  163. with pytest.raises(ValueError):
  164. func("{{r}}", "n", recursive=False)
  165. fake = parse("{{a}}").get(0)
  166. with pytest.raises(ValueError):
  167. func(fake, "n", recursive=True)
  168. with pytest.raises(ValueError):
  169. func(fake, "n", recursive=False)
  170. code2 = parse("{{a}}{{a}}{{a}}{{b}}{{b}}{{b}}")
  171. func = partial(meth, code2)
  172. func(code2.get(1), "c", recursive=False)
  173. func("{{a}}", "d", recursive=False)
  174. func(code2.get(-1), "e", recursive=True)
  175. func("{{b}}", "f", recursive=True)
  176. assert expected[1] == code2
  177. code3 = parse("{{a|{{b}}|{{c|d={{f}}}}}}")
  178. func = partial(meth, code3)
  179. obj = cast(Template, code3.get(0)).params[0].value.get(0)
  180. with pytest.raises(ValueError):
  181. func(obj, "x", recursive=False)
  182. func(obj, "x", recursive=True)
  183. with pytest.raises(ValueError):
  184. func("{{f}}", "y", recursive=False)
  185. func("{{f}}", "y", recursive=True)
  186. assert expected[2] == code3
  187. code4 = parse("{{a}}{{b}}{{c}}{{d}}{{e}}{{f}}{{g}}{{h}}{{i}}{{j}}")
  188. func = partial(meth, code4)
  189. fake = parse("{{b}}{{c}}")
  190. with pytest.raises(ValueError):
  191. func(fake, "q", recursive=False)
  192. with pytest.raises(ValueError):
  193. func(fake, "q", recursive=True)
  194. func("{{b}}{{c}}", "w", recursive=False)
  195. func("{{d}}{{e}}", "x", recursive=True)
  196. func(Wikicode(code4.nodes[-2:]), "y", recursive=False)
  197. func(Wikicode(code4.nodes[-2:]), "z", recursive=True)
  198. assert expected[3] == code4
  199. with pytest.raises(ValueError):
  200. func("{{c}}{{d}}", "q", recursive=False)
  201. with pytest.raises(ValueError):
  202. func("{{c}}{{d}}", "q", recursive=True)
  203. code5 = parse("{{a|{{b}}{{c}}|{{f|{{g}}={{h}}{{i}}}}}}")
  204. func = partial(meth, code5)
  205. with pytest.raises(ValueError):
  206. func("{{b}}{{c}}", "x", recursive=False)
  207. func("{{b}}{{c}}", "x", recursive=True)
  208. obj = (
  209. cast(Template, cast(Template, code5.get(0)).params[1].value.get(0))
  210. .params[0]
  211. .value
  212. )
  213. with pytest.raises(ValueError):
  214. func(obj, "y", recursive=False)
  215. func(obj, "y", recursive=True)
  216. assert expected[4] == code5
  217. code6 = parse("here is {{some text and a {{template}}}}")
  218. func = partial(meth, code6)
  219. with pytest.raises(ValueError):
  220. func("text and", "ab", recursive=False)
  221. func("text and", "ab", recursive=True)
  222. with pytest.raises(ValueError):
  223. func("is {{some", "cd", recursive=False)
  224. func("is {{some", "cd", recursive=True)
  225. assert expected[5] == code6
  226. code7 = parse("{{foo}}{{bar}}{{baz}}{{foo}}{{baz}}")
  227. func = partial(meth, code7)
  228. obj = wrap([code7.get(0), code7.get(2)])
  229. with pytest.raises(ValueError):
  230. func(obj, "{{lol}}")
  231. func("{{foo}}{{baz}}", "{{lol}}")
  232. assert expected[6] == code7
  233. code8 = parse("== header ==")
  234. func = partial(meth, code8)
  235. sec1, sec2 = code8.get_sections(include_headings=False)
  236. func(sec1, "lead\n")
  237. func(sec2, "\nbody")
  238. assert expected[7] == code8
  239. code9 = parse("{{foo}}")
  240. meth(code9.get_sections()[0], code9.get_sections()[0], "{{bar}}")
  241. meth(code9.get_sections()[0], code9, "{{baz}}")
  242. meth(code9, code9, "{{qux}}")
  243. meth(code9, code9.get_sections()[0], "{{quz}}")
  244. assert expected[8] == code9
  245. def test_insert_before():
  246. """test Wikicode.insert_before()"""
  247. def meth(code, *args, **kw):
  248. return code.insert_before(*args, **kw)
  249. expected = [
  250. "{{a}}xz{{b}}{{c}}[[y]]{{d}}{{e}}",
  251. "d{{a}}cd{{a}}d{{a}}f{{b}}f{{b}}ef{{b}}",
  252. "{{a|x{{b}}|{{c|d=y{{f}}}}}}",
  253. "{{a}}w{{b}}{{c}}x{{d}}{{e}}{{f}}{{g}}{{h}}yz{{i}}{{j}}",
  254. "{{a|x{{b}}{{c}}|{{f|{{g}}=y{{h}}{{i}}}}}}",
  255. "here cdis {{some abtext and a {{template}}}}",
  256. "{{foo}}{{bar}}{{baz}}{{lol}}{{foo}}{{baz}}",
  257. "lead\n== header ==\nbody",
  258. "{{quz}}{{qux}}{{baz}}{{bar}}{{foo}}",
  259. ]
  260. _test_search(meth, expected)
  261. def test_insert_after():
  262. """test Wikicode.insert_after()"""
  263. def meth(code, *args, **kw):
  264. return code.insert_after(*args, **kw)
  265. expected = [
  266. "{{a}}{{b}}xz{{c}}{{d}}[[y]]{{e}}",
  267. "{{a}}d{{a}}dc{{a}}d{{b}}f{{b}}f{{b}}fe",
  268. "{{a|{{b}}x|{{c|d={{f}}y}}}}",
  269. "{{a}}{{b}}{{c}}w{{d}}{{e}}x{{f}}{{g}}{{h}}{{i}}{{j}}yz",
  270. "{{a|{{b}}{{c}}x|{{f|{{g}}={{h}}{{i}}y}}}}",
  271. "here is {{somecd text andab a {{template}}}}",
  272. "{{foo}}{{bar}}{{baz}}{{foo}}{{baz}}{{lol}}",
  273. "lead\n== header ==\nbody",
  274. "{{foo}}{{bar}}{{baz}}{{qux}}{{quz}}",
  275. ]
  276. _test_search(meth, expected)
  277. def test_replace():
  278. """test Wikicode.replace()"""
  279. def meth(code, *args, **kw):
  280. return code.replace(*args, **kw)
  281. expected = [
  282. "{{a}}xz[[y]]{{e}}",
  283. "dcdffe",
  284. "{{a|x|{{c|d=y}}}}",
  285. "{{a}}wx{{f}}{{g}}z",
  286. "{{a|x|{{f|{{g}}=y}}}}",
  287. "here cd ab a {{template}}}}",
  288. "{{foo}}{{bar}}{{baz}}{{lol}}",
  289. "lead\n== header ==\nbody",
  290. "{{quz}}",
  291. ]
  292. _test_search(meth, expected)
  293. def test_append():
  294. """test Wikicode.append()"""
  295. code = parse("Have a {{template}}")
  296. code.append("{{{argument}}}")
  297. assert "Have a {{template}}{{{argument}}}" == code
  298. assert isinstance(code.get(2), Argument)
  299. code.append(None)
  300. assert "Have a {{template}}{{{argument}}}" == code
  301. code.append(Text(" foo"))
  302. assert "Have a {{template}}{{{argument}}} foo" == code
  303. with pytest.raises(ValueError):
  304. code.append(slice(0, 1))
  305. def test_remove():
  306. """test Wikicode.remove()"""
  307. def meth(code, obj, value, **kw):
  308. return code.remove(obj, **kw)
  309. expected = [
  310. "{{a}}{{c}}",
  311. "",
  312. "{{a||{{c|d=}}}}",
  313. "{{a}}{{f}}",
  314. "{{a||{{f|{{g}}=}}}}",
  315. "here a {{template}}}}",
  316. "{{foo}}{{bar}}{{baz}}",
  317. "== header ==",
  318. "",
  319. ]
  320. _test_search(meth, expected)
  321. def test_matches():
  322. """test Wikicode.matches()"""
  323. code1 = parse("Cleanup")
  324. code2 = parse("\nstub<!-- TODO: make more specific -->")
  325. code3 = parse("Hello world!")
  326. code4 = parse("World,_hello?")
  327. code5 = parse("")
  328. assert code1.matches("Cleanup") is True
  329. assert code1.matches("cleanup") is True
  330. assert code1.matches(" cleanup\n") is True
  331. assert code1.matches("CLEANup") is False
  332. assert code1.matches("Blah") is False
  333. assert code2.matches("stub") is True
  334. assert code2.matches("Stub<!-- no, it's fine! -->") is True
  335. assert code2.matches("StuB") is False
  336. assert code1.matches(("cleanup", "stub")) is True
  337. assert code2.matches(("cleanup", "stub")) is True
  338. assert code2.matches(("StuB", "sTUb", "foobar")) is False
  339. assert code2.matches(["StuB", "sTUb", "foobar"]) is False
  340. assert code2.matches(("StuB", "sTUb", "foo", "bar", "Stub")) is True
  341. assert code2.matches(["StuB", "sTUb", "foo", "bar", "Stub"]) is True
  342. assert code3.matches("hello world!") is True
  343. assert code3.matches("hello_world!") is True
  344. assert code3.matches("hello__world!") is False
  345. assert code4.matches("World,_hello?") is True
  346. assert code4.matches("World, hello?") is True
  347. assert code4.matches("World, hello?") is False
  348. assert code5.matches("") is True
  349. assert code5.matches("<!-- nothing -->") is True
  350. assert code5.matches(("a", "b", "")) is True
  351. def test_filter_family():
  352. """test the Wikicode.i?filter() family of functions"""
  353. def genlist(gen):
  354. assert isinstance(gen, GeneratorType)
  355. return list(gen)
  356. def ifilter(code):
  357. return lambda *a, **k: genlist(code.ifilter(*a, **k))
  358. code = parse("a{{b}}c[[d]]{{{e}}}{{f}}[[g]]")
  359. for func in (code.filter, ifilter(code)):
  360. assert [
  361. "a",
  362. "{{b}}",
  363. "b",
  364. "c",
  365. "[[d]]",
  366. "d",
  367. "{{{e}}}",
  368. "e",
  369. "{{f}}",
  370. "f",
  371. "[[g]]",
  372. "g",
  373. ] == func()
  374. assert ["{{{e}}}"] == func(forcetype=Argument)
  375. assert code.get(4) is func(forcetype=Argument)[0]
  376. assert list("abcdefg") == func(forcetype=Text)
  377. assert [] == func(forcetype=Heading)
  378. with pytest.raises(TypeError):
  379. func(forcetype=True) # type: ignore
  380. funcs = [
  381. lambda name, **kw: getattr(code, "filter_" + name)(**kw),
  382. lambda name, **kw: genlist(getattr(code, "ifilter_" + name)(**kw)),
  383. ]
  384. for get_filter in funcs:
  385. assert ["{{{e}}}"] == get_filter("arguments")
  386. assert code.get(4) is get_filter("arguments")[0]
  387. assert [] == get_filter("comments")
  388. assert [] == get_filter("external_links")
  389. assert [] == get_filter("headings")
  390. assert [] == get_filter("html_entities")
  391. assert [] == get_filter("tags")
  392. assert ["{{b}}", "{{f}}"] == get_filter("templates")
  393. assert list("abcdefg") == get_filter("text")
  394. assert ["[[d]]", "[[g]]"] == get_filter("wikilinks")
  395. code2 = parse("{{a|{{b}}|{{c|d={{f}}{{h}}}}}}")
  396. for func in (code2.filter, ifilter(code2)):
  397. assert ["{{a|{{b}}|{{c|d={{f}}{{h}}}}}}"] == func(
  398. recursive=False, forcetype=Template
  399. )
  400. assert [
  401. "{{a|{{b}}|{{c|d={{f}}{{h}}}}}}",
  402. "{{b}}",
  403. "{{c|d={{f}}{{h}}}}",
  404. "{{f}}",
  405. "{{h}}",
  406. ] == func(recursive=True, forcetype=Template)
  407. code3 = parse("{{foobar}}{{FOO}}{{baz}}{{bz}}{{barfoo}}")
  408. for func in (code3.filter, ifilter(code3)):
  409. assert ["{{foobar}}", "{{barfoo}}"] == func(
  410. False, matches=lambda node: "foo" in node
  411. )
  412. assert ["{{foobar}}", "{{FOO}}", "{{barfoo}}"] == func(False, matches=r"foo")
  413. assert ["{{foobar}}", "{{FOO}}"] == func(matches=r"^{{foo.*?}}")
  414. assert ["{{foobar}}"] == func(matches=r"^{{foo.*?}}", flags=re.UNICODE)
  415. assert ["{{baz}}", "{{bz}}"] == func(matches=r"^{{b.*?z")
  416. assert ["{{baz}}"] == func(matches=r"^{{b.+?z}}")
  417. exp_rec = [
  418. "{{a|{{b}}|{{c|d={{f}}{{h}}}}}}",
  419. "{{b}}",
  420. "{{c|d={{f}}{{h}}}}",
  421. "{{f}}",
  422. "{{h}}",
  423. ]
  424. exp_unrec = ["{{a|{{b}}|{{c|d={{f}}{{h}}}}}}"]
  425. assert exp_rec == code2.filter_templates()
  426. assert exp_unrec == code2.filter_templates(recursive=False)
  427. assert exp_rec == code2.filter_templates(recursive=True)
  428. assert exp_rec == code2.filter_templates(True)
  429. assert exp_unrec == code2.filter_templates(False)
  430. assert ["{{foobar}}"] == code3.filter_templates(
  431. matches=lambda node: node.name.matches("Foobar")
  432. )
  433. assert ["{{baz}}", "{{bz}}"] == code3.filter_templates(matches=r"^{{b.*?z")
  434. assert [] == code3.filter_tags(matches=r"^{{b.*?z")
  435. assert [] == code3.filter_tags(matches=r"^{{b.*?z", flags=0)
  436. with pytest.raises(TypeError):
  437. code.filter_templates(a=42) # type: ignore
  438. with pytest.raises(TypeError):
  439. code.filter_templates(forcetype=Template) # type: ignore
  440. with pytest.raises(TypeError):
  441. code.filter_templates(1, 0, 0, Template) # type: ignore
  442. code4 = parse("{{foo}}<b>{{foo|{{bar}}}}</b>")
  443. actual1 = code4.filter_templates(recursive=code4.RECURSE_OTHERS)
  444. actual2 = code4.filter_templates(code4.RECURSE_OTHERS)
  445. assert ["{{foo}}", "{{foo|{{bar}}}}"] == actual1
  446. assert ["{{foo}}", "{{foo|{{bar}}}}"] == actual2
  447. def test_get_sections():
  448. """test Wikicode.get_sections()"""
  449. page1 = parse("")
  450. page2 = parse("==Heading==")
  451. page3 = parse("===Heading===\nFoo bar baz\n====Gnidaeh====\n")
  452. p4_lead = "This is a lead.\n"
  453. p4_IA = "=== Section I.A ===\nSection I.A [[body]].\n"
  454. p4_IB1 = "==== Section I.B.1 ====\nSection I.B.1 body.\n\n&bull;Some content.\n\n"
  455. p4_IB = "=== Section I.B ===\n" + p4_IB1
  456. p4_I = "== Section I ==\nSection I body. {{and a|template}}\n" + p4_IA + p4_IB
  457. p4_II = "== Section II ==\nSection II body.\n\n"
  458. p4_IIIA1a = "===== Section III.A.1.a =====\nMore text.\n"
  459. p4_IIIA2ai1 = "======= Section III.A.2.a.i.1 =======\nAn invalid section!"
  460. p4_IIIA2 = "==== Section III.A.2 ====\nEven more text.\n" + p4_IIIA2ai1
  461. p4_IIIA = "=== Section III.A ===\nText.\n" + p4_IIIA1a + p4_IIIA2
  462. p4_III = "== Section III ==\n" + p4_IIIA
  463. page4 = parse(p4_lead + p4_I + p4_II + p4_III)
  464. assert [""] == page1.get_sections()
  465. assert ["", "==Heading=="] == page2.get_sections()
  466. assert [
  467. "",
  468. "===Heading===\nFoo bar baz\n====Gnidaeh====\n",
  469. "====Gnidaeh====\n",
  470. ] == page3.get_sections()
  471. assert [
  472. p4_lead,
  473. p4_I,
  474. p4_IA,
  475. p4_IB,
  476. p4_IB1,
  477. p4_II,
  478. p4_III,
  479. p4_IIIA,
  480. p4_IIIA1a,
  481. p4_IIIA2,
  482. p4_IIIA2ai1,
  483. ] == page4.get_sections()
  484. assert ["====Gnidaeh====\n"] == page3.get_sections(levels=[4])
  485. assert ["===Heading===\nFoo bar baz\n====Gnidaeh====\n"] == page3.get_sections(
  486. levels=(2, 3)
  487. )
  488. assert ["===Heading===\nFoo bar baz\n"] == page3.get_sections(
  489. levels=(2, 3), flat=True
  490. )
  491. assert [] == page3.get_sections(levels=[0])
  492. assert ["", "====Gnidaeh====\n"] == page3.get_sections(
  493. levels=[4], include_lead=True
  494. )
  495. assert [
  496. "===Heading===\nFoo bar baz\n====Gnidaeh====\n",
  497. "====Gnidaeh====\n",
  498. ] == page3.get_sections(include_lead=False)
  499. assert ["===Heading===\nFoo bar baz\n", "====Gnidaeh====\n"] == page3.get_sections(
  500. flat=True, include_lead=False
  501. )
  502. assert [p4_IB1, p4_IIIA2] == page4.get_sections(levels=[4])
  503. assert [p4_IA, p4_IB, p4_IIIA] == page4.get_sections(levels=[3])
  504. assert [
  505. p4_IA,
  506. "=== Section I.B ===\n",
  507. "=== Section III.A ===\nText.\n",
  508. ] == page4.get_sections(levels=[3], flat=True)
  509. assert ["", ""] == page2.get_sections(include_headings=False)
  510. assert [
  511. "\nSection I.B.1 body.\n\n&bull;Some content.\n\n",
  512. "\nEven more text.\n" + p4_IIIA2ai1,
  513. ] == page4.get_sections(levels=[4], include_headings=False)
  514. assert [] == page4.get_sections(matches=r"body")
  515. assert [p4_I, p4_IA, p4_IB, p4_IB1] == page4.get_sections(
  516. matches=r"Section\sI[.\s].*?"
  517. )
  518. assert [p4_IA, p4_IIIA, p4_IIIA1a, p4_IIIA2, p4_IIIA2ai1] == page4.get_sections(
  519. matches=r".*?a.*?"
  520. )
  521. assert [p4_IIIA1a, p4_IIIA2ai1] == page4.get_sections(
  522. matches=r".*?a.*?", flags=re.U
  523. )
  524. assert ["\nMore text.\n", "\nAn invalid section!"] == page4.get_sections(
  525. matches=r".*?a.*?", flags=re.U, include_headings=False
  526. )
  527. sections = page2.get_sections(include_headings=False)
  528. sections[0].append("Lead!\n")
  529. sections[1].append("\nFirst section!")
  530. assert "Lead!\n==Heading==\nFirst section!" == page2
  531. page5 = parse("X\n== Foo ==\nBar\n== Baz ==\nBuzz")
  532. section = page5.get_sections(matches="Foo")[0]
  533. section.replace("\nBar\n", "\nBarf ")
  534. section.append("{{Haha}}\n")
  535. assert "== Foo ==\nBarf {{Haha}}\n" == section
  536. assert "X\n== Foo ==\nBarf {{Haha}}\n== Baz ==\nBuzz" == page5
  537. def test_strip_code():
  538. """test Wikicode.strip_code()"""
  539. # Since individual nodes have test cases for their __strip__ methods,
  540. # we're only going to do an integration test:
  541. code = parse("Foo [[bar]]\n\n{{baz|hello}}\n\n[[a|b]] &Sigma;")
  542. assert "Foo bar\n\nb Σ" == code.strip_code(normalize=True, collapse=True)
  543. assert "Foo bar\n\n\n\nb Σ" == code.strip_code(normalize=True, collapse=False)
  544. assert "Foo bar\n\nb &Sigma;" == code.strip_code(normalize=False, collapse=True)
  545. assert "Foo bar\n\n\n\nb &Sigma;" == code.strip_code(
  546. normalize=False, collapse=False
  547. )
  548. assert "Foo bar\n\nhello\n\nb Σ" == code.strip_code(
  549. normalize=True, collapse=True, keep_template_params=True
  550. )
  551. def test_get_tree():
  552. """test Wikicode.get_tree()"""
  553. # Since individual nodes have test cases for their __showtree___
  554. # methods, and the docstring covers all possibilities for the output of
  555. # __showtree__, we'll test it only:
  556. code = parse("Lorem ipsum {{foo|bar|{{baz}}|spam=eggs}}")
  557. expected = (
  558. "Lorem ipsum \n{{\n\t foo\n\t| 1\n\t= bar\n\t| 2\n\t= "
  559. + "{{\n\t\t\tbaz\n\t }}\n\t| spam\n\t= eggs\n}}"
  560. )
  561. assert expected.expandtabs(4) == code.get_tree()