@@ -1,4 +1,24 @@ | |||||
v0.1.1 (19da4d2144) to v0.2: | |||||
v0.3 (released August 24, 2013): | |||||
- Added complete support for HTML Tags, including forms like <ref>foo</ref>, | |||||
<ref name="bar"/>, and wiki-markup tags like bold ('''), italics (''), and | |||||
lists (*, #, ; and :). | |||||
- Added support for ExternalLinks (http://example.com/ and | |||||
[http://example.com/ Example]). | |||||
- Wikicode's filter methods are now passed 'recursive=True' by default instead | |||||
of False. This is a breaking change if you rely on any filter() methods being | |||||
non-recursive by default. | |||||
- Added a matches() method to Wikicode for page/template name comparisons. | |||||
- The 'obj' param of Wikicode.insert_before(), insert_after(), replace(), and | |||||
remove() now accepts other Wikicode objects and strings representing parts of | |||||
wikitext, instead of just nodes. These methods also make all possible | |||||
substitutions instead of just one. | |||||
- Renamed Template.has_param() to has() for consistency with Template's other | |||||
methods; has_param() is now an alias. | |||||
- The C tokenizer extension now works on Python 3 in addition to Python 2.7. | |||||
- Various bugfixes, internal changes, and cleanup. | |||||
v0.2 (released June 20, 2013): | |||||
- The parser now fully supports Python 3 in addition to Python 2.7. | - The parser now fully supports Python 3 in addition to Python 2.7. | ||||
- Added a C tokenizer extension that is significantly faster than its Python | - Added a C tokenizer extension that is significantly faster than its Python | ||||
@@ -24,10 +44,14 @@ v0.1.1 (19da4d2144) to v0.2: | |||||
- Fixed some broken example code in the README; other copyedits. | - Fixed some broken example code in the README; other copyedits. | ||||
- Other bugfixes and code cleanup. | - Other bugfixes and code cleanup. | ||||
v0.1 (ba94938fe8) to v0.1.1 (19da4d2144): | |||||
v0.1.1 (released September 21, 2012): | |||||
- Added support for Comments (<!-- foo -->) and Wikilinks ([[foo]]). | - Added support for Comments (<!-- foo -->) and Wikilinks ([[foo]]). | ||||
- Added corresponding ifilter_links() and filter_links() methods to Wikicode. | - Added corresponding ifilter_links() and filter_links() methods to Wikicode. | ||||
- Fixed a bug when parsing incomplete templates. | - Fixed a bug when parsing incomplete templates. | ||||
- Fixed strip_code() to affect the contents of headings. | - Fixed strip_code() to affect the contents of headings. | ||||
- Various copyedits in documentation and comments. | - Various copyedits in documentation and comments. | ||||
v0.1 (released August 23, 2012): | |||||
- Initial release. |
@@ -9,7 +9,8 @@ mwparserfromhell | |||||
that provides an easy-to-use and outrageously powerful parser for MediaWiki_ | that provides an easy-to-use and outrageously powerful parser for MediaWiki_ | ||||
wikicode. It supports Python 2 and Python 3. | wikicode. It supports Python 2 and Python 3. | ||||
Developed by Earwig_ with help from `Σ`_. | |||||
Developed by Earwig_ with help from `Σ`_. Full documentation is available on | |||||
ReadTheDocs_. | |||||
Installation | Installation | ||||
------------ | ------------ | ||||
@@ -18,7 +19,7 @@ The easiest way to install the parser is through the `Python Package Index`_, | |||||
so you can install the latest release with ``pip install mwparserfromhell`` | so you can install the latest release with ``pip install mwparserfromhell`` | ||||
(`get pip`_). Alternatively, get the latest development version:: | (`get pip`_). Alternatively, get the latest development version:: | ||||
git clone git://github.com/earwig/mwparserfromhell.git | |||||
git clone https://github.com/earwig/mwparserfromhell.git | |||||
cd mwparserfromhell | cd mwparserfromhell | ||||
python setup.py install | python setup.py install | ||||
@@ -59,13 +60,20 @@ For example:: | |||||
>>> print template.get("eggs").value | >>> print template.get("eggs").value | ||||
spam | spam | ||||
Since every node you reach is also a ``Wikicode`` object, it's trivial to get | |||||
nested templates:: | |||||
Since nodes can contain other nodes, getting nested templates is trivial:: | |||||
>>> text = "{{foo|{{bar}}={{baz|{{spam}}}}}}" | |||||
>>> mwparserfromhell.parse(text).filter_templates() | |||||
['{{foo|{{bar}}={{baz|{{spam}}}}}}', '{{bar}}', '{{baz|{{spam}}}}', '{{spam}}'] | |||||
You can also pass ``recursive=False`` to ``filter_templates()`` and explore | |||||
templates manually. This is possible because nodes can contain additional | |||||
``Wikicode`` objects:: | |||||
>>> code = mwparserfromhell.parse("{{foo|this {{includes a|template}}}}") | >>> code = mwparserfromhell.parse("{{foo|this {{includes a|template}}}}") | ||||
>>> print code.filter_templates() | |||||
>>> print code.filter_templates(recursive=False) | |||||
['{{foo|this {{includes a|template}}}}'] | ['{{foo|this {{includes a|template}}}}'] | ||||
>>> foo = code.filter_templates()[0] | |||||
>>> foo = code.filter_templates(recursive=False)[0] | |||||
>>> print foo.get(1).value | >>> print foo.get(1).value | ||||
this {{includes a|template}} | this {{includes a|template}} | ||||
>>> print foo.get(1).value.filter_templates()[0] | >>> print foo.get(1).value.filter_templates()[0] | ||||
@@ -73,21 +81,16 @@ nested templates:: | |||||
>>> print foo.get(1).value.filter_templates()[0].get(1).value | >>> print foo.get(1).value.filter_templates()[0].get(1).value | ||||
template | template | ||||
Additionally, you can include nested templates in ``filter_templates()`` by | |||||
passing ``recursive=True``:: | |||||
>>> text = "{{foo|{{bar}}={{baz|{{spam}}}}}}" | |||||
>>> mwparserfromhell.parse(text).filter_templates(recursive=True) | |||||
['{{foo|{{bar}}={{baz|{{spam}}}}}}', '{{bar}}', '{{baz|{{spam}}}}', '{{spam}}'] | |||||
Templates can be easily modified to add, remove, or alter params. ``Wikicode`` | Templates can be easily modified to add, remove, or alter params. ``Wikicode`` | ||||
can also be treated like a list with ``append()``, ``insert()``, ``remove()``, | |||||
``replace()``, and more:: | |||||
objects can be treated like lists, with ``append()``, ``insert()``, | |||||
``remove()``, ``replace()``, and more. They also have a ``matches()`` method | |||||
for comparing page or template names, which takes care of capitalization and | |||||
whitespace:: | |||||
>>> text = "{{cleanup}} '''Foo''' is a [[bar]]. {{uncategorized}}" | >>> text = "{{cleanup}} '''Foo''' is a [[bar]]. {{uncategorized}}" | ||||
>>> code = mwparserfromhell.parse(text) | >>> code = mwparserfromhell.parse(text) | ||||
>>> for template in code.filter_templates(): | >>> for template in code.filter_templates(): | ||||
... if template.name == "cleanup" and not template.has_param("date"): | |||||
... if template.name.matches("Cleanup") and not template.has("date"): | |||||
... template.add("date", "July 2012") | ... template.add("date", "July 2012") | ||||
... | ... | ||||
>>> print code | >>> print code | ||||
@@ -142,6 +145,7 @@ following code (via the API_):: | |||||
return mwparserfromhell.parse(text) | return mwparserfromhell.parse(text) | ||||
.. _MediaWiki: http://mediawiki.org | .. _MediaWiki: http://mediawiki.org | ||||
.. _ReadTheDocs: http://mwparserfromhell.readthedocs.org | |||||
.. _Earwig: http://en.wikipedia.org/wiki/User:The_Earwig | .. _Earwig: http://en.wikipedia.org/wiki/User:The_Earwig | ||||
.. _Σ: http://en.wikipedia.org/wiki/User:%CE%A3 | .. _Σ: http://en.wikipedia.org/wiki/User:%CE%A3 | ||||
.. _Python Package Index: http://pypi.python.org | .. _Python Package Index: http://pypi.python.org | ||||
@@ -25,6 +25,14 @@ nodes Package | |||||
:undoc-members: | :undoc-members: | ||||
:show-inheritance: | :show-inheritance: | ||||
:mod:`external_link` Module | |||||
--------------------------- | |||||
.. automodule:: mwparserfromhell.nodes.external_link | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
:mod:`heading` Module | :mod:`heading` Module | ||||
--------------------- | --------------------- | ||||
@@ -46,6 +54,7 @@ nodes Package | |||||
.. automodule:: mwparserfromhell.nodes.tag | .. automodule:: mwparserfromhell.nodes.tag | ||||
:members: | :members: | ||||
:undoc-members: | |||||
:show-inheritance: | :show-inheritance: | ||||
:mod:`template` Module | :mod:`template` Module | ||||
@@ -30,6 +30,12 @@ mwparserfromhell Package | |||||
:members: | :members: | ||||
:undoc-members: | :undoc-members: | ||||
:mod:`definitions` Module | |||||
------------------------- | |||||
.. automodule:: mwparserfromhell.definitions | |||||
:members: | |||||
:mod:`utils` Module | :mod:`utils` Module | ||||
------------------- | ------------------- | ||||
@@ -1,10 +1,38 @@ | |||||
Changelog | Changelog | ||||
========= | ========= | ||||
v0.3 | |||||
---- | |||||
`Released August 24, 2013 <https://github.com/earwig/mwparserfromhell/tree/v0.3>`_ | |||||
(`changes <https://github.com/earwig/mwparserfromhell/compare/v0.2...v0.3>`__): | |||||
- Added complete support for HTML :py:class:`Tags <.Tag>`, including forms like | |||||
``<ref>foo</ref>``, ``<ref name="bar"/>``, and wiki-markup tags like bold | |||||
(``'''``), italics (``''``), and lists (``*``, ``#``, ``;`` and ``:``). | |||||
- Added support for :py:class:`.ExternalLink`\ s (``http://example.com/`` and | |||||
``[http://example.com/ Example]``). | |||||
- :py:class:`Wikicode's <.Wikicode>` :py:meth:`.filter` methods are now passed | |||||
*recursive=True* by default instead of *False*. **This is a breaking change | |||||
if you rely on any filter() methods being non-recursive by default.** | |||||
- Added a :py:meth:`.matches` method to :py:class:`~.Wikicode` for | |||||
page/template name comparisons. | |||||
- The *obj* param of :py:meth:`Wikicode.insert_before() <.insert_before>`, | |||||
:py:meth:`~.insert_after`, :py:meth:`~.Wikicode.replace`, and | |||||
:py:meth:`~.Wikicode.remove` now accepts :py:class:`~.Wikicode` objects and | |||||
strings representing parts of wikitext, instead of just nodes. These methods | |||||
also make all possible substitutions instead of just one. | |||||
- Renamed :py:meth:`Template.has_param() <.has_param>` to | |||||
:py:meth:`~.Template.has` for consistency with :py:class:`~.Template`\ 's | |||||
other methods; :py:meth:`~.has_param` is now an alias. | |||||
- The C tokenizer extension now works on Python 3 in addition to Python 2.7. | |||||
- Various bugfixes, internal changes, and cleanup. | |||||
v0.2 | v0.2 | ||||
---- | ---- | ||||
19da4d2144_ to master_ (released June 20, 2013) | |||||
`Released June 20, 2013 <https://github.com/earwig/mwparserfromhell/tree/v0.2>`_ | |||||
(`changes <https://github.com/earwig/mwparserfromhell/compare/v0.1.1...v0.2>`__): | |||||
- The parser now fully supports Python 3 in addition to Python 2.7. | - The parser now fully supports Python 3 in addition to Python 2.7. | ||||
- Added a C tokenizer extension that is significantly faster than its Python | - Added a C tokenizer extension that is significantly faster than its Python | ||||
@@ -38,7 +66,8 @@ v0.2 | |||||
v0.1.1 | v0.1.1 | ||||
------ | ------ | ||||
ba94938fe8_ to 19da4d2144_ (released September 21, 2012) | |||||
`Released September 21, 2012 <https://github.com/earwig/mwparserfromhell/tree/v0.1.1>`_ | |||||
(`changes <https://github.com/earwig/mwparserfromhell/compare/v0.1...v0.1.1>`__): | |||||
- Added support for :py:class:`Comments <.Comment>` (``<!-- foo -->``) and | - Added support for :py:class:`Comments <.Comment>` (``<!-- foo -->``) and | ||||
:py:class:`Wikilinks <.Wikilink>` (``[[foo]]``). | :py:class:`Wikilinks <.Wikilink>` (``[[foo]]``). | ||||
@@ -51,8 +80,6 @@ ba94938fe8_ to 19da4d2144_ (released September 21, 2012) | |||||
v0.1 | v0.1 | ||||
---- | ---- | ||||
ba94938fe8_ (released August 23, 2012) | |||||
`Released August 23, 2012 <https://github.com/earwig/mwparserfromhell/tree/v0.1>`_: | |||||
.. _master: https://github.com/earwig/mwparserfromhell/tree/v0.2 | |||||
.. _19da4d2144: https://github.com/earwig/mwparserfromhell/tree/v0.1.1 | |||||
.. _ba94938fe8: https://github.com/earwig/mwparserfromhell/tree/v0.1 | |||||
- Initial release. |
@@ -1,15 +1,18 @@ | |||||
MWParserFromHell v0.2 Documentation | |||||
=================================== | |||||
MWParserFromHell v\ |version| Documentation | |||||
=========================================== | |||||
:py:mod:`mwparserfromhell` (the *MediaWiki Parser from Hell*) is a Python | :py:mod:`mwparserfromhell` (the *MediaWiki Parser from Hell*) is a Python | ||||
package that provides an easy-to-use and outrageously powerful parser for | package that provides an easy-to-use and outrageously powerful parser for | ||||
MediaWiki_ wikicode. It supports Python 2 and Python 3. | MediaWiki_ wikicode. It supports Python 2 and Python 3. | ||||
Developed by Earwig_ with help from `Σ`_. | |||||
Developed by Earwig_ with contributions from `Σ`_, Legoktm_, and others. | |||||
Development occurs on GitHub_. | |||||
.. _MediaWiki: http://mediawiki.org | .. _MediaWiki: http://mediawiki.org | ||||
.. _Earwig: http://en.wikipedia.org/wiki/User:The_Earwig | .. _Earwig: http://en.wikipedia.org/wiki/User:The_Earwig | ||||
.. _Σ: http://en.wikipedia.org/wiki/User:%CE%A3 | .. _Σ: http://en.wikipedia.org/wiki/User:%CE%A3 | ||||
.. _Legoktm: http://en.wikipedia.org/wiki/User:Legoktm | |||||
.. _GitHub: https://github.com/earwig/mwparserfromhell | |||||
Installation | Installation | ||||
------------ | ------------ | ||||
@@ -18,7 +21,7 @@ The easiest way to install the parser is through the `Python Package Index`_, | |||||
so you can install the latest release with ``pip install mwparserfromhell`` | so you can install the latest release with ``pip install mwparserfromhell`` | ||||
(`get pip`_). Alternatively, get the latest development version:: | (`get pip`_). Alternatively, get the latest development version:: | ||||
git clone git://github.com/earwig/mwparserfromhell.git | |||||
git clone https://github.com/earwig/mwparserfromhell.git | |||||
cd mwparserfromhell | cd mwparserfromhell | ||||
python setup.py install | python setup.py install | ||||
@@ -27,13 +27,20 @@ some extra methods. For example:: | |||||
>>> print template.get("eggs").value | >>> print template.get("eggs").value | ||||
spam | spam | ||||
Since every node you reach is also a :py:class:`~.Wikicode` object, it's | |||||
trivial to get nested templates:: | |||||
Since nodes can contain other nodes, getting nested templates is trivial:: | |||||
>>> text = "{{foo|{{bar}}={{baz|{{spam}}}}}}" | |||||
>>> mwparserfromhell.parse(text).filter_templates() | |||||
['{{foo|{{bar}}={{baz|{{spam}}}}}}', '{{bar}}', '{{baz|{{spam}}}}', '{{spam}}'] | |||||
You can also pass *recursive=False* to :py:meth:`~.filter_templates` and | |||||
explore templates manually. This is possible because nodes can contain | |||||
additional :py:class:`~.Wikicode` objects:: | |||||
>>> code = mwparserfromhell.parse("{{foo|this {{includes a|template}}}}") | >>> code = mwparserfromhell.parse("{{foo|this {{includes a|template}}}}") | ||||
>>> print code.filter_templates() | |||||
>>> print code.filter_templates(recursive=False) | |||||
['{{foo|this {{includes a|template}}}}'] | ['{{foo|this {{includes a|template}}}}'] | ||||
>>> foo = code.filter_templates()[0] | |||||
>>> foo = code.filter_templates(recursive=False)[0] | |||||
>>> print foo.get(1).value | >>> print foo.get(1).value | ||||
this {{includes a|template}} | this {{includes a|template}} | ||||
>>> print foo.get(1).value.filter_templates()[0] | >>> print foo.get(1).value.filter_templates()[0] | ||||
@@ -41,22 +48,17 @@ trivial to get nested templates:: | |||||
>>> print foo.get(1).value.filter_templates()[0].get(1).value | >>> print foo.get(1).value.filter_templates()[0].get(1).value | ||||
template | template | ||||
Additionally, you can include nested templates in :py:meth:`~.filter_templates` | |||||
by passing *recursive=True*:: | |||||
>>> text = "{{foo|{{bar}}={{baz|{{spam}}}}}}" | |||||
>>> mwparserfromhell.parse(text).filter_templates(recursive=True) | |||||
['{{foo|{{bar}}={{baz|{{spam}}}}}}', '{{bar}}', '{{baz|{{spam}}}}', '{{spam}}'] | |||||
Templates can be easily modified to add, remove, or alter params. | Templates can be easily modified to add, remove, or alter params. | ||||
:py:class:`~.Wikicode` can also be treated like a list with | |||||
:py:class:`~.Wikicode` objects can be treated like lists, with | |||||
:py:meth:`~.Wikicode.append`, :py:meth:`~.Wikicode.insert`, | :py:meth:`~.Wikicode.append`, :py:meth:`~.Wikicode.insert`, | ||||
:py:meth:`~.Wikicode.remove`, :py:meth:`~.Wikicode.replace`, and more:: | |||||
:py:meth:`~.Wikicode.remove`, :py:meth:`~.Wikicode.replace`, and more. They | |||||
also have a :py:meth:`~.Wikicode.matches` method for comparing page or template | |||||
names, which takes care of capitalization and whitespace:: | |||||
>>> text = "{{cleanup}} '''Foo''' is a [[bar]]. {{uncategorized}}" | >>> text = "{{cleanup}} '''Foo''' is a [[bar]]. {{uncategorized}}" | ||||
>>> code = mwparserfromhell.parse(text) | >>> code = mwparserfromhell.parse(text) | ||||
>>> for template in code.filter_templates(): | >>> for template in code.filter_templates(): | ||||
... if template.name == "cleanup" and not template.has_param("date"): | |||||
... if template.name.matches("Cleanup") and not template.has("date"): | |||||
... template.add("date", "July 2012") | ... template.add("date", "July 2012") | ||||
... | ... | ||||
>>> print code | >>> print code | ||||
@@ -31,9 +31,10 @@ from __future__ import unicode_literals | |||||
__author__ = "Ben Kurtovic" | __author__ = "Ben Kurtovic" | ||||
__copyright__ = "Copyright (C) 2012, 2013 Ben Kurtovic" | __copyright__ = "Copyright (C) 2012, 2013 Ben Kurtovic" | ||||
__license__ = "MIT License" | __license__ = "MIT License" | ||||
__version__ = "0.2" | |||||
__version__ = "0.3" | |||||
__email__ = "ben.kurtovic@verizon.net" | __email__ = "ben.kurtovic@verizon.net" | ||||
from . import compat, nodes, parser, smart_list, string_mixin, utils, wikicode | |||||
from . import (compat, definitions, nodes, parser, smart_list, string_mixin, | |||||
utils, wikicode) | |||||
parse = utils.parse_anything | parse = utils.parse_anything |
@@ -15,14 +15,12 @@ py3k = sys.version_info[0] == 3 | |||||
if py3k: | if py3k: | ||||
bytes = bytes | bytes = bytes | ||||
str = str | str = str | ||||
basestring = str | |||||
maxsize = sys.maxsize | maxsize = sys.maxsize | ||||
import html.entities as htmlentities | import html.entities as htmlentities | ||||
else: | else: | ||||
bytes = str | bytes = str | ||||
str = unicode | str = unicode | ||||
basestring = basestring | |||||
maxsize = sys.maxint | maxsize = sys.maxint | ||||
import htmlentitydefs as htmlentities | import htmlentitydefs as htmlentities | ||||
@@ -0,0 +1,91 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2012-2013 Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
"""Contains data about certain markup, like HTML tags and external links.""" | |||||
from __future__ import unicode_literals | |||||
__all__ = ["get_html_tag", "is_parsable", "is_visible", "is_single", | |||||
"is_single_only", "is_scheme"] | |||||
URI_SCHEMES = { | |||||
# [mediawiki/core.git]/includes/DefaultSettings.php @ 374a0ad943 | |||||
"http": True, "https": True, "ftp": True, "ftps": True, "ssh": True, | |||||
"sftp": True, "irc": True, "ircs": True, "xmpp": False, "sip": False, | |||||
"sips": False, "gopher": True, "telnet": True, "nntp": True, | |||||
"worldwind": True, "mailto": False, "tel": False, "sms": False, | |||||
"news": False, "svn": True, "git": True, "mms": True, "bitcoin": False, | |||||
"magnet": False, "urn": False, "geo": False | |||||
} | |||||
PARSER_BLACKLIST = [ | |||||
# enwiki extensions @ 2013-06-28 | |||||
"categorytree", "gallery", "hiero", "imagemap", "inputbox", "math", | |||||
"nowiki", "pre", "score", "section", "source", "syntaxhighlight", | |||||
"templatedata", "timeline" | |||||
] | |||||
INVISIBLE_TAGS = [ | |||||
# enwiki extensions @ 2013-06-28 | |||||
"categorytree", "gallery", "imagemap", "inputbox", "math", "score", | |||||
"section", "templatedata", "timeline" | |||||
] | |||||
# [mediawiki/core.git]/includes/Sanitizer.php @ 87a0aef762 | |||||
SINGLE_ONLY = ["br", "hr", "meta", "link", "img"] | |||||
SINGLE = SINGLE_ONLY + ["li", "dt", "dd"] | |||||
MARKUP_TO_HTML = { | |||||
"#": "li", | |||||
"*": "li", | |||||
";": "dt", | |||||
":": "dd" | |||||
} | |||||
def get_html_tag(markup): | |||||
"""Return the HTML tag associated with the given wiki-markup.""" | |||||
return MARKUP_TO_HTML[markup] | |||||
def is_parsable(tag): | |||||
"""Return if the given *tag*'s contents should be passed to the parser.""" | |||||
return tag.lower() not in PARSER_BLACKLIST | |||||
def is_visible(tag): | |||||
"""Return whether or not the given *tag* contains visible text.""" | |||||
return tag.lower() not in INVISIBLE_TAGS | |||||
def is_single(tag): | |||||
"""Return whether or not the given *tag* can exist without a close tag.""" | |||||
return tag.lower() in SINGLE | |||||
def is_single_only(tag): | |||||
"""Return whether or not the given *tag* must exist without a close tag.""" | |||||
return tag.lower() in SINGLE_ONLY | |||||
def is_scheme(scheme, slashes=True, reverse=False): | |||||
"""Return whether *scheme* is valid for external links.""" | |||||
if reverse: # Convenience for C | |||||
scheme = scheme[::-1] | |||||
scheme = scheme.lower() | |||||
if slashes: | |||||
return scheme in URI_SCHEMES | |||||
return scheme in URI_SCHEMES and not URI_SCHEMES[scheme] |
@@ -69,6 +69,7 @@ from . import extras | |||||
from .text import Text | from .text import Text | ||||
from .argument import Argument | from .argument import Argument | ||||
from .comment import Comment | from .comment import Comment | ||||
from .external_link import ExternalLink | |||||
from .heading import Heading | from .heading import Heading | ||||
from .html_entity import HTMLEntity | from .html_entity import HTMLEntity | ||||
from .tag import Tag | from .tag import Tag | ||||
@@ -0,0 +1,97 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2012-2013 Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
from __future__ import unicode_literals | |||||
from . import Node | |||||
from ..compat import str | |||||
from ..utils import parse_anything | |||||
__all__ = ["ExternalLink"] | |||||
class ExternalLink(Node): | |||||
"""Represents an external link, like ``[http://example.com/ Example]``.""" | |||||
def __init__(self, url, title=None, brackets=True): | |||||
super(ExternalLink, self).__init__() | |||||
self._url = url | |||||
self._title = title | |||||
self._brackets = brackets | |||||
def __unicode__(self): | |||||
if self.brackets: | |||||
if self.title is not None: | |||||
return "[" + str(self.url) + " " + str(self.title) + "]" | |||||
return "[" + str(self.url) + "]" | |||||
return str(self.url) | |||||
def __iternodes__(self, getter): | |||||
yield None, self | |||||
for child in getter(self.url): | |||||
yield self.url, child | |||||
if self.title is not None: | |||||
for child in getter(self.title): | |||||
yield self.title, child | |||||
def __strip__(self, normalize, collapse): | |||||
if self.brackets: | |||||
if self.title: | |||||
return self.title.strip_code(normalize, collapse) | |||||
return None | |||||
return self.url.strip_code(normalize, collapse) | |||||
def __showtree__(self, write, get, mark): | |||||
if self.brackets: | |||||
write("[") | |||||
get(self.url) | |||||
if self.title is not None: | |||||
get(self.title) | |||||
if self.brackets: | |||||
write("]") | |||||
@property | |||||
def url(self): | |||||
"""The URL of the link target, as a :py:class:`~.Wikicode` object.""" | |||||
return self._url | |||||
@property | |||||
def title(self): | |||||
"""The link title (if given), as a :py:class:`~.Wikicode` object.""" | |||||
return self._title | |||||
@property | |||||
def brackets(self): | |||||
"""Whether to enclose the URL in brackets or display it straight.""" | |||||
return self._brackets | |||||
@url.setter | |||||
def url(self, value): | |||||
from ..parser import contexts | |||||
self._url = parse_anything(value, contexts.EXT_LINK_URI) | |||||
@title.setter | |||||
def title(self, value): | |||||
self._title = None if value is None else parse_anything(value) | |||||
@brackets.setter | |||||
def brackets(self, value): | |||||
self._brackets = bool(value) |
@@ -36,18 +36,34 @@ class Attribute(StringMixIn): | |||||
whose value is ``"foo"``. | whose value is ``"foo"``. | ||||
""" | """ | ||||
def __init__(self, name, value=None, quoted=True): | |||||
def __init__(self, name, value=None, quoted=True, pad_first=" ", | |||||
pad_before_eq="", pad_after_eq=""): | |||||
super(Attribute, self).__init__() | super(Attribute, self).__init__() | ||||
self._name = name | self._name = name | ||||
self._value = value | self._value = value | ||||
self._quoted = quoted | self._quoted = quoted | ||||
self._pad_first = pad_first | |||||
self._pad_before_eq = pad_before_eq | |||||
self._pad_after_eq = pad_after_eq | |||||
def __unicode__(self): | def __unicode__(self): | ||||
if self.value: | |||||
result = self.pad_first + str(self.name) + self.pad_before_eq | |||||
if self.value is not None: | |||||
result += "=" + self.pad_after_eq | |||||
if self.quoted: | if self.quoted: | ||||
return str(self.name) + '="' + str(self.value) + '"' | |||||
return str(self.name) + "=" + str(self.value) | |||||
return str(self.name) | |||||
return result + '"' + str(self.value) + '"' | |||||
return result + str(self.value) | |||||
return result | |||||
def _set_padding(self, attr, value): | |||||
"""Setter for the value of a padding attribute.""" | |||||
if not value: | |||||
setattr(self, attr, "") | |||||
else: | |||||
value = str(value) | |||||
if not value.isspace(): | |||||
raise ValueError("padding must be entirely whitespace") | |||||
setattr(self, attr, value) | |||||
@property | @property | ||||
def name(self): | def name(self): | ||||
@@ -64,14 +80,41 @@ class Attribute(StringMixIn): | |||||
"""Whether the attribute's value is quoted with double quotes.""" | """Whether the attribute's value is quoted with double quotes.""" | ||||
return self._quoted | return self._quoted | ||||
@property | |||||
def pad_first(self): | |||||
"""Spacing to insert right before the attribute.""" | |||||
return self._pad_first | |||||
@property | |||||
def pad_before_eq(self): | |||||
"""Spacing to insert right before the equal sign.""" | |||||
return self._pad_before_eq | |||||
@property | |||||
def pad_after_eq(self): | |||||
"""Spacing to insert right after the equal sign.""" | |||||
return self._pad_after_eq | |||||
@name.setter | @name.setter | ||||
def name(self, newval): | |||||
self._name = parse_anything(newval) | |||||
def name(self, value): | |||||
self._name = parse_anything(value) | |||||
@value.setter | @value.setter | ||||
def value(self, newval): | def value(self, newval): | ||||
self._value = parse_anything(newval) | |||||
self._value = None if newval is None else parse_anything(newval) | |||||
@quoted.setter | @quoted.setter | ||||
def quoted(self, newval): | |||||
self._quoted = bool(newval) | |||||
def quoted(self, value): | |||||
self._quoted = bool(value) | |||||
@pad_first.setter | |||||
def pad_first(self, value): | |||||
self._set_padding("_pad_first", value) | |||||
@pad_before_eq.setter | |||||
def pad_before_eq(self, value): | |||||
self._set_padding("_pad_before_eq", value) | |||||
@pad_after_eq.setter | |||||
def pad_after_eq(self, value): | |||||
self._set_padding("_pad_after_eq", value) |
@@ -22,8 +22,10 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
from . import Node, Text | |||||
from . import Node | |||||
from .extras import Attribute | |||||
from ..compat import str | from ..compat import str | ||||
from ..definitions import is_visible | |||||
from ..utils import parse_anything | from ..utils import parse_anything | ||||
__all__ = ["Tag"] | __all__ = ["Tag"] | ||||
@@ -31,146 +33,85 @@ __all__ = ["Tag"] | |||||
class Tag(Node): | class Tag(Node): | ||||
"""Represents an HTML-style tag in wikicode, like ``<ref>``.""" | """Represents an HTML-style tag in wikicode, like ``<ref>``.""" | ||||
TAG_UNKNOWN = 0 | |||||
# Basic HTML: | |||||
TAG_ITALIC = 1 | |||||
TAG_BOLD = 2 | |||||
TAG_UNDERLINE = 3 | |||||
TAG_STRIKETHROUGH = 4 | |||||
TAG_UNORDERED_LIST = 5 | |||||
TAG_ORDERED_LIST = 6 | |||||
TAG_DEF_TERM = 7 | |||||
TAG_DEF_ITEM = 8 | |||||
TAG_BLOCKQUOTE = 9 | |||||
TAG_RULE = 10 | |||||
TAG_BREAK = 11 | |||||
TAG_ABBR = 12 | |||||
TAG_PRE = 13 | |||||
TAG_MONOSPACE = 14 | |||||
TAG_CODE = 15 | |||||
TAG_SPAN = 16 | |||||
TAG_DIV = 17 | |||||
TAG_FONT = 18 | |||||
TAG_SMALL = 19 | |||||
TAG_BIG = 20 | |||||
TAG_CENTER = 21 | |||||
# MediaWiki parser hooks: | |||||
TAG_REF = 101 | |||||
TAG_GALLERY = 102 | |||||
TAG_MATH = 103 | |||||
TAG_NOWIKI = 104 | |||||
TAG_NOINCLUDE = 105 | |||||
TAG_INCLUDEONLY = 106 | |||||
TAG_ONLYINCLUDE = 107 | |||||
# Additional parser hooks: | |||||
TAG_SYNTAXHIGHLIGHT = 201 | |||||
TAG_POEM = 202 | |||||
# Lists of tags: | |||||
TAGS_INVISIBLE = set((TAG_REF, TAG_GALLERY, TAG_MATH, TAG_NOINCLUDE)) | |||||
TAGS_VISIBLE = set(range(300)) - TAGS_INVISIBLE | |||||
def __init__(self, type_, tag, contents=None, attrs=None, showtag=True, | |||||
self_closing=False, open_padding=0, close_padding=0): | |||||
def __init__(self, tag, contents=None, attrs=None, wiki_markup=None, | |||||
self_closing=False, invalid=False, implicit=False, padding="", | |||||
closing_tag=None): | |||||
super(Tag, self).__init__() | super(Tag, self).__init__() | ||||
self._type = type_ | |||||
self._tag = tag | self._tag = tag | ||||
self._contents = contents | |||||
if attrs: | |||||
self._attrs = attrs | |||||
if contents is None and not self_closing: | |||||
self._contents = parse_anything("") | |||||
else: | else: | ||||
self._attrs = [] | |||||
self._showtag = showtag | |||||
self._contents = contents | |||||
self._attrs = attrs if attrs else [] | |||||
self._wiki_markup = wiki_markup | |||||
self._self_closing = self_closing | self._self_closing = self_closing | ||||
self._open_padding = open_padding | |||||
self._close_padding = close_padding | |||||
self._invalid = invalid | |||||
self._implicit = implicit | |||||
self._padding = padding | |||||
if closing_tag: | |||||
self._closing_tag = closing_tag | |||||
else: | |||||
self._closing_tag = tag | |||||
def __unicode__(self): | def __unicode__(self): | ||||
if not self.showtag: | |||||
open_, close = self._translate() | |||||
if self.wiki_markup: | |||||
if self.self_closing: | if self.self_closing: | ||||
return open_ | |||||
return self.wiki_markup | |||||
else: | else: | ||||
return open_ + str(self.contents) + close | |||||
return self.wiki_markup + str(self.contents) + self.wiki_markup | |||||
result = "<" + str(self.tag) | |||||
if self.attrs: | |||||
result += " " + " ".join([str(attr) for attr in self.attrs]) | |||||
result = ("</" if self.invalid else "<") + str(self.tag) | |||||
if self.attributes: | |||||
result += "".join([str(attr) for attr in self.attributes]) | |||||
if self.self_closing: | if self.self_closing: | ||||
result += " " * self.open_padding + "/>" | |||||
result += self.padding + (">" if self.implicit else "/>") | |||||
else: | else: | ||||
result += " " * self.open_padding + ">" + str(self.contents) | |||||
result += "</" + str(self.tag) + " " * self.close_padding + ">" | |||||
result += self.padding + ">" + str(self.contents) | |||||
result += "</" + str(self.closing_tag) + ">" | |||||
return result | return result | ||||
def __iternodes__(self, getter): | def __iternodes__(self, getter): | ||||
yield None, self | yield None, self | ||||
if self.showtag: | |||||
if not self.wiki_markup: | |||||
for child in getter(self.tag): | for child in getter(self.tag): | ||||
yield self.tag, child | yield self.tag, child | ||||
for attr in self.attrs: | |||||
for attr in self.attributes: | |||||
for child in getter(attr.name): | for child in getter(attr.name): | ||||
yield attr.name, child | yield attr.name, child | ||||
if attr.value: | if attr.value: | ||||
for child in getter(attr.value): | for child in getter(attr.value): | ||||
yield attr.value, child | yield attr.value, child | ||||
for child in getter(self.contents): | |||||
yield self.contents, child | |||||
if self.contents: | |||||
for child in getter(self.contents): | |||||
yield self.contents, child | |||||
if not self.self_closing and not self.wiki_markup and self.closing_tag: | |||||
for child in getter(self.closing_tag): | |||||
yield self.closing_tag, child | |||||
def __strip__(self, normalize, collapse): | def __strip__(self, normalize, collapse): | ||||
if self.type in self.TAGS_VISIBLE: | |||||
if self.contents and is_visible(self.tag): | |||||
return self.contents.strip_code(normalize, collapse) | return self.contents.strip_code(normalize, collapse) | ||||
return None | return None | ||||
def __showtree__(self, write, get, mark): | def __showtree__(self, write, get, mark): | ||||
tagnodes = self.tag.nodes | |||||
if (not self.attrs and len(tagnodes) == 1 and isinstance(tagnodes[0], Text)): | |||||
write("<" + str(tagnodes[0]) + ">") | |||||
write("</" if self.invalid else "<") | |||||
get(self.tag) | |||||
for attr in self.attributes: | |||||
get(attr.name) | |||||
if not attr.value: | |||||
continue | |||||
write(" = ") | |||||
mark() | |||||
get(attr.value) | |||||
if self.self_closing: | |||||
write(">" if self.implicit else "/>") | |||||
else: | else: | ||||
write("<") | |||||
get(self.tag) | |||||
for attr in self.attrs: | |||||
get(attr.name) | |||||
if not attr.value: | |||||
continue | |||||
write(" = ") | |||||
mark() | |||||
get(attr.value) | |||||
write(">") | write(">") | ||||
get(self.contents) | |||||
if len(tagnodes) == 1 and isinstance(tagnodes[0], Text): | |||||
write("</" + str(tagnodes[0]) + ">") | |||||
else: | |||||
get(self.contents) | |||||
write("</") | write("</") | ||||
get(self.tag) | |||||
get(self.closing_tag) | |||||
write(">") | write(">") | ||||
def _translate(self): | |||||
"""If the HTML-style tag has a wikicode representation, return that. | |||||
For example, ``<b>Foo</b>`` can be represented as ``'''Foo'''``. This | |||||
returns a tuple of the character starting the sequence and the | |||||
character ending it. | |||||
""" | |||||
translations = { | |||||
self.TAG_ITALIC: ("''", "''"), | |||||
self.TAG_BOLD: ("'''", "'''"), | |||||
self.TAG_UNORDERED_LIST: ("*", ""), | |||||
self.TAG_ORDERED_LIST: ("#", ""), | |||||
self.TAG_DEF_TERM: (";", ""), | |||||
self.TAG_DEF_ITEM: (":", ""), | |||||
self.TAG_RULE: ("----", ""), | |||||
} | |||||
return translations[self.type] | |||||
@property | |||||
def type(self): | |||||
"""The tag type.""" | |||||
return self._type | |||||
@property | @property | ||||
def tag(self): | def tag(self): | ||||
"""The tag itself, as a :py:class:`~.Wikicode` object.""" | """The tag itself, as a :py:class:`~.Wikicode` object.""" | ||||
@@ -182,7 +123,7 @@ class Tag(Node): | |||||
return self._contents | return self._contents | ||||
@property | @property | ||||
def attrs(self): | |||||
def attributes(self): | |||||
"""The list of attributes affecting the tag. | """The list of attributes affecting the tag. | ||||
Each attribute is an instance of :py:class:`~.Attribute`. | Each attribute is an instance of :py:class:`~.Attribute`. | ||||
@@ -190,52 +131,142 @@ class Tag(Node): | |||||
return self._attrs | return self._attrs | ||||
@property | @property | ||||
def showtag(self): | |||||
"""Whether to show the tag itself instead of a wikicode version.""" | |||||
return self._showtag | |||||
def wiki_markup(self): | |||||
"""The wikified version of a tag to show instead of HTML. | |||||
If set to a value, this will be displayed instead of the brackets. | |||||
For example, set to ``''`` to replace ``<i>`` or ``----`` to replace | |||||
``<hr>``. | |||||
""" | |||||
return self._wiki_markup | |||||
@property | @property | ||||
def self_closing(self): | def self_closing(self): | ||||
"""Whether the tag is self-closing with no content.""" | |||||
"""Whether the tag is self-closing with no content (like ``<br/>``).""" | |||||
return self._self_closing | return self._self_closing | ||||
@property | @property | ||||
def open_padding(self): | |||||
"""How much spacing to insert before the first closing >.""" | |||||
return self._open_padding | |||||
def invalid(self): | |||||
"""Whether the tag starts with a backslash after the opening bracket. | |||||
This makes the tag look like a lone close tag. It is technically | |||||
invalid and is only parsable Wikicode when the tag itself is | |||||
single-only, like ``<br>`` and ``<img>``. See | |||||
:py:func:`.definitions.is_single_only`. | |||||
""" | |||||
return self._invalid | |||||
@property | @property | ||||
def close_padding(self): | |||||
"""How much spacing to insert before the last closing >.""" | |||||
return self._close_padding | |||||
def implicit(self): | |||||
"""Whether the tag is implicitly self-closing, with no ending slash. | |||||
@type.setter | |||||
def type(self, value): | |||||
value = int(value) | |||||
if value not in self.TAGS_INVISIBLE | self.TAGS_VISIBLE: | |||||
raise ValueError(value) | |||||
self._type = value | |||||
This is only possible for specific "single" tags like ``<br>`` and | |||||
``<li>``. See :py:func:`.definitions.is_single`. This field only has an | |||||
effect if :py:attr:`self_closing` is also ``True``. | |||||
""" | |||||
return self._implicit | |||||
@property | |||||
def padding(self): | |||||
"""Spacing to insert before the first closing ``>``.""" | |||||
return self._padding | |||||
@property | |||||
def closing_tag(self): | |||||
"""The closing tag, as a :py:class:`~.Wikicode` object. | |||||
This will usually equal :py:attr:`tag`, unless there is additional | |||||
spacing, comments, or the like. | |||||
""" | |||||
return self._closing_tag | |||||
@tag.setter | @tag.setter | ||||
def tag(self, value): | def tag(self, value): | ||||
self._tag = parse_anything(value) | |||||
self._tag = self._closing_tag = parse_anything(value) | |||||
@contents.setter | @contents.setter | ||||
def contents(self, value): | def contents(self, value): | ||||
self._contents = parse_anything(value) | self._contents = parse_anything(value) | ||||
@showtag.setter | |||||
def showtag(self, value): | |||||
self._showtag = bool(value) | |||||
@wiki_markup.setter | |||||
def wiki_markup(self, value): | |||||
self._wiki_markup = str(value) if value else None | |||||
@self_closing.setter | @self_closing.setter | ||||
def self_closing(self, value): | def self_closing(self, value): | ||||
self._self_closing = bool(value) | self._self_closing = bool(value) | ||||
@open_padding.setter | |||||
def open_padding(self, value): | |||||
self._open_padding = int(value) | |||||
@invalid.setter | |||||
def invalid(self, value): | |||||
self._invalid = bool(value) | |||||
@implicit.setter | |||||
def implicit(self, value): | |||||
self._implicit = bool(value) | |||||
@close_padding.setter | |||||
def close_padding(self, value): | |||||
self._close_padding = int(value) | |||||
@padding.setter | |||||
def padding(self, value): | |||||
if not value: | |||||
self._padding = "" | |||||
else: | |||||
value = str(value) | |||||
if not value.isspace(): | |||||
raise ValueError("padding must be entirely whitespace") | |||||
self._padding = value | |||||
@closing_tag.setter | |||||
def closing_tag(self, value): | |||||
self._closing_tag = parse_anything(value) | |||||
def has(self, name): | |||||
"""Return whether any attribute in the tag has the given *name*. | |||||
Note that a tag may have multiple attributes with the same name, but | |||||
only the last one is read by the MediaWiki parser. | |||||
""" | |||||
for attr in self.attributes: | |||||
if attr.name == name.strip(): | |||||
return True | |||||
return False | |||||
def get(self, name): | |||||
"""Get the attribute with the given *name*. | |||||
The returned object is a :py:class:`~.Attribute` instance. Raises | |||||
:py:exc:`ValueError` if no attribute has this name. Since multiple | |||||
attributes can have the same name, we'll return the last match, since | |||||
all but the last are ignored by the MediaWiki parser. | |||||
""" | |||||
for attr in reversed(self.attributes): | |||||
if attr.name == name.strip(): | |||||
return attr | |||||
raise ValueError(name) | |||||
def add(self, name, value=None, quoted=True, pad_first=" ", | |||||
pad_before_eq="", pad_after_eq=""): | |||||
"""Add an attribute with the given *name* and *value*. | |||||
*name* and *value* can be anything parasable by | |||||
:py:func:`.utils.parse_anything`; *value* can be omitted if the | |||||
attribute is valueless. *quoted* is a bool telling whether to wrap the | |||||
*value* in double quotes (this is recommended). *pad_first*, | |||||
*pad_before_eq*, and *pad_after_eq* are whitespace used as padding | |||||
before the name, before the equal sign (or after the name if no value), | |||||
and after the equal sign (ignored if no value), respectively. | |||||
""" | |||||
if value is not None: | |||||
value = parse_anything(value) | |||||
attr = Attribute(parse_anything(name), value, quoted) | |||||
attr.pad_first = pad_first | |||||
attr.pad_before_eq = pad_before_eq | |||||
attr.pad_after_eq = pad_after_eq | |||||
self.attributes.append(attr) | |||||
return attr | |||||
def remove(self, name): | |||||
"""Remove all attributes with the given *name*.""" | |||||
attrs = [attr for attr in self.attributes if attr.name == name.strip()] | |||||
if not attrs: | |||||
raise ValueError(name) | |||||
for attr in attrs: | |||||
self.attributes.remove(attr) |
@@ -26,7 +26,7 @@ import re | |||||
from . import HTMLEntity, Node, Text | from . import HTMLEntity, Node, Text | ||||
from .extras import Parameter | from .extras import Parameter | ||||
from ..compat import basestring, str | |||||
from ..compat import str | |||||
from ..utils import parse_anything | from ..utils import parse_anything | ||||
__all__ = ["Template"] | __all__ = ["Template"] | ||||
@@ -84,7 +84,7 @@ class Template(Node): | |||||
replacement = str(HTMLEntity(value=ord(char))) | replacement = str(HTMLEntity(value=ord(char))) | ||||
for node in code.filter_text(recursive=False): | for node in code.filter_text(recursive=False): | ||||
if char in node: | if char in node: | ||||
code.replace(node, node.replace(char, replacement)) | |||||
code.replace(node, node.replace(char, replacement), False) | |||||
def _blank_param_value(self, value): | def _blank_param_value(self, value): | ||||
"""Remove the content from *value* while keeping its whitespace. | """Remove the content from *value* while keeping its whitespace. | ||||
@@ -164,15 +164,15 @@ class Template(Node): | |||||
def name(self, value): | def name(self, value): | ||||
self._name = parse_anything(value) | self._name = parse_anything(value) | ||||
def has_param(self, name, ignore_empty=True): | |||||
def has(self, name, ignore_empty=True): | |||||
"""Return ``True`` if any parameter in the template is named *name*. | """Return ``True`` if any parameter in the template is named *name*. | ||||
With *ignore_empty*, ``False`` will be returned even if the template | With *ignore_empty*, ``False`` will be returned even if the template | ||||
contains a parameter with the name *name*, if the parameter's value | contains a parameter with the name *name*, if the parameter's value | ||||
is empty. Note that a template may have multiple parameters with the | is empty. Note that a template may have multiple parameters with the | ||||
same name. | |||||
same name, but only the last one is read by the MediaWiki parser. | |||||
""" | """ | ||||
name = name.strip() if isinstance(name, basestring) else str(name) | |||||
name = str(name).strip() | |||||
for param in self.params: | for param in self.params: | ||||
if param.name.strip() == name: | if param.name.strip() == name: | ||||
if ignore_empty and not param.value.strip(): | if ignore_empty and not param.value.strip(): | ||||
@@ -180,6 +180,9 @@ class Template(Node): | |||||
return True | return True | ||||
return False | return False | ||||
has_param = lambda self, *args, **kwargs: self.has(*args, **kwargs) | |||||
has_param.__doc__ = "Alias for :py:meth:`has`." | |||||
def get(self, name): | def get(self, name): | ||||
"""Get the parameter whose name is *name*. | """Get the parameter whose name is *name*. | ||||
@@ -188,7 +191,7 @@ class Template(Node): | |||||
parameters can have the same name, we'll return the last match, since | parameters can have the same name, we'll return the last match, since | ||||
the last parameter is the only one read by the MediaWiki parser. | the last parameter is the only one read by the MediaWiki parser. | ||||
""" | """ | ||||
name = name.strip() if isinstance(name, basestring) else str(name) | |||||
name = str(name).strip() | |||||
for param in reversed(self.params): | for param in reversed(self.params): | ||||
if param.name.strip() == name: | if param.name.strip() == name: | ||||
return param | return param | ||||
@@ -226,7 +229,7 @@ class Template(Node): | |||||
name, value = parse_anything(name), parse_anything(value) | name, value = parse_anything(name), parse_anything(value) | ||||
self._surface_escape(value, "|") | self._surface_escape(value, "|") | ||||
if self.has_param(name): | |||||
if self.has(name): | |||||
self.remove(name, keep_field=True) | self.remove(name, keep_field=True) | ||||
existing = self.get(name) | existing = self.get(name) | ||||
if showkey is not None: | if showkey is not None: | ||||
@@ -291,7 +294,7 @@ class Template(Node): | |||||
the first instance if none have dependents, otherwise the one with | the first instance if none have dependents, otherwise the one with | ||||
dependents will be kept). | dependents will be kept). | ||||
""" | """ | ||||
name = name.strip() if isinstance(name, basestring) else str(name) | |||||
name = str(name).strip() | |||||
removed = False | removed = False | ||||
to_remove = [] | to_remove = [] | ||||
for i, param in enumerate(self.params): | for i, param in enumerate(self.params): | ||||
@@ -46,16 +46,15 @@ class Parser(object): | |||||
:py:class:`~.Node`\ s by the :py:class:`~.Builder`. | :py:class:`~.Node`\ s by the :py:class:`~.Builder`. | ||||
""" | """ | ||||
def __init__(self, text): | |||||
self.text = text | |||||
def __init__(self): | |||||
if use_c and CTokenizer: | if use_c and CTokenizer: | ||||
self._tokenizer = CTokenizer() | self._tokenizer = CTokenizer() | ||||
else: | else: | ||||
self._tokenizer = Tokenizer() | self._tokenizer = Tokenizer() | ||||
self._builder = Builder() | self._builder = Builder() | ||||
def parse(self): | |||||
"""Return a string as a parsed :py:class:`~.Wikicode` object tree.""" | |||||
tokens = self._tokenizer.tokenize(self.text) | |||||
def parse(self, text, context=0): | |||||
"""Parse *text*, returning a :py:class:`~.Wikicode` object tree.""" | |||||
tokens = self._tokenizer.tokenize(text, context) | |||||
code = self._builder.build(tokens) | code = self._builder.build(tokens) | ||||
return code | return code |
@@ -24,8 +24,8 @@ from __future__ import unicode_literals | |||||
from . import tokens | from . import tokens | ||||
from ..compat import str | from ..compat import str | ||||
from ..nodes import (Argument, Comment, Heading, HTMLEntity, Tag, Template, | |||||
Text, Wikilink) | |||||
from ..nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, Tag, | |||||
Template, Text, Wikilink) | |||||
from ..nodes.extras import Attribute, Parameter | from ..nodes.extras import Attribute, Parameter | ||||
from ..smart_list import SmartList | from ..smart_list import SmartList | ||||
from ..wikicode import Wikicode | from ..wikicode import Wikicode | ||||
@@ -83,7 +83,7 @@ class Builder(object): | |||||
tokens.TemplateClose)): | tokens.TemplateClose)): | ||||
self._tokens.append(token) | self._tokens.append(token) | ||||
value = self._pop() | value = self._pop() | ||||
if not key: | |||||
if key is None: | |||||
key = self._wrap([Text(str(default))]) | key = self._wrap([Text(str(default))]) | ||||
return Parameter(key, value, showkey) | return Parameter(key, value, showkey) | ||||
else: | else: | ||||
@@ -142,6 +142,22 @@ class Builder(object): | |||||
else: | else: | ||||
self._write(self._handle_token(token)) | self._write(self._handle_token(token)) | ||||
def _handle_external_link(self, token): | |||||
"""Handle when an external link is at the head of the tokens.""" | |||||
brackets, url = token.brackets, None | |||||
self._push() | |||||
while self._tokens: | |||||
token = self._tokens.pop() | |||||
if isinstance(token, tokens.ExternalLinkSeparator): | |||||
url = self._pop() | |||||
self._push() | |||||
elif isinstance(token, tokens.ExternalLinkClose): | |||||
if url is not None: | |||||
return ExternalLink(url, self._pop(), brackets) | |||||
return ExternalLink(self._pop(), brackets=brackets) | |||||
else: | |||||
self._write(self._handle_token(token)) | |||||
def _handle_entity(self): | def _handle_entity(self): | ||||
"""Handle a case where an HTML entity is at the head of the tokens.""" | """Handle a case where an HTML entity is at the head of the tokens.""" | ||||
token = self._tokens.pop() | token = self._tokens.pop() | ||||
@@ -170,7 +186,7 @@ class Builder(object): | |||||
self._write(self._handle_token(token)) | self._write(self._handle_token(token)) | ||||
def _handle_comment(self): | def _handle_comment(self): | ||||
"""Handle a case where a hidden comment is at the head of the tokens.""" | |||||
"""Handle a case where an HTML comment is at the head of the tokens.""" | |||||
self._push() | self._push() | ||||
while self._tokens: | while self._tokens: | ||||
token = self._tokens.pop() | token = self._tokens.pop() | ||||
@@ -180,7 +196,7 @@ class Builder(object): | |||||
else: | else: | ||||
self._write(self._handle_token(token)) | self._write(self._handle_token(token)) | ||||
def _handle_attribute(self): | |||||
def _handle_attribute(self, start): | |||||
"""Handle a case where a tag attribute is at the head of the tokens.""" | """Handle a case where a tag attribute is at the head of the tokens.""" | ||||
name, quoted = None, False | name, quoted = None, False | ||||
self._push() | self._push() | ||||
@@ -191,37 +207,46 @@ class Builder(object): | |||||
self._push() | self._push() | ||||
elif isinstance(token, tokens.TagAttrQuote): | elif isinstance(token, tokens.TagAttrQuote): | ||||
quoted = True | quoted = True | ||||
elif isinstance(token, (tokens.TagAttrStart, | |||||
tokens.TagCloseOpen)): | |||||
elif isinstance(token, (tokens.TagAttrStart, tokens.TagCloseOpen, | |||||
tokens.TagCloseSelfclose)): | |||||
self._tokens.append(token) | self._tokens.append(token) | ||||
if name is not None: | |||||
return Attribute(name, self._pop(), quoted) | |||||
return Attribute(self._pop(), quoted=quoted) | |||||
if name: | |||||
value = self._pop() | |||||
else: | |||||
name, value = self._pop(), None | |||||
return Attribute(name, value, quoted, start.pad_first, | |||||
start.pad_before_eq, start.pad_after_eq) | |||||
else: | else: | ||||
self._write(self._handle_token(token)) | self._write(self._handle_token(token)) | ||||
def _handle_tag(self, token): | def _handle_tag(self, token): | ||||
"""Handle a case where a tag is at the head of the tokens.""" | """Handle a case where a tag is at the head of the tokens.""" | ||||
type_, showtag = token.type, token.showtag | |||||
attrs = [] | |||||
close_tokens = (tokens.TagCloseSelfclose, tokens.TagCloseClose) | |||||
implicit, attrs, contents, closing_tag = False, [], None, None | |||||
wiki_markup, invalid = token.wiki_markup, token.invalid or False | |||||
self._push() | self._push() | ||||
while self._tokens: | while self._tokens: | ||||
token = self._tokens.pop() | token = self._tokens.pop() | ||||
if isinstance(token, tokens.TagAttrStart): | if isinstance(token, tokens.TagAttrStart): | ||||
attrs.append(self._handle_attribute()) | |||||
attrs.append(self._handle_attribute(token)) | |||||
elif isinstance(token, tokens.TagCloseOpen): | elif isinstance(token, tokens.TagCloseOpen): | ||||
open_pad = token.padding | |||||
padding = token.padding or "" | |||||
tag = self._pop() | tag = self._pop() | ||||
self._push() | self._push() | ||||
elif isinstance(token, tokens.TagCloseSelfclose): | |||||
tag = self._pop() | |||||
return Tag(type_, tag, attrs=attrs, showtag=showtag, | |||||
self_closing=True, open_padding=token.padding) | |||||
elif isinstance(token, tokens.TagOpenClose): | elif isinstance(token, tokens.TagOpenClose): | ||||
contents = self._pop() | contents = self._pop() | ||||
elif isinstance(token, tokens.TagCloseClose): | |||||
return Tag(type_, tag, contents, attrs, showtag, False, | |||||
open_pad, token.padding) | |||||
self._push() | |||||
elif isinstance(token, close_tokens): | |||||
if isinstance(token, tokens.TagCloseSelfclose): | |||||
tag = self._pop() | |||||
self_closing = True | |||||
padding = token.padding or "" | |||||
implicit = token.implicit or False | |||||
else: | |||||
self_closing = False | |||||
closing_tag = self._pop() | |||||
return Tag(tag, contents, attrs, wiki_markup, self_closing, | |||||
invalid, implicit, padding, closing_tag) | |||||
else: | else: | ||||
self._write(self._handle_token(token)) | self._write(self._handle_token(token)) | ||||
@@ -235,6 +260,8 @@ class Builder(object): | |||||
return self._handle_argument() | return self._handle_argument() | ||||
elif isinstance(token, tokens.WikilinkOpen): | elif isinstance(token, tokens.WikilinkOpen): | ||||
return self._handle_wikilink() | return self._handle_wikilink() | ||||
elif isinstance(token, tokens.ExternalLinkOpen): | |||||
return self._handle_external_link(token) | |||||
elif isinstance(token, tokens.HTMLEntityStart): | elif isinstance(token, tokens.HTMLEntityStart): | ||||
return self._handle_entity() | return self._handle_entity() | ||||
elif isinstance(token, tokens.HeadingStart): | elif isinstance(token, tokens.HeadingStart): | ||||
@@ -51,6 +51,12 @@ Local (stack-specific) contexts: | |||||
* :py:const:`WIKILINK_TITLE` | * :py:const:`WIKILINK_TITLE` | ||||
* :py:const:`WIKILINK_TEXT` | * :py:const:`WIKILINK_TEXT` | ||||
* :py:const:`EXT_LINK` | |||||
* :py:const:`EXT_LINK_URI` | |||||
* :py:const:`EXT_LINK_TITLE` | |||||
* :py:const:`EXT_LINK_BRACKETS` | |||||
* :py:const:`HEADING` | * :py:const:`HEADING` | ||||
* :py:const:`HEADING_LEVEL_1` | * :py:const:`HEADING_LEVEL_1` | ||||
@@ -60,7 +66,21 @@ Local (stack-specific) contexts: | |||||
* :py:const:`HEADING_LEVEL_5` | * :py:const:`HEADING_LEVEL_5` | ||||
* :py:const:`HEADING_LEVEL_6` | * :py:const:`HEADING_LEVEL_6` | ||||
* :py:const:`COMMENT` | |||||
* :py:const:`TAG` | |||||
* :py:const:`TAG_OPEN` | |||||
* :py:const:`TAG_ATTR` | |||||
* :py:const:`TAG_BODY` | |||||
* :py:const:`TAG_CLOSE` | |||||
* :py:const:`STYLE` | |||||
* :py:const:`STYLE_ITALICS` | |||||
* :py:const:`STYLE_BOLD` | |||||
* :py:const:`STYLE_PASS_AGAIN` | |||||
* :py:const:`STYLE_SECOND_PASS` | |||||
* :py:const:`DL_TERM` | |||||
* :py:const:`SAFETY_CHECK` | * :py:const:`SAFETY_CHECK` | ||||
@@ -74,41 +94,76 @@ Local (stack-specific) contexts: | |||||
Global contexts: | Global contexts: | ||||
* :py:const:`GL_HEADING` | * :py:const:`GL_HEADING` | ||||
Aggregate contexts: | |||||
* :py:const:`FAIL` | |||||
* :py:const:`UNSAFE` | |||||
* :py:const:`DOUBLE` | |||||
* :py:const:`INVALID_LINK` | |||||
""" | """ | ||||
# Local contexts: | # Local contexts: | ||||
TEMPLATE = 0b00000000000000000111 | |||||
TEMPLATE_NAME = 0b00000000000000000001 | |||||
TEMPLATE_PARAM_KEY = 0b00000000000000000010 | |||||
TEMPLATE_PARAM_VALUE = 0b00000000000000000100 | |||||
ARGUMENT = 0b00000000000000011000 | |||||
ARGUMENT_NAME = 0b00000000000000001000 | |||||
ARGUMENT_DEFAULT = 0b00000000000000010000 | |||||
WIKILINK = 0b00000000000001100000 | |||||
WIKILINK_TITLE = 0b00000000000000100000 | |||||
WIKILINK_TEXT = 0b00000000000001000000 | |||||
HEADING = 0b00000001111110000000 | |||||
HEADING_LEVEL_1 = 0b00000000000010000000 | |||||
HEADING_LEVEL_2 = 0b00000000000100000000 | |||||
HEADING_LEVEL_3 = 0b00000000001000000000 | |||||
HEADING_LEVEL_4 = 0b00000000010000000000 | |||||
HEADING_LEVEL_5 = 0b00000000100000000000 | |||||
HEADING_LEVEL_6 = 0b00000001000000000000 | |||||
COMMENT = 0b00000010000000000000 | |||||
SAFETY_CHECK = 0b11111100000000000000 | |||||
HAS_TEXT = 0b00000100000000000000 | |||||
FAIL_ON_TEXT = 0b00001000000000000000 | |||||
FAIL_NEXT = 0b00010000000000000000 | |||||
FAIL_ON_LBRACE = 0b00100000000000000000 | |||||
FAIL_ON_RBRACE = 0b01000000000000000000 | |||||
FAIL_ON_EQUALS = 0b10000000000000000000 | |||||
TEMPLATE_NAME = 1 << 0 | |||||
TEMPLATE_PARAM_KEY = 1 << 1 | |||||
TEMPLATE_PARAM_VALUE = 1 << 2 | |||||
TEMPLATE = TEMPLATE_NAME + TEMPLATE_PARAM_KEY + TEMPLATE_PARAM_VALUE | |||||
ARGUMENT_NAME = 1 << 3 | |||||
ARGUMENT_DEFAULT = 1 << 4 | |||||
ARGUMENT = ARGUMENT_NAME + ARGUMENT_DEFAULT | |||||
WIKILINK_TITLE = 1 << 5 | |||||
WIKILINK_TEXT = 1 << 6 | |||||
WIKILINK = WIKILINK_TITLE + WIKILINK_TEXT | |||||
EXT_LINK_URI = 1 << 7 | |||||
EXT_LINK_TITLE = 1 << 8 | |||||
EXT_LINK_BRACKETS = 1 << 9 | |||||
EXT_LINK = EXT_LINK_URI + EXT_LINK_TITLE + EXT_LINK_BRACKETS | |||||
HEADING_LEVEL_1 = 1 << 10 | |||||
HEADING_LEVEL_2 = 1 << 11 | |||||
HEADING_LEVEL_3 = 1 << 12 | |||||
HEADING_LEVEL_4 = 1 << 13 | |||||
HEADING_LEVEL_5 = 1 << 14 | |||||
HEADING_LEVEL_6 = 1 << 15 | |||||
HEADING = (HEADING_LEVEL_1 + HEADING_LEVEL_2 + HEADING_LEVEL_3 + | |||||
HEADING_LEVEL_4 + HEADING_LEVEL_5 + HEADING_LEVEL_6) | |||||
TAG_OPEN = 1 << 16 | |||||
TAG_ATTR = 1 << 17 | |||||
TAG_BODY = 1 << 18 | |||||
TAG_CLOSE = 1 << 19 | |||||
TAG = TAG_OPEN + TAG_ATTR + TAG_BODY + TAG_CLOSE | |||||
STYLE_ITALICS = 1 << 20 | |||||
STYLE_BOLD = 1 << 21 | |||||
STYLE_PASS_AGAIN = 1 << 22 | |||||
STYLE_SECOND_PASS = 1 << 23 | |||||
STYLE = STYLE_ITALICS + STYLE_BOLD + STYLE_PASS_AGAIN + STYLE_SECOND_PASS | |||||
DL_TERM = 1 << 24 | |||||
HAS_TEXT = 1 << 25 | |||||
FAIL_ON_TEXT = 1 << 26 | |||||
FAIL_NEXT = 1 << 27 | |||||
FAIL_ON_LBRACE = 1 << 28 | |||||
FAIL_ON_RBRACE = 1 << 29 | |||||
FAIL_ON_EQUALS = 1 << 30 | |||||
SAFETY_CHECK = (HAS_TEXT + FAIL_ON_TEXT + FAIL_NEXT + FAIL_ON_LBRACE + | |||||
FAIL_ON_RBRACE + FAIL_ON_EQUALS) | |||||
# Global contexts: | # Global contexts: | ||||
GL_HEADING = 0b1 | |||||
GL_HEADING = 1 << 0 | |||||
# Aggregate contexts: | |||||
FAIL = TEMPLATE + ARGUMENT + WIKILINK + EXT_LINK_TITLE + HEADING + TAG + STYLE | |||||
UNSAFE = (TEMPLATE_NAME + WIKILINK + EXT_LINK_TITLE + TEMPLATE_PARAM_KEY + | |||||
ARGUMENT_NAME + TAG_CLOSE) | |||||
DOUBLE = TEMPLATE_PARAM_KEY + TAG_CLOSE | |||||
INVALID_LINK = TEMPLATE_NAME + ARGUMENT_NAME + WIKILINK + EXT_LINK |
@@ -28,6 +28,7 @@ SOFTWARE. | |||||
#include <Python.h> | #include <Python.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include <structmember.h> | #include <structmember.h> | ||||
#include <bytesobject.h> | |||||
#if PY_MAJOR_VERSION >= 3 | #if PY_MAJOR_VERSION >= 3 | ||||
#define IS_PY3K | #define IS_PY3K | ||||
@@ -41,8 +42,8 @@ SOFTWARE. | |||||
#define ALPHANUM "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | #define ALPHANUM "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||||
static const char* MARKERS[] = { | static const char* MARKERS[] = { | ||||
"{", "}", "[", "]", "<", ">", "|", "=", "&", "#", "*", ";", ":", "/", "-", | |||||
"!", "\n", ""}; | |||||
"{", "}", "[", "]", "<", ">", "|", "=", "&", "'", "#", "*", ";", ":", "/", | |||||
"-", "\n", ""}; | |||||
#define NUM_MARKERS 18 | #define NUM_MARKERS 18 | ||||
#define TEXTBUFFER_BLOCKSIZE 1024 | #define TEXTBUFFER_BLOCKSIZE 1024 | ||||
@@ -51,19 +52,20 @@ static const char* MARKERS[] = { | |||||
#define MAX_BRACES 255 | #define MAX_BRACES 255 | ||||
#define MAX_ENTITY_SIZE 8 | #define MAX_ENTITY_SIZE 8 | ||||
static int route_state = 0; | |||||
#define BAD_ROUTE (route_state) | |||||
#define FAIL_ROUTE() (route_state = 1) | |||||
#define RESET_ROUTE() (route_state = 0) | |||||
static int route_state = 0, route_context = 0; | |||||
#define BAD_ROUTE route_state | |||||
#define BAD_ROUTE_CONTEXT route_context | |||||
#define FAIL_ROUTE(context) route_state = 1; route_context = context | |||||
#define RESET_ROUTE() route_state = 0 | |||||
static char** entitydefs; | static char** entitydefs; | ||||
static PyObject* EMPTY; | static PyObject* EMPTY; | ||||
static PyObject* NOARGS; | static PyObject* NOARGS; | ||||
static PyObject* tokens; | |||||
static PyObject* definitions; | |||||
/* Tokens */ | |||||
/* Tokens: */ | |||||
static PyObject* Text; | static PyObject* Text; | ||||
@@ -80,6 +82,10 @@ static PyObject* WikilinkOpen; | |||||
static PyObject* WikilinkSeparator; | static PyObject* WikilinkSeparator; | ||||
static PyObject* WikilinkClose; | static PyObject* WikilinkClose; | ||||
static PyObject* ExternalLinkOpen; | |||||
static PyObject* ExternalLinkSeparator; | |||||
static PyObject* ExternalLinkClose; | |||||
static PyObject* HTMLEntityStart; | static PyObject* HTMLEntityStart; | ||||
static PyObject* HTMLEntityNumeric; | static PyObject* HTMLEntityNumeric; | ||||
static PyObject* HTMLEntityHex; | static PyObject* HTMLEntityHex; | ||||
@@ -102,47 +108,83 @@ static PyObject* TagCloseClose; | |||||
/* Local contexts: */ | /* Local contexts: */ | ||||
#define LC_TEMPLATE 0x00007 | |||||
#define LC_TEMPLATE_NAME 0x00001 | |||||
#define LC_TEMPLATE_PARAM_KEY 0x00002 | |||||
#define LC_TEMPLATE_PARAM_VALUE 0x00004 | |||||
#define LC_ARGUMENT 0x00018 | |||||
#define LC_ARGUMENT_NAME 0x00008 | |||||
#define LC_ARGUMENT_DEFAULT 0x00010 | |||||
#define LC_WIKILINK 0x00060 | |||||
#define LC_WIKILINK_TITLE 0x00020 | |||||
#define LC_WIKILINK_TEXT 0x00040 | |||||
#define LC_HEADING 0x01F80 | |||||
#define LC_HEADING_LEVEL_1 0x00080 | |||||
#define LC_HEADING_LEVEL_2 0x00100 | |||||
#define LC_HEADING_LEVEL_3 0x00200 | |||||
#define LC_HEADING_LEVEL_4 0x00400 | |||||
#define LC_HEADING_LEVEL_5 0x00800 | |||||
#define LC_HEADING_LEVEL_6 0x01000 | |||||
#define LC_COMMENT 0x02000 | |||||
#define LC_SAFETY_CHECK 0xFC000 | |||||
#define LC_HAS_TEXT 0x04000 | |||||
#define LC_FAIL_ON_TEXT 0x08000 | |||||
#define LC_FAIL_NEXT 0x10000 | |||||
#define LC_FAIL_ON_LBRACE 0x20000 | |||||
#define LC_FAIL_ON_RBRACE 0x40000 | |||||
#define LC_FAIL_ON_EQUALS 0x80000 | |||||
#define LC_TEMPLATE 0x00000007 | |||||
#define LC_TEMPLATE_NAME 0x00000001 | |||||
#define LC_TEMPLATE_PARAM_KEY 0x00000002 | |||||
#define LC_TEMPLATE_PARAM_VALUE 0x00000004 | |||||
#define LC_ARGUMENT 0x00000018 | |||||
#define LC_ARGUMENT_NAME 0x00000008 | |||||
#define LC_ARGUMENT_DEFAULT 0x00000010 | |||||
#define LC_WIKILINK 0x00000060 | |||||
#define LC_WIKILINK_TITLE 0x00000020 | |||||
#define LC_WIKILINK_TEXT 0x00000040 | |||||
#define LC_EXT_LINK 0x00000380 | |||||
#define LC_EXT_LINK_URI 0x00000080 | |||||
#define LC_EXT_LINK_TITLE 0x00000100 | |||||
#define LC_EXT_LINK_BRACKETS 0x00000200 | |||||
#define LC_HEADING 0x0000FC00 | |||||
#define LC_HEADING_LEVEL_1 0x00000400 | |||||
#define LC_HEADING_LEVEL_2 0x00000800 | |||||
#define LC_HEADING_LEVEL_3 0x00001000 | |||||
#define LC_HEADING_LEVEL_4 0x00002000 | |||||
#define LC_HEADING_LEVEL_5 0x00004000 | |||||
#define LC_HEADING_LEVEL_6 0x00008000 | |||||
#define LC_TAG 0x000F0000 | |||||
#define LC_TAG_OPEN 0x00010000 | |||||
#define LC_TAG_ATTR 0x00020000 | |||||
#define LC_TAG_BODY 0x00040000 | |||||
#define LC_TAG_CLOSE 0x00080000 | |||||
#define LC_STYLE 0x00F00000 | |||||
#define LC_STYLE_ITALICS 0x00100000 | |||||
#define LC_STYLE_BOLD 0x00200000 | |||||
#define LC_STYLE_PASS_AGAIN 0x00400000 | |||||
#define LC_STYLE_SECOND_PASS 0x00800000 | |||||
#define LC_DLTERM 0x01000000 | |||||
#define LC_SAFETY_CHECK 0x7E000000 | |||||
#define LC_HAS_TEXT 0x02000000 | |||||
#define LC_FAIL_ON_TEXT 0x04000000 | |||||
#define LC_FAIL_NEXT 0x08000000 | |||||
#define LC_FAIL_ON_LBRACE 0x10000000 | |||||
#define LC_FAIL_ON_RBRACE 0x20000000 | |||||
#define LC_FAIL_ON_EQUALS 0x40000000 | |||||
/* Global contexts: */ | /* Global contexts: */ | ||||
#define GL_HEADING 0x1 | #define GL_HEADING 0x1 | ||||
/* Aggregate contexts: */ | |||||
#define AGG_FAIL (LC_TEMPLATE | LC_ARGUMENT | LC_WIKILINK | LC_EXT_LINK_TITLE | LC_HEADING | LC_TAG | LC_STYLE) | |||||
#define AGG_UNSAFE (LC_TEMPLATE_NAME | LC_WIKILINK | LC_EXT_LINK_TITLE | LC_TEMPLATE_PARAM_KEY | LC_ARGUMENT_NAME) | |||||
#define AGG_DOUBLE (LC_TEMPLATE_PARAM_KEY | LC_TAG_CLOSE) | |||||
#define AGG_INVALID_LINK (LC_TEMPLATE_NAME | LC_ARGUMENT_NAME | LC_WIKILINK | LC_EXT_LINK) | |||||
/* Tag contexts: */ | |||||
#define TAG_NAME 0x01 | |||||
#define TAG_ATTR_READY 0x02 | |||||
#define TAG_ATTR_NAME 0x04 | |||||
#define TAG_ATTR_VALUE 0x08 | |||||
#define TAG_QUOTED 0x10 | |||||
#define TAG_NOTE_SPACE 0x20 | |||||
#define TAG_NOTE_EQUALS 0x40 | |||||
#define TAG_NOTE_QUOTE 0x80 | |||||
/* Miscellaneous structs: */ | /* Miscellaneous structs: */ | ||||
struct Textbuffer { | struct Textbuffer { | ||||
Py_ssize_t size; | Py_ssize_t size; | ||||
Py_UNICODE* data; | Py_UNICODE* data; | ||||
struct Textbuffer* prev; | |||||
struct Textbuffer* next; | struct Textbuffer* next; | ||||
}; | }; | ||||
@@ -158,13 +200,24 @@ typedef struct { | |||||
int level; | int level; | ||||
} HeadingData; | } HeadingData; | ||||
typedef struct { | |||||
int context; | |||||
struct Textbuffer* pad_first; | |||||
struct Textbuffer* pad_before_eq; | |||||
struct Textbuffer* pad_after_eq; | |||||
Py_ssize_t reset; | |||||
} TagData; | |||||
typedef struct Textbuffer Textbuffer; | |||||
typedef struct Stack Stack; | |||||
/* Tokenizer object definition: */ | /* Tokenizer object definition: */ | ||||
typedef struct { | typedef struct { | ||||
PyObject_HEAD | PyObject_HEAD | ||||
PyObject* text; /* text to tokenize */ | PyObject* text; /* text to tokenize */ | ||||
struct Stack* topstack; /* topmost stack */ | |||||
Stack* topstack; /* topmost stack */ | |||||
Py_ssize_t head; /* current position in text */ | Py_ssize_t head; /* current position in text */ | ||||
Py_ssize_t length; /* length of text */ | Py_ssize_t length; /* length of text */ | ||||
int global; /* global context */ | int global; /* global context */ | ||||
@@ -173,78 +226,80 @@ typedef struct { | |||||
} Tokenizer; | } Tokenizer; | ||||
/* Macros for accessing Tokenizer data: */ | |||||
/* Macros related to Tokenizer functions: */ | |||||
#define Tokenizer_READ(self, delta) (*PyUnicode_AS_UNICODE(Tokenizer_read(self, delta))) | #define Tokenizer_READ(self, delta) (*PyUnicode_AS_UNICODE(Tokenizer_read(self, delta))) | ||||
#define Tokenizer_READ_BACKWARDS(self, delta) \ | |||||
(*PyUnicode_AS_UNICODE(Tokenizer_read_backwards(self, delta))) | |||||
#define Tokenizer_CAN_RECURSE(self) (self->depth < MAX_DEPTH && self->cycles < MAX_CYCLES) | #define Tokenizer_CAN_RECURSE(self) (self->depth < MAX_DEPTH && self->cycles < MAX_CYCLES) | ||||
#define Tokenizer_emit(self, token) Tokenizer_emit_token(self, token, 0) | |||||
#define Tokenizer_emit_first(self, token) Tokenizer_emit_token(self, token, 1) | |||||
#define Tokenizer_emit_kwargs(self, token, kwargs) Tokenizer_emit_token_kwargs(self, token, kwargs, 0) | |||||
#define Tokenizer_emit_first_kwargs(self, token, kwargs) Tokenizer_emit_token_kwargs(self, token, kwargs, 1) | |||||
/* Macros for accessing definitions: */ | |||||
#define GET_HTML_TAG(markup) (markup == *":" ? "dd" : markup == *";" ? "dt" : "li") | |||||
#define IS_PARSABLE(tag) (call_def_func("is_parsable", tag, NULL, NULL)) | |||||
#define IS_SINGLE(tag) (call_def_func("is_single", tag, NULL, NULL)) | |||||
#define IS_SINGLE_ONLY(tag) (call_def_func("is_single_only", tag, NULL, NULL)) | |||||
#define IS_SCHEME(scheme, slashes, reverse) \ | |||||
(call_def_func("is_scheme", scheme, slashes ? Py_True : Py_False, reverse ? Py_True : Py_False)) | |||||
/* Function prototypes: */ | /* Function prototypes: */ | ||||
static int heading_level_from_context(int); | |||||
static Textbuffer* Textbuffer_new(void); | |||||
static void Textbuffer_dealloc(Textbuffer*); | |||||
static TagData* TagData_new(void); | |||||
static void TagData_dealloc(TagData*); | |||||
static PyObject* Tokenizer_new(PyTypeObject*, PyObject*, PyObject*); | static PyObject* Tokenizer_new(PyTypeObject*, PyObject*, PyObject*); | ||||
static struct Textbuffer* Textbuffer_new(void); | |||||
static void Tokenizer_dealloc(Tokenizer*); | static void Tokenizer_dealloc(Tokenizer*); | ||||
static void Textbuffer_dealloc(struct Textbuffer*); | |||||
static int Tokenizer_init(Tokenizer*, PyObject*, PyObject*); | static int Tokenizer_init(Tokenizer*, PyObject*, PyObject*); | ||||
static int Tokenizer_push(Tokenizer*, int); | |||||
static PyObject* Textbuffer_render(struct Textbuffer*); | |||||
static int Tokenizer_push_textbuffer(Tokenizer*); | |||||
static void Tokenizer_delete_top_of_stack(Tokenizer*); | |||||
static PyObject* Tokenizer_pop(Tokenizer*); | |||||
static PyObject* Tokenizer_pop_keeping_context(Tokenizer*); | |||||
static void* Tokenizer_fail_route(Tokenizer*); | |||||
static int Tokenizer_write(Tokenizer*, PyObject*); | |||||
static int Tokenizer_write_first(Tokenizer*, PyObject*); | |||||
static int Tokenizer_write_text(Tokenizer*, Py_UNICODE); | |||||
static int Tokenizer_write_all(Tokenizer*, PyObject*); | |||||
static int Tokenizer_write_text_then_stack(Tokenizer*, const char*); | |||||
static PyObject* Tokenizer_read(Tokenizer*, Py_ssize_t); | |||||
static PyObject* Tokenizer_read_backwards(Tokenizer*, Py_ssize_t); | |||||
static int Tokenizer_parse_template_or_argument(Tokenizer*); | |||||
static int Tokenizer_parse_template(Tokenizer*); | |||||
static int Tokenizer_parse_argument(Tokenizer*); | |||||
static int Tokenizer_handle_template_param(Tokenizer*); | |||||
static int Tokenizer_handle_template_param_value(Tokenizer*); | |||||
static PyObject* Tokenizer_handle_template_end(Tokenizer*); | |||||
static int Tokenizer_handle_argument_separator(Tokenizer*); | |||||
static PyObject* Tokenizer_handle_argument_end(Tokenizer*); | |||||
static int Tokenizer_parse_wikilink(Tokenizer*); | |||||
static int Tokenizer_handle_wikilink_separator(Tokenizer*); | |||||
static PyObject* Tokenizer_handle_wikilink_end(Tokenizer*); | |||||
static int Tokenizer_parse_heading(Tokenizer*); | |||||
static HeadingData* Tokenizer_handle_heading_end(Tokenizer*); | |||||
static int Tokenizer_really_parse_entity(Tokenizer*); | |||||
static int Tokenizer_parse_entity(Tokenizer*); | static int Tokenizer_parse_entity(Tokenizer*); | ||||
static int Tokenizer_parse_comment(Tokenizer*); | |||||
static int Tokenizer_verify_safe(Tokenizer*, int, Py_UNICODE); | |||||
static PyObject* Tokenizer_parse(Tokenizer*, int); | |||||
static int Tokenizer_handle_dl_term(Tokenizer*); | |||||
static int Tokenizer_parse_tag(Tokenizer*); | |||||
static PyObject* Tokenizer_parse(Tokenizer*, int, int); | |||||
static PyObject* Tokenizer_tokenize(Tokenizer*, PyObject*); | static PyObject* Tokenizer_tokenize(Tokenizer*, PyObject*); | ||||
/* Macros for Python 2/3 compatibility: */ | |||||
#ifdef IS_PY3K | |||||
#define NEW_INT_FUNC PyLong_FromSsize_t | |||||
#define IMPORT_NAME_FUNC PyUnicode_FromString | |||||
#define CREATE_MODULE PyModule_Create(&module_def); | |||||
#define ENTITYDEFS_MODULE "html.entities" | |||||
#define INIT_FUNC_NAME PyInit__tokenizer | |||||
#define INIT_ERROR return NULL | |||||
#else | |||||
#define NEW_INT_FUNC PyInt_FromSsize_t | |||||
#define IMPORT_NAME_FUNC PyBytes_FromString | |||||
#define CREATE_MODULE Py_InitModule("_tokenizer", NULL); | |||||
#define ENTITYDEFS_MODULE "htmlentitydefs" | |||||
#define INIT_FUNC_NAME init_tokenizer | |||||
#define INIT_ERROR return | |||||
#endif | |||||
/* More structs for creating the Tokenizer type: */ | /* More structs for creating the Tokenizer type: */ | ||||
static PyMethodDef | |||||
Tokenizer_methods[] = { | |||||
static PyMethodDef Tokenizer_methods[] = { | |||||
{"tokenize", (PyCFunction) Tokenizer_tokenize, METH_VARARGS, | {"tokenize", (PyCFunction) Tokenizer_tokenize, METH_VARARGS, | ||||
"Build a list of tokens from a string of wikicode and return it."}, | "Build a list of tokens from a string of wikicode and return it."}, | ||||
{NULL} | {NULL} | ||||
}; | }; | ||||
static PyMemberDef | |||||
Tokenizer_members[] = { | |||||
static PyMemberDef Tokenizer_members[] = { | |||||
{NULL} | {NULL} | ||||
}; | }; | ||||
static PyMethodDef | |||||
module_methods[] = { | |||||
{NULL} | |||||
}; | |||||
static PyTypeObject | |||||
TokenizerType = { | |||||
PyObject_HEAD_INIT(NULL) | |||||
0, /* ob_size */ | |||||
static PyTypeObject TokenizerType = { | |||||
PyVarObject_HEAD_INIT(NULL, 0) | |||||
"_tokenizer.CTokenizer", /* tp_name */ | "_tokenizer.CTokenizer", /* tp_name */ | ||||
sizeof(Tokenizer), /* tp_basicsize */ | sizeof(Tokenizer), /* tp_basicsize */ | ||||
0, /* tp_itemsize */ | 0, /* tp_itemsize */ | ||||
@@ -283,3 +338,12 @@ TokenizerType = { | |||||
0, /* tp_alloc */ | 0, /* tp_alloc */ | ||||
Tokenizer_new, /* tp_new */ | Tokenizer_new, /* tp_new */ | ||||
}; | }; | ||||
#ifdef IS_PY3K | |||||
static PyModuleDef module_def = { | |||||
PyModuleDef_HEAD_INIT, | |||||
"_tokenizer", | |||||
"Creates a list of tokens from a string of wikicode.", | |||||
-1, NULL, NULL, NULL, NULL, NULL | |||||
}; | |||||
#endif |
@@ -30,7 +30,7 @@ into the :py:class`~.Wikicode` tree by the :py:class:`~.Builder`. | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
from ..compat import basestring, py3k | |||||
from ..compat import py3k, str | |||||
__all__ = ["Token"] | __all__ = ["Token"] | ||||
@@ -43,7 +43,7 @@ class Token(object): | |||||
def __repr__(self): | def __repr__(self): | ||||
args = [] | args = [] | ||||
for key, value in self._kwargs.items(): | for key, value in self._kwargs.items(): | ||||
if isinstance(value, basestring) and len(value) > 100: | |||||
if isinstance(value, str) and len(value) > 100: | |||||
args.append(key + "=" + repr(value[:97] + "...")) | args.append(key + "=" + repr(value[:97] + "...")) | ||||
else: | else: | ||||
args.append(key + "=" + repr(value)) | args.append(key + "=" + repr(value)) | ||||
@@ -55,7 +55,7 @@ class Token(object): | |||||
return False | return False | ||||
def __getattr__(self, key): | def __getattr__(self, key): | ||||
return self._kwargs[key] | |||||
return self._kwargs.get(key) | |||||
def __setattr__(self, key, value): | def __setattr__(self, key, value): | ||||
self._kwargs[key] = value | self._kwargs[key] = value | ||||
@@ -84,6 +84,10 @@ WikilinkOpen = make("WikilinkOpen") # [[ | |||||
WikilinkSeparator = make("WikilinkSeparator") # | | WikilinkSeparator = make("WikilinkSeparator") # | | ||||
WikilinkClose = make("WikilinkClose") # ]] | WikilinkClose = make("WikilinkClose") # ]] | ||||
ExternalLinkOpen = make("ExternalLinkOpen") # [ | |||||
ExternalLinkSeparator = make("ExternalLinkSeparator") # | |||||
ExternalLinkClose = make("ExternalLinkClose") # ] | |||||
HTMLEntityStart = make("HTMLEntityStart") # & | HTMLEntityStart = make("HTMLEntityStart") # & | ||||
HTMLEntityNumeric = make("HTMLEntityNumeric") # # | HTMLEntityNumeric = make("HTMLEntityNumeric") # # | ||||
HTMLEntityHex = make("HTMLEntityHex") # x | HTMLEntityHex = make("HTMLEntityHex") # x | ||||
@@ -31,7 +31,9 @@ from .compat import bytes, str | |||||
from .nodes import Node | from .nodes import Node | ||||
from .smart_list import SmartList | from .smart_list import SmartList | ||||
def parse_anything(value): | |||||
__all__ = ["parse_anything"] | |||||
def parse_anything(value, context=0): | |||||
"""Return a :py:class:`~.Wikicode` for *value*, allowing multiple types. | """Return a :py:class:`~.Wikicode` for *value*, allowing multiple types. | ||||
This differs from :py:meth:`.Parser.parse` in that we accept more than just | This differs from :py:meth:`.Parser.parse` in that we accept more than just | ||||
@@ -42,6 +44,12 @@ def parse_anything(value): | |||||
on-the-fly by various methods of :py:class:`~.Wikicode` and others like | on-the-fly by various methods of :py:class:`~.Wikicode` and others like | ||||
:py:class:`~.Template`, such as :py:meth:`wikicode.insert() | :py:class:`~.Template`, such as :py:meth:`wikicode.insert() | ||||
<.Wikicode.insert>` or setting :py:meth:`template.name <.Template.name>`. | <.Wikicode.insert>` or setting :py:meth:`template.name <.Template.name>`. | ||||
If given, *context* will be passed as a starting context to the parser. | |||||
This is helpful when this function is used inside node attribute setters. | |||||
For example, :py:class:`~.ExternalLink`\ 's :py:attr:`~.ExternalLink.url` | |||||
setter sets *context* to :py:mod:`contexts.EXT_LINK_URI <.contexts>` to | |||||
prevent the URL itself from becoming an :py:class:`~.ExternalLink`. | |||||
""" | """ | ||||
from .parser import Parser | from .parser import Parser | ||||
from .wikicode import Wikicode | from .wikicode import Wikicode | ||||
@@ -51,17 +59,17 @@ def parse_anything(value): | |||||
elif isinstance(value, Node): | elif isinstance(value, Node): | ||||
return Wikicode(SmartList([value])) | return Wikicode(SmartList([value])) | ||||
elif isinstance(value, str): | elif isinstance(value, str): | ||||
return Parser(value).parse() | |||||
return Parser().parse(value, context) | |||||
elif isinstance(value, bytes): | elif isinstance(value, bytes): | ||||
return Parser(value.decode("utf8")).parse() | |||||
return Parser().parse(value.decode("utf8"), context) | |||||
elif isinstance(value, int): | elif isinstance(value, int): | ||||
return Parser(str(value)).parse() | |||||
return Parser().parse(str(value), context) | |||||
elif value is None: | elif value is None: | ||||
return Wikicode(SmartList()) | return Wikicode(SmartList()) | ||||
try: | try: | ||||
nodelist = SmartList() | nodelist = SmartList() | ||||
for item in value: | for item in value: | ||||
nodelist += parse_anything(item).nodes | |||||
nodelist += parse_anything(item, context).nodes | |||||
except TypeError: | except TypeError: | ||||
error = "Needs string, Node, Wikicode, int, None, or iterable of these, but got {0}: {1}" | error = "Needs string, Node, Wikicode, int, None, or iterable of these, but got {0}: {1}" | ||||
raise ValueError(error.format(type(value).__name__, value)) | raise ValueError(error.format(type(value).__name__, value)) | ||||
@@ -24,8 +24,8 @@ from __future__ import unicode_literals | |||||
import re | import re | ||||
from .compat import maxsize, py3k, str | from .compat import maxsize, py3k, str | ||||
from .nodes import (Argument, Comment, Heading, HTMLEntity, Node, Tag, | |||||
Template, Text, Wikilink) | |||||
from .nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, | |||||
Node, Tag, Template, Text, Wikilink) | |||||
from .string_mixin import StringMixIn | from .string_mixin import StringMixIn | ||||
from .utils import parse_anything | from .utils import parse_anything | ||||
@@ -60,19 +60,6 @@ class Wikicode(StringMixIn): | |||||
for context, child in node.__iternodes__(self._get_all_nodes): | for context, child in node.__iternodes__(self._get_all_nodes): | ||||
yield child | yield child | ||||
def _get_context(self, node, obj): | |||||
"""Return a ``Wikicode`` that contains *obj* in its descendants. | |||||
The closest (shortest distance from *node*) suitable ``Wikicode`` will | |||||
be returned, or ``None`` if the *obj* is the *node* itself. | |||||
Raises ``ValueError`` if *obj* is not within *node*. | |||||
""" | |||||
for context, child in node.__iternodes__(self._get_all_nodes): | |||||
if self._is_equivalent(obj, child): | |||||
return context | |||||
raise ValueError(obj) | |||||
def _get_all_nodes(self, code): | def _get_all_nodes(self, code): | ||||
"""Iterate over all of our descendant nodes. | """Iterate over all of our descendant nodes. | ||||
@@ -105,26 +92,56 @@ class Wikicode(StringMixIn): | |||||
return False | return False | ||||
return obj in nodes | return obj in nodes | ||||
def _do_search(self, obj, recursive, callback, context, *args, **kwargs): | |||||
"""Look within *context* for *obj*, executing *callback* if found. | |||||
def _do_search(self, obj, recursive, context=None, literal=None): | |||||
"""Return some info about the location of *obj* within *context*. | |||||
If *recursive* is ``True``, we'll look within context and its | |||||
descendants, otherwise we'll just execute callback. We raise | |||||
:py:exc:`ValueError` if *obj* isn't in our node list or context. If | |||||
found, *callback* is passed the context, the index of the node within | |||||
the context, and whatever were passed as ``*args`` and ``**kwargs``. | |||||
If *recursive* is ``True``, we'll look within *context* (``self`` by | |||||
default) and its descendants, otherwise just *context*. We raise | |||||
:py:exc:`ValueError` if *obj* isn't found. The return data is a list of | |||||
3-tuples (*type*, *context*, *data*) where *type* is *obj*\ 's best | |||||
type resolution (either ``Node``, ``Wikicode``, or ``str``), *context* | |||||
is the closest ``Wikicode`` encompassing it, and *data* is either a | |||||
``Node``, a list of ``Node``\ s, or ``None`` depending on *type*. | |||||
""" | """ | ||||
if recursive: | |||||
for i, node in enumerate(context.nodes): | |||||
if self._is_equivalent(obj, node): | |||||
return callback(context, i, *args, **kwargs) | |||||
if self._contains(self._get_children(node), obj): | |||||
context = self._get_context(node, obj) | |||||
return self._do_search(obj, recursive, callback, context, | |||||
*args, **kwargs) | |||||
raise ValueError(obj) | |||||
if not context: | |||||
context = self | |||||
literal = isinstance(obj, (Node, Wikicode)) | |||||
obj = parse_anything(obj) | |||||
if not obj or obj not in self: | |||||
raise ValueError(obj) | |||||
if len(obj.nodes) == 1: | |||||
obj = obj.get(0) | |||||
compare = lambda a, b: (a is b) if literal else (a == b) | |||||
results = [] | |||||
i = 0 | |||||
while i < len(context.nodes): | |||||
node = context.get(i) | |||||
if isinstance(obj, Node) and compare(obj, node): | |||||
results.append((Node, context, node)) | |||||
elif isinstance(obj, Wikicode) and compare(obj.get(0), node): | |||||
for j in range(1, len(obj.nodes)): | |||||
if not compare(obj.get(j), context.get(i + j)): | |||||
break | |||||
else: | |||||
nodes = list(context.nodes[i:i + len(obj.nodes)]) | |||||
results.append((Wikicode, context, nodes)) | |||||
i += len(obj.nodes) - 1 | |||||
elif recursive: | |||||
contexts = node.__iternodes__(self._get_all_nodes) | |||||
processed = [] | |||||
for code in (ctx for ctx, child in contexts): | |||||
if code and code not in processed and obj in code: | |||||
search = self._do_search(obj, recursive, code, literal) | |||||
results.extend(search) | |||||
processed.append(code) | |||||
i += 1 | |||||
callback(context, self.index(obj, recursive=False), *args, **kwargs) | |||||
if not results and not literal and recursive: | |||||
results.append((str, context, None)) | |||||
if not results and context is self: | |||||
raise ValueError(obj) | |||||
return results | |||||
def _get_tree(self, code, lines, marker, indent): | def _get_tree(self, code, lines, marker, indent): | ||||
"""Build a tree to illustrate the way the Wikicode object was parsed. | """Build a tree to illustrate the way the Wikicode object was parsed. | ||||
@@ -253,41 +270,64 @@ class Wikicode(StringMixIn): | |||||
def insert_before(self, obj, value, recursive=True): | def insert_before(self, obj, value, recursive=True): | ||||
"""Insert *value* immediately before *obj* in the list of nodes. | """Insert *value* immediately before *obj* in the list of nodes. | ||||
*obj* can be either a string or a :py:class:`~.Node`. *value* can be | |||||
anything parasable by :py:func:`.parse_anything`. If *recursive* is | |||||
``True``, we will try to find *obj* within our child nodes even if it | |||||
is not a direct descendant of this :py:class:`~.Wikicode` object. If | |||||
*obj* is not in the node list, :py:exc:`ValueError` is raised. | |||||
*obj* can be either a string, a :py:class:`~.Node`, or other | |||||
:py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, | |||||
for example). *value* can be anything parasable by | |||||
:py:func:`.parse_anything`. If *recursive* is ``True``, we will try to | |||||
find *obj* within our child nodes even if it is not a direct descendant | |||||
of this :py:class:`~.Wikicode` object. If *obj* is not found, | |||||
:py:exc:`ValueError` is raised. | |||||
""" | """ | ||||
callback = lambda self, i, value: self.insert(i, value) | |||||
self._do_search(obj, recursive, callback, self, value) | |||||
for restype, context, data in self._do_search(obj, recursive): | |||||
if restype in (Node, Wikicode): | |||||
i = context.index(data if restype is Node else data[0], False) | |||||
context.insert(i, value) | |||||
else: | |||||
obj = str(obj) | |||||
context.nodes = str(context).replace(obj, str(value) + obj) | |||||
def insert_after(self, obj, value, recursive=True): | def insert_after(self, obj, value, recursive=True): | ||||
"""Insert *value* immediately after *obj* in the list of nodes. | """Insert *value* immediately after *obj* in the list of nodes. | ||||
*obj* can be either a string or a :py:class:`~.Node`. *value* can be | |||||
anything parasable by :py:func:`.parse_anything`. If *recursive* is | |||||
``True``, we will try to find *obj* within our child nodes even if it | |||||
is not a direct descendant of this :py:class:`~.Wikicode` object. If | |||||
*obj* is not in the node list, :py:exc:`ValueError` is raised. | |||||
*obj* can be either a string, a :py:class:`~.Node`, or other | |||||
:py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, | |||||
for example). *value* can be anything parasable by | |||||
:py:func:`.parse_anything`. If *recursive* is ``True``, we will try to | |||||
find *obj* within our child nodes even if it is not a direct descendant | |||||
of this :py:class:`~.Wikicode` object. If *obj* is not found, | |||||
:py:exc:`ValueError` is raised. | |||||
""" | """ | ||||
callback = lambda self, i, value: self.insert(i + 1, value) | |||||
self._do_search(obj, recursive, callback, self, value) | |||||
for restype, context, data in self._do_search(obj, recursive): | |||||
if restype in (Node, Wikicode): | |||||
i = context.index(data if restype is Node else data[-1], False) | |||||
context.insert(i + 1, value) | |||||
else: | |||||
obj = str(obj) | |||||
context.nodes = str(context).replace(obj, obj + str(value)) | |||||
def replace(self, obj, value, recursive=True): | def replace(self, obj, value, recursive=True): | ||||
"""Replace *obj* with *value* in the list of nodes. | """Replace *obj* with *value* in the list of nodes. | ||||
*obj* can be either a string or a :py:class:`~.Node`. *value* can be | |||||
anything parasable by :py:func:`.parse_anything`. If *recursive* is | |||||
``True``, we will try to find *obj* within our child nodes even if it | |||||
is not a direct descendant of this :py:class:`~.Wikicode` object. If | |||||
*obj* is not in the node list, :py:exc:`ValueError` is raised. | |||||
*obj* can be either a string, a :py:class:`~.Node`, or other | |||||
:py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, | |||||
for example). *value* can be anything parasable by | |||||
:py:func:`.parse_anything`. If *recursive* is ``True``, we will try to | |||||
find *obj* within our child nodes even if it is not a direct descendant | |||||
of this :py:class:`~.Wikicode` object. If *obj* is not found, | |||||
:py:exc:`ValueError` is raised. | |||||
""" | """ | ||||
def callback(self, i, value): | |||||
self.nodes.pop(i) | |||||
self.insert(i, value) | |||||
self._do_search(obj, recursive, callback, self, value) | |||||
for restype, context, data in self._do_search(obj, recursive): | |||||
if restype is Node: | |||||
i = context.index(data, False) | |||||
context.nodes.pop(i) | |||||
context.insert(i, value) | |||||
elif restype is Wikicode: | |||||
i = context.index(data[0], False) | |||||
for _ in data: | |||||
context.nodes.pop(i) | |||||
context.insert(i, value) | |||||
else: | |||||
context.nodes = str(context).replace(str(obj), str(value)) | |||||
def append(self, value): | def append(self, value): | ||||
"""Insert *value* at the end of the list of nodes. | """Insert *value* at the end of the list of nodes. | ||||
@@ -301,15 +341,39 @@ class Wikicode(StringMixIn): | |||||
def remove(self, obj, recursive=True): | def remove(self, obj, recursive=True): | ||||
"""Remove *obj* from the list of nodes. | """Remove *obj* from the list of nodes. | ||||
*obj* can be either a string or a :py:class:`~.Node`. If *recursive* is | |||||
``True``, we will try to find *obj* within our child nodes even if it | |||||
is not a direct descendant of this :py:class:`~.Wikicode` object. If | |||||
*obj* is not in the node list, :py:exc:`ValueError` is raised. | |||||
*obj* can be either a string, a :py:class:`~.Node`, or other | |||||
:py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, | |||||
for example). If *recursive* is ``True``, we will try to find *obj* | |||||
within our child nodes even if it is not a direct descendant of this | |||||
:py:class:`~.Wikicode` object. If *obj* is not found, | |||||
:py:exc:`ValueError` is raised. | |||||
""" | |||||
for restype, context, data in self._do_search(obj, recursive): | |||||
if restype is Node: | |||||
context.nodes.pop(context.index(data, False)) | |||||
elif restype is Wikicode: | |||||
i = context.index(data[0], False) | |||||
for _ in data: | |||||
context.nodes.pop(i) | |||||
else: | |||||
context.nodes = str(context).replace(str(obj), "") | |||||
def matches(self, other): | |||||
"""Do a loose equivalency test suitable for comparing page names. | |||||
*other* can be any string-like object, including | |||||
:py:class:`~.Wikicode`. This operation is symmetric; both sides are | |||||
adjusted. Specifically, whitespace and markup is stripped and the first | |||||
letter's case is normalized. Typical usage is | |||||
``if template.name.matches("stub"): ...``. | |||||
""" | """ | ||||
callback = lambda self, i: self.nodes.pop(i) | |||||
self._do_search(obj, recursive, callback, self) | |||||
this = self.strip_code().strip() | |||||
that = parse_anything(other).strip_code().strip() | |||||
if not this or not that: | |||||
return this == that | |||||
return this[0].upper() + this[1:] == that[0].upper() + that[1:] | |||||
def ifilter(self, recursive=False, matches=None, flags=FLAGS, | |||||
def ifilter(self, recursive=True, matches=None, flags=FLAGS, | |||||
forcetype=None): | forcetype=None): | ||||
"""Iterate over nodes in our list matching certain conditions. | """Iterate over nodes in our list matching certain conditions. | ||||
@@ -327,7 +391,7 @@ class Wikicode(StringMixIn): | |||||
if not matches or re.search(matches, str(node), flags): | if not matches or re.search(matches, str(node), flags): | ||||
yield node | yield node | ||||
def filter(self, recursive=False, matches=None, flags=FLAGS, | |||||
def filter(self, recursive=True, matches=None, flags=FLAGS, | |||||
forcetype=None): | forcetype=None): | ||||
"""Return a list of nodes within our list matching certain conditions. | """Return a list of nodes within our list matching certain conditions. | ||||
@@ -360,9 +424,8 @@ class Wikicode(StringMixIn): | |||||
""" | """ | ||||
if matches: | if matches: | ||||
matches = r"^(=+?)\s*" + matches + r"\s*\1$" | matches = r"^(=+?)\s*" + matches + r"\s*\1$" | ||||
headings = self.filter_headings(recursive=True) | |||||
filtered = self.filter_headings(recursive=True, matches=matches, | |||||
flags=flags) | |||||
headings = self.filter_headings() | |||||
filtered = self.filter_headings(matches=matches, flags=flags) | |||||
if levels: | if levels: | ||||
filtered = [head for head in filtered if head.level in levels] | filtered = [head for head in filtered if head.level in levels] | ||||
@@ -446,6 +509,6 @@ class Wikicode(StringMixIn): | |||||
return "\n".join(self._get_tree(self, [], marker, 0)) | return "\n".join(self._get_tree(self, [], marker, 0)) | ||||
Wikicode._build_filter_methods( | Wikicode._build_filter_methods( | ||||
arguments=Argument, comments=Comment, headings=Heading, | |||||
html_entities=HTMLEntity, tags=Tag, templates=Template, text=Text, | |||||
wikilinks=Wikilink) | |||||
arguments=Argument, comments=Comment, external_links=ExternalLink, | |||||
headings=Heading, html_entities=HTMLEntity, tags=Tag, templates=Template, | |||||
text=Text, wikilinks=Wikilink) |
@@ -29,16 +29,13 @@ from mwparserfromhell.compat import py3k | |||||
with open("README.rst") as fp: | with open("README.rst") as fp: | ||||
long_docs = fp.read() | long_docs = fp.read() | ||||
# builder = Extension("mwparserfromhell.parser._builder", | |||||
# sources = ["mwparserfromhell/parser/builder.c"]) | |||||
tokenizer = Extension("mwparserfromhell.parser._tokenizer", | tokenizer = Extension("mwparserfromhell.parser._tokenizer", | ||||
sources = ["mwparserfromhell/parser/tokenizer.c"]) | sources = ["mwparserfromhell/parser/tokenizer.c"]) | ||||
setup( | setup( | ||||
name = "mwparserfromhell", | name = "mwparserfromhell", | ||||
packages = find_packages(exclude=("tests",)), | packages = find_packages(exclude=("tests",)), | ||||
ext_modules = [] if py3k else [tokenizer], | |||||
ext_modules = [tokenizer], | |||||
test_suite = "tests", | test_suite = "tests", | ||||
version = __version__, | version = __version__, | ||||
author = "Ben Kurtovic", | author = "Ben Kurtovic", | ||||
@@ -50,13 +47,13 @@ setup( | |||||
keywords = "earwig mwparserfromhell wikipedia wiki mediawiki wikicode template parsing", | keywords = "earwig mwparserfromhell wikipedia wiki mediawiki wikicode template parsing", | ||||
license = "MIT License", | license = "MIT License", | ||||
classifiers = [ | classifiers = [ | ||||
"Development Status :: 3 - Alpha", | |||||
"Development Status :: 4 - Beta", | |||||
"Environment :: Console", | "Environment :: Console", | ||||
"Intended Audience :: Developers", | "Intended Audience :: Developers", | ||||
"License :: OSI Approved :: MIT License", | "License :: OSI Approved :: MIT License", | ||||
"Operating System :: OS Independent", | "Operating System :: OS Independent", | ||||
"Programming Language :: Python :: 2.7", | "Programming Language :: Python :: 2.7", | ||||
"Programming Language :: Python :: 3", | |||||
"Programming Language :: Python :: 3.3", | |||||
"Topic :: Text Processing :: Markup" | "Topic :: Text Processing :: Markup" | ||||
], | ], | ||||
) | ) |
@@ -91,7 +91,27 @@ class TreeEqualityTestCase(TestCase): | |||||
def assertTagNodeEqual(self, expected, actual): | def assertTagNodeEqual(self, expected, actual): | ||||
"""Assert that two Tag nodes have the same data.""" | """Assert that two Tag nodes have the same data.""" | ||||
self.fail("Holding this until feature/html_tags is ready.") | |||||
self.assertWikicodeEqual(expected.tag, actual.tag) | |||||
if expected.contents is not None: | |||||
self.assertWikicodeEqual(expected.contents, actual.contents) | |||||
length = len(expected.attributes) | |||||
self.assertEqual(length, len(actual.attributes)) | |||||
for i in range(length): | |||||
exp_attr = expected.attributes[i] | |||||
act_attr = actual.attributes[i] | |||||
self.assertWikicodeEqual(exp_attr.name, act_attr.name) | |||||
if exp_attr.value is not None: | |||||
self.assertWikicodeEqual(exp_attr.value, act_attr.value) | |||||
self.assertIs(exp_attr.quoted, act_attr.quoted) | |||||
self.assertEqual(exp_attr.pad_first, act_attr.pad_first) | |||||
self.assertEqual(exp_attr.pad_before_eq, act_attr.pad_before_eq) | |||||
self.assertEqual(exp_attr.pad_after_eq, act_attr.pad_after_eq) | |||||
self.assertIs(expected.wiki_markup, actual.wiki_markup) | |||||
self.assertIs(expected.self_closing, actual.self_closing) | |||||
self.assertIs(expected.invalid, actual.invalid) | |||||
self.assertIs(expected.implicit, actual.implicit) | |||||
self.assertEqual(expected.padding, actual.padding) | |||||
self.assertWikicodeEqual(expected.closing_tag, actual.closing_tag) | |||||
def assertTemplateNodeEqual(self, expected, actual): | def assertTemplateNodeEqual(self, expected, actual): | ||||
"""Assert that two Template nodes have the same data.""" | """Assert that two Template nodes have the same data.""" | ||||
@@ -0,0 +1,89 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2012-2013 Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
from __future__ import unicode_literals | |||||
import unittest | |||||
from mwparserfromhell.compat import str | |||||
from mwparserfromhell.nodes import Template | |||||
from mwparserfromhell.nodes.extras import Attribute | |||||
from ._test_tree_equality import TreeEqualityTestCase, wrap, wraptext | |||||
class TestAttribute(TreeEqualityTestCase): | |||||
"""Test cases for the Attribute node extra.""" | |||||
def test_unicode(self): | |||||
"""test Attribute.__unicode__()""" | |||||
node = Attribute(wraptext("foo")) | |||||
self.assertEqual(" foo", str(node)) | |||||
node2 = Attribute(wraptext("foo"), wraptext("bar")) | |||||
self.assertEqual(' foo="bar"', str(node2)) | |||||
node3 = Attribute(wraptext("a"), wraptext("b"), True, "", " ", " ") | |||||
self.assertEqual('a = "b"', str(node3)) | |||||
node3 = Attribute(wraptext("a"), wraptext("b"), False, "", " ", " ") | |||||
self.assertEqual("a = b", str(node3)) | |||||
node4 = Attribute(wraptext("a"), wrap([]), False, " ", "", " ") | |||||
self.assertEqual(" a= ", str(node4)) | |||||
def test_name(self): | |||||
"""test getter/setter for the name attribute""" | |||||
name = wraptext("id") | |||||
node = Attribute(name, wraptext("bar")) | |||||
self.assertIs(name, node.name) | |||||
node.name = "{{id}}" | |||||
self.assertWikicodeEqual(wrap([Template(wraptext("id"))]), node.name) | |||||
def test_value(self): | |||||
"""test getter/setter for the value attribute""" | |||||
value = wraptext("foo") | |||||
node = Attribute(wraptext("id"), value) | |||||
self.assertIs(value, node.value) | |||||
node.value = "{{bar}}" | |||||
self.assertWikicodeEqual(wrap([Template(wraptext("bar"))]), node.value) | |||||
node.value = None | |||||
self.assertIs(None, node.value) | |||||
def test_quoted(self): | |||||
"""test getter/setter for the quoted attribute""" | |||||
node1 = Attribute(wraptext("id"), wraptext("foo"), False) | |||||
node2 = Attribute(wraptext("id"), wraptext("bar")) | |||||
self.assertFalse(node1.quoted) | |||||
self.assertTrue(node2.quoted) | |||||
node1.quoted = True | |||||
node2.quoted = "" | |||||
self.assertTrue(node1.quoted) | |||||
self.assertFalse(node2.quoted) | |||||
def test_padding(self): | |||||
"""test getter/setter for the padding attributes""" | |||||
for pad in ["pad_first", "pad_before_eq", "pad_after_eq"]: | |||||
node = Attribute(wraptext("id"), wraptext("foo"), **{pad: "\n"}) | |||||
self.assertEqual("\n", getattr(node, pad)) | |||||
setattr(node, pad, " ") | |||||
self.assertEqual(" ", getattr(node, pad)) | |||||
setattr(node, pad, None) | |||||
self.assertEqual("", getattr(node, pad)) | |||||
self.assertRaises(ValueError, setattr, node, pad, True) | |||||
if __name__ == "__main__": | |||||
unittest.main(verbosity=2) |
@@ -23,8 +23,8 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import unittest | import unittest | ||||
from mwparserfromhell.nodes import (Argument, Comment, Heading, HTMLEntity, | |||||
Tag, Template, Text, Wikilink) | |||||
from mwparserfromhell.nodes import (Argument, Comment, ExternalLink, Heading, | |||||
HTMLEntity, Tag, Template, Text, Wikilink) | |||||
from mwparserfromhell.nodes.extras import Attribute, Parameter | from mwparserfromhell.nodes.extras import Attribute, Parameter | ||||
from mwparserfromhell.parser import tokens | from mwparserfromhell.parser import tokens | ||||
from mwparserfromhell.parser.builder import Builder | from mwparserfromhell.parser.builder import Builder | ||||
@@ -72,6 +72,14 @@ class TestBuilder(TreeEqualityTestCase): | |||||
wrap([Template(wraptext("foo"), params=[ | wrap([Template(wraptext("foo"), params=[ | ||||
Parameter(wraptext("bar"), wraptext("baz"))])])), | Parameter(wraptext("bar"), wraptext("baz"))])])), | ||||
([tokens.TemplateOpen(), tokens.TemplateParamSeparator(), | |||||
tokens.TemplateParamSeparator(), tokens.TemplateParamEquals(), | |||||
tokens.TemplateParamSeparator(), tokens.TemplateClose()], | |||||
wrap([Template(wrap([]), params=[ | |||||
Parameter(wraptext("1"), wrap([]), showkey=False), | |||||
Parameter(wrap([]), wrap([]), showkey=True), | |||||
Parameter(wraptext("2"), wrap([]), showkey=False)])])), | |||||
([tokens.TemplateOpen(), tokens.Text(text="foo"), | ([tokens.TemplateOpen(), tokens.Text(text="foo"), | ||||
tokens.TemplateParamSeparator(), tokens.Text(text="bar"), | tokens.TemplateParamSeparator(), tokens.Text(text="bar"), | ||||
tokens.TemplateParamEquals(), tokens.Text(text="baz"), | tokens.TemplateParamEquals(), tokens.Text(text="baz"), | ||||
@@ -142,6 +150,48 @@ class TestBuilder(TreeEqualityTestCase): | |||||
for test, valid in tests: | for test, valid in tests: | ||||
self.assertWikicodeEqual(valid, self.builder.build(test)) | self.assertWikicodeEqual(valid, self.builder.build(test)) | ||||
def test_external_link(self): | |||||
"""tests for building ExternalLink nodes""" | |||||
tests = [ | |||||
([tokens.ExternalLinkOpen(brackets=False), | |||||
tokens.Text(text="http://example.com/"), | |||||
tokens.ExternalLinkClose()], | |||||
wrap([ExternalLink(wraptext("http://example.com/"), | |||||
brackets=False)])), | |||||
([tokens.ExternalLinkOpen(brackets=True), | |||||
tokens.Text(text="http://example.com/"), | |||||
tokens.ExternalLinkClose()], | |||||
wrap([ExternalLink(wraptext("http://example.com/"))])), | |||||
([tokens.ExternalLinkOpen(brackets=True), | |||||
tokens.Text(text="http://example.com/"), | |||||
tokens.ExternalLinkSeparator(), tokens.ExternalLinkClose()], | |||||
wrap([ExternalLink(wraptext("http://example.com/"), wrap([]))])), | |||||
([tokens.ExternalLinkOpen(brackets=True), | |||||
tokens.Text(text="http://example.com/"), | |||||
tokens.ExternalLinkSeparator(), tokens.Text(text="Example"), | |||||
tokens.ExternalLinkClose()], | |||||
wrap([ExternalLink(wraptext("http://example.com/"), | |||||
wraptext("Example"))])), | |||||
([tokens.ExternalLinkOpen(brackets=False), | |||||
tokens.Text(text="http://example"), tokens.Text(text=".com/foo"), | |||||
tokens.ExternalLinkClose()], | |||||
wrap([ExternalLink(wraptext("http://example", ".com/foo"), | |||||
brackets=False)])), | |||||
([tokens.ExternalLinkOpen(brackets=True), | |||||
tokens.Text(text="http://example"), tokens.Text(text=".com/foo"), | |||||
tokens.ExternalLinkSeparator(), tokens.Text(text="Example"), | |||||
tokens.Text(text=" Web Page"), tokens.ExternalLinkClose()], | |||||
wrap([ExternalLink(wraptext("http://example", ".com/foo"), | |||||
wraptext("Example", " Web Page"))])), | |||||
] | |||||
for test, valid in tests: | |||||
self.assertWikicodeEqual(valid, self.builder.build(test)) | |||||
def test_html_entity(self): | def test_html_entity(self): | ||||
"""tests for building HTMLEntity nodes""" | """tests for building HTMLEntity nodes""" | ||||
tests = [ | tests = [ | ||||
@@ -190,6 +240,129 @@ class TestBuilder(TreeEqualityTestCase): | |||||
for test, valid in tests: | for test, valid in tests: | ||||
self.assertWikicodeEqual(valid, self.builder.build(test)) | self.assertWikicodeEqual(valid, self.builder.build(test)) | ||||
def test_tag(self): | |||||
"""tests for building Tag nodes""" | |||||
tests = [ | |||||
# <ref></ref> | |||||
([tokens.TagOpenOpen(), tokens.Text(text="ref"), | |||||
tokens.TagCloseOpen(padding=""), tokens.TagOpenClose(), | |||||
tokens.Text(text="ref"), tokens.TagCloseClose()], | |||||
wrap([Tag(wraptext("ref"), wrap([]), | |||||
closing_tag=wraptext("ref"))])), | |||||
# <ref name></ref> | |||||
([tokens.TagOpenOpen(), tokens.Text(text="ref"), | |||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | |||||
pad_after_eq=""), | |||||
tokens.Text(text="name"), tokens.TagCloseOpen(padding=""), | |||||
tokens.TagOpenClose(), tokens.Text(text="ref"), | |||||
tokens.TagCloseClose()], | |||||
wrap([Tag(wraptext("ref"), wrap([]), | |||||
attrs=[Attribute(wraptext("name"))])])), | |||||
# <ref name="abc" /> | |||||
([tokens.TagOpenOpen(), tokens.Text(text="ref"), | |||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | |||||
pad_after_eq=""), | |||||
tokens.Text(text="name"), tokens.TagAttrEquals(), | |||||
tokens.TagAttrQuote(), tokens.Text(text="abc"), | |||||
tokens.TagCloseSelfclose(padding=" ")], | |||||
wrap([Tag(wraptext("ref"), | |||||
attrs=[Attribute(wraptext("name"), wraptext("abc"))], | |||||
self_closing=True, padding=" ")])), | |||||
# <br/> | |||||
([tokens.TagOpenOpen(), tokens.Text(text="br"), | |||||
tokens.TagCloseSelfclose(padding="")], | |||||
wrap([Tag(wraptext("br"), self_closing=True)])), | |||||
# <li> | |||||
([tokens.TagOpenOpen(), tokens.Text(text="li"), | |||||
tokens.TagCloseSelfclose(padding="", implicit=True)], | |||||
wrap([Tag(wraptext("li"), self_closing=True, implicit=True)])), | |||||
# </br> | |||||
([tokens.TagOpenOpen(invalid=True), tokens.Text(text="br"), | |||||
tokens.TagCloseSelfclose(padding="", implicit=True)], | |||||
wrap([Tag(wraptext("br"), self_closing=True, invalid=True, | |||||
implicit=True)])), | |||||
# </br/> | |||||
([tokens.TagOpenOpen(invalid=True), tokens.Text(text="br"), | |||||
tokens.TagCloseSelfclose(padding="")], | |||||
wrap([Tag(wraptext("br"), self_closing=True, invalid=True)])), | |||||
# <ref name={{abc}} foo="bar {{baz}}" abc={{de}}f ghi=j{{k}}{{l}} | |||||
# mno = "{{p}} [[q]] {{r}}">[[Source]]</ref> | |||||
([tokens.TagOpenOpen(), tokens.Text(text="ref"), | |||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | |||||
pad_after_eq=""), | |||||
tokens.Text(text="name"), tokens.TagAttrEquals(), | |||||
tokens.TemplateOpen(), tokens.Text(text="abc"), | |||||
tokens.TemplateClose(), | |||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | |||||
pad_after_eq=""), | |||||
tokens.Text(text="foo"), tokens.TagAttrEquals(), | |||||
tokens.TagAttrQuote(), tokens.Text(text="bar "), | |||||
tokens.TemplateOpen(), tokens.Text(text="baz"), | |||||
tokens.TemplateClose(), | |||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | |||||
pad_after_eq=""), | |||||
tokens.Text(text="abc"), tokens.TagAttrEquals(), | |||||
tokens.TemplateOpen(), tokens.Text(text="de"), | |||||
tokens.TemplateClose(), tokens.Text(text="f"), | |||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | |||||
pad_after_eq=""), | |||||
tokens.Text(text="ghi"), tokens.TagAttrEquals(), | |||||
tokens.Text(text="j"), tokens.TemplateOpen(), | |||||
tokens.Text(text="k"), tokens.TemplateClose(), | |||||
tokens.TemplateOpen(), tokens.Text(text="l"), | |||||
tokens.TemplateClose(), | |||||
tokens.TagAttrStart(pad_first=" \n ", pad_before_eq=" ", | |||||
pad_after_eq=" "), | |||||
tokens.Text(text="mno"), tokens.TagAttrEquals(), | |||||
tokens.TagAttrQuote(), tokens.TemplateOpen(), | |||||
tokens.Text(text="p"), tokens.TemplateClose(), | |||||
tokens.Text(text=" "), tokens.WikilinkOpen(), | |||||
tokens.Text(text="q"), tokens.WikilinkClose(), | |||||
tokens.Text(text=" "), tokens.TemplateOpen(), | |||||
tokens.Text(text="r"), tokens.TemplateClose(), | |||||
tokens.TagCloseOpen(padding=""), tokens.WikilinkOpen(), | |||||
tokens.Text(text="Source"), tokens.WikilinkClose(), | |||||
tokens.TagOpenClose(), tokens.Text(text="ref"), | |||||
tokens.TagCloseClose()], | |||||
wrap([Tag(wraptext("ref"), wrap([Wikilink(wraptext("Source"))]), [ | |||||
Attribute(wraptext("name"), | |||||
wrap([Template(wraptext("abc"))]), False), | |||||
Attribute(wraptext("foo"), wrap([Text("bar "), | |||||
Template(wraptext("baz"))]), pad_first=" "), | |||||
Attribute(wraptext("abc"), wrap([Template(wraptext("de")), | |||||
Text("f")]), False), | |||||
Attribute(wraptext("ghi"), wrap([Text("j"), | |||||
Template(wraptext("k")), | |||||
Template(wraptext("l"))]), False), | |||||
Attribute(wraptext("mno"), wrap([Template(wraptext("p")), | |||||
Text(" "), Wikilink(wraptext("q")), Text(" "), | |||||
Template(wraptext("r"))]), True, " \n ", " ", | |||||
" ")])])), | |||||
# "''italic text''" | |||||
([tokens.TagOpenOpen(wiki_markup="''"), tokens.Text(text="i"), | |||||
tokens.TagCloseOpen(), tokens.Text(text="italic text"), | |||||
tokens.TagOpenClose(), tokens.Text(text="i"), | |||||
tokens.TagCloseClose()], | |||||
wrap([Tag(wraptext("i"), wraptext("italic text"), | |||||
wiki_markup="''")])), | |||||
# * bullet | |||||
([tokens.TagOpenOpen(wiki_markup="*"), tokens.Text(text="li"), | |||||
tokens.TagCloseSelfclose(), tokens.Text(text=" bullet")], | |||||
wrap([Tag(wraptext("li"), wiki_markup="*", self_closing=True), | |||||
Text(" bullet")])), | |||||
] | |||||
for test, valid in tests: | |||||
self.assertWikicodeEqual(valid, self.builder.build(test)) | |||||
def test_integration(self): | def test_integration(self): | ||||
"""a test for building a combination of templates together""" | """a test for building a combination of templates together""" | ||||
# {{{{{{{{foo}}bar|baz=biz}}buzz}}usr|{{bin}}}} | # {{{{{{{{foo}}bar|baz=biz}}buzz}}usr|{{bin}}}} | ||||
@@ -61,36 +61,36 @@ class TestDocs(unittest.TestCase): | |||||
def test_readme_2(self): | def test_readme_2(self): | ||||
"""test a block of example code in the README""" | """test a block of example code in the README""" | ||||
text = "{{foo|{{bar}}={{baz|{{spam}}}}}}" | |||||
temps = mwparserfromhell.parse(text).filter_templates() | |||||
if py3k: | |||||
res = "['{{foo|{{bar}}={{baz|{{spam}}}}}}', '{{bar}}', '{{baz|{{spam}}}}', '{{spam}}']" | |||||
else: | |||||
res = "[u'{{foo|{{bar}}={{baz|{{spam}}}}}}', u'{{bar}}', u'{{baz|{{spam}}}}', u'{{spam}}']" | |||||
self.assertPrint(temps, res) | |||||
def test_readme_3(self): | |||||
"""test a block of example code in the README""" | |||||
code = mwparserfromhell.parse("{{foo|this {{includes a|template}}}}") | code = mwparserfromhell.parse("{{foo|this {{includes a|template}}}}") | ||||
if py3k: | if py3k: | ||||
self.assertPrint(code.filter_templates(), | |||||
self.assertPrint(code.filter_templates(recursive=False), | |||||
"['{{foo|this {{includes a|template}}}}']") | "['{{foo|this {{includes a|template}}}}']") | ||||
else: | else: | ||||
self.assertPrint(code.filter_templates(), | |||||
self.assertPrint(code.filter_templates(recursive=False), | |||||
"[u'{{foo|this {{includes a|template}}}}']") | "[u'{{foo|this {{includes a|template}}}}']") | ||||
foo = code.filter_templates()[0] | |||||
foo = code.filter_templates(recursive=False)[0] | |||||
self.assertPrint(foo.get(1).value, "this {{includes a|template}}") | self.assertPrint(foo.get(1).value, "this {{includes a|template}}") | ||||
self.assertPrint(foo.get(1).value.filter_templates()[0], | self.assertPrint(foo.get(1).value.filter_templates()[0], | ||||
"{{includes a|template}}") | "{{includes a|template}}") | ||||
self.assertPrint(foo.get(1).value.filter_templates()[0].get(1).value, | self.assertPrint(foo.get(1).value.filter_templates()[0].get(1).value, | ||||
"template") | "template") | ||||
def test_readme_3(self): | |||||
"""test a block of example code in the README""" | |||||
text = "{{foo|{{bar}}={{baz|{{spam}}}}}}" | |||||
temps = mwparserfromhell.parse(text).filter_templates(recursive=True) | |||||
if py3k: | |||||
res = "['{{foo|{{bar}}={{baz|{{spam}}}}}}', '{{bar}}', '{{baz|{{spam}}}}', '{{spam}}']" | |||||
else: | |||||
res = "[u'{{foo|{{bar}}={{baz|{{spam}}}}}}', u'{{bar}}', u'{{baz|{{spam}}}}', u'{{spam}}']" | |||||
self.assertPrint(temps, res) | |||||
def test_readme_4(self): | def test_readme_4(self): | ||||
"""test a block of example code in the README""" | """test a block of example code in the README""" | ||||
text = "{{cleanup}} '''Foo''' is a [[bar]]. {{uncategorized}}" | text = "{{cleanup}} '''Foo''' is a [[bar]]. {{uncategorized}}" | ||||
code = mwparserfromhell.parse(text) | code = mwparserfromhell.parse(text) | ||||
for template in code.filter_templates(): | for template in code.filter_templates(): | ||||
if template.name == "cleanup" and not template.has_param("date"): | |||||
if template.name.matches("Cleanup") and not template.has("date"): | |||||
template.add("date", "July 2012") | template.add("date", "July 2012") | ||||
res = "{{cleanup|date=July 2012}} '''Foo''' is a [[bar]]. {{uncategorized}}" | res = "{{cleanup|date=July 2012}} '''Foo''' is a [[bar]]. {{uncategorized}}" | ||||
self.assertPrint(code, res) | self.assertPrint(code, res) | ||||
@@ -0,0 +1,130 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2012-2013 Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
from __future__ import unicode_literals | |||||
import unittest | |||||
from mwparserfromhell.compat import str | |||||
from mwparserfromhell.nodes import ExternalLink, Text | |||||
from ._test_tree_equality import TreeEqualityTestCase, getnodes, wrap, wraptext | |||||
class TestExternalLink(TreeEqualityTestCase): | |||||
"""Test cases for the ExternalLink node.""" | |||||
def test_unicode(self): | |||||
"""test ExternalLink.__unicode__()""" | |||||
node = ExternalLink(wraptext("http://example.com/"), brackets=False) | |||||
self.assertEqual("http://example.com/", str(node)) | |||||
node2 = ExternalLink(wraptext("http://example.com/")) | |||||
self.assertEqual("[http://example.com/]", str(node2)) | |||||
node3 = ExternalLink(wraptext("http://example.com/"), wrap([])) | |||||
self.assertEqual("[http://example.com/ ]", str(node3)) | |||||
node4 = ExternalLink(wraptext("http://example.com/"), | |||||
wraptext("Example Web Page")) | |||||
self.assertEqual("[http://example.com/ Example Web Page]", str(node4)) | |||||
def test_iternodes(self): | |||||
"""test ExternalLink.__iternodes__()""" | |||||
node1n1 = Text("http://example.com/") | |||||
node2n1 = Text("http://example.com/") | |||||
node2n2, node2n3 = Text("Example"), Text("Page") | |||||
node1 = ExternalLink(wrap([node1n1]), brackets=False) | |||||
node2 = ExternalLink(wrap([node2n1]), wrap([node2n2, node2n3])) | |||||
gen1 = node1.__iternodes__(getnodes) | |||||
gen2 = node2.__iternodes__(getnodes) | |||||
self.assertEqual((None, node1), next(gen1)) | |||||
self.assertEqual((None, node2), next(gen2)) | |||||
self.assertEqual((node1.url, node1n1), next(gen1)) | |||||
self.assertEqual((node2.url, node2n1), next(gen2)) | |||||
self.assertEqual((node2.title, node2n2), next(gen2)) | |||||
self.assertEqual((node2.title, node2n3), next(gen2)) | |||||
self.assertRaises(StopIteration, next, gen1) | |||||
self.assertRaises(StopIteration, next, gen2) | |||||
def test_strip(self): | |||||
"""test ExternalLink.__strip__()""" | |||||
node1 = ExternalLink(wraptext("http://example.com"), brackets=False) | |||||
node2 = ExternalLink(wraptext("http://example.com")) | |||||
node3 = ExternalLink(wraptext("http://example.com"), wrap([])) | |||||
node4 = ExternalLink(wraptext("http://example.com"), wraptext("Link")) | |||||
for a in (True, False): | |||||
for b in (True, False): | |||||
self.assertEqual("http://example.com", node1.__strip__(a, b)) | |||||
self.assertEqual(None, node2.__strip__(a, b)) | |||||
self.assertEqual(None, node3.__strip__(a, b)) | |||||
self.assertEqual("Link", node4.__strip__(a, b)) | |||||
def test_showtree(self): | |||||
"""test ExternalLink.__showtree__()""" | |||||
output = [] | |||||
getter, marker = object(), object() | |||||
get = lambda code: output.append((getter, code)) | |||||
mark = lambda: output.append(marker) | |||||
node1 = ExternalLink(wraptext("http://example.com"), brackets=False) | |||||
node2 = ExternalLink(wraptext("http://example.com"), wraptext("Link")) | |||||
node1.__showtree__(output.append, get, mark) | |||||
node2.__showtree__(output.append, get, mark) | |||||
valid = [ | |||||
(getter, node1.url), "[", (getter, node2.url), | |||||
(getter, node2.title), "]"] | |||||
self.assertEqual(valid, output) | |||||
def test_url(self): | |||||
"""test getter/setter for the url attribute""" | |||||
url = wraptext("http://example.com/") | |||||
node1 = ExternalLink(url, brackets=False) | |||||
node2 = ExternalLink(url, wraptext("Example")) | |||||
self.assertIs(url, node1.url) | |||||
self.assertIs(url, node2.url) | |||||
node1.url = "mailto:héhehé@spam.com" | |||||
node2.url = "mailto:héhehé@spam.com" | |||||
self.assertWikicodeEqual(wraptext("mailto:héhehé@spam.com"), node1.url) | |||||
self.assertWikicodeEqual(wraptext("mailto:héhehé@spam.com"), node2.url) | |||||
def test_title(self): | |||||
"""test getter/setter for the title attribute""" | |||||
title = wraptext("Example!") | |||||
node1 = ExternalLink(wraptext("http://example.com/"), brackets=False) | |||||
node2 = ExternalLink(wraptext("http://example.com/"), title) | |||||
self.assertIs(None, node1.title) | |||||
self.assertIs(title, node2.title) | |||||
node2.title = None | |||||
self.assertIs(None, node2.title) | |||||
node2.title = "My Website" | |||||
self.assertWikicodeEqual(wraptext("My Website"), node2.title) | |||||
def test_brackets(self): | |||||
"""test getter/setter for the brackets attribute""" | |||||
node1 = ExternalLink(wraptext("http://example.com/"), brackets=False) | |||||
node2 = ExternalLink(wraptext("http://example.com/"), wraptext("Link")) | |||||
self.assertFalse(node1.brackets) | |||||
self.assertTrue(node2.brackets) | |||||
node1.brackets = True | |||||
node2.brackets = False | |||||
self.assertTrue(node1.brackets) | |||||
self.assertFalse(node2.brackets) | |||||
self.assertEqual("[http://example.com/]", str(node1)) | |||||
self.assertEqual("http://example.com/", str(node2)) | |||||
if __name__ == "__main__": | |||||
unittest.main(verbosity=2) |
@@ -36,9 +36,9 @@ class TestParser(TreeEqualityTestCase): | |||||
def test_use_c(self): | def test_use_c(self): | ||||
"""make sure the correct tokenizer is used""" | """make sure the correct tokenizer is used""" | ||||
if parser.use_c: | if parser.use_c: | ||||
self.assertTrue(parser.Parser(None)._tokenizer.USES_C) | |||||
self.assertTrue(parser.Parser()._tokenizer.USES_C) | |||||
parser.use_c = False | parser.use_c = False | ||||
self.assertFalse(parser.Parser(None)._tokenizer.USES_C) | |||||
self.assertFalse(parser.Parser()._tokenizer.USES_C) | |||||
def test_parsing(self): | def test_parsing(self): | ||||
"""integration test for parsing overall""" | """integration test for parsing overall""" | ||||
@@ -59,7 +59,7 @@ class TestParser(TreeEqualityTestCase): | |||||
])) | ])) | ||||
]) | ]) | ||||
]) | ]) | ||||
actual = parser.Parser(text).parse() | |||||
actual = parser.Parser().parse(text) | |||||
self.assertWikicodeEqual(expected, actual) | self.assertWikicodeEqual(expected, actual) | ||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
@@ -0,0 +1,315 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2012-2013 Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
from __future__ import unicode_literals | |||||
import unittest | |||||
from mwparserfromhell.compat import str | |||||
from mwparserfromhell.nodes import Tag, Template, Text | |||||
from mwparserfromhell.nodes.extras import Attribute | |||||
from ._test_tree_equality import TreeEqualityTestCase, getnodes, wrap, wraptext | |||||
agen = lambda name, value: Attribute(wraptext(name), wraptext(value)) | |||||
agennq = lambda name, value: Attribute(wraptext(name), wraptext(value), False) | |||||
agenp = lambda name, v, a, b, c: Attribute(wraptext(name), v, True, a, b, c) | |||||
agenpnv = lambda name, a, b, c: Attribute(wraptext(name), None, True, a, b, c) | |||||
class TestTag(TreeEqualityTestCase): | |||||
"""Test cases for the Tag node.""" | |||||
def test_unicode(self): | |||||
"""test Tag.__unicode__()""" | |||||
node1 = Tag(wraptext("ref")) | |||||
node2 = Tag(wraptext("span"), wraptext("foo"), | |||||
[agen("style", "color: red;")]) | |||||
node3 = Tag(wraptext("ref"), | |||||
attrs=[agennq("name", "foo"), | |||||
agenpnv("some_attr", " ", "", "")], | |||||
self_closing=True) | |||||
node4 = Tag(wraptext("br"), self_closing=True, padding=" ") | |||||
node5 = Tag(wraptext("br"), self_closing=True, implicit=True) | |||||
node6 = Tag(wraptext("br"), self_closing=True, invalid=True, | |||||
implicit=True) | |||||
node7 = Tag(wraptext("br"), self_closing=True, invalid=True, | |||||
padding=" ") | |||||
node8 = Tag(wraptext("hr"), wiki_markup="----", self_closing=True) | |||||
node9 = Tag(wraptext("i"), wraptext("italics!"), wiki_markup="''") | |||||
self.assertEqual("<ref></ref>", str(node1)) | |||||
self.assertEqual('<span style="color: red;">foo</span>', str(node2)) | |||||
self.assertEqual("<ref name=foo some_attr/>", str(node3)) | |||||
self.assertEqual("<br />", str(node4)) | |||||
self.assertEqual("<br>", str(node5)) | |||||
self.assertEqual("</br>", str(node6)) | |||||
self.assertEqual("</br />", str(node7)) | |||||
self.assertEqual("----", str(node8)) | |||||
self.assertEqual("''italics!''", str(node9)) | |||||
def test_iternodes(self): | |||||
"""test Tag.__iternodes__()""" | |||||
node1n1, node1n2 = Text("ref"), Text("foobar") | |||||
node2n1, node3n1, node3n2 = Text("bold text"), Text("img"), Text("id") | |||||
node3n3, node3n4, node3n5 = Text("foo"), Text("class"), Text("bar") | |||||
# <ref>foobar</ref> | |||||
node1 = Tag(wrap([node1n1]), wrap([node1n2])) | |||||
# '''bold text''' | |||||
node2 = Tag(wraptext("b"), wrap([node2n1]), wiki_markup="'''") | |||||
# <img id="foo" class="bar" /> | |||||
node3 = Tag(wrap([node3n1]), | |||||
attrs=[Attribute(wrap([node3n2]), wrap([node3n3])), | |||||
Attribute(wrap([node3n4]), wrap([node3n5]))], | |||||
self_closing=True, padding=" ") | |||||
gen1 = node1.__iternodes__(getnodes) | |||||
gen2 = node2.__iternodes__(getnodes) | |||||
gen3 = node3.__iternodes__(getnodes) | |||||
self.assertEqual((None, node1), next(gen1)) | |||||
self.assertEqual((None, node2), next(gen2)) | |||||
self.assertEqual((None, node3), next(gen3)) | |||||
self.assertEqual((node1.tag, node1n1), next(gen1)) | |||||
self.assertEqual((node3.tag, node3n1), next(gen3)) | |||||
self.assertEqual((node3.attributes[0].name, node3n2), next(gen3)) | |||||
self.assertEqual((node3.attributes[0].value, node3n3), next(gen3)) | |||||
self.assertEqual((node3.attributes[1].name, node3n4), next(gen3)) | |||||
self.assertEqual((node3.attributes[1].value, node3n5), next(gen3)) | |||||
self.assertEqual((node1.contents, node1n2), next(gen1)) | |||||
self.assertEqual((node2.contents, node2n1), next(gen2)) | |||||
self.assertEqual((node1.closing_tag, node1n1), next(gen1)) | |||||
self.assertRaises(StopIteration, next, gen1) | |||||
self.assertRaises(StopIteration, next, gen2) | |||||
self.assertRaises(StopIteration, next, gen3) | |||||
def test_strip(self): | |||||
"""test Tag.__strip__()""" | |||||
node1 = Tag(wraptext("i"), wraptext("foobar")) | |||||
node2 = Tag(wraptext("math"), wraptext("foobar")) | |||||
node3 = Tag(wraptext("br"), self_closing=True) | |||||
for a in (True, False): | |||||
for b in (True, False): | |||||
self.assertEqual("foobar", node1.__strip__(a, b)) | |||||
self.assertEqual(None, node2.__strip__(a, b)) | |||||
self.assertEqual(None, node3.__strip__(a, b)) | |||||
def test_showtree(self): | |||||
"""test Tag.__showtree__()""" | |||||
output = [] | |||||
getter, marker = object(), object() | |||||
get = lambda code: output.append((getter, code)) | |||||
mark = lambda: output.append(marker) | |||||
node1 = Tag(wraptext("ref"), wraptext("text"), [agen("name", "foo")]) | |||||
node2 = Tag(wraptext("br"), self_closing=True, padding=" ") | |||||
node3 = Tag(wraptext("br"), self_closing=True, invalid=True, | |||||
implicit=True, padding=" ") | |||||
node1.__showtree__(output.append, get, mark) | |||||
node2.__showtree__(output.append, get, mark) | |||||
node3.__showtree__(output.append, get, mark) | |||||
valid = [ | |||||
"<", (getter, node1.tag), (getter, node1.attributes[0].name), | |||||
" = ", marker, (getter, node1.attributes[0].value), ">", | |||||
(getter, node1.contents), "</", (getter, node1.closing_tag), ">", | |||||
"<", (getter, node2.tag), "/>", "</", (getter, node3.tag), ">"] | |||||
self.assertEqual(valid, output) | |||||
def test_tag(self): | |||||
"""test getter/setter for the tag attribute""" | |||||
tag = wraptext("ref") | |||||
node = Tag(tag, wraptext("text")) | |||||
self.assertIs(tag, node.tag) | |||||
self.assertIs(tag, node.closing_tag) | |||||
node.tag = "span" | |||||
self.assertWikicodeEqual(wraptext("span"), node.tag) | |||||
self.assertWikicodeEqual(wraptext("span"), node.closing_tag) | |||||
self.assertEqual("<span>text</span>", node) | |||||
def test_contents(self): | |||||
"""test getter/setter for the contents attribute""" | |||||
contents = wraptext("text") | |||||
node = Tag(wraptext("ref"), contents) | |||||
self.assertIs(contents, node.contents) | |||||
node.contents = "text and a {{template}}" | |||||
parsed = wrap([Text("text and a "), Template(wraptext("template"))]) | |||||
self.assertWikicodeEqual(parsed, node.contents) | |||||
self.assertEqual("<ref>text and a {{template}}</ref>", node) | |||||
def test_attributes(self): | |||||
"""test getter for the attributes attribute""" | |||||
attrs = [agen("name", "bar")] | |||||
node1 = Tag(wraptext("ref"), wraptext("foo")) | |||||
node2 = Tag(wraptext("ref"), wraptext("foo"), attrs) | |||||
self.assertEqual([], node1.attributes) | |||||
self.assertIs(attrs, node2.attributes) | |||||
def test_wiki_markup(self): | |||||
"""test getter/setter for the wiki_markup attribute""" | |||||
node = Tag(wraptext("i"), wraptext("italic text")) | |||||
self.assertIs(None, node.wiki_markup) | |||||
node.wiki_markup = "''" | |||||
self.assertEqual("''", node.wiki_markup) | |||||
self.assertEqual("''italic text''", node) | |||||
node.wiki_markup = False | |||||
self.assertFalse(node.wiki_markup) | |||||
self.assertEqual("<i>italic text</i>", node) | |||||
def test_self_closing(self): | |||||
"""test getter/setter for the self_closing attribute""" | |||||
node = Tag(wraptext("ref"), wraptext("foobar")) | |||||
self.assertFalse(node.self_closing) | |||||
node.self_closing = True | |||||
self.assertTrue(node.self_closing) | |||||
self.assertEqual("<ref/>", node) | |||||
node.self_closing = 0 | |||||
self.assertFalse(node.self_closing) | |||||
self.assertEqual("<ref>foobar</ref>", node) | |||||
def test_invalid(self): | |||||
"""test getter/setter for the invalid attribute""" | |||||
node = Tag(wraptext("br"), self_closing=True, implicit=True) | |||||
self.assertFalse(node.invalid) | |||||
node.invalid = True | |||||
self.assertTrue(node.invalid) | |||||
self.assertEqual("</br>", node) | |||||
node.invalid = 0 | |||||
self.assertFalse(node.invalid) | |||||
self.assertEqual("<br>", node) | |||||
def test_implicit(self): | |||||
"""test getter/setter for the implicit attribute""" | |||||
node = Tag(wraptext("br"), self_closing=True) | |||||
self.assertFalse(node.implicit) | |||||
node.implicit = True | |||||
self.assertTrue(node.implicit) | |||||
self.assertEqual("<br>", node) | |||||
node.implicit = 0 | |||||
self.assertFalse(node.implicit) | |||||
self.assertEqual("<br/>", node) | |||||
def test_padding(self): | |||||
"""test getter/setter for the padding attribute""" | |||||
node = Tag(wraptext("ref"), wraptext("foobar")) | |||||
self.assertEqual("", node.padding) | |||||
node.padding = " " | |||||
self.assertEqual(" ", node.padding) | |||||
self.assertEqual("<ref >foobar</ref>", node) | |||||
node.padding = None | |||||
self.assertEqual("", node.padding) | |||||
self.assertEqual("<ref>foobar</ref>", node) | |||||
self.assertRaises(ValueError, setattr, node, "padding", True) | |||||
def test_closing_tag(self): | |||||
"""test getter/setter for the closing_tag attribute""" | |||||
tag = wraptext("ref") | |||||
node = Tag(tag, wraptext("foobar")) | |||||
self.assertIs(tag, node.closing_tag) | |||||
node.closing_tag = "ref {{ignore me}}" | |||||
parsed = wrap([Text("ref "), Template(wraptext("ignore me"))]) | |||||
self.assertWikicodeEqual(parsed, node.closing_tag) | |||||
self.assertEqual("<ref>foobar</ref {{ignore me}}>", node) | |||||
def test_has(self): | |||||
"""test Tag.has()""" | |||||
node = Tag(wraptext("ref"), wraptext("cite"), [agen("name", "foo")]) | |||||
self.assertTrue(node.has("name")) | |||||
self.assertTrue(node.has(" name ")) | |||||
self.assertTrue(node.has(wraptext("name"))) | |||||
self.assertFalse(node.has("Name")) | |||||
self.assertFalse(node.has("foo")) | |||||
attrs = [agen("id", "foo"), agenp("class", "bar", " ", "\n", "\n"), | |||||
agen("foo", "bar"), agenpnv("foo", " ", " \n ", " \t")] | |||||
node2 = Tag(wraptext("div"), attrs=attrs, self_closing=True) | |||||
self.assertTrue(node2.has("id")) | |||||
self.assertTrue(node2.has("class")) | |||||
self.assertTrue(node2.has(attrs[1].pad_first + str(attrs[1].name) + | |||||
attrs[1].pad_before_eq)) | |||||
self.assertTrue(node2.has(attrs[3])) | |||||
self.assertTrue(node2.has(str(attrs[3]))) | |||||
self.assertFalse(node2.has("idclass")) | |||||
self.assertFalse(node2.has("id class")) | |||||
self.assertFalse(node2.has("id=foo")) | |||||
def test_get(self): | |||||
"""test Tag.get()""" | |||||
attrs = [agen("name", "foo")] | |||||
node = Tag(wraptext("ref"), wraptext("cite"), attrs) | |||||
self.assertIs(attrs[0], node.get("name")) | |||||
self.assertIs(attrs[0], node.get(" name ")) | |||||
self.assertIs(attrs[0], node.get(wraptext("name"))) | |||||
self.assertRaises(ValueError, node.get, "Name") | |||||
self.assertRaises(ValueError, node.get, "foo") | |||||
attrs = [agen("id", "foo"), agenp("class", "bar", " ", "\n", "\n"), | |||||
agen("foo", "bar"), agenpnv("foo", " ", " \n ", " \t")] | |||||
node2 = Tag(wraptext("div"), attrs=attrs, self_closing=True) | |||||
self.assertIs(attrs[0], node2.get("id")) | |||||
self.assertIs(attrs[1], node2.get("class")) | |||||
self.assertIs(attrs[1], node2.get( | |||||
attrs[1].pad_first + str(attrs[1].name) + attrs[1].pad_before_eq)) | |||||
self.assertIs(attrs[3], node2.get(attrs[3])) | |||||
self.assertIs(attrs[3], node2.get(str(attrs[3]))) | |||||
self.assertIs(attrs[3], node2.get(" foo")) | |||||
self.assertRaises(ValueError, node2.get, "idclass") | |||||
self.assertRaises(ValueError, node2.get, "id class") | |||||
self.assertRaises(ValueError, node2.get, "id=foo") | |||||
def test_add(self): | |||||
"""test Tag.add()""" | |||||
node = Tag(wraptext("ref"), wraptext("cite")) | |||||
node.add("name", "value") | |||||
node.add("name", "value", quoted=False) | |||||
node.add("name") | |||||
node.add(1, False) | |||||
node.add("style", "{{foobar}}") | |||||
node.add("name", "value", True, "\n", " ", " ") | |||||
attr1 = ' name="value"' | |||||
attr2 = " name=value" | |||||
attr3 = " name" | |||||
attr4 = ' 1="False"' | |||||
attr5 = ' style="{{foobar}}"' | |||||
attr6 = '\nname = "value"' | |||||
self.assertEqual(attr1, node.attributes[0]) | |||||
self.assertEqual(attr2, node.attributes[1]) | |||||
self.assertEqual(attr3, node.attributes[2]) | |||||
self.assertEqual(attr4, node.attributes[3]) | |||||
self.assertEqual(attr5, node.attributes[4]) | |||||
self.assertEqual(attr6, node.attributes[5]) | |||||
self.assertEqual(attr6, node.get("name")) | |||||
self.assertWikicodeEqual(wrap([Template(wraptext("foobar"))]), | |||||
node.attributes[4].value) | |||||
self.assertEqual("".join(("<ref", attr1, attr2, attr3, attr4, attr5, | |||||
attr6, ">cite</ref>")), node) | |||||
def test_remove(self): | |||||
"""test Tag.remove()""" | |||||
attrs = [agen("id", "foo"), agenp("class", "bar", " ", "\n", "\n"), | |||||
agen("foo", "bar"), agenpnv("foo", " ", " \n ", " \t")] | |||||
node = Tag(wraptext("div"), attrs=attrs, self_closing=True) | |||||
node.remove("class") | |||||
self.assertEqual('<div id="foo" foo="bar" foo \n />', node) | |||||
node.remove("foo") | |||||
self.assertEqual('<div id="foo"/>', node) | |||||
self.assertRaises(ValueError, node.remove, "foo") | |||||
node.remove("id") | |||||
self.assertEqual('<div/>', node) | |||||
if __name__ == "__main__": | |||||
unittest.main(verbosity=2) |
@@ -115,23 +115,23 @@ class TestTemplate(TreeEqualityTestCase): | |||||
self.assertEqual([], node1.params) | self.assertEqual([], node1.params) | ||||
self.assertIs(plist, node2.params) | self.assertIs(plist, node2.params) | ||||
def test_has_param(self): | |||||
"""test Template.has_param()""" | |||||
def test_has(self): | |||||
"""test Template.has()""" | |||||
node1 = Template(wraptext("foobar")) | node1 = Template(wraptext("foobar")) | ||||
node2 = Template(wraptext("foo"), | node2 = Template(wraptext("foo"), | ||||
[pgenh("1", "bar"), pgens("\nabc ", "def")]) | [pgenh("1", "bar"), pgens("\nabc ", "def")]) | ||||
node3 = Template(wraptext("foo"), | node3 = Template(wraptext("foo"), | ||||
[pgenh("1", "a"), pgens("b", "c"), pgens("1", "d")]) | [pgenh("1", "a"), pgens("b", "c"), pgens("1", "d")]) | ||||
node4 = Template(wraptext("foo"), [pgenh("1", "a"), pgens("b", " ")]) | node4 = Template(wraptext("foo"), [pgenh("1", "a"), pgens("b", " ")]) | ||||
self.assertFalse(node1.has_param("foobar")) | |||||
self.assertTrue(node2.has_param(1)) | |||||
self.assertTrue(node2.has_param("abc")) | |||||
self.assertFalse(node2.has_param("def")) | |||||
self.assertTrue(node3.has_param("1")) | |||||
self.assertTrue(node3.has_param(" b ")) | |||||
self.assertFalse(node4.has_param("b")) | |||||
self.assertTrue(node3.has_param("b", False)) | |||||
self.assertTrue(node4.has_param("b", False)) | |||||
self.assertFalse(node1.has("foobar")) | |||||
self.assertTrue(node2.has(1)) | |||||
self.assertTrue(node2.has("abc")) | |||||
self.assertFalse(node2.has("def")) | |||||
self.assertTrue(node3.has("1")) | |||||
self.assertTrue(node3.has(" b ")) | |||||
self.assertFalse(node4.has("b")) | |||||
self.assertTrue(node3.has("b", False)) | |||||
self.assertTrue(node4.has("b", False)) | |||||
def test_get(self): | def test_get(self): | ||||
"""test Template.get()""" | """test Template.get()""" | ||||
@@ -44,8 +44,8 @@ class TestTokens(unittest.TestCase): | |||||
self.assertEqual("bar", token2.foo) | self.assertEqual("bar", token2.foo) | ||||
self.assertEqual(123, token2.baz) | self.assertEqual(123, token2.baz) | ||||
self.assertRaises(KeyError, lambda: token1.foo) | |||||
self.assertRaises(KeyError, lambda: token2.bar) | |||||
self.assertFalse(token1.foo) | |||||
self.assertFalse(token2.bar) | |||||
token1.spam = "eggs" | token1.spam = "eggs" | ||||
token2.foo = "ham" | token2.foo = "ham" | ||||
@@ -53,7 +53,7 @@ class TestTokens(unittest.TestCase): | |||||
self.assertEqual("eggs", token1.spam) | self.assertEqual("eggs", token1.spam) | ||||
self.assertEqual("ham", token2.foo) | self.assertEqual("ham", token2.foo) | ||||
self.assertRaises(KeyError, lambda: token2.baz) | |||||
self.assertFalse(token2.baz) | |||||
self.assertRaises(KeyError, delattr, token2, "baz") | self.assertRaises(KeyError, delattr, token2, "baz") | ||||
def test_repr(self): | def test_repr(self): | ||||
@@ -21,6 +21,7 @@ | |||||
# SOFTWARE. | # SOFTWARE. | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
from functools import partial | |||||
import re | import re | ||||
from types import GeneratorType | from types import GeneratorType | ||||
import unittest | import unittest | ||||
@@ -122,66 +123,99 @@ class TestWikicode(TreeEqualityTestCase): | |||||
code3.insert(-1000, "derp") | code3.insert(-1000, "derp") | ||||
self.assertEqual("derp{{foo}}bar[[baz]]", code3) | self.assertEqual("derp{{foo}}bar[[baz]]", code3) | ||||
def _test_search(self, meth, expected): | |||||
"""Base test for insert_before(), insert_after(), and replace().""" | |||||
code = parse("{{a}}{{b}}{{c}}{{d}}{{e}}") | |||||
func = partial(meth, code) | |||||
func("{{b}}", "x", recursive=True) | |||||
func("{{d}}", "[[y]]", recursive=False) | |||||
func(code.get(2), "z") | |||||
self.assertEqual(expected[0], code) | |||||
self.assertRaises(ValueError, func, "{{r}}", "n", recursive=True) | |||||
self.assertRaises(ValueError, func, "{{r}}", "n", recursive=False) | |||||
fake = parse("{{a}}").get(0) | |||||
self.assertRaises(ValueError, func, fake, "n", recursive=True) | |||||
self.assertRaises(ValueError, func, fake, "n", recursive=False) | |||||
code2 = parse("{{a}}{{a}}{{a}}{{b}}{{b}}{{b}}") | |||||
func = partial(meth, code2) | |||||
func(code2.get(1), "c", recursive=False) | |||||
func("{{a}}", "d", recursive=False) | |||||
func(code2.get(-1), "e", recursive=True) | |||||
func("{{b}}", "f", recursive=True) | |||||
self.assertEqual(expected[1], code2) | |||||
code3 = parse("{{a|{{b}}|{{c|d={{f}}}}}}") | |||||
func = partial(meth, code3) | |||||
obj = code3.get(0).params[0].value.get(0) | |||||
self.assertRaises(ValueError, func, obj, "x", recursive=False) | |||||
func(obj, "x", recursive=True) | |||||
self.assertRaises(ValueError, func, "{{f}}", "y", recursive=False) | |||||
func("{{f}}", "y", recursive=True) | |||||
self.assertEqual(expected[2], code3) | |||||
code4 = parse("{{a}}{{b}}{{c}}{{d}}{{e}}{{f}}{{g}}{{h}}{{i}}{{j}}") | |||||
func = partial(meth, code4) | |||||
fake = parse("{{b}}{{c}}") | |||||
self.assertRaises(ValueError, func, fake, "q", recursive=False) | |||||
self.assertRaises(ValueError, func, fake, "q", recursive=True) | |||||
func("{{b}}{{c}}", "w", recursive=False) | |||||
func("{{d}}{{e}}", "x", recursive=True) | |||||
func(wrap(code4.nodes[-2:]), "y", recursive=False) | |||||
func(wrap(code4.nodes[-2:]), "z", recursive=True) | |||||
self.assertEqual(expected[3], code4) | |||||
self.assertRaises(ValueError, func, "{{c}}{{d}}", "q", recursive=False) | |||||
self.assertRaises(ValueError, func, "{{c}}{{d}}", "q", recursive=True) | |||||
code5 = parse("{{a|{{b}}{{c}}|{{f|{{g}}={{h}}{{i}}}}}}") | |||||
func = partial(meth, code5) | |||||
self.assertRaises(ValueError, func, "{{b}}{{c}}", "x", recursive=False) | |||||
func("{{b}}{{c}}", "x", recursive=True) | |||||
obj = code5.get(0).params[1].value.get(0).params[0].value | |||||
self.assertRaises(ValueError, func, obj, "y", recursive=False) | |||||
func(obj, "y", recursive=True) | |||||
self.assertEqual(expected[4], code5) | |||||
code6 = parse("here is {{some text and a {{template}}}}") | |||||
func = partial(meth, code6) | |||||
self.assertRaises(ValueError, func, "text and", "ab", recursive=False) | |||||
func("text and", "ab", recursive=True) | |||||
self.assertRaises(ValueError, func, "is {{some", "cd", recursive=False) | |||||
func("is {{some", "cd", recursive=True) | |||||
self.assertEqual(expected[5], code6) | |||||
def test_insert_before(self): | def test_insert_before(self): | ||||
"""test Wikicode.insert_before()""" | """test Wikicode.insert_before()""" | ||||
code = parse("{{a}}{{b}}{{c}}{{d}}") | |||||
code.insert_before("{{b}}", "x", recursive=True) | |||||
code.insert_before("{{d}}", "[[y]]", recursive=False) | |||||
self.assertEqual("{{a}}x{{b}}{{c}}[[y]]{{d}}", code) | |||||
code.insert_before(code.get(2), "z") | |||||
self.assertEqual("{{a}}xz{{b}}{{c}}[[y]]{{d}}", code) | |||||
self.assertRaises(ValueError, code.insert_before, "{{r}}", "n", | |||||
recursive=True) | |||||
self.assertRaises(ValueError, code.insert_before, "{{r}}", "n", | |||||
recursive=False) | |||||
code2 = parse("{{a|{{b}}|{{c|d={{f}}}}}}") | |||||
code2.insert_before(code2.get(0).params[0].value.get(0), "x", | |||||
recursive=True) | |||||
code2.insert_before("{{f}}", "y", recursive=True) | |||||
self.assertEqual("{{a|x{{b}}|{{c|d=y{{f}}}}}}", code2) | |||||
self.assertRaises(ValueError, code2.insert_before, "{{f}}", "y", | |||||
recursive=False) | |||||
meth = lambda code, *args, **kw: code.insert_before(*args, **kw) | |||||
expected = [ | |||||
"{{a}}xz{{b}}{{c}}[[y]]{{d}}{{e}}", | |||||
"d{{a}}cd{{a}}d{{a}}f{{b}}f{{b}}ef{{b}}", | |||||
"{{a|x{{b}}|{{c|d=y{{f}}}}}}", | |||||
"{{a}}w{{b}}{{c}}x{{d}}{{e}}{{f}}{{g}}{{h}}yz{{i}}{{j}}", | |||||
"{{a|x{{b}}{{c}}|{{f|{{g}}=y{{h}}{{i}}}}}}", | |||||
"here cdis {{some abtext and a {{template}}}}"] | |||||
self._test_search(meth, expected) | |||||
def test_insert_after(self): | def test_insert_after(self): | ||||
"""test Wikicode.insert_after()""" | """test Wikicode.insert_after()""" | ||||
code = parse("{{a}}{{b}}{{c}}{{d}}") | |||||
code.insert_after("{{b}}", "x", recursive=True) | |||||
code.insert_after("{{d}}", "[[y]]", recursive=False) | |||||
self.assertEqual("{{a}}{{b}}x{{c}}{{d}}[[y]]", code) | |||||
code.insert_after(code.get(2), "z") | |||||
self.assertEqual("{{a}}{{b}}xz{{c}}{{d}}[[y]]", code) | |||||
self.assertRaises(ValueError, code.insert_after, "{{r}}", "n", | |||||
recursive=True) | |||||
self.assertRaises(ValueError, code.insert_after, "{{r}}", "n", | |||||
recursive=False) | |||||
code2 = parse("{{a|{{b}}|{{c|d={{f}}}}}}") | |||||
code2.insert_after(code2.get(0).params[0].value.get(0), "x", | |||||
recursive=True) | |||||
code2.insert_after("{{f}}", "y", recursive=True) | |||||
self.assertEqual("{{a|{{b}}x|{{c|d={{f}}y}}}}", code2) | |||||
self.assertRaises(ValueError, code2.insert_after, "{{f}}", "y", | |||||
recursive=False) | |||||
meth = lambda code, *args, **kw: code.insert_after(*args, **kw) | |||||
expected = [ | |||||
"{{a}}{{b}}xz{{c}}{{d}}[[y]]{{e}}", | |||||
"{{a}}d{{a}}dc{{a}}d{{b}}f{{b}}f{{b}}fe", | |||||
"{{a|{{b}}x|{{c|d={{f}}y}}}}", | |||||
"{{a}}{{b}}{{c}}w{{d}}{{e}}x{{f}}{{g}}{{h}}{{i}}{{j}}yz", | |||||
"{{a|{{b}}{{c}}x|{{f|{{g}}={{h}}{{i}}y}}}}", | |||||
"here is {{somecd text andab a {{template}}}}"] | |||||
self._test_search(meth, expected) | |||||
def test_replace(self): | def test_replace(self): | ||||
"""test Wikicode.replace()""" | """test Wikicode.replace()""" | ||||
code = parse("{{a}}{{b}}{{c}}{{d}}") | |||||
code.replace("{{b}}", "x", recursive=True) | |||||
code.replace("{{d}}", "[[y]]", recursive=False) | |||||
self.assertEqual("{{a}}x{{c}}[[y]]", code) | |||||
code.replace(code.get(1), "z") | |||||
self.assertEqual("{{a}}z{{c}}[[y]]", code) | |||||
self.assertRaises(ValueError, code.replace, "{{r}}", "n", | |||||
recursive=True) | |||||
self.assertRaises(ValueError, code.replace, "{{r}}", "n", | |||||
recursive=False) | |||||
code2 = parse("{{a|{{b}}|{{c|d={{f}}}}}}") | |||||
code2.replace(code2.get(0).params[0].value.get(0), "x", recursive=True) | |||||
code2.replace("{{f}}", "y", recursive=True) | |||||
self.assertEqual("{{a|x|{{c|d=y}}}}", code2) | |||||
self.assertRaises(ValueError, code2.replace, "y", "z", recursive=False) | |||||
meth = lambda code, *args, **kw: code.replace(*args, **kw) | |||||
expected = [ | |||||
"{{a}}xz[[y]]{{e}}", "dcdffe", "{{a|x|{{c|d=y}}}}", | |||||
"{{a}}wx{{f}}{{g}}z", "{{a|x|{{f|{{g}}=y}}}}", | |||||
"here cd ab a {{template}}}}"] | |||||
self._test_search(meth, expected) | |||||
def test_append(self): | def test_append(self): | ||||
"""test Wikicode.append()""" | """test Wikicode.append()""" | ||||
@@ -197,18 +231,25 @@ class TestWikicode(TreeEqualityTestCase): | |||||
def test_remove(self): | def test_remove(self): | ||||
"""test Wikicode.remove()""" | """test Wikicode.remove()""" | ||||
code = parse("{{a}}{{b}}{{c}}{{d}}") | |||||
code.remove("{{b}}", recursive=True) | |||||
code.remove(code.get(1), recursive=True) | |||||
self.assertEqual("{{a}}{{d}}", code) | |||||
self.assertRaises(ValueError, code.remove, "{{r}}", recursive=True) | |||||
self.assertRaises(ValueError, code.remove, "{{r}}", recursive=False) | |||||
code2 = parse("{{a|{{b}}|{{c|d={{f}}{{h}}}}}}") | |||||
code2.remove(code2.get(0).params[0].value.get(0), recursive=True) | |||||
code2.remove("{{f}}", recursive=True) | |||||
self.assertEqual("{{a||{{c|d={{h}}}}}}", code2) | |||||
self.assertRaises(ValueError, code2.remove, "{{h}}", recursive=False) | |||||
meth = lambda code, obj, value, **kw: code.remove(obj, **kw) | |||||
expected = [ | |||||
"{{a}}{{c}}", "", "{{a||{{c|d=}}}}", "{{a}}{{f}}", | |||||
"{{a||{{f|{{g}}=}}}}", "here a {{template}}}}" | |||||
] | |||||
self._test_search(meth, expected) | |||||
def test_matches(self): | |||||
"""test Wikicode.matches()""" | |||||
code1 = parse("Cleanup") | |||||
code2 = parse("\nstub<!-- TODO: make more specific -->") | |||||
self.assertTrue(code1.matches("Cleanup")) | |||||
self.assertTrue(code1.matches("cleanup")) | |||||
self.assertTrue(code1.matches(" cleanup\n")) | |||||
self.assertFalse(code1.matches("CLEANup")) | |||||
self.assertFalse(code1.matches("Blah")) | |||||
self.assertTrue(code2.matches("stub")) | |||||
self.assertTrue(code2.matches("Stub<!-- no, it's fine! -->")) | |||||
self.assertFalse(code2.matches("StuB")) | |||||
def test_filter_family(self): | def test_filter_family(self): | ||||
"""test the Wikicode.i?filter() family of functions""" | """test the Wikicode.i?filter() family of functions""" | ||||
@@ -219,11 +260,11 @@ class TestWikicode(TreeEqualityTestCase): | |||||
code = parse("a{{b}}c[[d]]{{{e}}}{{f}}[[g]]") | code = parse("a{{b}}c[[d]]{{{e}}}{{f}}[[g]]") | ||||
for func in (code.filter, ifilter(code)): | for func in (code.filter, ifilter(code)): | ||||
self.assertEqual(["a", "{{b}}", "c", "[[d]]", "{{{e}}}", "{{f}}", | |||||
"[[g]]"], func()) | |||||
self.assertEqual(["a", "{{b}}", "b", "c", "[[d]]", "d", "{{{e}}}", | |||||
"e", "{{f}}", "f", "[[g]]", "g"], func()) | |||||
self.assertEqual(["{{{e}}}"], func(forcetype=Argument)) | self.assertEqual(["{{{e}}}"], func(forcetype=Argument)) | ||||
self.assertIs(code.get(4), func(forcetype=Argument)[0]) | self.assertIs(code.get(4), func(forcetype=Argument)[0]) | ||||
self.assertEqual(["a", "c"], func(forcetype=Text)) | |||||
self.assertEqual(list("abcdefg"), func(forcetype=Text)) | |||||
self.assertEqual([], func(forcetype=Heading)) | self.assertEqual([], func(forcetype=Heading)) | ||||
self.assertRaises(TypeError, func, forcetype=True) | self.assertRaises(TypeError, func, forcetype=True) | ||||
@@ -235,11 +276,12 @@ class TestWikicode(TreeEqualityTestCase): | |||||
self.assertEqual(["{{{e}}}"], get_filter("arguments")) | self.assertEqual(["{{{e}}}"], get_filter("arguments")) | ||||
self.assertIs(code.get(4), get_filter("arguments")[0]) | self.assertIs(code.get(4), get_filter("arguments")[0]) | ||||
self.assertEqual([], get_filter("comments")) | self.assertEqual([], get_filter("comments")) | ||||
self.assertEqual([], get_filter("external_links")) | |||||
self.assertEqual([], get_filter("headings")) | self.assertEqual([], get_filter("headings")) | ||||
self.assertEqual([], get_filter("html_entities")) | self.assertEqual([], get_filter("html_entities")) | ||||
self.assertEqual([], get_filter("tags")) | self.assertEqual([], get_filter("tags")) | ||||
self.assertEqual(["{{b}}", "{{f}}"], get_filter("templates")) | self.assertEqual(["{{b}}", "{{f}}"], get_filter("templates")) | ||||
self.assertEqual(["a", "c"], get_filter("text")) | |||||
self.assertEqual(list("abcdefg"), get_filter("text")) | |||||
self.assertEqual(["[[d]]", "[[g]]"], get_filter("wikilinks")) | self.assertEqual(["[[d]]", "[[g]]"], get_filter("wikilinks")) | ||||
code2 = parse("{{a|{{b}}|{{c|d={{f}}{{h}}}}}}") | code2 = parse("{{a|{{b}}|{{c|d={{f}}{{h}}}}}}") | ||||
@@ -252,13 +294,13 @@ class TestWikicode(TreeEqualityTestCase): | |||||
code3 = parse("{{foobar}}{{FOO}}{{baz}}{{bz}}") | code3 = parse("{{foobar}}{{FOO}}{{baz}}{{bz}}") | ||||
for func in (code3.filter, ifilter(code3)): | for func in (code3.filter, ifilter(code3)): | ||||
self.assertEqual(["{{foobar}}", "{{FOO}}"], func(matches=r"foo")) | |||||
self.assertEqual(["{{foobar}}", "{{FOO}}"], func(recursive=False, matches=r"foo")) | |||||
self.assertEqual(["{{foobar}}", "{{FOO}}"], | self.assertEqual(["{{foobar}}", "{{FOO}}"], | ||||
func(matches=r"^{{foo.*?}}")) | |||||
func(recursive=False, matches=r"^{{foo.*?}}")) | |||||
self.assertEqual(["{{foobar}}"], | self.assertEqual(["{{foobar}}"], | ||||
func(matches=r"^{{foo.*?}}", flags=re.UNICODE)) | |||||
self.assertEqual(["{{baz}}", "{{bz}}"], func(matches=r"^{{b.*?z")) | |||||
self.assertEqual(["{{baz}}"], func(matches=r"^{{b.+?z}}")) | |||||
func(recursive=False, matches=r"^{{foo.*?}}", flags=re.UNICODE)) | |||||
self.assertEqual(["{{baz}}", "{{bz}}"], func(recursive=False, matches=r"^{{b.*?z")) | |||||
self.assertEqual(["{{baz}}"], func(recursive=False, matches=r"^{{b.+?z}}")) | |||||
self.assertEqual(["{{a|{{b}}|{{c|d={{f}}{{h}}}}}}"], | self.assertEqual(["{{a|{{b}}|{{c|d={{f}}{{h}}}}}}"], | ||||
code2.filter_templates(recursive=False)) | code2.filter_templates(recursive=False)) | ||||
@@ -0,0 +1,473 @@ | |||||
name: basic | |||||
label: basic external link | |||||
input: "http://example.com/" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://example.com/"), ExternalLinkClose()] | |||||
--- | |||||
name: basic_brackets | |||||
label: basic external link in brackets | |||||
input: "[http://example.com/]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.com/"), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_space | |||||
label: basic external link in brackets, with a space after | |||||
input: "[http://example.com/ ]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.com/"), ExternalLinkSeparator(), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_title | |||||
label: basic external link in brackets, with a title | |||||
input: "[http://example.com/ Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.com/"), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_multiword_title | |||||
label: basic external link in brackets, with a multi-word title | |||||
input: "[http://example.com/ Example Web Page]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.com/"), ExternalLinkSeparator(), Text(text="Example Web Page"), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_adjacent | |||||
label: three adjacent bracket-enclosed external links | |||||
input: "[http://foo.com/ Foo][http://bar.com/ Bar]\n[http://baz.com/ Baz]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://foo.com/"), ExternalLinkSeparator(), Text(text="Foo"), ExternalLinkClose(), ExternalLinkOpen(brackets=True), Text(text="http://bar.com/"), ExternalLinkSeparator(), Text(text="Bar"), ExternalLinkClose(), Text(text="\n"), ExternalLinkOpen(brackets=True), Text(text="http://baz.com/"), ExternalLinkSeparator(), Text(text="Baz"), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_newline_before | |||||
label: bracket-enclosed link with a newline before the title | |||||
input: "[http://example.com/ \nExample]" | |||||
output: [Text(text="["), ExternalLinkOpen(brackets=False), Text(text="http://example.com/"), ExternalLinkClose(), Text(text=" \nExample]")] | |||||
--- | |||||
name: brackets_newline_inside | |||||
label: bracket-enclosed link with a newline in the title | |||||
input: "[http://example.com/ Example \nWeb Page]" | |||||
output: [Text(text="["), ExternalLinkOpen(brackets=False), Text(text="http://example.com/"), ExternalLinkClose(), Text(text=" Example \nWeb Page]")] | |||||
--- | |||||
name: brackets_newline_after | |||||
label: bracket-enclosed link with a newline after the title | |||||
input: "[http://example.com/ Example\n]" | |||||
output: [Text(text="["), ExternalLinkOpen(brackets=False), Text(text="http://example.com/"), ExternalLinkClose(), Text(text=" Example\n]")] | |||||
--- | |||||
name: brackets_space_before | |||||
label: bracket-enclosed link with a space before the URL | |||||
input: "[ http://example.com Example]" | |||||
output: [Text(text="[ "), ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose(), Text(text=" Example]")] | |||||
--- | |||||
name: brackets_title_like_url | |||||
label: bracket-enclosed link with a title that looks like a URL | |||||
input: "[http://example.com http://example.com]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.com"), ExternalLinkSeparator(), Text(text="http://example.com"), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_recursive | |||||
label: bracket-enclosed link with a bracket-enclosed link as the title | |||||
input: "[http://example.com [http://example.com]]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.com"), ExternalLinkSeparator(), Text(text="[http://example.com"), ExternalLinkClose(), Text(text="]")] | |||||
--- | |||||
name: period_after | |||||
label: a period after a free link that is excluded | |||||
input: "http://example.com." | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose(), Text(text=".")] | |||||
--- | |||||
name: colons_after | |||||
label: colons after a free link that are excluded | |||||
input: "http://example.com/foo:bar.:;baz!?," | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://example.com/foo:bar.:;baz"), ExternalLinkClose(), Text(text="!?,")] | |||||
--- | |||||
name: close_paren_after_excluded | |||||
label: a closing parenthesis after a free link that is excluded | |||||
input: "http://example.)com)" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://example.)com"), ExternalLinkClose(), Text(text=")")] | |||||
--- | |||||
name: close_paren_after_included | |||||
label: a closing parenthesis after a free link that is included because of an opening parenthesis in the URL | |||||
input: "http://example.(com)" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://example.(com)"), ExternalLinkClose()] | |||||
--- | |||||
name: open_bracket_inside | |||||
label: an open bracket inside a free link that causes it to be ended abruptly | |||||
input: "http://foobar[baz.com" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://foobar"), ExternalLinkClose(), Text(text="[baz.com")] | |||||
--- | |||||
name: brackets_period_after | |||||
label: a period after a bracket-enclosed link that is included | |||||
input: "[http://example.com. Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.com."), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_colons_after | |||||
label: colons after a bracket-enclosed link that are included | |||||
input: "[http://example.com/foo:bar.:;baz!?, Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.com/foo:bar.:;baz!?,"), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_close_paren_after_included | |||||
label: a closing parenthesis after a bracket-enclosed link that is included | |||||
input: "[http://example.)com) Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.)com)"), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_close_paren_after_included_2 | |||||
label: a closing parenthesis after a bracket-enclosed link that is also included | |||||
input: "[http://example.(com) Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://example.(com)"), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: brackets_open_bracket_inside | |||||
label: an open bracket inside a bracket-enclosed link that is also included | |||||
input: "[http://foobar[baz.com Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="http://foobar[baz.com"), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: adjacent_space | |||||
label: two free links separated by a space | |||||
input: "http://example.com http://example.com" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose(), Text(text=" "), ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose()] | |||||
--- | |||||
name: adjacent_newline | |||||
label: two free links separated by a newline | |||||
input: "http://example.com\nhttp://example.com" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose(), Text(text="\n"), ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose()] | |||||
--- | |||||
name: adjacent_close_bracket | |||||
label: two free links separated by a close bracket | |||||
input: "http://example.com]http://example.com" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose(), Text(text="]"), ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose()] | |||||
--- | |||||
name: html_entity_in_url | |||||
label: a HTML entity parsed correctly inside a free link | |||||
input: "http://exa mple.com/" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://exa"), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), Text(text="mple.com/"), ExternalLinkClose()] | |||||
--- | |||||
name: template_in_url | |||||
label: a template parsed correctly inside a free link | |||||
input: "http://exa{{template}}mple.com/" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://exa"), TemplateOpen(), Text(text="template"), TemplateClose(), Text(text="mple.com/"), ExternalLinkClose()] | |||||
--- | |||||
name: argument_in_url | |||||
label: an argument parsed correctly inside a free link | |||||
input: "http://exa{{{argument}}}mple.com/" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://exa"), ArgumentOpen(), Text(text="argument"), ArgumentClose(), Text(text="mple.com/"), ExternalLinkClose()] | |||||
--- | |||||
name: wikilink_in_url | |||||
label: a wikilink that destroys a free link | |||||
input: "http://exa[[wikilink]]mple.com/" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://exa"), ExternalLinkClose(), WikilinkOpen(), Text(text="wikilink"), WikilinkClose(), Text(text="mple.com/")] | |||||
--- | |||||
name: external_link_in_url | |||||
label: a bracketed link that destroys a free link | |||||
input: "http://exa[http://example.com/]mple.com/" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="http://exa"), ExternalLinkClose(), ExternalLinkOpen(brackets=True), Text(text="http://example.com/"), ExternalLinkClose(), Text(text="mple.com/")] | |||||
--- | |||||
name: spaces_padding | |||||
label: spaces padding a free link | |||||
input: " http://example.com " | |||||
output: [Text(text=" "), ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose(), Text(text=" ")] | |||||
--- | |||||
name: text_and_spaces_padding | |||||
label: text and spaces padding a free link | |||||
input: "x http://example.com x" | |||||
output: [Text(text="x "), ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose(), Text(text=" x")] | |||||
--- | |||||
name: template_before | |||||
label: a template before a free link | |||||
input: "{{foo}}http://example.com" | |||||
output: [TemplateOpen(), Text(text="foo"), TemplateClose(), ExternalLinkOpen(brackets=False), Text(text="http://example.com"), ExternalLinkClose()] | |||||
--- | |||||
name: spaces_padding_no_slashes | |||||
label: spaces padding a free link with no slashes after the colon | |||||
input: " mailto:example@example.com " | |||||
output: [Text(text=" "), ExternalLinkOpen(brackets=False), Text(text="mailto:example@example.com"), ExternalLinkClose(), Text(text=" ")] | |||||
--- | |||||
name: text_and_spaces_padding_no_slashes | |||||
label: text and spaces padding a free link with no slashes after the colon | |||||
input: "x mailto:example@example.com x" | |||||
output: [Text(text="x "), ExternalLinkOpen(brackets=False), Text(text="mailto:example@example.com"), ExternalLinkClose(), Text(text=" x")] | |||||
--- | |||||
name: template_before_no_slashes | |||||
label: a template before a free link with no slashes after the colon | |||||
input: "{{foo}}mailto:example@example.com" | |||||
output: [TemplateOpen(), Text(text="foo"), TemplateClose(), ExternalLinkOpen(brackets=False), Text(text="mailto:example@example.com"), ExternalLinkClose()] | |||||
--- | |||||
name: no_slashes | |||||
label: a free link with no slashes after the colon | |||||
input: "mailto:example@example.com" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="mailto:example@example.com"), ExternalLinkClose()] | |||||
--- | |||||
name: slashes_optional | |||||
label: a free link using a scheme that doesn't need slashes, but has them anyway | |||||
input: "mailto://example@example.com" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="mailto://example@example.com"), ExternalLinkClose()] | |||||
--- | |||||
name: short | |||||
label: a very short free link | |||||
input: "mailto://abc" | |||||
output: [ExternalLinkOpen(brackets=False), Text(text="mailto://abc"), ExternalLinkClose()] | |||||
--- | |||||
name: slashes_missing | |||||
label: slashes missing from a free link with a scheme that requires them | |||||
input: "http:example@example.com" | |||||
output: [Text(text="http:example@example.com")] | |||||
--- | |||||
name: no_scheme_but_slashes | |||||
label: no scheme in a free link, but slashes (protocol-relative free links are not supported) | |||||
input: "//example.com" | |||||
output: [Text(text="//example.com")] | |||||
--- | |||||
name: no_scheme_but_colon | |||||
label: no scheme in a free link, but a colon | |||||
input: " :example.com" | |||||
output: [Text(text=" :example.com")] | |||||
--- | |||||
name: no_scheme_but_colon_and_slashes | |||||
label: no scheme in a free link, but a colon and slashes | |||||
input: " ://example.com" | |||||
output: [Text(text=" ://example.com")] | |||||
--- | |||||
name: fake_scheme_no_slashes | |||||
label: a nonexistent scheme in a free link, without slashes | |||||
input: "fake:example.com" | |||||
output: [Text(text="fake:example.com")] | |||||
--- | |||||
name: fake_scheme_slashes | |||||
label: a nonexistent scheme in a free link, with slashes | |||||
input: "fake://example.com" | |||||
output: [Text(text="fake://example.com")] | |||||
--- | |||||
name: fake_scheme_brackets_no_slashes | |||||
label: a nonexistent scheme in a bracketed link, without slashes | |||||
input: "[fake:example.com]" | |||||
output: [Text(text="[fake:example.com]")] | |||||
--- | |||||
name: fake_scheme_brackets_slashes | |||||
label: #=a nonexistent scheme in a bracketed link, with slashes | |||||
input: "[fake://example.com]" | |||||
output: [Text(text="[fake://example.com]")] | |||||
--- | |||||
name: interrupted_scheme | |||||
label: an otherwise valid scheme with something in the middle of it, in a free link | |||||
input: "ht?tp://example.com" | |||||
output: [Text(text="ht?tp://example.com")] | |||||
--- | |||||
name: interrupted_scheme_brackets | |||||
label: an otherwise valid scheme with something in the middle of it, in a bracketed link | |||||
input: "[ht?tp://example.com]" | |||||
output: [Text(text="[ht?tp://example.com]")] | |||||
--- | |||||
name: no_slashes_brackets | |||||
label: no slashes after the colon in a bracketed link | |||||
input: "[mailto:example@example.com Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="mailto:example@example.com"), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: space_before_no_slashes_brackets | |||||
label: a space before a bracketed link with no slashes after the colon | |||||
input: "[ mailto:example@example.com Example]" | |||||
output: [Text(text="[ "), ExternalLinkOpen(brackets=False), Text(text="mailto:example@example.com"), ExternalLinkClose(), Text(text=" Example]")] | |||||
--- | |||||
name: slashes_optional_brackets | |||||
label: a bracketed link using a scheme that doesn't need slashes, but has them anyway | |||||
input: "[mailto://example@example.com Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="mailto://example@example.com"), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: short_brackets | |||||
label: a very short link in brackets | |||||
input: "[mailto://abc Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="mailto://abc"), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: slashes_missing_brackets | |||||
label: slashes missing from a scheme that requires them in a bracketed link | |||||
input: "[http:example@example.com Example]" | |||||
output: [Text(text="[http:example@example.com Example]")] | |||||
--- | |||||
name: protcol_relative | |||||
label: a protocol-relative link (in brackets) | |||||
input: "[//example.com Example]" | |||||
output: [ExternalLinkOpen(brackets=True), Text(text="//example.com"), ExternalLinkSeparator(), Text(text="Example"), ExternalLinkClose()] | |||||
--- | |||||
name: scheme_missing_but_colon_brackets | |||||
label: scheme missing from a bracketed link, but with a colon | |||||
input: "[:example.com Example]" | |||||
output: [Text(text="[:example.com Example]")] | |||||
--- | |||||
name: scheme_missing_but_colon_slashes_brackets | |||||
label: scheme missing from a bracketed link, but with a colon and slashes | |||||
input: "[://example.com Example]" | |||||
output: [Text(text="[://example.com Example]")] | |||||
--- | |||||
name: unclosed_protocol_relative | |||||
label: an unclosed protocol-relative bracketed link | |||||
input: "[//example.com" | |||||
output: [Text(text="[//example.com")] | |||||
--- | |||||
name: space_before_protcol_relative | |||||
label: a space before a protocol-relative bracketed link | |||||
input: "[ //example.com]" | |||||
output: [Text(text="[ //example.com]")] | |||||
--- | |||||
name: unclosed_just_scheme | |||||
label: an unclosed bracketed link, ending after the scheme | |||||
input: "[http" | |||||
output: [Text(text="[http")] | |||||
--- | |||||
name: unclosed_scheme_colon | |||||
label: an unclosed bracketed link, ending after the colon | |||||
input: "[http:" | |||||
output: [Text(text="[http:")] | |||||
--- | |||||
name: unclosed_scheme_colon_slashes | |||||
label: an unclosed bracketed link, ending after the slashes | |||||
input: "[http://" | |||||
output: [Text(text="[http://")] | |||||
--- | |||||
name: incomplete_bracket | |||||
label: just an open bracket | |||||
input: "[" | |||||
output: [Text(text="[")] | |||||
--- | |||||
name: incomplete_scheme_colon | |||||
label: a free link with just a scheme and a colon | |||||
input: "http:" | |||||
output: [Text(text="http:")] | |||||
--- | |||||
name: incomplete_scheme_colon_slashes | |||||
label: a free link with just a scheme, colon, and slashes | |||||
input: "http://" | |||||
output: [Text(text="http://")] | |||||
--- | |||||
name: brackets_scheme_but_no_url | |||||
label: brackets around a scheme and a colon | |||||
input: "[mailto:]" | |||||
output: [Text(text="[mailto:]")] | |||||
--- | |||||
name: brackets_scheme_slashes_but_no_url | |||||
label: brackets around a scheme, colon, and slashes | |||||
input: "[http://]" | |||||
output: [Text(text="[http://]")] | |||||
--- | |||||
name: brackets_scheme_title_but_no_url | |||||
label: brackets around a scheme, colon, and slashes, with a title | |||||
input: "[http:// Example]" | |||||
output: [Text(text="[http:// Example]")] |
@@ -117,6 +117,20 @@ output: [Text(text="&;")] | |||||
--- | --- | ||||
name: invalid_partial_amp_pound | |||||
label: invalid entities: just an ampersand, pound sign | |||||
input: "&#" | |||||
output: [Text(text="&#")] | |||||
--- | |||||
name: invalid_partial_amp_pound_x | |||||
label: invalid entities: just an ampersand, pound sign, x | |||||
input: "&#x" | |||||
output: [Text(text="&#x")] | |||||
--- | |||||
name: invalid_partial_amp_pound_semicolon | name: invalid_partial_amp_pound_semicolon | ||||
label: invalid entities: an ampersand, pound sign, and semicolon | label: invalid entities: an ampersand, pound sign, and semicolon | ||||
input: "&#;" | input: "&#;" | ||||
@@ -12,6 +12,13 @@ output: [TemplateOpen(), ArgumentOpen(), ArgumentOpen(), Text(text="foo"), Argum | |||||
--- | --- | ||||
name: link_in_template_name | |||||
label: a wikilink inside a template name, which breaks the template | |||||
input: "{{foo[[bar]]}}" | |||||
output: [Text(text="{{foo"), WikilinkOpen(), Text(text="bar"), WikilinkClose(), Text(text="}}")] | |||||
--- | |||||
name: rich_heading | name: rich_heading | ||||
label: a heading with templates/wikilinks in it | label: a heading with templates/wikilinks in it | ||||
input: "== Head{{ing}} [[with]] {{{funky|{{stuf}}}}} ==" | input: "== Head{{ing}} [[with]] {{{funky|{{stuf}}}}} ==" | ||||
@@ -33,6 +40,13 @@ output: [Text(text="&n"), CommentStart(), Text(text="foo"), CommentEnd(), Text(t | |||||
--- | --- | ||||
name: rich_tags | |||||
label: a HTML tag with tons of other things in it | |||||
input: "{{dubious claim}}<ref name={{abc}} foo="bar {{baz}}" abc={{de}}f ghi=j{{k}}{{l}} \n mno = "{{p}} [[q]] {{r}}">[[Source]]</ref>" | |||||
output: [TemplateOpen(), Text(text="dubious claim"), TemplateClose(), TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TemplateOpen(), Text(text="abc"), TemplateClose(), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="foo"), TagAttrEquals(), TagAttrQuote(), Text(text="bar "), TemplateOpen(), Text(text="baz"), TemplateClose(), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="abc"), TagAttrEquals(), TemplateOpen(), Text(text="de"), TemplateClose(), Text(text="f"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="ghi"), TagAttrEquals(), Text(text="j"), TemplateOpen(), Text(text="k"), TemplateClose(), TemplateOpen(), Text(text="l"), TemplateClose(), TagAttrStart(pad_first=" \n ", pad_before_eq=" ", pad_after_eq=" "), Text(text="mno"), TagAttrEquals(), TagAttrQuote(), TemplateOpen(), Text(text="p"), TemplateClose(), Text(text=" "), WikilinkOpen(), Text(text="q"), WikilinkClose(), Text(text=" "), TemplateOpen(), Text(text="r"), TemplateClose(), TagCloseOpen(padding=""), WikilinkOpen(), Text(text="Source"), WikilinkClose(), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: wildcard | name: wildcard | ||||
label: a wildcard assortment of various things | label: a wildcard assortment of various things | ||||
input: "{{{{{{{{foo}}bar|baz=biz}}buzz}}usr|{{bin}}}}" | input: "{{{{{{{{foo}}bar|baz=biz}}buzz}}usr|{{bin}}}}" | ||||
@@ -44,3 +58,17 @@ name: wildcard_redux | |||||
label: an even wilder assortment of various things | label: an even wilder assortment of various things | ||||
input: "{{a|b|{{c|[[d]]{{{e}}}}}}}[[f|{{{g}}}<!--h-->]]{{i|j= }}" | input: "{{a|b|{{c|[[d]]{{{e}}}}}}}[[f|{{{g}}}<!--h-->]]{{i|j= }}" | ||||
output: [TemplateOpen(), Text(text="a"), TemplateParamSeparator(), Text(text="b"), TemplateParamSeparator(), TemplateOpen(), Text(text="c"), TemplateParamSeparator(), WikilinkOpen(), Text(text="d"), WikilinkClose(), ArgumentOpen(), Text(text="e"), ArgumentClose(), TemplateClose(), TemplateClose(), WikilinkOpen(), Text(text="f"), WikilinkSeparator(), ArgumentOpen(), Text(text="g"), ArgumentClose(), CommentStart(), Text(text="h"), CommentEnd(), WikilinkClose(), TemplateOpen(), Text(text="i"), TemplateParamSeparator(), Text(text="j"), TemplateParamEquals(), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), TemplateClose()] | output: [TemplateOpen(), Text(text="a"), TemplateParamSeparator(), Text(text="b"), TemplateParamSeparator(), TemplateOpen(), Text(text="c"), TemplateParamSeparator(), WikilinkOpen(), Text(text="d"), WikilinkClose(), ArgumentOpen(), Text(text="e"), ArgumentClose(), TemplateClose(), TemplateClose(), WikilinkOpen(), Text(text="f"), WikilinkSeparator(), ArgumentOpen(), Text(text="g"), ArgumentClose(), CommentStart(), Text(text="h"), CommentEnd(), WikilinkClose(), TemplateOpen(), Text(text="i"), TemplateParamSeparator(), Text(text="j"), TemplateParamEquals(), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), TemplateClose()] | ||||
--- | |||||
name: link_inside_dl | |||||
label: an external link inside a def list, such that the external link is parsed | |||||
input: ";;;mailto:example" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), ExternalLinkOpen(brackets=False), Text(text="mailto:example"), ExternalLinkClose()] | |||||
--- | |||||
name: link_inside_dl_2 | |||||
label: an external link inside a def list, such that the external link is not parsed | |||||
input: ";;;malito:example" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="malito"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="example")] |
@@ -0,0 +1,578 @@ | |||||
name: basic | |||||
label: a basic tag with an open and close | |||||
input: "<ref></ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: basic_selfclosing | |||||
label: a basic self-closing tag | |||||
input: "<ref/>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagCloseSelfclose(padding="")] | |||||
--- | |||||
name: content | |||||
label: a tag with some content in the middle | |||||
input: "<ref>this is a reference</ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagCloseOpen(padding=""), Text(text="this is a reference"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: padded_open | |||||
label: a tag with some padding in the open tag | |||||
input: "<ref ></ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagCloseOpen(padding=" "), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: padded_close | |||||
label: a tag with some padding in the close tag | |||||
input: "<ref></ref >" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref "), TagCloseClose()] | |||||
--- | |||||
name: padded_selfclosing | |||||
label: a self-closing tag with padding | |||||
input: "<ref />" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagCloseSelfclose(padding=" ")] | |||||
--- | |||||
name: attribute | |||||
label: a tag with a single attribute | |||||
input: "<ref name></ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: attribute_value | |||||
label: a tag with a single attribute with a value | |||||
input: "<ref name=foo></ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), Text(text="foo"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: attribute_quoted | |||||
label: a tag with a single quoted attribute | |||||
input: "<ref name="foo bar"></ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), Text(text="foo bar"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: attribute_hyphen | |||||
label: a tag with a single attribute, containing a hyphen | |||||
input: "<ref name=foo-bar></ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), Text(text="foo-bar"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: attribute_quoted_hyphen | |||||
label: a tag with a single quoted attribute, containing a hyphen | |||||
input: "<ref name="foo-bar"></ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), Text(text="foo-bar"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: attribute_selfclosing | |||||
label: a self-closing tag with a single attribute | |||||
input: "<ref name/>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagCloseSelfclose(padding="")] | |||||
--- | |||||
name: attribute_selfclosing_value | |||||
label: a self-closing tag with a single attribute with a value | |||||
input: "<ref name=foo/>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), Text(text="foo"), TagCloseSelfclose(padding="")] | |||||
--- | |||||
name: attribute_selfclosing_value_quoted | |||||
label: a self-closing tag with a single quoted attribute | |||||
input: "<ref name="foo"/>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), Text(text="foo"), TagCloseSelfclose(padding="")] | |||||
--- | |||||
name: nested_tag | |||||
label: a tag nested within the attributes of another | |||||
input: "<ref name=<span style="color: red;">foo</span>>citation</ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="style"), TagAttrEquals(), TagAttrQuote(), Text(text="color: red;"), TagCloseOpen(padding=""), Text(text="foo"), TagOpenClose(), Text(text="span"), TagCloseClose(), TagCloseOpen(padding=""), Text(text="citation"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: nested_tag_quoted | |||||
label: a tag nested within the attributes of another, quoted | |||||
input: "<ref name="<span style="color: red;">foo</span>">citation</ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="style"), TagAttrEquals(), TagAttrQuote(), Text(text="color: red;"), TagCloseOpen(padding=""), Text(text="foo"), TagOpenClose(), Text(text="span"), TagCloseClose(), TagCloseOpen(padding=""), Text(text="citation"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: nested_troll_tag | |||||
label: a bogus tag that appears to be nested within the attributes of another | |||||
input: "<ref name=</ ><//>>citation</ref>" | |||||
output: [Text(text="<ref name=</ ><//>>citation</ref>")] | |||||
--- | |||||
name: nested_troll_tag_quoted | |||||
label: a bogus tag that appears to be nested within the attributes of another, quoted | |||||
input: "<ref name="</ ><//>">citation</ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), Text(text="</ ><//>"), TagCloseOpen(padding=""), Text(text="citation"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: invalid_space_begin_open | |||||
label: invalid tag: a space at the beginning of the open tag | |||||
input: "< ref>test</ref>" | |||||
output: [Text(text="< ref>test</ref>")] | |||||
--- | |||||
name: invalid_space_begin_close | |||||
label: invalid tag: a space at the beginning of the close tag | |||||
input: "<ref>test</ ref>" | |||||
output: [Text(text="<ref>test</ ref>")] | |||||
--- | |||||
name: valid_space_end | |||||
label: valid tag: spaces at the ends of both the open and close tags | |||||
input: "<ref >test</ref >" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagCloseOpen(padding=" "), Text(text="test"), TagOpenClose(), Text(text="ref "), TagCloseClose()] | |||||
--- | |||||
name: invalid_template_ends | |||||
label: invalid tag: a template at the ends of both the open and close tags | |||||
input: "<ref {{foo}}>test</ref {{foo}}>" | |||||
output: [Text(text="<ref "), TemplateOpen(), Text(text="foo"), TemplateClose(), Text(text=">test</ref "), TemplateOpen(), Text(text="foo"), TemplateClose(), Text(text=">")] | |||||
--- | |||||
name: invalid_template_ends_nospace | |||||
label: invalid tag: a template at the ends of both the open and close tags, without spacing | |||||
input: "<ref {{foo}}>test</ref{{foo}}>" | |||||
output: [Text(text="<ref "), TemplateOpen(), Text(text="foo"), TemplateClose(), Text(text=">test</ref"), TemplateOpen(), Text(text="foo"), TemplateClose(), Text(text=">")] | |||||
--- | |||||
name: valid_template_end_open | |||||
label: valid tag: a template at the end of the open tag | |||||
input: "<ref {{foo}}>test</ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), TemplateOpen(), Text(text="foo"), TemplateClose(), TagCloseOpen(padding=""), Text(text="test"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: valid_template_end_open_space_end_close | |||||
label: valid tag: a template at the end of the open tag; whitespace at the end of the close tag | |||||
input: "<ref {{foo}}>test</ref\n>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), TemplateOpen(), Text(text="foo"), TemplateClose(), TagCloseOpen(padding=""), Text(text="test"), TagOpenClose(), Text(text="ref\n"), TagCloseClose()] | |||||
--- | |||||
name: invalid_template_end_open_nospace | |||||
label: invalid tag: a template at the end of the open tag, without spacing | |||||
input: "<ref{{foo}}>test</ref>" | |||||
output: [Text(text="<ref"), TemplateOpen(), Text(text="foo"), TemplateClose(), Text(text=">test</ref>")] | |||||
--- | |||||
name: invalid_template_start_close | |||||
label: invalid tag: a template at the beginning of the close tag | |||||
input: "<ref>test</{{foo}}ref>" | |||||
output: [Text(text="<ref>test</"), TemplateOpen(), Text(text="foo"), TemplateClose(), Text(text="ref>")] | |||||
--- | |||||
name: invalid_template_start_open | |||||
label: invalid tag: a template at the beginning of the open tag | |||||
input: "<{{foo}}ref>test</ref>" | |||||
output: [Text(text="<"), TemplateOpen(), Text(text="foo"), TemplateClose(), Text(text="ref>test</ref>")] | |||||
--- | |||||
name: unclosed_quote | |||||
label: a quoted attribute that is never closed | |||||
input: "<span style="foobar>stuff</span>" | |||||
output: [TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="style"), TagAttrEquals(), Text(text="\"foobar"), TagCloseOpen(padding=""), Text(text="stuff"), TagOpenClose(), Text(text="span"), TagCloseClose()] | |||||
--- | |||||
name: fake_quote | |||||
label: a fake quoted attribute | |||||
input: "<span style="foo"bar>stuff</span>" | |||||
output: [TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="style"), TagAttrEquals(), Text(text="\"foo\"bar"), TagCloseOpen(padding=""), Text(text="stuff"), TagOpenClose(), Text(text="span"), TagCloseClose()] | |||||
--- | |||||
name: fake_quote_complex | |||||
label: a fake quoted attribute, with spaces and templates and links | |||||
input: "<span style="foo {{bar}}\n[[baz]]"buzz >stuff</span>" | |||||
output: [TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="style"), TagAttrEquals(), Text(text="\"foo"), TagAttrStart(pad_first=" ", pad_before_eq="\n", pad_after_eq=""), TemplateOpen(), Text(text="bar"), TemplateClose(), TagAttrStart(pad_first="", pad_before_eq=" ", pad_after_eq=""), WikilinkOpen(), Text(text="baz"), WikilinkClose(), Text(text="\"buzz"), TagCloseOpen(padding=""), Text(text="stuff"), TagOpenClose(), Text(text="span"), TagCloseClose()] | |||||
--- | |||||
name: incomplete_lbracket | |||||
label: incomplete tags: just a left bracket | |||||
input: "<" | |||||
output: [Text(text="<")] | |||||
--- | |||||
name: incomplete_lbracket_junk | |||||
label: incomplete tags: just a left bracket, surrounded by stuff | |||||
input: "foo<bar" | |||||
output: [Text(text="foo<bar")] | |||||
--- | |||||
name: incomplete_unclosed_open | |||||
label: incomplete tags: an unclosed open tag | |||||
input: "junk <ref" | |||||
output: [Text(text="junk <ref")] | |||||
--- | |||||
name: incomplete_unclosed_open_space | |||||
label: incomplete tags: an unclosed open tag, space | |||||
input: "junk <ref " | |||||
output: [Text(text="junk <ref ")] | |||||
--- | |||||
name: incomplete_unclosed_open_unnamed_attr | |||||
label: incomplete tags: an unclosed open tag, unnamed attribute | |||||
input: "junk <ref name" | |||||
output: [Text(text="junk <ref name")] | |||||
--- | |||||
name: incomplete_unclosed_open_attr_equals | |||||
label: incomplete tags: an unclosed open tag, attribute, equal sign | |||||
input: "junk <ref name=" | |||||
output: [Text(text="junk <ref name=")] | |||||
--- | |||||
name: incomplete_unclosed_open_attr_equals_quoted | |||||
label: incomplete tags: an unclosed open tag, attribute, equal sign, quote | |||||
input: "junk <ref name="" | |||||
output: [Text(text="junk <ref name=\"")] | |||||
--- | |||||
name: incomplete_unclosed_open_attr | |||||
label: incomplete tags: an unclosed open tag, attribute with a key/value | |||||
input: "junk <ref name=foo" | |||||
output: [Text(text="junk <ref name=foo")] | |||||
--- | |||||
name: incomplete_unclosed_open_attr_quoted | |||||
label: incomplete tags: an unclosed open tag, attribute with a key/value, quoted | |||||
input: "junk <ref name="foo"" | |||||
output: [Text(text="junk <ref name=\"foo\"")] | |||||
--- | |||||
name: incomplete_open | |||||
label: incomplete tags: an open tag | |||||
input: "junk <ref>" | |||||
output: [Text(text="junk <ref>")] | |||||
--- | |||||
name: incomplete_open_unnamed_attr | |||||
label: incomplete tags: an open tag, unnamed attribute | |||||
input: "junk <ref name>" | |||||
output: [Text(text="junk <ref name>")] | |||||
--- | |||||
name: incomplete_open_attr_equals | |||||
label: incomplete tags: an open tag, attribute, equal sign | |||||
input: "junk <ref name=>" | |||||
output: [Text(text="junk <ref name=>")] | |||||
--- | |||||
name: incomplete_open_attr | |||||
label: incomplete tags: an open tag, attribute with a key/value | |||||
input: "junk <ref name=foo>" | |||||
output: [Text(text="junk <ref name=foo>")] | |||||
--- | |||||
name: incomplete_open_attr_quoted | |||||
label: incomplete tags: an open tag, attribute with a key/value, quoted | |||||
input: "junk <ref name="foo">" | |||||
output: [Text(text="junk <ref name=\"foo\">")] | |||||
--- | |||||
name: incomplete_open_text | |||||
label: incomplete tags: an open tag, text | |||||
input: "junk <ref>foo" | |||||
output: [Text(text="junk <ref>foo")] | |||||
--- | |||||
name: incomplete_open_attr_text | |||||
label: incomplete tags: an open tag, attribute with a key/value, text | |||||
input: "junk <ref name=foo>bar" | |||||
output: [Text(text="junk <ref name=foo>bar")] | |||||
--- | |||||
name: incomplete_open_text_lbracket | |||||
label: incomplete tags: an open tag, text, left open bracket | |||||
input: "junk <ref>bar<" | |||||
output: [Text(text="junk <ref>bar<")] | |||||
--- | |||||
name: incomplete_open_text_lbracket_slash | |||||
label: incomplete tags: an open tag, text, left bracket, slash | |||||
input: "junk <ref>bar</" | |||||
output: [Text(text="junk <ref>bar</")] | |||||
--- | |||||
name: incomplete_open_text_unclosed_close | |||||
label: incomplete tags: an open tag, text, unclosed close | |||||
input: "junk <ref>bar</ref" | |||||
output: [Text(text="junk <ref>bar</ref")] | |||||
--- | |||||
name: incomplete_open_text_wrong_close | |||||
label: incomplete tags: an open tag, text, wrong close | |||||
input: "junk <ref>bar</span>" | |||||
output: [Text(text="junk <ref>bar</span>")] | |||||
--- | |||||
name: incomplete_unclosed_close | |||||
label: incomplete tags: an unclosed close tag | |||||
input: "junk </" | |||||
output: [Text(text="junk </")] | |||||
--- | |||||
name: incomplete_unclosed_close_text | |||||
label: incomplete tags: an unclosed close tag, with text | |||||
input: "junk </br" | |||||
output: [Text(text="junk </br")] | |||||
--- | |||||
name: incomplete_close | |||||
label: incomplete tags: a close tag | |||||
input: "junk </ref>" | |||||
output: [Text(text="junk </ref>")] | |||||
--- | |||||
name: incomplete_no_tag_name_open | |||||
label: incomplete tags: no tag name within brackets; just an open | |||||
input: "junk <>" | |||||
output: [Text(text="junk <>")] | |||||
--- | |||||
name: incomplete_no_tag_name_selfclosing | |||||
label: incomplete tags: no tag name within brackets; self-closing | |||||
input: "junk < />" | |||||
output: [Text(text="junk < />")] | |||||
--- | |||||
name: incomplete_no_tag_name_open_close | |||||
label: incomplete tags: no tag name within brackets; open and close | |||||
input: "junk <></>" | |||||
output: [Text(text="junk <></>")] | |||||
--- | |||||
name: backslash_premature_before | |||||
label: a backslash before a quote before a space | |||||
input: "<foo attribute="this is\\" quoted">blah</foo>" | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this is\\\" quoted"), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | |||||
name: backslash_premature_after | |||||
label: a backslash before a quote after a space | |||||
input: "<foo attribute="this is \\"quoted">blah</foo>" | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this is \\\"quoted"), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | |||||
name: backslash_premature_middle | |||||
label: a backslash before a quote in the middle of a word | |||||
input: "<foo attribute="this i\\"s quoted">blah</foo>" | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this i\\\"s quoted"), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | |||||
name: backslash_adjacent | |||||
label: escaped quotes next to unescaped quotes | |||||
input: "<foo attribute="\\"this is quoted\\"">blah</foo>" | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="\\\"this is quoted\\\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | |||||
name: backslash_endquote | |||||
label: backslashes before the end quote, causing the attribute to become unquoted | |||||
input: "<foo attribute="this_is quoted\\">blah</foo>" | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), Text(text="\"this_is"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="quoted\\\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | |||||
name: backslash_double | |||||
label: two adjacent backslashes, which do *not* affect the quote | |||||
input: "<foo attribute="this is\\\\" quoted">blah</foo>" | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this is\\\\"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="quoted\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | |||||
name: backslash_triple | |||||
label: three adjacent backslashes, which do *not* affect the quote | |||||
input: "<foo attribute="this is\\\\\\" quoted">blah</foo>" | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this is\\\\\\"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="quoted\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | |||||
name: backslash_unaffecting | |||||
label: backslashes near quotes, but not immediately adjacent, thus having no effect | |||||
input: "<foo attribute="\\quote\\d" also="quote\\d\\">blah</foo>" | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="\\quote\\d"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="also"), TagAttrEquals(), Text(text="\"quote\\d\\\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | |||||
name: unparsable | |||||
label: a tag that should not be put through the normal parser | |||||
input: "{{t1}}<nowiki>{{t2}}</nowiki>{{t3}}" | |||||
output: [TemplateOpen(), Text(text="t1"), TemplateClose(), TagOpenOpen(), Text(text="nowiki"), TagCloseOpen(padding=""), Text(text="{{t2}}"), TagOpenClose(), Text(text="nowiki"), TagCloseClose(), TemplateOpen(), Text(text="t3"), TemplateClose()] | |||||
--- | |||||
name: unparsable_complex | |||||
label: a tag that should not be put through the normal parser; lots of stuff inside | |||||
input: "{{t1}}<pre>{{t2}}\n==Heading==\nThis is some text with a [[page|link]].</pre>{{t3}}" | |||||
output: [TemplateOpen(), Text(text="t1"), TemplateClose(), TagOpenOpen(), Text(text="pre"), TagCloseOpen(padding=""), Text(text="{{t2}}\n==Heading==\nThis is some text with a [[page|link]]."), TagOpenClose(), Text(text="pre"), TagCloseClose(), TemplateOpen(), Text(text="t3"), TemplateClose()] | |||||
--- | |||||
name: unparsable_attributed | |||||
label: a tag that should not be put through the normal parser; parsed attributes | |||||
input: "{{t1}}<nowiki attr=val attr2="{{val2}}">{{t2}}</nowiki>{{t3}}" | |||||
output: [TemplateOpen(), Text(text=u't1'), TemplateClose(), TagOpenOpen(), Text(text="nowiki"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr"), TagAttrEquals(), Text(text="val"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr2"), TagAttrEquals(), TagAttrQuote(), TemplateOpen(), Text(text="val2"), TemplateClose(), TagCloseOpen(padding=""), Text(text="{{t2}}"), TagOpenClose(), Text(text="nowiki"), TagCloseClose(), TemplateOpen(), Text(text="t3"), TemplateClose()] | |||||
--- | |||||
name: unparsable_incomplete | |||||
label: a tag that should not be put through the normal parser; incomplete | |||||
input: "{{t1}}<nowiki>{{t2}}{{t3}}" | |||||
output: [TemplateOpen(), Text(text="t1"), TemplateClose(), Text(text="<nowiki>"), TemplateOpen(), Text(text="t2"), TemplateClose(), TemplateOpen(), Text(text="t3"), TemplateClose()] | |||||
--- | |||||
name: unparsable_entity | |||||
label: a HTML entity inside unparsable text is still parsed | |||||
input: "{{t1}}<nowiki>{{t2}} {{t3}}</nowiki>{{t4}}" | |||||
output: [TemplateOpen(), Text(text="t1"), TemplateClose(), TagOpenOpen(), Text(text="nowiki"), TagCloseOpen(padding=""), Text(text="{{t2}}"), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), Text(text="{{t3}}"), TagOpenClose(), Text(text="nowiki"), TagCloseClose(), TemplateOpen(), Text(text="t4"), TemplateClose()] | |||||
--- | |||||
name: unparsable_entity_incomplete | |||||
label: an incomplete HTML entity inside unparsable text | |||||
input: "<nowiki>&</nowiki>" | |||||
output: [TagOpenOpen(), Text(text="nowiki"), TagCloseOpen(padding=""), Text(text="&"), TagOpenClose(), Text(text="nowiki"), TagCloseClose()] | |||||
--- | |||||
name: unparsable_entity_incomplete_2 | |||||
label: an incomplete HTML entity inside unparsable text | |||||
input: "<nowiki>&" | |||||
output: [Text(text="<nowiki>&")] | |||||
--- | |||||
name: single_open_close | |||||
label: a tag that supports being single; both an open and a close tag | |||||
input: "foo<li>bar{{baz}}</li>" | |||||
output: [Text(text="foo"), TagOpenOpen(), Text(text="li"), TagCloseOpen(padding=""), Text(text="bar"), TemplateOpen(), Text(text="baz"), TemplateClose(), TagOpenClose(), Text(text="li"), TagCloseClose()] | |||||
--- | |||||
name: single_open | |||||
label: a tag that supports being single; just an open tag | |||||
input: "foo<li>bar{{baz}}" | |||||
output: [Text(text="foo"), TagOpenOpen(), Text(text="li"), TagCloseSelfclose(padding="", implicit=True), Text(text="bar"), TemplateOpen(), Text(text="baz"), TemplateClose()] | |||||
--- | |||||
name: single_selfclose | |||||
label: a tag that supports being single; a self-closing tag | |||||
input: "foo<li/>bar{{baz}}" | |||||
output: [Text(text="foo"), TagOpenOpen(), Text(text="li"), TagCloseSelfclose(padding=""), Text(text="bar"), TemplateOpen(), Text(text="baz"), TemplateClose()] | |||||
--- | |||||
name: single_close | |||||
label: a tag that supports being single; just a close tag | |||||
input: "foo</li>bar{{baz}}" | |||||
output: [Text(text="foo</li>bar"), TemplateOpen(), Text(text="baz"), TemplateClose()] | |||||
--- | |||||
name: single_only_open_close | |||||
label: a tag that can only be single; both an open and a close tag | |||||
input: "foo<br>bar{{baz}}</br>" | |||||
output: [Text(text="foo"), TagOpenOpen(), Text(text="br"), TagCloseSelfclose(padding="", implicit=True), Text(text="bar"), TemplateOpen(), Text(text="baz"), TemplateClose(), TagOpenOpen(invalid=True), Text(text="br"), TagCloseSelfclose(padding="", implicit=True)] | |||||
--- | |||||
name: single_only_open | |||||
label: a tag that can only be single; just an open tag | |||||
input: "foo<br>bar{{baz}}" | |||||
output: [Text(text="foo"), TagOpenOpen(), Text(text="br"), TagCloseSelfclose(padding="", implicit=True), Text(text="bar"), TemplateOpen(), Text(text="baz"), TemplateClose()] | |||||
--- | |||||
name: single_only_selfclose | |||||
label: a tag that can only be single; a self-closing tag | |||||
input: "foo<br/>bar{{baz}}" | |||||
output: [Text(text="foo"), TagOpenOpen(), Text(text="br"), TagCloseSelfclose(padding=""), Text(text="bar"), TemplateOpen(), Text(text="baz"), TemplateClose()] | |||||
--- | |||||
name: single_only_close | |||||
label: a tag that can only be single; just a close tag | |||||
input: "foo</br>bar{{baz}}" | |||||
output: [Text(text="foo"), TagOpenOpen(invalid=True), Text(text="br"), TagCloseSelfclose(padding="", implicit=True), Text(text="bar"), TemplateOpen(), Text(text="baz"), TemplateClose()] | |||||
--- | |||||
name: single_only_double | |||||
label: a tag that can only be single; a tag with backslashes at the beginning and end | |||||
input: "foo</br/>bar{{baz}}" | |||||
output: [Text(text="foo"), TagOpenOpen(invalid=True), Text(text="br"), TagCloseSelfclose(padding=""), Text(text="bar"), TemplateOpen(), Text(text="baz"), TemplateClose()] | |||||
--- | |||||
name: single_only_close_attribute | |||||
label: a tag that can only be single; presented as a close tag with an attribute | |||||
input: "</br id="break">" | |||||
output: [TagOpenOpen(invalid=True), Text(text="br"), TagAttrStart(pad_first=" ", pad_after_eq="", pad_before_eq=""), Text(text="id"), TagAttrEquals(), TagAttrQuote(), Text(text="break"), TagCloseSelfclose(padding="", implicit=True)] | |||||
--- | |||||
name: capitalization | |||||
label: caps should be ignored within tag names | |||||
input: "<NoWiKi>{{test}}</nOwIkI>" | |||||
output: [TagOpenOpen(), Text(text="NoWiKi"), TagCloseOpen(padding=""), Text(text="{{test}}"), TagOpenClose(), Text(text="nOwIkI"), TagCloseClose()] |
@@ -0,0 +1,523 @@ | |||||
name: basic_italics | |||||
label: basic italic text | |||||
input: "''text''" | |||||
output: [TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="text"), TagOpenClose(), Text(text="i"), TagCloseClose()] | |||||
--- | |||||
name: basic_bold | |||||
label: basic bold text | |||||
input: "'''text'''" | |||||
output: [TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="text"), TagOpenClose(), Text(text="b"), TagCloseClose()] | |||||
--- | |||||
name: basic_ul | |||||
label: basic unordered list | |||||
input: "*text" | |||||
output: [TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="text")] | |||||
--- | |||||
name: basic_ol | |||||
label: basic ordered list | |||||
input: "#text" | |||||
output: [TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="text")] | |||||
--- | |||||
name: basic_dt | |||||
label: basic description term | |||||
input: ";text" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="text")] | |||||
--- | |||||
name: basic_dd | |||||
label: basic description item | |||||
input: ":text" | |||||
output: [TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="text")] | |||||
--- | |||||
name: basic_hr | |||||
label: basic horizontal rule | |||||
input: "----" | |||||
output: [TagOpenOpen(wiki_markup="----"), Text(text="hr"), TagCloseSelfclose()] | |||||
--- | |||||
name: complex_italics | |||||
label: italics with a lot in them | |||||
input: "''this is a test of [[Italic text|italics]] with {{plenty|of|stuff}}''" | |||||
output: [TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="this is a"), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), Text(text="test of "), WikilinkOpen(), Text(text="Italic text"), WikilinkSeparator(), Text(text="italics"), WikilinkClose(), Text(text=" with "), TemplateOpen(), Text(text="plenty"), TemplateParamSeparator(), Text(text="of"), TemplateParamSeparator(), Text(text="stuff"), TemplateClose(), TagOpenClose(), Text(text="i"), TagCloseClose()] | |||||
--- | |||||
name: multiline_italics | |||||
label: italics spanning mulitple lines | |||||
input: "foo\nbar''testing\ntext\nspanning\n\n\n\n\nmultiple\nlines''foo\n\nbar" | |||||
output: [Text(text="foo\nbar"), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="testing\ntext\nspanning\n\n\n\n\nmultiple\nlines"), TagOpenClose(), Text(text="i"), TagCloseClose(), Text(text="foo\n\nbar")] | |||||
--- | |||||
name: unending_italics | |||||
label: italics without an ending tag | |||||
input: "''unending formatting!" | |||||
output: [Text(text="''unending formatting!")] | |||||
--- | |||||
name: misleading_italics_end | |||||
label: italics with something that looks like an end but isn't | |||||
input: "''this is 'not' the en'd'<nowiki>''</nowiki>" | |||||
output: [Text(text="''this is 'not' the en'd'"), TagOpenOpen(), Text(text="nowiki"), TagCloseOpen(padding=""), Text(text="''"), TagOpenClose(), Text(text="nowiki"), TagCloseClose()] | |||||
] | |||||
--- | |||||
name: italics_start_outside_end_inside | |||||
label: italics that start outside a link and end inside it | |||||
input: "''foo[[bar|baz'']]spam" | |||||
output: [Text(text="''foo"), WikilinkOpen(), Text(text="bar"), WikilinkSeparator(), Text(text="baz''"), WikilinkClose(), Text(text="spam")] | |||||
--- | |||||
name: italics_start_inside_end_outside | |||||
label: italics that start inside a link and end outside it | |||||
input: "[[foo|''bar]]baz''spam" | |||||
output: [Text(text="[[foo|"), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="bar]]baz"), TagOpenClose(), Text(text="i"), TagCloseClose(), Text(text="spam")] | |||||
--- | |||||
name: complex_bold | |||||
label: bold with a lot in it | |||||
input: "'''this is a test of [[Bold text|bold]] with {{plenty|of|stuff}}'''" | |||||
output: [TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="this is a"), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), Text(text="test of "), WikilinkOpen(), Text(text="Bold text"), WikilinkSeparator(), Text(text="bold"), WikilinkClose(), Text(text=" with "), TemplateOpen(), Text(text="plenty"), TemplateParamSeparator(), Text(text="of"), TemplateParamSeparator(), Text(text="stuff"), TemplateClose(), TagOpenClose(), Text(text="b"), TagCloseClose()] | |||||
--- | |||||
name: multiline_bold | |||||
label: bold spanning mulitple lines | |||||
input: "foo\nbar'''testing\ntext\nspanning\n\n\n\n\nmultiple\nlines'''foo\n\nbar" | |||||
output: [Text(text="foo\nbar"), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="testing\ntext\nspanning\n\n\n\n\nmultiple\nlines"), TagOpenClose(), Text(text="b"), TagCloseClose(), Text(text="foo\n\nbar")] | |||||
--- | |||||
name: unending_bold | |||||
label: bold without an ending tag | |||||
input: "'''unending formatting!" | |||||
output: [Text(text="'''unending formatting!")] | |||||
--- | |||||
name: misleading_bold_end | |||||
label: bold with something that looks like an end but isn't | |||||
input: "'''this is 'not' the en''d'<nowiki>'''</nowiki>" | |||||
output: [Text(text="'"), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="this is 'not' the en"), TagOpenClose(), Text(text="i"), TagCloseClose(), Text(text="d'"), TagOpenOpen(), Text(text="nowiki"), TagCloseOpen(padding=""), Text(text="'''"), TagOpenClose(), Text(text="nowiki"), TagCloseClose()] | |||||
--- | |||||
name: bold_start_outside_end_inside | |||||
label: bold that start outside a link and end inside it | |||||
input: "'''foo[[bar|baz''']]spam" | |||||
output: [Text(text="'''foo"), WikilinkOpen(), Text(text="bar"), WikilinkSeparator(), Text(text="baz'''"), WikilinkClose(), Text(text="spam")] | |||||
--- | |||||
name: bold_start_inside_end_outside | |||||
label: bold that start inside a link and end outside it | |||||
input: "[[foo|'''bar]]baz'''spam" | |||||
output: [Text(text="[[foo|"), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="bar]]baz"), TagOpenClose(), Text(text="b"), TagCloseClose(), Text(text="spam")] | |||||
--- | |||||
name: bold_and_italics | |||||
label: bold and italics together | |||||
input: "this is '''''bold and italic text'''''!" | |||||
output: [Text(text="this is "), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="bold and italic text"), TagOpenClose(), Text(text="b"), TagCloseClose(), TagOpenClose(), Text(text="i"), TagCloseClose(), Text(text="!")] | |||||
--- | |||||
name: both_then_bold | |||||
label: text that starts bold/italic, then is just bold | |||||
input: "'''''both''bold'''" | |||||
output: [TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="both"), TagOpenClose(), Text(text="i"), TagCloseClose(), Text(text="bold"), TagOpenClose(), Text(text="b"), TagCloseClose()] | |||||
--- | |||||
name: both_then_italics | |||||
label: text that starts bold/italic, then is just italic | |||||
input: "'''''both'''italics''" | |||||
output: [TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="both"), TagOpenClose(), Text(text="b"), TagCloseClose(), Text(text="italics"), TagOpenClose(), Text(text="i"), TagCloseClose()] | |||||
--- | |||||
name: bold_then_both | |||||
label: text that starts just bold, then is bold/italic | |||||
input: "'''bold''both'''''" | |||||
output: [TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="bold"), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="both"), TagOpenClose(), Text(text="i"), TagCloseClose(), TagOpenClose(), Text(text="b"), TagCloseClose()] | |||||
--- | |||||
name: italics_then_both | |||||
label: text that starts just italic, then is bold/italic | |||||
input: "''italics'''both'''''" | |||||
output: [TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="italics"), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="both"), TagOpenClose(), Text(text="b"), TagCloseClose(), TagOpenClose(), Text(text="i"), TagCloseClose()] | |||||
--- | |||||
name: italics_then_bold | |||||
label: text that starts italic, then is bold | |||||
input: "none''italics'''''bold'''none" | |||||
output: [Text(text="none"), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="italics"), TagOpenClose(), Text(text="i"), TagCloseClose(), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="bold"), TagOpenClose(), Text(text="b"), TagCloseClose(), Text(text="none")] | |||||
--- | |||||
name: bold_then_italics | |||||
label: text that starts bold, then is italic | |||||
input: "none'''bold'''''italics''none" | |||||
output: [Text(text="none"), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="bold"), TagOpenClose(), Text(text="b"), TagCloseClose(), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="italics"), TagOpenClose(), Text(text="i"), TagCloseClose(), Text(text="none")] | |||||
--- | |||||
name: five_three | |||||
label: five ticks to open, three to close (bold) | |||||
input: "'''''foobar'''" | |||||
output: [Text(text="''"), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="foobar"), TagOpenClose(), Text(text="b"), TagCloseClose()] | |||||
--- | |||||
name: five_two | |||||
label: five ticks to open, two to close (bold) | |||||
input: "'''''foobar''" | |||||
output: [Text(text="'''"), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="foobar"), TagOpenClose(), Text(text="i"), TagCloseClose()] | |||||
--- | |||||
name: four | |||||
label: four ticks | |||||
input: "foo ''''bar'''' baz" | |||||
output: [Text(text="foo '"), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="bar'"), TagOpenClose(), Text(text="b"), TagCloseClose(), Text(text=" baz")] | |||||
--- | |||||
name: four_two | |||||
label: four ticks to open, two to close | |||||
input: "foo ''''bar'' baz" | |||||
output: [Text(text="foo ''"), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="bar"), TagOpenClose(), Text(text="i"), TagCloseClose(), Text(text=" baz")] | |||||
--- | |||||
name: two_three | |||||
label: two ticks to open, three to close | |||||
input: "foo ''bar''' baz" | |||||
output: [Text(text="foo "), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="bar'"), TagOpenClose(), Text(text="i"), TagCloseClose(), Text(text=" baz")] | |||||
--- | |||||
name: two_four | |||||
label: two ticks to open, four to close | |||||
input: "foo ''bar'''' baz" | |||||
output: [Text(text="foo "), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="bar''"), TagOpenClose(), Text(text="i"), TagCloseClose(), Text(text=" baz")] | |||||
--- | |||||
name: two_three_two | |||||
label: two ticks to open, three to close, two afterwards | |||||
input: "foo ''bar''' baz''" | |||||
output: [Text(text="foo "), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), Text(text="bar''' baz"), TagOpenClose(), Text(text="i"), TagCloseClose()] | |||||
--- | |||||
name: two_four_four | |||||
label: two ticks to open, four to close, four afterwards | |||||
input: "foo ''bar'''' baz''''" | |||||
output: [Text(text="foo ''bar'"), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text=" baz'"), TagOpenClose(), Text(text="b"), TagCloseClose()] | |||||
--- | |||||
name: seven | |||||
label: seven ticks | |||||
input: "'''''''seven'''''''" | |||||
output: [Text(text="''"), TagOpenOpen(wiki_markup="''"), Text(text="i"), TagCloseOpen(), TagOpenOpen(wiki_markup="'''"), Text(text="b"), TagCloseOpen(), Text(text="seven''"), TagOpenClose(), Text(text="b"), TagCloseClose(), TagOpenClose(), Text(text="i"), TagCloseClose()] | |||||
--- | |||||
name: complex_ul | |||||
label: ul with a lot in it | |||||
input: "* this is a test of an [[Unordered list|ul]] with {{plenty|of|stuff}}" | |||||
output: [TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text=" this is a"), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), Text(text="test of an "), WikilinkOpen(), Text(text="Unordered list"), WikilinkSeparator(), Text(text="ul"), WikilinkClose(), Text(text=" with "), TemplateOpen(), Text(text="plenty"), TemplateParamSeparator(), Text(text="of"), TemplateParamSeparator(), Text(text="stuff"), TemplateClose()] | |||||
--- | |||||
name: ul_multiline_template | |||||
label: ul with a template that spans multiple lines | |||||
input: "* this has a template with a {{line|\nbreak}}\nthis is not part of the list" | |||||
output: [TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text=" this has a template with a "), TemplateOpen(), Text(text="line"), TemplateParamSeparator(), Text(text="\nbreak"), TemplateClose(), Text(text="\nthis is not part of the list")] | |||||
--- | |||||
name: ul_adjacent | |||||
label: multiple adjacent uls | |||||
input: "a\n*b\n*c\nd\n*e\nf" | |||||
output: [Text(text="a\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="c\nd\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="e\nf")] | |||||
--- | |||||
name: ul_depths | |||||
label: multiple adjacent uls, with differing depths | |||||
input: "*a\n**b\n***c\n********d\n**e\nf\n***g" | |||||
output: [TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="a\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="c\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="d\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="e\nf\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="g")] | |||||
--- | |||||
name: ul_space_before | |||||
label: uls with space before them | |||||
input: "foo *bar\n *baz\n*buzz" | |||||
output: [Text(text="foo *bar\n *baz\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="buzz")] | |||||
--- | |||||
name: ul_interruption | |||||
label: high-depth ul with something blocking it | |||||
input: "**f*oobar" | |||||
output: [TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="f*oobar")] | |||||
--- | |||||
name: complex_ol | |||||
label: ol with a lot in it | |||||
input: "# this is a test of an [[Ordered list|ol]] with {{plenty|of|stuff}}" | |||||
output: [TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text=" this is a"), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), Text(text="test of an "), WikilinkOpen(), Text(text="Ordered list"), WikilinkSeparator(), Text(text="ol"), WikilinkClose(), Text(text=" with "), TemplateOpen(), Text(text="plenty"), TemplateParamSeparator(), Text(text="of"), TemplateParamSeparator(), Text(text="stuff"), TemplateClose()] | |||||
--- | |||||
name: ol_multiline_template | |||||
label: ol with a template that spans moltiple lines | |||||
input: "# this has a template with a {{line|\nbreak}}\nthis is not part of the list" | |||||
output: [TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text=" this has a template with a "), TemplateOpen(), Text(text="line"), TemplateParamSeparator(), Text(text="\nbreak"), TemplateClose(), Text(text="\nthis is not part of the list")] | |||||
--- | |||||
name: ol_adjacent | |||||
label: moltiple adjacent ols | |||||
input: "a\n#b\n#c\nd\n#e\nf" | |||||
output: [Text(text="a\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="c\nd\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="e\nf")] | |||||
--- | |||||
name: ol_depths | |||||
label: moltiple adjacent ols, with differing depths | |||||
input: "#a\n##b\n###c\n########d\n##e\nf\n###g" | |||||
output: [TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="a\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="c\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="d\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="e\nf\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="g")] | |||||
--- | |||||
name: ol_space_before | |||||
label: ols with space before them | |||||
input: "foo #bar\n #baz\n#buzz" | |||||
output: [Text(text="foo #bar\n #baz\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="buzz")] | |||||
--- | |||||
name: ol_interruption | |||||
label: high-depth ol with something blocking it | |||||
input: "##f#oobar" | |||||
output: [TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="f#oobar")] | |||||
--- | |||||
name: ul_ol_mix | |||||
label: a mix of adjacent uls and ols | |||||
input: "*a\n*#b\n*##c\n*##*#*#*d\n*#e\nf\n##*g" | |||||
output: [TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="a\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="c\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="d\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="e\nf\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="g")] | |||||
--- | |||||
name: complex_dt | |||||
label: dt with a lot in it | |||||
input: "; this is a test of an [[description term|dt]] with {{plenty|of|stuff}}" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text=" this is a"), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), Text(text="test of an "), WikilinkOpen(), Text(text="description term"), WikilinkSeparator(), Text(text="dt"), WikilinkClose(), Text(text=" with "), TemplateOpen(), Text(text="plenty"), TemplateParamSeparator(), Text(text="of"), TemplateParamSeparator(), Text(text="stuff"), TemplateClose()] | |||||
--- | |||||
name: dt_multiline_template | |||||
label: dt with a template that spans mdttiple lines | |||||
input: "; this has a template with a {{line|\nbreak}}\nthis is not part of the list" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text=" this has a template with a "), TemplateOpen(), Text(text="line"), TemplateParamSeparator(), Text(text="\nbreak"), TemplateClose(), Text(text="\nthis is not part of the list")] | |||||
--- | |||||
name: dt_adjacent | |||||
label: mdttiple adjacent dts | |||||
input: "a\n;b\n;c\nd\n;e\nf" | |||||
output: [Text(text="a\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="c\nd\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="e\nf")] | |||||
--- | |||||
name: dt_depths | |||||
label: mdttiple adjacent dts, with differing depths | |||||
input: ";a\n;;b\n;;;c\n;;;;;;;;d\n;;e\nf\n;;;g" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="a\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="c\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="d\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="e\nf\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="g")] | |||||
--- | |||||
name: dt_space_before | |||||
label: dts with space before them | |||||
input: "foo ;bar\n ;baz\n;buzz" | |||||
output: [Text(text="foo ;bar\n ;baz\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="buzz")] | |||||
--- | |||||
name: dt_interruption | |||||
label: high-depth dt with something blocking it | |||||
input: ";;f;oobar" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="f;oobar")] | |||||
--- | |||||
name: complex_dd | |||||
label: dd with a lot in it | |||||
input: ": this is a test of an [[description item|dd]] with {{plenty|of|stuff}}" | |||||
output: [TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text=" this is a"), HTMLEntityStart(), Text(text="nbsp"), HTMLEntityEnd(), Text(text="test of an "), WikilinkOpen(), Text(text="description item"), WikilinkSeparator(), Text(text="dd"), WikilinkClose(), Text(text=" with "), TemplateOpen(), Text(text="plenty"), TemplateParamSeparator(), Text(text="of"), TemplateParamSeparator(), Text(text="stuff"), TemplateClose()] | |||||
--- | |||||
name: dd_multiline_template | |||||
label: dd with a template that spans mddtiple lines | |||||
input: ": this has a template with a {{line|\nbreak}}\nthis is not part of the list" | |||||
output: [TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text=" this has a template with a "), TemplateOpen(), Text(text="line"), TemplateParamSeparator(), Text(text="\nbreak"), TemplateClose(), Text(text="\nthis is not part of the list")] | |||||
--- | |||||
name: dd_adjacent | |||||
label: mddtiple adjacent dds | |||||
input: "a\n:b\n:c\nd\n:e\nf" | |||||
output: [Text(text="a\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="c\nd\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="e\nf")] | |||||
--- | |||||
name: dd_depths | |||||
label: mddtiple adjacent dds, with differing depths | |||||
input: ":a\n::b\n:::c\n::::::::d\n::e\nf\n:::g" | |||||
output: [TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="a\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="c\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="d\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="e\nf\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="g")] | |||||
--- | |||||
name: dd_space_before | |||||
label: dds with space before them | |||||
input: "foo :bar\n :baz\n:buzz" | |||||
output: [Text(text="foo :bar\n :baz\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="buzz")] | |||||
--- | |||||
name: dd_interruption | |||||
label: high-depth dd with something blocking it | |||||
input: "::f:oobar" | |||||
output: [TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="f:oobar")] | |||||
--- | |||||
name: dt_dd_mix | |||||
label: a mix of adjacent dts and dds | |||||
input: ";a\n;:b\n;::c\n;::;:;:;d\n;:e\nf\n::;g" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="a\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="b\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="c\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="d\n"), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="e\nf\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="g")] | |||||
--- | |||||
name: dt_dd_mix2 | |||||
label: the correct usage of a dt/dd unit, as in a dl | |||||
input: ";foo:bar:baz" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="foo"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="bar:baz")] | |||||
--- | |||||
name: dt_dd_mix3 | |||||
label: another example of correct (but strange) dt/dd usage | |||||
input: ":;;::foo:bar:baz" | |||||
output: [TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="foo"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="bar:baz")] | |||||
--- | |||||
name: ul_ol_dt_dd_mix | |||||
label: an assortment of uls, ols, dds, and dts | |||||
input: ";:#*foo\n:#*;foo\n#*;:foo\n*;:#foo" | |||||
output: [TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), Text(text="foo\n"), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), Text(text="foo\n"), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), Text(text="foo\n"), TagOpenOpen(wiki_markup="*"), Text(text="li"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=";"), Text(text="dt"), TagCloseSelfclose(), TagOpenOpen(wiki_markup=":"), Text(text="dd"), TagCloseSelfclose(), TagOpenOpen(wiki_markup="#"), Text(text="li"), TagCloseSelfclose(), Text(text="foo")] | |||||
--- | |||||
name: hr_text_before | |||||
label: text before an otherwise-valid hr | |||||
input: "foo----" | |||||
output: [Text(text="foo----")] | |||||
--- | |||||
name: hr_text_after | |||||
label: text after a valid hr | |||||
input: "----bar" | |||||
output: [TagOpenOpen(wiki_markup="----"), Text(text="hr"), TagCloseSelfclose(), Text(text="bar")] | |||||
--- | |||||
name: hr_text_before_after | |||||
label: text at both ends of an otherwise-valid hr | |||||
input: "foo----bar" | |||||
output: [Text(text="foo----bar")] | |||||
--- | |||||
name: hr_newlines | |||||
label: newlines surrounding a valid hr | |||||
input: "foo\n----\nbar" | |||||
output: [Text(text="foo\n"), TagOpenOpen(wiki_markup="----"), Text(text="hr"), TagCloseSelfclose(), Text(text="\nbar")] | |||||
--- | |||||
name: hr_adjacent | |||||
label: two adjacent hrs | |||||
input: "----\n----" | |||||
output: [TagOpenOpen(wiki_markup="----"), Text(text="hr"), TagCloseSelfclose(), Text(text="\n"), TagOpenOpen(wiki_markup="----"), Text(text="hr"), TagCloseSelfclose()] | |||||
--- | |||||
name: hr_adjacent_space | |||||
label: two adjacent hrs, with a space before the second one, making it invalid | |||||
input: "----\n ----" | |||||
output: [TagOpenOpen(wiki_markup="----"), Text(text="hr"), TagCloseSelfclose(), Text(text="\n ----")] | |||||
--- | |||||
name: hr_short | |||||
label: an invalid three-hyphen-long hr | |||||
input: "---" | |||||
output: [Text(text="---")] | |||||
--- | |||||
name: hr_long | |||||
label: a very long, valid hr | |||||
input: "------------------------------------------" | |||||
output: [TagOpenOpen(wiki_markup="------------------------------------------"), Text(text="hr"), TagCloseSelfclose()] | |||||
--- | |||||
name: hr_interruption_short | |||||
label: a hr that is interrupted, making it invalid | |||||
input: "---x-" | |||||
output: [Text(text="---x-")] | |||||
--- | |||||
name: hr_interruption_long | |||||
label: a hr that is interrupted, but the first part remains valid because it is long enough | |||||
input: "----x--" | |||||
output: [TagOpenOpen(wiki_markup="----"), Text(text="hr"), TagCloseSelfclose(), Text(text="x--")] | |||||
--- | |||||
name: nowiki_cancel | |||||
label: a nowiki tag before a list causes it to not be parsed | |||||
input: "<nowiki />* Unordered list" | |||||
output: [TagOpenOpen(), Text(text="nowiki"), TagCloseSelfclose(padding=" "), Text(text="* Unordered list")] |
@@ -23,3 +23,10 @@ name: unicode2 | |||||
label: additional unicode check for non-BMP codepoints | label: additional unicode check for non-BMP codepoints | ||||
input: "𐌲𐌿𐍄𐌰𐍂𐌰𐌶𐌳𐌰" | input: "𐌲𐌿𐍄𐌰𐍂𐌰𐌶𐌳𐌰" | ||||
output: [Text(text="𐌲𐌿𐍄𐌰𐍂𐌰𐌶𐌳𐌰")] | output: [Text(text="𐌲𐌿𐍄𐌰𐍂𐌰𐌶𐌳𐌰")] | ||||
--- | |||||
name: large | |||||
label: a lot of text, requiring multiple textbuffer blocks in the C tokenizer | |||||
input: "ZWfsZYcZyhGbkDYJiguJuuhsNyHGFkFhnjkbLJyXIygTHqcXdhsDkEOTSIKYlBiohLIkiXxvyebUyCGvvBcYqFdtcftGmaAanKXEIyYSEKlTfEEbdGhdePVwVImOyKiHSzAEuGyEVRIKPZaNjQsYqpqARIQfvAklFtQyTJVGlLwjJIxYkiqmHBmdOvTyNqJRbMvouoqXRyOhYDwowtkcZGSOcyzVxibQdnzhDYbrgbatUrlOMRvFSzmLWHRihtXnddwYadPgFWUOxAzAgddJVDXHerawdkrRuWaEXfuwQSkQUmLEJUmrgXDVlXCpciaisfuOUjBldElygamkkXbewzLucKRnAEBimIIotXeslRRhnqQjrypnLQvvdCsKFWPVTZaHvzJMFEahDHWcCbyXgxFvknWjhVfiLSDuFhGoFxqSvhjnnRZLmCMhmWeOgSoanDEInKTWHnbpKyUlabLppITDFFxyWKAnUYJQIcmYnrvMmzmtYvsbCYbebgAhMFVVFAKUSvlkLFYluDpbpBaNFWyfXTaOdSBrfiHDTWGBTUCXMqVvRCIMrEjWpQaGsABkioGnveQWqBTDdRQlxQiUipwfyqAocMddXqdvTHhEwjEzMkOSWVPjJvDtClhYwpvRztPmRKCSpGIpXQqrYtTLmShFdpKtOxGtGOZYIdyUGPjdmyvhJTQMtgYJWUUZnecRjBfQXsyWQWikyONySLzLEqRFqcJYdRNFcGwWZtfZasfFWcvdsHRXoqKlKYihRAOJdrPBDdxksXFwKceQVncmFXfUfBsNgjKzoObVExSnRnjegeEhqxXzPmFcuiasViAFeaXrAxXhSfSyCILkKYpjxNeKynUmdcGAbwRwRnlAFbOSCafmzXddiNpLCFTHBELvArdXFpKUGpSHRekhrMedMRNkQzmSyFKjVwiWwCvbNWjgxJRzYeRxHiCCRMXktmKBxbxGZvOpvZIJOwvGIxcBLzsMFlDqAMLtScdsJtrbIUAvKfcdChXGnBzIxGxXMgxJhayrziaCswdpjJJJhkaYnGhHXqZwOzHFdhhUIEtfjERdLaSPRTDDMHpQtonNaIgXUYhjdbnnKppfMBxgNSOOXJAPtFjfAKnrRDrumZBpNhxMstqjTGBViRkDqbTdXYUirsedifGYzZpQkvdNhtFTOPgsYXYCwZHLcSLSfwfpQKtWfZuRUUryHJsbVsAOQcIJdSKKlOvCeEjUQNRPHKXuBJUjPuaAJJxcDMqyaufqfVwUmHLdjeYZzSiiGLHOTCInpVAalbXXTMLugLiwFiyPSuSFiyJUKVrWjbZAHaJtZnQmnvorRrxdPKThqXzNgTjszQiCoMczRnwGYJMERUWGXFyrSbAqsHmLwLlnJOJoXNsjVehQjVOpQOQJAZWwFZBlgyVIplzLTlFwumPgBLYrUIAJAcmvHPGfHfWQguCjfTYzxYfbohaLFAPwxFRrNuCdCzLlEbuhyYjCmuDBTJDMCdLpNRVqEALjnPSaBPsKWRCKNGwEMFpiEWbYZRwaMopjoUuBUvMpvyLfsPKDrfQLiFOQIWPtLIMoijUEUYfhykHrSKbTtrvjwIzHdWZDVwLIpNkloCqpzIsErxxKAFuFEjikWNYChqYqVslXMtoSWzNhbMuxYbzLfJIcPGoUeGPkGyPQNhDyrjgdKekzftFrRPTuyLYqCArkDcWHTrjPQHfoThBNnTQyMwLEWxEnBXLtzJmFVLGEPrdbEwlXpgYfnVnWoNXgPQKKyiXifpvrmJATzQOzYwFhliiYxlbnsEPKbHYUfJLrwYPfSUwTIHiEvBFMrEtVmqJobfcwsiiEudTIiAnrtuywgKLOiMYbEIOAOJdOXqroPjWnQQcTNxFvkIEIsuHLyhSqSphuSmlvknzydQEnebOreeZwOouXYKlObAkaWHhOdTFLoMCHOWrVKeXjcniaxtgCziKEqWOZUWHJQpcDJzYnnduDZrmxgjZroBRwoPBUTJMYipsgJwbTSlvMyXXdAmiEWGMiQxhGvHGPLOKeTxNaLnFVbWpiYIVyqN" | |||||
output: [Text(text="ZWfsZYcZyhGbkDYJiguJuuhsNyHGFkFhnjkbLJyXIygTHqcXdhsDkEOTSIKYlBiohLIkiXxvyebUyCGvvBcYqFdtcftGmaAanKXEIyYSEKlTfEEbdGhdePVwVImOyKiHSzAEuGyEVRIKPZaNjQsYqpqARIQfvAklFtQyTJVGlLwjJIxYkiqmHBmdOvTyNqJRbMvouoqXRyOhYDwowtkcZGSOcyzVxibQdnzhDYbrgbatUrlOMRvFSzmLWHRihtXnddwYadPgFWUOxAzAgddJVDXHerawdkrRuWaEXfuwQSkQUmLEJUmrgXDVlXCpciaisfuOUjBldElygamkkXbewzLucKRnAEBimIIotXeslRRhnqQjrypnLQvvdCsKFWPVTZaHvzJMFEahDHWcCbyXgxFvknWjhVfiLSDuFhGoFxqSvhjnnRZLmCMhmWeOgSoanDEInKTWHnbpKyUlabLppITDFFxyWKAnUYJQIcmYnrvMmzmtYvsbCYbebgAhMFVVFAKUSvlkLFYluDpbpBaNFWyfXTaOdSBrfiHDTWGBTUCXMqVvRCIMrEjWpQaGsABkioGnveQWqBTDdRQlxQiUipwfyqAocMddXqdvTHhEwjEzMkOSWVPjJvDtClhYwpvRztPmRKCSpGIpXQqrYtTLmShFdpKtOxGtGOZYIdyUGPjdmyvhJTQMtgYJWUUZnecRjBfQXsyWQWikyONySLzLEqRFqcJYdRNFcGwWZtfZasfFWcvdsHRXoqKlKYihRAOJdrPBDdxksXFwKceQVncmFXfUfBsNgjKzoObVExSnRnjegeEhqxXzPmFcuiasViAFeaXrAxXhSfSyCILkKYpjxNeKynUmdcGAbwRwRnlAFbOSCafmzXddiNpLCFTHBELvArdXFpKUGpSHRekhrMedMRNkQzmSyFKjVwiWwCvbNWjgxJRzYeRxHiCCRMXktmKBxbxGZvOpvZIJOwvGIxcBLzsMFlDqAMLtScdsJtrbIUAvKfcdChXGnBzIxGxXMgxJhayrziaCswdpjJJJhkaYnGhHXqZwOzHFdhhUIEtfjERdLaSPRTDDMHpQtonNaIgXUYhjdbnnKppfMBxgNSOOXJAPtFjfAKnrRDrumZBpNhxMstqjTGBViRkDqbTdXYUirsedifGYzZpQkvdNhtFTOPgsYXYCwZHLcSLSfwfpQKtWfZuRUUryHJsbVsAOQcIJdSKKlOvCeEjUQNRPHKXuBJUjPuaAJJxcDMqyaufqfVwUmHLdjeYZzSiiGLHOTCInpVAalbXXTMLugLiwFiyPSuSFiyJUKVrWjbZAHaJtZnQmnvorRrxdPKThqXzNgTjszQiCoMczRnwGYJMERUWGXFyrSbAqsHmLwLlnJOJoXNsjVehQjVOpQOQJAZWwFZBlgyVIplzLTlFwumPgBLYrUIAJAcmvHPGfHfWQguCjfTYzxYfbohaLFAPwxFRrNuCdCzLlEbuhyYjCmuDBTJDMCdLpNRVqEALjnPSaBPsKWRCKNGwEMFpiEWbYZRwaMopjoUuBUvMpvyLfsPKDrfQLiFOQIWPtLIMoijUEUYfhykHrSKbTtrvjwIzHdWZDVwLIpNkloCqpzIsErxxKAFuFEjikWNYChqYqVslXMtoSWzNhbMuxYbzLfJIcPGoUeGPkGyPQNhDyrjgdKekzftFrRPTuyLYqCArkDcWHTrjPQHfoThBNnTQyMwLEWxEnBXLtzJmFVLGEPrdbEwlXpgYfnVnWoNXgPQKKyiXifpvrmJATzQOzYwFhliiYxlbnsEPKbHYUfJLrwYPfSUwTIHiEvBFMrEtVmqJobfcwsiiEudTIiAnrtuywgKLOiMYbEIOAOJdOXqroPjWnQQcTNxFvkIEIsuHLyhSqSphuSmlvknzydQEnebOreeZwOouXYKlObAkaWHhOdTFLoMCHOWrVKeXjcniaxtgCziKEqWOZUWHJQpcDJzYnnduDZrmxgjZroBRwoPBUTJMYipsgJwbTSlvMyXXdAmiEWGMiQxhGvHGPLOKeTxNaLnFVbWpiYIVyqN")] |
@@ -40,17 +40,17 @@ output: [WikilinkOpen(), Text(text="foo"), WikilinkSeparator(), Text(text="bar|b | |||||
--- | --- | ||||
name: nested | |||||
label: a wikilink nested within the value of another | |||||
input: "[[foo|[[bar]]]]" | |||||
output: [WikilinkOpen(), Text(text="foo"), WikilinkSeparator(), WikilinkOpen(), Text(text="bar"), WikilinkClose(), WikilinkClose()] | |||||
name: newline_text | |||||
label: a newline in the middle of the text | |||||
input: "[[foo|foo\nbar]]" | |||||
output: [WikilinkOpen(), Text(text="foo"), WikilinkSeparator(), Text(text="foo\nbar"), WikilinkClose()] | |||||
--- | --- | ||||
name: nested_with_text | |||||
label: a wikilink nested within the value of another, separated by other data | |||||
input: "[[foo|a[[b]]c]]" | |||||
output: [WikilinkOpen(), Text(text="foo"), WikilinkSeparator(), Text(text="a"), WikilinkOpen(), Text(text="b"), WikilinkClose(), Text(text="c"), WikilinkClose()] | |||||
name: bracket_text | |||||
label: a left bracket in the middle of the text | |||||
input: "[[foo|bar[baz]]" | |||||
output: [WikilinkOpen(), Text(text="foo"), WikilinkSeparator(), Text(text="bar[baz"), WikilinkClose()] | |||||
--- | --- | ||||
@@ -96,13 +96,34 @@ output: [Text(text="[[foo"), WikilinkOpen(), Text(text="bar"), WikilinkClose(), | |||||
--- | --- | ||||
name: invalid_nested_text | |||||
name: invalid_nested_padding | |||||
label: invalid wikilink: trying to nest in the wrong context, with a text param | label: invalid wikilink: trying to nest in the wrong context, with a text param | ||||
input: "[[foo[[bar]]|baz]]" | input: "[[foo[[bar]]|baz]]" | ||||
output: [Text(text="[[foo"), WikilinkOpen(), Text(text="bar"), WikilinkClose(), Text(text="|baz]]")] | output: [Text(text="[[foo"), WikilinkOpen(), Text(text="bar"), WikilinkClose(), Text(text="|baz]]")] | ||||
--- | --- | ||||
name: invalid_nested_text | |||||
label: invalid wikilink: a wikilink nested within the value of another | |||||
input: "[[foo|[[bar]]" | |||||
output: [Text(text="[[foo|"), WikilinkOpen(), Text(text="bar"), WikilinkClose()] | |||||
--- | |||||
name: invalid_nested_text_2 | |||||
label: invalid wikilink: a wikilink nested within the value of another, two pairs of closing brackets | |||||
input: "[[foo|[[bar]]]]" | |||||
output: [Text(text="[[foo|"), WikilinkOpen(), Text(text="bar"), WikilinkClose(), Text(text="]]")] | |||||
--- | |||||
name: invalid_nested_text_padding | |||||
label: invalid wikilink: a wikilink nested within the value of another, separated by other data | |||||
input: "[[foo|a[[b]]c]]" | |||||
output: [Text(text="[[foo|a"), WikilinkOpen(), Text(text="b"), WikilinkClose(), Text(text="c]]")] | |||||
--- | |||||
name: incomplete_open_only | name: incomplete_open_only | ||||
label: incomplete wikilinks: just an open | label: incomplete wikilinks: just an open | ||||
input: "[[" | input: "[[" | ||||