@@ -24,7 +24,7 @@ from __future__ import unicode_literals | |||||
from . import Node, Text | from . import Node, Text | ||||
from ..compat import str | from ..compat import str | ||||
from ..tag_defs import get_wikicode, is_visible | |||||
from ..tag_defs import is_visible | |||||
from ..utils import parse_anything | from ..utils import parse_anything | ||||
__all__ = ["Tag"] | __all__ = ["Tag"] | ||||
@@ -32,7 +32,7 @@ __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>``.""" | ||||
def __init__(self, tag, contents=None, attrs=None, showtag=True, | |||||
def __init__(self, tag, contents=None, attrs=None, wiki_markup=None, | |||||
self_closing=False, invalid=False, implicit=False, padding="", | self_closing=False, invalid=False, implicit=False, padding="", | ||||
closing_tag=None): | closing_tag=None): | ||||
super(Tag, self).__init__() | super(Tag, self).__init__() | ||||
@@ -42,7 +42,7 @@ class Tag(Node): | |||||
else: | else: | ||||
self._contents = contents | self._contents = contents | ||||
self._attrs = attrs if attrs else [] | self._attrs = attrs if attrs else [] | ||||
self._showtag = showtag | |||||
self._wiki_markup = wiki_markup | |||||
self._self_closing = self_closing | self._self_closing = self_closing | ||||
self._invalid = invalid | self._invalid = invalid | ||||
self._implicit = implicit | self._implicit = implicit | ||||
@@ -53,12 +53,11 @@ class Tag(Node): | |||||
self._closing_tag = tag | self._closing_tag = tag | ||||
def __unicode__(self): | def __unicode__(self): | ||||
if not self.showtag: | |||||
open_, close = get_wikicode(self.tag) | |||||
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 = ("</" if self.invalid else "<") + str(self.tag) | result = ("</" if self.invalid else "<") + str(self.tag) | ||||
if self.attributes: | if self.attributes: | ||||
@@ -72,7 +71,7 @@ class Tag(Node): | |||||
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.attributes: | for attr in self.attributes: | ||||
@@ -84,7 +83,7 @@ class Tag(Node): | |||||
if self.contents: | if self.contents: | ||||
for child in getter(self.contents): | for child in getter(self.contents): | ||||
yield self.contents, child | yield self.contents, child | ||||
if not self.self_closing and self.showtag and self.closing_tag: | |||||
if not self.self_closing and not self.wiki_markup and self.closing_tag: | |||||
for child in getter(self.closing_tag): | for child in getter(self.closing_tag): | ||||
yield self.closing_tag, child | yield self.closing_tag, child | ||||
@@ -131,9 +130,14 @@ 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): | ||||
@@ -183,9 +187,9 @@ class Tag(Node): | |||||
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): | ||||
@@ -207,15 +207,14 @@ class Builder(object): | |||||
"""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.""" | ||||
close_tokens = (tokens.TagCloseSelfclose, tokens.TagCloseClose) | close_tokens = (tokens.TagCloseSelfclose, tokens.TagCloseClose) | ||||
implicit, attrs, contents, closing_tag = False, [], None, None | implicit, attrs, contents, closing_tag = False, [], None, None | ||||
showtag = token.get("showtag", True) | |||||
invalid = token.get("invalid", False) | |||||
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(token)) | attrs.append(self._handle_attribute(token)) | ||||
elif isinstance(token, tokens.TagCloseOpen): | elif isinstance(token, tokens.TagCloseOpen): | ||||
padding = token.padding | |||||
padding = token.padding or "" | |||||
tag = self._pop() | tag = self._pop() | ||||
self._push() | self._push() | ||||
elif isinstance(token, tokens.TagOpenClose): | elif isinstance(token, tokens.TagOpenClose): | ||||
@@ -225,12 +224,12 @@ class Builder(object): | |||||
if isinstance(token, tokens.TagCloseSelfclose): | if isinstance(token, tokens.TagCloseSelfclose): | ||||
tag = self._pop() | tag = self._pop() | ||||
self_closing = True | self_closing = True | ||||
padding = token.padding | |||||
implicit = token.get("implicit", False) | |||||
padding = token.padding or "" | |||||
implicit = token.implicit or False | |||||
else: | else: | ||||
self_closing = False | self_closing = False | ||||
closing_tag = self._pop() | closing_tag = self._pop() | ||||
return Tag(tag, contents, attrs, showtag, self_closing, | |||||
return Tag(tag, contents, attrs, wiki_markup, self_closing, | |||||
invalid, implicit, padding, closing_tag) | invalid, implicit, padding, closing_tag) | ||||
else: | else: | ||||
self._write(self._handle_token(token)) | self._write(self._handle_token(token)) | ||||
@@ -69,6 +69,15 @@ Local (stack-specific) contexts: | |||||
* :py:const:`TAG_BODY` | * :py:const:`TAG_BODY` | ||||
* :py:const:`TAG_CLOSE` | * :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` | ||||
* :py:const:`HAS_TEXT` | * :py:const:`HAS_TEXT` | ||||
@@ -115,12 +124,20 @@ TAG_BODY = 1 << 16 | |||||
TAG_CLOSE = 1 << 17 | TAG_CLOSE = 1 << 17 | ||||
TAG = TAG_OPEN + TAG_ATTR + TAG_BODY + TAG_CLOSE | TAG = TAG_OPEN + TAG_ATTR + TAG_BODY + TAG_CLOSE | ||||
HAS_TEXT = 1 << 18 | |||||
FAIL_ON_TEXT = 1 << 19 | |||||
FAIL_NEXT = 1 << 20 | |||||
FAIL_ON_LBRACE = 1 << 21 | |||||
FAIL_ON_RBRACE = 1 << 22 | |||||
FAIL_ON_EQUALS = 1 << 23 | |||||
STYLE_ITALICS = 1 << 18 | |||||
STYLE_BOLD = 1 << 19 | |||||
STYLE_PASS_AGAIN = 1 << 20 | |||||
STYLE_SECOND_PASS = 1 << 21 | |||||
STYLE = STYLE_ITALICS + STYLE_BOLD + STYLE_PASS_AGAIN + STYLE_SECOND_PASS | |||||
DL_TERM = 1 << 22 | |||||
HAS_TEXT = 1 << 23 | |||||
FAIL_ON_TEXT = 1 << 24 | |||||
FAIL_NEXT = 1 << 25 | |||||
FAIL_ON_LBRACE = 1 << 26 | |||||
FAIL_ON_RBRACE = 1 << 27 | |||||
FAIL_ON_EQUALS = 1 << 28 | |||||
SAFETY_CHECK = (HAS_TEXT + FAIL_ON_TEXT + FAIL_NEXT + FAIL_ON_LBRACE + | SAFETY_CHECK = (HAS_TEXT + FAIL_ON_TEXT + FAIL_NEXT + FAIL_ON_LBRACE + | ||||
FAIL_ON_RBRACE + FAIL_ON_EQUALS) | FAIL_ON_RBRACE + FAIL_ON_EQUALS) | ||||
@@ -29,6 +29,7 @@ SOFTWARE. | |||||
static int heading_level_from_context(int n) | static int heading_level_from_context(int n) | ||||
{ | { | ||||
int level; | int level; | ||||
n /= LC_HEADING_LEVEL_1; | n /= LC_HEADING_LEVEL_1; | ||||
for (level = 1; n > 1; n >>= 1) | for (level = 1; n > 1; n >>= 1) | ||||
level++; | level++; | ||||
@@ -72,6 +73,7 @@ static PyObject* strip_tag_name(PyObject* token) | |||||
static Textbuffer* Textbuffer_new(void) | static Textbuffer* Textbuffer_new(void) | ||||
{ | { | ||||
Textbuffer* buffer = malloc(sizeof(Textbuffer)); | Textbuffer* buffer = malloc(sizeof(Textbuffer)); | ||||
if (!buffer) { | if (!buffer) { | ||||
PyErr_NoMemory(); | PyErr_NoMemory(); | ||||
return NULL; | return NULL; | ||||
@@ -90,6 +92,7 @@ static Textbuffer* Textbuffer_new(void) | |||||
static void Textbuffer_dealloc(Textbuffer* self) | static void Textbuffer_dealloc(Textbuffer* self) | ||||
{ | { | ||||
Textbuffer* next; | Textbuffer* next; | ||||
while (self) { | while (self) { | ||||
free(self->data); | free(self->data); | ||||
next = self->next; | next = self->next; | ||||
@@ -99,11 +102,12 @@ static void Textbuffer_dealloc(Textbuffer* self) | |||||
} | } | ||||
/* | /* | ||||
Write text to the given textbuffer. | |||||
Write a Unicode codepoint to the given textbuffer. | |||||
*/ | */ | ||||
static int Textbuffer_write(Textbuffer** this, Py_UNICODE text) | |||||
static int Textbuffer_write(Textbuffer** this, Py_UNICODE code) | |||||
{ | { | ||||
Textbuffer* self = *this; | Textbuffer* self = *this; | ||||
if (self->size == TEXTBUFFER_BLOCKSIZE) { | if (self->size == TEXTBUFFER_BLOCKSIZE) { | ||||
Textbuffer* new = Textbuffer_new(); | Textbuffer* new = Textbuffer_new(); | ||||
if (!new) | if (!new) | ||||
@@ -111,7 +115,7 @@ static int Textbuffer_write(Textbuffer** this, Py_UNICODE text) | |||||
new->next = self; | new->next = self; | ||||
*this = self = new; | *this = self = new; | ||||
} | } | ||||
self->data[self->size] = text; | |||||
self->data[self->size] = code; | |||||
self->size++; | self->size++; | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -123,6 +127,7 @@ static PyObject* Textbuffer_render(Textbuffer* self) | |||||
{ | { | ||||
PyObject *result = PyUnicode_FromUnicode(self->data, self->size); | PyObject *result = PyUnicode_FromUnicode(self->data, self->size); | ||||
PyObject *left, *concat; | PyObject *left, *concat; | ||||
while (self->next) { | while (self->next) { | ||||
self = self->next; | self = self->next; | ||||
left = PyUnicode_FromUnicode(self->data, self->size); | left = PyUnicode_FromUnicode(self->data, self->size); | ||||
@@ -208,6 +213,7 @@ static void Tokenizer_dealloc(Tokenizer* self) | |||||
static int Tokenizer_init(Tokenizer* self, PyObject* args, PyObject* kwds) | static int Tokenizer_init(Tokenizer* self, PyObject* args, PyObject* kwds) | ||||
{ | { | ||||
static char* kwlist[] = {NULL}; | static char* kwlist[] = {NULL}; | ||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "", kwlist)) | if (!PyArg_ParseTupleAndKeywords(args, kwds, "", kwlist)) | ||||
return -1; | return -1; | ||||
self->text = Py_None; | self->text = Py_None; | ||||
@@ -223,6 +229,7 @@ static int Tokenizer_init(Tokenizer* self, PyObject* args, PyObject* kwds) | |||||
static int Tokenizer_push(Tokenizer* self, int context) | static int Tokenizer_push(Tokenizer* self, int context) | ||||
{ | { | ||||
Stack* top = malloc(sizeof(Stack)); | Stack* top = malloc(sizeof(Stack)); | ||||
if (!top) { | if (!top) { | ||||
PyErr_NoMemory(); | PyErr_NoMemory(); | ||||
return -1; | return -1; | ||||
@@ -246,6 +253,7 @@ static int Tokenizer_push_textbuffer(Tokenizer* self) | |||||
{ | { | ||||
PyObject *text, *kwargs, *token; | PyObject *text, *kwargs, *token; | ||||
Textbuffer* buffer = self->topstack->textbuffer; | Textbuffer* buffer = self->topstack->textbuffer; | ||||
if (buffer->size == 0 && !buffer->next) | if (buffer->size == 0 && !buffer->next) | ||||
return 0; | return 0; | ||||
text = Textbuffer_render(buffer); | text = Textbuffer_render(buffer); | ||||
@@ -280,6 +288,7 @@ static int Tokenizer_push_textbuffer(Tokenizer* self) | |||||
static void Tokenizer_delete_top_of_stack(Tokenizer* self) | static void Tokenizer_delete_top_of_stack(Tokenizer* self) | ||||
{ | { | ||||
Stack* top = self->topstack; | Stack* top = self->topstack; | ||||
Py_DECREF(top->stack); | Py_DECREF(top->stack); | ||||
Textbuffer_dealloc(top->textbuffer); | Textbuffer_dealloc(top->textbuffer); | ||||
self->topstack = top->next; | self->topstack = top->next; | ||||
@@ -293,6 +302,7 @@ static void Tokenizer_delete_top_of_stack(Tokenizer* self) | |||||
static PyObject* Tokenizer_pop(Tokenizer* self) | static PyObject* Tokenizer_pop(Tokenizer* self) | ||||
{ | { | ||||
PyObject* stack; | PyObject* stack; | ||||
if (Tokenizer_push_textbuffer(self)) | if (Tokenizer_push_textbuffer(self)) | ||||
return NULL; | return NULL; | ||||
stack = self->topstack->stack; | stack = self->topstack->stack; | ||||
@@ -309,6 +319,7 @@ static PyObject* Tokenizer_pop_keeping_context(Tokenizer* self) | |||||
{ | { | ||||
PyObject* stack; | PyObject* stack; | ||||
int context; | int context; | ||||
if (Tokenizer_push_textbuffer(self)) | if (Tokenizer_push_textbuffer(self)) | ||||
return NULL; | return NULL; | ||||
stack = self->topstack->stack; | stack = self->topstack->stack; | ||||
@@ -325,9 +336,11 @@ static PyObject* Tokenizer_pop_keeping_context(Tokenizer* self) | |||||
*/ | */ | ||||
static void* Tokenizer_fail_route(Tokenizer* self) | static void* Tokenizer_fail_route(Tokenizer* self) | ||||
{ | { | ||||
int context = self->topstack->context; | |||||
PyObject* stack = Tokenizer_pop(self); | PyObject* stack = Tokenizer_pop(self); | ||||
Py_XDECREF(stack); | Py_XDECREF(stack); | ||||
FAIL_ROUTE(); | |||||
FAIL_ROUTE(context); | |||||
return NULL; | return NULL; | ||||
} | } | ||||
@@ -356,11 +369,26 @@ static int Tokenizer_emit_first(Tokenizer* self, PyObject* token) | |||||
} | } | ||||
/* | /* | ||||
Write text to the current textbuffer. | |||||
Write a Unicode codepoint to the current textbuffer. | |||||
*/ | */ | ||||
static int Tokenizer_emit_text(Tokenizer* self, Py_UNICODE text) | |||||
static int Tokenizer_emit_char(Tokenizer* self, Py_UNICODE code) | |||||
{ | { | ||||
return Textbuffer_write(&(self->topstack->textbuffer), text); | |||||
return Textbuffer_write(&(self->topstack->textbuffer), code); | |||||
} | |||||
/* | |||||
Write a string of text to the current textbuffer. | |||||
*/ | |||||
static int Tokenizer_emit_text(Tokenizer* self, const char* text) | |||||
{ | |||||
int i = 0; | |||||
while (text[i]) { | |||||
if (Tokenizer_emit_char(self, text[i])) | |||||
return -1; | |||||
i++; | |||||
} | |||||
return 0; | |||||
} | } | ||||
/* | /* | ||||
@@ -427,15 +455,10 @@ static int Tokenizer_emit_all(Tokenizer* self, PyObject* tokenlist) | |||||
static int Tokenizer_emit_text_then_stack(Tokenizer* self, const char* text) | static int Tokenizer_emit_text_then_stack(Tokenizer* self, const char* text) | ||||
{ | { | ||||
PyObject* stack = Tokenizer_pop(self); | PyObject* stack = Tokenizer_pop(self); | ||||
int i = 0; | |||||
while (1) { | |||||
if (!text[i]) | |||||
break; | |||||
if (Tokenizer_emit_text(self, (Py_UNICODE) text[i])) { | |||||
Py_XDECREF(stack); | |||||
return -1; | |||||
} | |||||
i++; | |||||
if (Tokenizer_emit_text(self, text)) { | |||||
Py_DECREF(stack); | |||||
return -1; | |||||
} | } | ||||
if (stack) { | if (stack) { | ||||
if (PyList_GET_SIZE(stack) > 0) { | if (PyList_GET_SIZE(stack) > 0) { | ||||
@@ -456,6 +479,7 @@ static int Tokenizer_emit_text_then_stack(Tokenizer* self, const char* text) | |||||
static PyObject* Tokenizer_read(Tokenizer* self, Py_ssize_t delta) | static PyObject* Tokenizer_read(Tokenizer* self, Py_ssize_t delta) | ||||
{ | { | ||||
Py_ssize_t index = self->head + delta; | Py_ssize_t index = self->head + delta; | ||||
if (index >= self->length) | if (index >= self->length) | ||||
return EMPTY; | return EMPTY; | ||||
return PyList_GET_ITEM(self->text, index); | return PyList_GET_ITEM(self->text, index); | ||||
@@ -467,6 +491,7 @@ static PyObject* Tokenizer_read(Tokenizer* self, Py_ssize_t delta) | |||||
static PyObject* Tokenizer_read_backwards(Tokenizer* self, Py_ssize_t delta) | static PyObject* Tokenizer_read_backwards(Tokenizer* self, Py_ssize_t delta) | ||||
{ | { | ||||
Py_ssize_t index; | Py_ssize_t index; | ||||
if (delta > self->head) | if (delta > self->head) | ||||
return EMPTY; | return EMPTY; | ||||
index = self->head - delta; | index = self->head - delta; | ||||
@@ -751,7 +776,6 @@ static int Tokenizer_parse_wikilink(Tokenizer* self) | |||||
{ | { | ||||
Py_ssize_t reset; | Py_ssize_t reset; | ||||
PyObject *wikilink, *token; | PyObject *wikilink, *token; | ||||
int i; | |||||
self->head += 2; | self->head += 2; | ||||
reset = self->head - 1; | reset = self->head - 1; | ||||
@@ -759,10 +783,8 @@ static int Tokenizer_parse_wikilink(Tokenizer* self) | |||||
if (BAD_ROUTE) { | if (BAD_ROUTE) { | ||||
RESET_ROUTE(); | RESET_ROUTE(); | ||||
self->head = reset; | self->head = reset; | ||||
for (i = 0; i < 2; i++) { | |||||
if (Tokenizer_emit_text(self, *"[")) | |||||
return -1; | |||||
} | |||||
if (Tokenizer_emit_text(self, "[[")) | |||||
return -1; | |||||
return 0; | return 0; | ||||
} | } | ||||
if (!wikilink) | if (!wikilink) | ||||
@@ -847,7 +869,7 @@ static int Tokenizer_parse_heading(Tokenizer* self) | |||||
RESET_ROUTE(); | RESET_ROUTE(); | ||||
self->head = reset + best - 1; | self->head = reset + best - 1; | ||||
for (i = 0; i < best; i++) { | for (i = 0; i < best; i++) { | ||||
if (Tokenizer_emit_text(self, *"=")) | |||||
if (Tokenizer_emit_char(self, *"=")) | |||||
return -1; | return -1; | ||||
} | } | ||||
self->global ^= GL_HEADING; | self->global ^= GL_HEADING; | ||||
@@ -885,7 +907,7 @@ static int Tokenizer_parse_heading(Tokenizer* self) | |||||
if (heading->level < best) { | if (heading->level < best) { | ||||
diff = best - heading->level; | diff = best - heading->level; | ||||
for (i = 0; i < diff; i++) { | for (i = 0; i < diff; i++) { | ||||
if (Tokenizer_emit_text(self, *"=")) { | |||||
if (Tokenizer_emit_char(self, *"=")) { | |||||
Py_DECREF(heading->title); | Py_DECREF(heading->title); | ||||
free(heading); | free(heading); | ||||
return -1; | return -1; | ||||
@@ -936,7 +958,7 @@ static HeadingData* Tokenizer_handle_heading_end(Tokenizer* self) | |||||
if (level < best) { | if (level < best) { | ||||
diff = best - level; | diff = best - level; | ||||
for (i = 0; i < diff; i++) { | for (i = 0; i < diff; i++) { | ||||
if (Tokenizer_emit_text(self, *"=")) | |||||
if (Tokenizer_emit_char(self, *"=")) | |||||
return NULL; | return NULL; | ||||
} | } | ||||
} | } | ||||
@@ -944,7 +966,7 @@ static HeadingData* Tokenizer_handle_heading_end(Tokenizer* self) | |||||
} | } | ||||
else { | else { | ||||
for (i = 0; i < best; i++) { | for (i = 0; i < best; i++) { | ||||
if (Tokenizer_emit_text(self, *"=")) { | |||||
if (Tokenizer_emit_char(self, *"=")) { | |||||
Py_DECREF(after->title); | Py_DECREF(after->title); | ||||
free(after); | free(after); | ||||
return NULL; | return NULL; | ||||
@@ -1160,7 +1182,7 @@ static int Tokenizer_parse_entity(Tokenizer* self) | |||||
if (BAD_ROUTE) { | if (BAD_ROUTE) { | ||||
RESET_ROUTE(); | RESET_ROUTE(); | ||||
self->head = reset; | self->head = reset; | ||||
if (Tokenizer_emit_text(self, *"&")) | |||||
if (Tokenizer_emit_char(self, *"&")) | |||||
return -1; | return -1; | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -1182,24 +1204,14 @@ static int Tokenizer_parse_comment(Tokenizer* self) | |||||
{ | { | ||||
Py_ssize_t reset = self->head + 3; | Py_ssize_t reset = self->head + 3; | ||||
PyObject *token, *comment; | PyObject *token, *comment; | ||||
int i; | |||||
self->head += 4; | self->head += 4; | ||||
comment = Tokenizer_parse(self, LC_COMMENT, 1); | comment = Tokenizer_parse(self, LC_COMMENT, 1); | ||||
if (BAD_ROUTE) { | if (BAD_ROUTE) { | ||||
const char* text = "<!--"; | |||||
RESET_ROUTE(); | RESET_ROUTE(); | ||||
self->head = reset; | self->head = reset; | ||||
i = 0; | |||||
while (1) { | |||||
if (!text[i]) | |||||
return 0; | |||||
if (Tokenizer_emit_text(self, (Py_UNICODE) text[i])) { | |||||
Py_XDECREF(text); | |||||
return -1; | |||||
} | |||||
i++; | |||||
} | |||||
if (Tokenizer_emit_text(self, "<!--")) | |||||
return -1; | |||||
return 0; | return 0; | ||||
} | } | ||||
if (!comment) | if (!comment) | ||||
@@ -1317,7 +1329,7 @@ Tokenizer_handle_tag_space(Tokenizer* self, TagData* data, Py_UNICODE text) | |||||
return -1; | return -1; | ||||
} | } | ||||
if (ctx & TAG_QUOTED && !(ctx & TAG_NOTE_SPACE)) { | if (ctx & TAG_QUOTED && !(ctx & TAG_NOTE_SPACE)) { | ||||
if (Tokenizer_emit_text(self, text)) | |||||
if (Tokenizer_emit_char(self, text)) | |||||
return -1; | return -1; | ||||
} | } | ||||
else if (data->context & TAG_ATTR_READY) | else if (data->context & TAG_ATTR_READY) | ||||
@@ -1342,14 +1354,14 @@ static int Tokenizer_handle_tag_text(Tokenizer* self, Py_UNICODE text) | |||||
} | } | ||||
} | } | ||||
if (!is_marker || !Tokenizer_CAN_RECURSE(self)) | if (!is_marker || !Tokenizer_CAN_RECURSE(self)) | ||||
return Tokenizer_emit_text(self, text); | |||||
return Tokenizer_emit_char(self, text); | |||||
else if (text == next && next == *"{") | else if (text == next && next == *"{") | ||||
return Tokenizer_parse_template_or_argument(self); | return Tokenizer_parse_template_or_argument(self); | ||||
else if (text == next && next == *"[") | else if (text == next && next == *"[") | ||||
return Tokenizer_parse_wikilink(self); | return Tokenizer_parse_wikilink(self); | ||||
else if (text == *"<") | else if (text == *"<") | ||||
return Tokenizer_parse_tag(self); | return Tokenizer_parse_tag(self); | ||||
return Tokenizer_emit_text(self, text); | |||||
return Tokenizer_emit_char(self, text); | |||||
} | } | ||||
/* | /* | ||||
@@ -1574,7 +1586,7 @@ static PyObject* Tokenizer_handle_blacklisted_tag(Tokenizer* self) | |||||
return NULL; | return NULL; | ||||
return Tokenizer_parse(self, 0, 0); | return Tokenizer_parse(self, 0, 0); | ||||
} | } | ||||
if (Tokenizer_emit_text(self, this)) | |||||
if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
} | } | ||||
} | } | ||||
@@ -1776,7 +1788,7 @@ static int Tokenizer_handle_invalid_tag_start(Tokenizer* self) | |||||
return -1; | return -1; | ||||
} | } | ||||
if (!IS_SINGLE_ONLY(name)) | if (!IS_SINGLE_ONLY(name)) | ||||
FAIL_ROUTE(); | |||||
FAIL_ROUTE(0); | |||||
break; | break; | ||||
} | } | ||||
Textbuffer_write(&buf, this); | Textbuffer_write(&buf, this); | ||||
@@ -1790,8 +1802,7 @@ static int Tokenizer_handle_invalid_tag_start(Tokenizer* self) | |||||
if (BAD_ROUTE) { | if (BAD_ROUTE) { | ||||
RESET_ROUTE(); | RESET_ROUTE(); | ||||
self->head = reset; | self->head = reset; | ||||
return (Tokenizer_emit_text(self, *"<") || | |||||
Tokenizer_emit_text(self, *"/")); | |||||
return Tokenizer_emit_text(self, "</"); | |||||
} | } | ||||
// Set invalid=True flag of TagOpenOpen | // Set invalid=True flag of TagOpenOpen | ||||
if (PyObject_SetAttrString(PyList_GET_ITEM(tag, 0), "invalid", Py_True)) | if (PyObject_SetAttrString(PyList_GET_ITEM(tag, 0), "invalid", Py_True)) | ||||
@@ -1812,7 +1823,7 @@ static int Tokenizer_parse_tag(Tokenizer* self) | |||||
if (BAD_ROUTE) { | if (BAD_ROUTE) { | ||||
RESET_ROUTE(); | RESET_ROUTE(); | ||||
self->head = reset; | self->head = reset; | ||||
return Tokenizer_emit_text(self, *"<"); | |||||
return Tokenizer_emit_char(self, *"<"); | |||||
} | } | ||||
if (!tag) { | if (!tag) { | ||||
return -1; | return -1; | ||||
@@ -1823,12 +1834,382 @@ static int Tokenizer_parse_tag(Tokenizer* self) | |||||
} | } | ||||
/* | /* | ||||
Write the body of a tag and the tokens that should surround it. | |||||
*/ | |||||
static int Tokenizer_emit_style_tag(Tokenizer* self, const char* tag, | |||||
const char* ticks, PyObject* body) | |||||
{ | |||||
PyObject *markup, *kwargs, *token; | |||||
markup = PyBytes_FromString(ticks); | |||||
if (!markup) | |||||
return -1; | |||||
kwargs = PyDict_New(); | |||||
if (!kwargs) { | |||||
Py_DECREF(markup); | |||||
return -1; | |||||
} | |||||
PyDict_SetItemString(kwargs, "wiki_markup", markup); | |||||
Py_DECREF(markup); | |||||
token = PyObject_Call(TagOpenOpen, NOARGS, kwargs); | |||||
if (!token) { | |||||
Py_DECREF(kwargs); | |||||
return -1; | |||||
} | |||||
Py_DECREF(kwargs); | |||||
if (Tokenizer_emit(self, token)) { | |||||
Py_DECREF(token); | |||||
return -1; | |||||
} | |||||
Py_DECREF(token); | |||||
if (Tokenizer_emit_text(self, tag)) | |||||
return -1; | |||||
token = PyObject_CallObject(TagCloseOpen, NULL); | |||||
if (!token) | |||||
return -1; | |||||
if (Tokenizer_emit(self, token)) { | |||||
Py_DECREF(token); | |||||
return -1; | |||||
} | |||||
Py_DECREF(token); | |||||
if (Tokenizer_emit_all(self, body)) | |||||
return -1; | |||||
token = PyObject_CallObject(TagOpenClose, NULL); | |||||
if (!token) | |||||
return -1; | |||||
if (Tokenizer_emit(self, token)) { | |||||
Py_DECREF(token); | |||||
return -1; | |||||
} | |||||
Py_DECREF(token); | |||||
if (Tokenizer_emit_text(self, tag)) | |||||
return -1; | |||||
token = PyObject_CallObject(TagCloseClose, NULL); | |||||
if (!token) | |||||
return -1; | |||||
if (Tokenizer_emit(self, token)) { | |||||
Py_DECREF(token); | |||||
return -1; | |||||
} | |||||
Py_DECREF(token); | |||||
Py_DECREF(body); | |||||
return 0; | |||||
} | |||||
/* | |||||
Parse wiki-style italics. | |||||
*/ | |||||
static int Tokenizer_parse_italics(Tokenizer* self) | |||||
{ | |||||
Py_ssize_t reset = self->head; | |||||
int context; | |||||
PyObject *stack; | |||||
stack = Tokenizer_parse(self, LC_STYLE_ITALICS, 1); | |||||
if (BAD_ROUTE) { | |||||
RESET_ROUTE(); | |||||
self->head = reset; | |||||
if (BAD_ROUTE_CONTEXT & LC_STYLE_PASS_AGAIN) { | |||||
context = LC_STYLE_ITALICS | LC_STYLE_SECOND_PASS; | |||||
stack = Tokenizer_parse(self, context, 1); | |||||
} | |||||
else | |||||
return Tokenizer_emit_text(self, "''"); | |||||
} | |||||
if (!stack) | |||||
return -1; | |||||
return Tokenizer_emit_style_tag(self, "i", "''", stack); | |||||
} | |||||
/* | |||||
Parse wiki-style bold. | |||||
*/ | |||||
static int Tokenizer_parse_bold(Tokenizer* self) | |||||
{ | |||||
Py_ssize_t reset = self->head; | |||||
PyObject *stack; | |||||
stack = Tokenizer_parse(self, LC_STYLE_BOLD, 1); | |||||
if (BAD_ROUTE) { | |||||
RESET_ROUTE(); | |||||
self->head = reset; | |||||
if (self->topstack->context & LC_STYLE_SECOND_PASS) | |||||
return Tokenizer_emit_char(self, *"'") ? -1 : 1; | |||||
if (self->topstack->context & LC_STYLE_ITALICS) { | |||||
self->topstack->context |= LC_STYLE_PASS_AGAIN; | |||||
return Tokenizer_emit_text(self, "'''"); | |||||
} | |||||
if (Tokenizer_emit_char(self, *"'")) | |||||
return -1; | |||||
return Tokenizer_parse_italics(self); | |||||
} | |||||
if (!stack) | |||||
return -1; | |||||
return Tokenizer_emit_style_tag(self, "b", "'''", stack); | |||||
} | |||||
/* | |||||
Parse wiki-style italics and bold together (i.e., five ticks). | |||||
*/ | |||||
static int Tokenizer_parse_italics_and_bold(Tokenizer* self) | |||||
{ | |||||
Py_ssize_t reset = self->head; | |||||
PyObject *stack, *stack2; | |||||
stack = Tokenizer_parse(self, LC_STYLE_BOLD, 1); | |||||
if (BAD_ROUTE) { | |||||
RESET_ROUTE(); | |||||
self->head = reset; | |||||
stack = Tokenizer_parse(self, LC_STYLE_ITALICS, 1); | |||||
if (BAD_ROUTE) { | |||||
RESET_ROUTE(); | |||||
self->head = reset; | |||||
return Tokenizer_emit_text(self, "'''''"); | |||||
} | |||||
if (!stack) | |||||
return -1; | |||||
reset = self->head; | |||||
stack2 = Tokenizer_parse(self, LC_STYLE_BOLD, 1); | |||||
if (BAD_ROUTE) { | |||||
RESET_ROUTE(); | |||||
self->head = reset; | |||||
if (Tokenizer_emit_text(self, "'''")) | |||||
return -1; | |||||
return Tokenizer_emit_style_tag(self, "i", "''", stack); | |||||
} | |||||
if (!stack2) | |||||
return -1; | |||||
if (Tokenizer_push(self, 0)) | |||||
return -1; | |||||
if (Tokenizer_emit_style_tag(self, "i", "''", stack)) | |||||
return -1; | |||||
if (Tokenizer_emit_all(self, stack2)) | |||||
return -1; | |||||
Py_DECREF(stack2); | |||||
stack2 = Tokenizer_pop(self); | |||||
if (!stack2) | |||||
return -1; | |||||
return Tokenizer_emit_style_tag(self, "b", "'''", stack2); | |||||
} | |||||
if (!stack) | |||||
return -1; | |||||
reset = self->head; | |||||
stack2 = Tokenizer_parse(self, LC_STYLE_ITALICS, 1); | |||||
if (BAD_ROUTE) { | |||||
RESET_ROUTE(); | |||||
self->head = reset; | |||||
if (Tokenizer_emit_text(self, "''")) | |||||
return -1; | |||||
return Tokenizer_emit_style_tag(self, "b", "'''", stack); | |||||
} | |||||
if (!stack2) | |||||
return -1; | |||||
if (Tokenizer_push(self, 0)) | |||||
return -1; | |||||
if (Tokenizer_emit_style_tag(self, "b", "'''", stack)) | |||||
return -1; | |||||
if (Tokenizer_emit_all(self, stack2)) | |||||
return -1; | |||||
Py_DECREF(stack2); | |||||
stack2 = Tokenizer_pop(self); | |||||
if (!stack2) | |||||
return -1; | |||||
return Tokenizer_emit_style_tag(self, "i", "''", stack2); | |||||
} | |||||
/* | |||||
Parse wiki-style formatting (''/''' for italics/bold). | |||||
*/ | |||||
static PyObject* Tokenizer_parse_style(Tokenizer* self) | |||||
{ | |||||
int context = self->topstack->context, ticks = 2, i; | |||||
self->head += 2; | |||||
while (Tokenizer_READ(self, 0) == *"'") { | |||||
self->head++; | |||||
ticks++; | |||||
} | |||||
if (ticks > 5) { | |||||
for (i = 0; i < ticks - 5; i++) { | |||||
if (Tokenizer_emit_char(self, *"'")) | |||||
return NULL; | |||||
} | |||||
ticks = 5; | |||||
} | |||||
else if (ticks == 4) { | |||||
if (Tokenizer_emit_char(self, *"'")) | |||||
return NULL; | |||||
ticks = 3; | |||||
} | |||||
if ((context & LC_STYLE_ITALICS && (ticks == 2 || ticks == 5)) || | |||||
(context & LC_STYLE_BOLD && (ticks == 3 || ticks == 5))) { | |||||
if (ticks == 5) | |||||
self->head -= context & LC_STYLE_ITALICS ? 3 : 2; | |||||
return Tokenizer_pop(self); | |||||
} | |||||
if (!Tokenizer_CAN_RECURSE(self)) { | |||||
if (ticks == 3) { | |||||
if (context & LC_STYLE_SECOND_PASS) { | |||||
if (Tokenizer_emit_char(self, *"'")) | |||||
return NULL; | |||||
return Tokenizer_pop(self); | |||||
} | |||||
self->topstack->context |= LC_STYLE_PASS_AGAIN; | |||||
} | |||||
for (i = 0; i < ticks; i++) { | |||||
if (Tokenizer_emit_char(self, *"'")) | |||||
return NULL; | |||||
} | |||||
} | |||||
else if (ticks == 2) { | |||||
if (Tokenizer_parse_italics(self)) | |||||
return NULL; | |||||
} | |||||
else if (ticks == 3) { | |||||
switch (Tokenizer_parse_bold(self)) { | |||||
case 1: | |||||
return Tokenizer_pop(self); | |||||
case -1: | |||||
return NULL; | |||||
} | |||||
} | |||||
else { | |||||
if (Tokenizer_parse_italics_and_bold(self)) | |||||
return NULL; | |||||
} | |||||
self->head--; | |||||
return Py_None; | |||||
} | |||||
/* | |||||
Handle a list marker at the head (#, *, ;, :). | |||||
*/ | |||||
static int Tokenizer_handle_list_marker(Tokenizer* self) | |||||
{ | |||||
PyObject *markup = Tokenizer_read(self, 0), *kwargs, *token; | |||||
Py_UNICODE code = *PyUnicode_AS_UNICODE(markup); | |||||
if (code == *";") | |||||
self->topstack->context |= LC_DLTERM; | |||||
kwargs = PyDict_New(); | |||||
if (!kwargs) | |||||
return -1; | |||||
PyDict_SetItemString(kwargs, "wiki_markup", markup); | |||||
token = PyObject_Call(TagOpenOpen, NOARGS, kwargs); | |||||
if (!token) { | |||||
Py_DECREF(kwargs); | |||||
return -1; | |||||
} | |||||
Py_DECREF(kwargs); | |||||
if (Tokenizer_emit(self, token)) { | |||||
Py_DECREF(token); | |||||
return -1; | |||||
} | |||||
Py_DECREF(token); | |||||
if (Tokenizer_emit_text(self, GET_HTML_TAG(code))) | |||||
return -1; | |||||
token = PyObject_CallObject(TagCloseSelfclose, NULL); | |||||
if (!token) | |||||
return -1; | |||||
if (Tokenizer_emit(self, token)) { | |||||
Py_DECREF(token); | |||||
return -1; | |||||
} | |||||
Py_DECREF(token); | |||||
return 0; | |||||
} | |||||
/* | |||||
Handle a wiki-style list (#, *, ;, :). | |||||
*/ | |||||
static int Tokenizer_handle_list(Tokenizer* self) | |||||
{ | |||||
Py_UNICODE marker = Tokenizer_READ(self, 1); | |||||
if (Tokenizer_handle_list_marker(self)) | |||||
return -1; | |||||
while (marker == *"#" || marker == *"*" || marker == *";" || | |||||
marker == *":") { | |||||
self->head++; | |||||
if (Tokenizer_handle_list_marker(self)) | |||||
return -1; | |||||
marker = Tokenizer_READ(self, 1); | |||||
} | |||||
return 0; | |||||
} | |||||
/* | |||||
Handle a wiki-style horizontal rule (----) in the string. | |||||
*/ | |||||
static int Tokenizer_handle_hr(Tokenizer* self) | |||||
{ | |||||
PyObject *markup, *kwargs, *token; | |||||
Textbuffer *buffer = Textbuffer_new(); | |||||
int i; | |||||
if (!buffer) | |||||
return -1; | |||||
self->head += 3; | |||||
for (i = 0; i < 4; i++) { | |||||
if (Textbuffer_write(&buffer, *"-")) | |||||
return -1; | |||||
} | |||||
while (Tokenizer_READ(self, 1) == *"-") { | |||||
if (Textbuffer_write(&buffer, *"-")) | |||||
return -1; | |||||
self->head++; | |||||
} | |||||
markup = Textbuffer_render(buffer); | |||||
if (!markup) | |||||
return -1; | |||||
Textbuffer_dealloc(buffer); | |||||
kwargs = PyDict_New(); | |||||
if (!kwargs) | |||||
return -1; | |||||
PyDict_SetItemString(kwargs, "wiki_markup", markup); | |||||
Py_DECREF(markup); | |||||
token = PyObject_Call(TagOpenOpen, NOARGS, kwargs); | |||||
if (!token) { | |||||
Py_DECREF(kwargs); | |||||
return -1; | |||||
} | |||||
Py_DECREF(kwargs); | |||||
if (Tokenizer_emit(self, token)) { | |||||
Py_DECREF(token); | |||||
return -1; | |||||
} | |||||
Py_DECREF(token); | |||||
if (Tokenizer_emit_text(self, "hr")) | |||||
return -1; | |||||
token = PyObject_CallObject(TagCloseSelfclose, NULL); | |||||
if (!token) | |||||
return -1; | |||||
if (Tokenizer_emit(self, token)) { | |||||
Py_DECREF(token); | |||||
return -1; | |||||
} | |||||
Py_DECREF(token); | |||||
return 0; | |||||
} | |||||
/* | |||||
Handle the term in a description list ('foo' in ';foo:bar'). | |||||
*/ | |||||
static int Tokenizer_handle_dl_term(Tokenizer* self) | |||||
{ | |||||
self->topstack->context ^= LC_DLTERM; | |||||
if (Tokenizer_READ(self, 0) == *":") | |||||
return Tokenizer_handle_list_marker(self); | |||||
return Tokenizer_emit_char(self, *"\n"); | |||||
} | |||||
/* | |||||
Handle the end of the stream of wikitext. | Handle the end of the stream of wikitext. | ||||
*/ | */ | ||||
static PyObject* Tokenizer_handle_end(Tokenizer* self, int context) | static PyObject* Tokenizer_handle_end(Tokenizer* self, int context) | ||||
{ | { | ||||
static int fail_contexts = (LC_TEMPLATE | LC_ARGUMENT | LC_WIKILINK | | static int fail_contexts = (LC_TEMPLATE | LC_ARGUMENT | LC_WIKILINK | | ||||
LC_HEADING | LC_COMMENT | LC_TAG); | |||||
LC_HEADING | LC_COMMENT | LC_TAG | LC_STYLE); | |||||
static int double_fail = (LC_TEMPLATE_PARAM_KEY | LC_TAG_CLOSE); | static int double_fail = (LC_TEMPLATE_PARAM_KEY | LC_TAG_CLOSE); | ||||
PyObject *token, *text, *trash; | PyObject *token, *text, *trash; | ||||
int single; | int single; | ||||
@@ -1943,7 +2324,7 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
static int double_unsafe = (LC_TEMPLATE_PARAM_KEY | LC_TAG_CLOSE); | static int double_unsafe = (LC_TEMPLATE_PARAM_KEY | LC_TAG_CLOSE); | ||||
int this_context, is_marker, i; | int this_context, is_marker, i; | ||||
Py_UNICODE this, next, next_next, last; | Py_UNICODE this, next, next_next, last; | ||||
PyObject* trash; | |||||
PyObject* temp; | |||||
if (push) { | if (push) { | ||||
if (Tokenizer_push(self, context)) | if (Tokenizer_push(self, context)) | ||||
@@ -1955,8 +2336,8 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
if (this_context & unsafe_contexts) { | if (this_context & unsafe_contexts) { | ||||
if (Tokenizer_verify_safe(self, this_context, this) < 0) { | if (Tokenizer_verify_safe(self, this_context, this) < 0) { | ||||
if (this_context & double_unsafe) { | if (this_context & double_unsafe) { | ||||
trash = Tokenizer_pop(self); | |||||
Py_XDECREF(trash); | |||||
temp = Tokenizer_pop(self); | |||||
Py_XDECREF(temp); | |||||
} | } | ||||
return Tokenizer_fail_route(self); | return Tokenizer_fail_route(self); | ||||
} | } | ||||
@@ -1969,7 +2350,7 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
} | } | ||||
} | } | ||||
if (!is_marker) { | if (!is_marker) { | ||||
if (Tokenizer_emit_text(self, this)) | |||||
if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
self->head++; | self->head++; | ||||
continue; | continue; | ||||
@@ -1977,12 +2358,13 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
if (this == *"") | if (this == *"") | ||||
return Tokenizer_handle_end(self, this_context); | return Tokenizer_handle_end(self, this_context); | ||||
next = Tokenizer_READ(self, 1); | next = Tokenizer_READ(self, 1); | ||||
last = Tokenizer_READ_BACKWARDS(self, 1); | |||||
if (this_context & LC_COMMENT) { | if (this_context & LC_COMMENT) { | ||||
if (this == next && next == *"-") { | if (this == next && next == *"-") { | ||||
if (Tokenizer_READ(self, 2) == *">") | if (Tokenizer_READ(self, 2) == *">") | ||||
return Tokenizer_pop(self); | return Tokenizer_pop(self); | ||||
} | } | ||||
if (Tokenizer_emit_text(self, this)) | |||||
if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (this == next && next == *"{") { | else if (this == next && next == *"{") { | ||||
@@ -1990,7 +2372,7 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
if (Tokenizer_parse_template_or_argument(self)) | if (Tokenizer_parse_template_or_argument(self)) | ||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (Tokenizer_emit_text(self, this)) | |||||
else if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (this == *"|" && this_context & LC_TEMPLATE) { | else if (this == *"|" && this_context & LC_TEMPLATE) { | ||||
@@ -2011,7 +2393,7 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
if (Tokenizer_READ(self, 2) == *"}") { | if (Tokenizer_READ(self, 2) == *"}") { | ||||
return Tokenizer_handle_argument_end(self); | return Tokenizer_handle_argument_end(self); | ||||
} | } | ||||
if (Tokenizer_emit_text(self, this)) | |||||
if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (this == next && next == *"[") { | else if (this == next && next == *"[") { | ||||
@@ -2020,7 +2402,7 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
if (Tokenizer_parse_wikilink(self)) | if (Tokenizer_parse_wikilink(self)) | ||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (Tokenizer_emit_text(self, this)) | |||||
else if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (this == *"|" && this_context & LC_WIKILINK_TITLE) { | else if (this == *"|" && this_context & LC_WIKILINK_TITLE) { | ||||
@@ -2030,12 +2412,11 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
else if (this == next && next == *"]" && this_context & LC_WIKILINK) | else if (this == next && next == *"]" && this_context & LC_WIKILINK) | ||||
return Tokenizer_handle_wikilink_end(self); | return Tokenizer_handle_wikilink_end(self); | ||||
else if (this == *"=" && !(self->global & GL_HEADING)) { | else if (this == *"=" && !(self->global & GL_HEADING)) { | ||||
last = Tokenizer_READ_BACKWARDS(self, 1); | |||||
if (last == *"\n" || last == *"") { | if (last == *"\n" || last == *"") { | ||||
if (Tokenizer_parse_heading(self)) | if (Tokenizer_parse_heading(self)) | ||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (Tokenizer_emit_text(self, this)) | |||||
else if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (this == *"=" && this_context & LC_HEADING) | else if (this == *"=" && this_context & LC_HEADING) | ||||
@@ -2052,7 +2433,7 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
if (Tokenizer_parse_comment(self)) | if (Tokenizer_parse_comment(self)) | ||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (Tokenizer_emit_text(self, this)) | |||||
else if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (this == *"<" && next == *"/" && | else if (this == *"<" && next == *"/" && | ||||
@@ -2072,12 +2453,35 @@ static PyObject* Tokenizer_parse(Tokenizer* self, int context, int push) | |||||
if (Tokenizer_parse_tag(self)) | if (Tokenizer_parse_tag(self)) | ||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (Tokenizer_emit_text(self, this)) | |||||
else if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
} | } | ||||
else if (this == *">" && this_context & LC_TAG_CLOSE) | else if (this == *">" && this_context & LC_TAG_CLOSE) | ||||
return Tokenizer_handle_tag_close_close(self); | return Tokenizer_handle_tag_close_close(self); | ||||
else if (Tokenizer_emit_text(self, this)) | |||||
else if (this == next && next == *"'") { | |||||
temp = Tokenizer_parse_style(self); | |||||
if (temp != Py_None) | |||||
return temp; | |||||
} | |||||
else if (last == *"\n" || last == *"") { | |||||
if (this == *"#" || this == *"*" || this == *";" || this == *":") { | |||||
if (Tokenizer_handle_list(self)) | |||||
return NULL; | |||||
} | |||||
else if (this == *"-" && this == next && | |||||
this == Tokenizer_READ(self, 2) && | |||||
this == Tokenizer_READ(self, 3)) { | |||||
if (Tokenizer_handle_hr(self)) | |||||
return NULL; | |||||
} | |||||
else if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | |||||
} | |||||
else if ((this == *"\n" || this == *":") && this_context & LC_DLTERM) { | |||||
if (Tokenizer_handle_dl_term(self)) | |||||
return NULL; | |||||
} | |||||
else if (Tokenizer_emit_char(self, this)) | |||||
return NULL; | return NULL; | ||||
self->head++; | self->head++; | ||||
} | } | ||||
@@ -41,20 +41,21 @@ SOFTWARE. | |||||
#define ALPHANUM "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | #define ALPHANUM "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||||
static const char* MARKERS[] = { | static const char* MARKERS[] = { | ||||
"{", "}", "[", "]", "<", ">", "|", "=", "&", "#", "*", ";", ":", "/", "-", | |||||
"\n", ""}; | |||||
"{", "}", "[", "]", "<", ">", "|", "=", "&", "'", "#", "*", ";", ":", "/", | |||||
"-", "\n", ""}; | |||||
#define NUM_MARKERS 17 | |||||
#define NUM_MARKERS 18 | |||||
#define TEXTBUFFER_BLOCKSIZE 1024 | #define TEXTBUFFER_BLOCKSIZE 1024 | ||||
#define MAX_DEPTH 40 | #define MAX_DEPTH 40 | ||||
#define MAX_CYCLES 100000 | #define MAX_CYCLES 100000 | ||||
#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; | ||||
@@ -102,42 +103,50 @@ static PyObject* TagCloseClose; | |||||
/* Local contexts: */ | /* Local contexts: */ | ||||
#define LC_TEMPLATE 0x000007 | |||||
#define LC_TEMPLATE_NAME 0x000001 | |||||
#define LC_TEMPLATE_PARAM_KEY 0x000002 | |||||
#define LC_TEMPLATE_PARAM_VALUE 0x000004 | |||||
#define LC_ARGUMENT 0x000018 | |||||
#define LC_ARGUMENT_NAME 0x000008 | |||||
#define LC_ARGUMENT_DEFAULT 0x000010 | |||||
#define LC_WIKILINK 0x000060 | |||||
#define LC_WIKILINK_TITLE 0x000020 | |||||
#define LC_WIKILINK_TEXT 0x000040 | |||||
#define LC_HEADING 0x001F80 | |||||
#define LC_HEADING_LEVEL_1 0x000080 | |||||
#define LC_HEADING_LEVEL_2 0x000100 | |||||
#define LC_HEADING_LEVEL_3 0x000200 | |||||
#define LC_HEADING_LEVEL_4 0x000400 | |||||
#define LC_HEADING_LEVEL_5 0x000800 | |||||
#define LC_HEADING_LEVEL_6 0x001000 | |||||
#define LC_COMMENT 0x002000 | |||||
#define LC_TAG 0x03C000 | |||||
#define LC_TAG_OPEN 0x004000 | |||||
#define LC_TAG_ATTR 0x008000 | |||||
#define LC_TAG_BODY 0x010000 | |||||
#define LC_TAG_CLOSE 0x020000 | |||||
#define LC_SAFETY_CHECK 0xFC0000 | |||||
#define LC_HAS_TEXT 0x040000 | |||||
#define LC_FAIL_ON_TEXT 0x080000 | |||||
#define LC_FAIL_NEXT 0x100000 | |||||
#define LC_FAIL_ON_LBRACE 0x200000 | |||||
#define LC_FAIL_ON_RBRACE 0x400000 | |||||
#define LC_FAIL_ON_EQUALS 0x800000 | |||||
#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_HEADING 0x00001F80 | |||||
#define LC_HEADING_LEVEL_1 0x00000080 | |||||
#define LC_HEADING_LEVEL_2 0x00000100 | |||||
#define LC_HEADING_LEVEL_3 0x00000200 | |||||
#define LC_HEADING_LEVEL_4 0x00000400 | |||||
#define LC_HEADING_LEVEL_5 0x00000800 | |||||
#define LC_HEADING_LEVEL_6 0x00001000 | |||||
#define LC_COMMENT 0x00002000 | |||||
#define LC_TAG 0x0003C000 | |||||
#define LC_TAG_OPEN 0x00004000 | |||||
#define LC_TAG_ATTR 0x00008000 | |||||
#define LC_TAG_BODY 0x00010000 | |||||
#define LC_TAG_CLOSE 0x00020000 | |||||
#define LC_STYLE 0x003C0000 | |||||
#define LC_STYLE_ITALICS 0x00040000 | |||||
#define LC_STYLE_BOLD 0x00080000 | |||||
#define LC_STYLE_PASS_AGAIN 0x00100000 | |||||
#define LC_STYLE_SECOND_PASS 0x00200000 | |||||
#define LC_DLTERM 0x00400000 | |||||
#define LC_SAFETY_CHECK 0x1F800000 | |||||
#define LC_HAS_TEXT 0x00800000 | |||||
#define LC_FAIL_ON_TEXT 0x01000000 | |||||
#define LC_FAIL_NEXT 0x02000000 | |||||
#define LC_FAIL_ON_LBRACE 0x04000000 | |||||
#define LC_FAIL_ON_RBRACE 0x08000000 | |||||
#define LC_FAIL_ON_EQUALS 0x10000000 | |||||
/* Global contexts: */ | /* Global contexts: */ | ||||
@@ -211,6 +220,7 @@ typedef struct { | |||||
/* Macros for accessing HTML tag definitions: */ | /* Macros for accessing HTML tag definitions: */ | ||||
#define GET_HTML_TAG(markup) (markup == *":" ? "dd" : markup == *";" ? "dt" : "li") | |||||
#define IS_PARSABLE(tag) (call_tag_def_func("is_parsable", tag)) | #define IS_PARSABLE(tag) (call_tag_def_func("is_parsable", tag)) | ||||
#define IS_SINGLE(tag) (call_tag_def_func("is_single", tag)) | #define IS_SINGLE(tag) (call_tag_def_func("is_single", tag)) | ||||
#define IS_SINGLE_ONLY(tag) (call_tag_def_func("is_single_only", tag)) | #define IS_SINGLE_ONLY(tag) (call_tag_def_func("is_single_only", tag)) | ||||
@@ -26,13 +26,15 @@ import re | |||||
from . import contexts, tokens | from . import contexts, tokens | ||||
from ..compat import htmlentities | from ..compat import htmlentities | ||||
from ..tag_defs import is_parsable, is_single, is_single_only | |||||
from ..tag_defs import get_html_tag, is_parsable, is_single, is_single_only | |||||
__all__ = ["Tokenizer"] | __all__ = ["Tokenizer"] | ||||
class BadRoute(Exception): | class BadRoute(Exception): | ||||
"""Raised internally when the current tokenization route is invalid.""" | """Raised internally when the current tokenization route is invalid.""" | ||||
pass | |||||
def __init__(self, context=0): | |||||
self.context = context | |||||
class _TagOpenData(object): | class _TagOpenData(object): | ||||
@@ -57,11 +59,11 @@ class Tokenizer(object): | |||||
USES_C = False | USES_C = False | ||||
START = object() | START = object() | ||||
END = object() | END = object() | ||||
MARKERS = ["{", "}", "[", "]", "<", ">", "|", "=", "&", "#", "*", ";", ":", | |||||
"/", "-", "\n", END] | |||||
MARKERS = ["{", "}", "[", "]", "<", ">", "|", "=", "&", "'", "#", "*", ";", | |||||
":", "/", "-", "\n", END] | |||||
MAX_DEPTH = 40 | MAX_DEPTH = 40 | ||||
MAX_CYCLES = 100000 | MAX_CYCLES = 100000 | ||||
regex = re.compile(r"([{}\[\]<>|=&#*;:/\\\"\-!\n])", flags=re.IGNORECASE) | |||||
regex = re.compile(r"([{}\[\]<>|=&'#*;:/\\\"\-!\n])", flags=re.IGNORECASE) | |||||
tag_splitter = re.compile(r"([\s\"\\]+)") | tag_splitter = re.compile(r"([\s\"\\]+)") | ||||
def __init__(self): | def __init__(self): | ||||
@@ -132,8 +134,9 @@ class Tokenizer(object): | |||||
Discards the current stack/context/textbuffer and raises | Discards the current stack/context/textbuffer and raises | ||||
:py:exc:`~.BadRoute`. | :py:exc:`~.BadRoute`. | ||||
""" | """ | ||||
context = self._context | |||||
self._pop() | self._pop() | ||||
raise BadRoute() | |||||
raise BadRoute(context) | |||||
def _emit(self, token): | def _emit(self, token): | ||||
"""Write a token to the end of the current token stack.""" | """Write a token to the end of the current token stack.""" | ||||
@@ -629,10 +632,164 @@ class Tokenizer(object): | |||||
else: | else: | ||||
self._emit_all(tag) | self._emit_all(tag) | ||||
def _emit_style_tag(self, tag, markup, body): | |||||
"""Write the body of a tag and the tokens that should surround it.""" | |||||
self._emit(tokens.TagOpenOpen(wiki_markup=markup)) | |||||
self._emit_text(tag) | |||||
self._emit(tokens.TagCloseOpen()) | |||||
self._emit_all(body) | |||||
self._emit(tokens.TagOpenClose()) | |||||
self._emit_text(tag) | |||||
self._emit(tokens.TagCloseClose()) | |||||
def _parse_italics(self): | |||||
"""Parse wiki-style italics.""" | |||||
reset = self._head | |||||
try: | |||||
stack = self._parse(contexts.STYLE_ITALICS) | |||||
except BadRoute as route: | |||||
self._head = reset | |||||
if route.context & contexts.STYLE_PASS_AGAIN: | |||||
stack = self._parse(route.context | contexts.STYLE_SECOND_PASS) | |||||
else: | |||||
return self._emit_text("''") | |||||
self._emit_style_tag("i", "''", stack) | |||||
def _parse_bold(self): | |||||
"""Parse wiki-style bold.""" | |||||
reset = self._head | |||||
try: | |||||
stack = self._parse(contexts.STYLE_BOLD) | |||||
except BadRoute: | |||||
self._head = reset | |||||
if self._context & contexts.STYLE_SECOND_PASS: | |||||
self._emit_text("'") | |||||
return True | |||||
elif self._context & contexts.STYLE_ITALICS: | |||||
self._context |= contexts.STYLE_PASS_AGAIN | |||||
self._emit_text("'''") | |||||
else: | |||||
self._emit_text("'") | |||||
self._parse_italics() | |||||
else: | |||||
self._emit_style_tag("b", "'''", stack) | |||||
def _parse_italics_and_bold(self): | |||||
"""Parse wiki-style italics and bold together (i.e., five ticks).""" | |||||
reset = self._head | |||||
try: | |||||
stack = self._parse(contexts.STYLE_BOLD) | |||||
except BadRoute: | |||||
self._head = reset | |||||
try: | |||||
stack = self._parse(contexts.STYLE_ITALICS) | |||||
except BadRoute: | |||||
self._head = reset | |||||
self._emit_text("'''''") | |||||
else: | |||||
reset = self._head | |||||
try: | |||||
stack2 = self._parse(contexts.STYLE_BOLD) | |||||
except BadRoute: | |||||
self._head = reset | |||||
self._emit_text("'''") | |||||
self._emit_style_tag("i", "''", stack) | |||||
else: | |||||
self._push() | |||||
self._emit_style_tag("i", "''", stack) | |||||
self._emit_all(stack2) | |||||
self._emit_style_tag("b", "'''", self._pop()) | |||||
else: | |||||
reset = self._head | |||||
try: | |||||
stack2 = self._parse(contexts.STYLE_ITALICS) | |||||
except BadRoute: | |||||
self._head = reset | |||||
self._emit_text("''") | |||||
self._emit_style_tag("b", "'''", stack) | |||||
else: | |||||
self._push() | |||||
self._emit_style_tag("b", "'''", stack) | |||||
self._emit_all(stack2) | |||||
self._emit_style_tag("i", "''", self._pop()) | |||||
def _parse_style(self): | |||||
"""Parse wiki-style formatting (``''``/``'''`` for italics/bold).""" | |||||
self._head += 2 | |||||
ticks = 2 | |||||
while self._read() == "'": | |||||
self._head += 1 | |||||
ticks += 1 | |||||
italics = self._context & contexts.STYLE_ITALICS | |||||
bold = self._context & contexts.STYLE_BOLD | |||||
if ticks > 5: | |||||
self._emit_text("'" * (ticks - 5)) | |||||
ticks = 5 | |||||
elif ticks == 4: | |||||
self._emit_text("'") | |||||
ticks = 3 | |||||
if (italics and ticks in (2, 5)) or (bold and ticks in (3, 5)): | |||||
if ticks == 5: | |||||
self._head -= 3 if italics else 2 | |||||
return self._pop() | |||||
elif not self._can_recurse(): | |||||
if ticks == 3: | |||||
if self._context & contexts.STYLE_SECOND_PASS: | |||||
self._emit_text("'") | |||||
return self._pop() | |||||
self._context |= contexts.STYLE_PASS_AGAIN | |||||
self._emit_text("'" * ticks) | |||||
elif ticks == 2: | |||||
self._parse_italics() | |||||
elif ticks == 3: | |||||
if self._parse_bold(): | |||||
return self._pop() | |||||
elif ticks == 5: | |||||
self._parse_italics_and_bold() | |||||
self._head -= 1 | |||||
def _handle_list_marker(self): | |||||
"""Handle a list marker at the head (``#``, ``*``, ``;``, ``:``).""" | |||||
markup = self._read() | |||||
if markup == ";": | |||||
self._context |= contexts.DL_TERM | |||||
self._emit(tokens.TagOpenOpen(wiki_markup=markup)) | |||||
self._emit_text(get_html_tag(markup)) | |||||
self._emit(tokens.TagCloseSelfclose()) | |||||
def _handle_list(self): | |||||
"""Handle a wiki-style list (``#``, ``*``, ``;``, ``:``).""" | |||||
self._handle_list_marker() | |||||
while self._read(1) in ("#", "*", ";", ":"): | |||||
self._head += 1 | |||||
self._handle_list_marker() | |||||
def _handle_hr(self): | |||||
"""Handle a wiki-style horizontal rule (``----``) in the string.""" | |||||
length = 4 | |||||
self._head += 3 | |||||
while self._read(1) == "-": | |||||
length += 1 | |||||
self._head += 1 | |||||
self._emit(tokens.TagOpenOpen(wiki_markup="-" * length)) | |||||
self._emit_text("hr") | |||||
self._emit(tokens.TagCloseSelfclose()) | |||||
def _handle_dl_term(self): | |||||
"""Handle the term in a description list (``foo`` in ``;foo:bar``).""" | |||||
self._context ^= contexts.DL_TERM | |||||
if self._read() == ":": | |||||
self._handle_list_marker() | |||||
else: | |||||
self._emit_text("\n") | |||||
def _handle_end(self): | def _handle_end(self): | ||||
"""Handle the end of the stream of wikitext.""" | """Handle the end of the stream of wikitext.""" | ||||
fail = (contexts.TEMPLATE | contexts.ARGUMENT | contexts.WIKILINK | | fail = (contexts.TEMPLATE | contexts.ARGUMENT | contexts.WIKILINK | | ||||
contexts.HEADING | contexts.COMMENT | contexts.TAG) | |||||
contexts.HEADING | contexts.COMMENT | contexts.TAG | | |||||
contexts.STYLE) | |||||
double_fail = (contexts.TEMPLATE_PARAM_KEY | contexts.TAG_CLOSE) | double_fail = (contexts.TEMPLATE_PARAM_KEY | contexts.TAG_CLOSE) | ||||
if self._context & fail: | if self._context & fail: | ||||
if self._context & contexts.TAG_BODY: | if self._context & contexts.TAG_BODY: | ||||
@@ -782,6 +939,19 @@ class Tokenizer(object): | |||||
self._emit_text("<") | self._emit_text("<") | ||||
elif this == ">" and self._context & contexts.TAG_CLOSE: | elif this == ">" and self._context & contexts.TAG_CLOSE: | ||||
return self._handle_tag_close_close() | return self._handle_tag_close_close() | ||||
elif this == next == "'": | |||||
result = self._parse_style() | |||||
if result is not None: | |||||
return result | |||||
elif self._read(-1) in ("\n", self.START): | |||||
if this in ("#", "*", ";", ":"): | |||||
self._handle_list() | |||||
elif this == next == self._read(2) == self._read(3) == "-": | |||||
self._handle_hr() | |||||
else: | |||||
self._emit_text(this) | |||||
elif this in ("\n", ":") and self._context & contexts.DL_TERM: | |||||
self._handle_dl_term() | |||||
else: | else: | ||||
self._emit_text(this) | self._emit_text(this) | ||||
self._head += 1 | self._head += 1 | ||||
@@ -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 | ||||
@@ -63,10 +63,6 @@ class Token(object): | |||||
def __delattr__(self, key): | def __delattr__(self, key): | ||||
del self._kwargs[key] | del self._kwargs[key] | ||||
def get(self, key, default=None): | |||||
"""Same as :py:meth:`__getattr__`, but has a *default* if missing.""" | |||||
return self._kwargs.get(key, default) | |||||
def make(name): | def make(name): | ||||
"""Create a new Token class using ``type()`` and add it to ``__all__``.""" | """Create a new Token class using ``type()`` and add it to ``__all__``.""" | ||||
@@ -24,7 +24,7 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
__all__ = ["get_wikicode", "is_parsable", "is_visible", "is_single", | |||||
__all__ = ["get_html_tag", "is_parsable", "is_visible", "is_single", | |||||
"is_single_only"] | "is_single_only"] | ||||
PARSER_BLACKLIST = [ | PARSER_BLACKLIST = [ | ||||
@@ -44,20 +44,16 @@ INVISIBLE_TAGS = [ | |||||
SINGLE_ONLY = ["br", "hr", "meta", "link", "img"] | SINGLE_ONLY = ["br", "hr", "meta", "link", "img"] | ||||
SINGLE = SINGLE_ONLY + ["li", "dt", "dd"] | SINGLE = SINGLE_ONLY + ["li", "dt", "dd"] | ||||
WIKICODE = { | |||||
"i": {"open": "''", "close": "''"}, | |||||
"b": {"open": "'''", "close": "'''"}, | |||||
"ul": {"open": "*"}, | |||||
"ol": {"open": "#"}, | |||||
"dt": {"open": ";"}, | |||||
"dd": {"open": ":"}, | |||||
"hr": {"open": "----"}, | |||||
MARKUP_TO_HTML = { | |||||
"#": "li", | |||||
"*": "li", | |||||
";": "dt", | |||||
":": "dd" | |||||
} | } | ||||
def get_wikicode(tag): | |||||
"""Return the appropriate wikicode before and after the given *tag*.""" | |||||
data = WIKICODE[tag.lower()] | |||||
return (data.get("open"), data.get("close")) | |||||
def get_html_tag(markup): | |||||
"""Return the HTML tag associated with the given wiki-markup.""" | |||||
return MARKUP_TO_HTML[markup] | |||||
def is_parsable(tag): | def is_parsable(tag): | ||||
"""Return if the given *tag*'s contents should be passed to the parser.""" | """Return if the given *tag*'s contents should be passed to the parser.""" | ||||
@@ -106,7 +106,7 @@ class TreeEqualityTestCase(TestCase): | |||||
self.assertEqual(exp_attr.pad_first, act_attr.pad_first) | 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_before_eq, act_attr.pad_before_eq) | ||||
self.assertEqual(exp_attr.pad_after_eq, act_attr.pad_after_eq) | self.assertEqual(exp_attr.pad_after_eq, act_attr.pad_after_eq) | ||||
self.assertIs(expected.showtag, actual.showtag) | |||||
self.assertIs(expected.wiki_markup, actual.wiki_markup) | |||||
self.assertIs(expected.self_closing, actual.self_closing) | self.assertIs(expected.self_closing, actual.self_closing) | ||||
self.assertIs(expected.invalid, actual.invalid) | self.assertIs(expected.invalid, actual.invalid) | ||||
self.assertIs(expected.implicit, actual.implicit) | self.assertIs(expected.implicit, actual.implicit) | ||||
@@ -303,6 +303,20 @@ class TestBuilder(TreeEqualityTestCase): | |||||
Text(" "), Wikilink(wraptext("q")), Text(" "), | Text(" "), Wikilink(wraptext("q")), Text(" "), | ||||
Template(wraptext("r"))]), True, " \n ", " ", | 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: | for test, valid in tests: | ||||
self.assertWikicodeEqual(valid, self.builder.build(test)) | self.assertWikicodeEqual(valid, self.builder.build(test)) | ||||
@@ -50,8 +50,8 @@ class TestTag(TreeEqualityTestCase): | |||||
implicit=True) | implicit=True) | ||||
node7 = Tag(wraptext("br"), self_closing=True, invalid=True, | node7 = Tag(wraptext("br"), self_closing=True, invalid=True, | ||||
padding=" ") | padding=" ") | ||||
node8 = Tag(wraptext("hr"), showtag=False, self_closing=True) | |||||
node9 = Tag(wraptext("i"), wraptext("italics!"), showtag=False) | |||||
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("<ref></ref>", str(node1)) | ||||
self.assertEqual('<span style="color: red;">foo</span>', str(node2)) | self.assertEqual('<span style="color: red;">foo</span>', str(node2)) | ||||
@@ -72,7 +72,7 @@ class TestTag(TreeEqualityTestCase): | |||||
# <ref>foobar</ref> | # <ref>foobar</ref> | ||||
node1 = Tag(wrap([node1n1]), wrap([node1n2])) | node1 = Tag(wrap([node1n1]), wrap([node1n2])) | ||||
# '''bold text''' | # '''bold text''' | ||||
node2 = Tag(wraptext("i"), wrap([node2n1]), showtag=False) | |||||
node2 = Tag(wraptext("b"), wrap([node2n1]), wiki_markup="'''") | |||||
# <img id="foo" class="bar" /> | # <img id="foo" class="bar" /> | ||||
node3 = Tag(wrap([node3n1]), | node3 = Tag(wrap([node3n1]), | ||||
attrs=[Attribute(wrap([node3n2]), wrap([node3n3])), | attrs=[Attribute(wrap([node3n2]), wrap([node3n3])), | ||||
@@ -158,15 +158,15 @@ class TestTag(TreeEqualityTestCase): | |||||
self.assertEqual([], node1.attributes) | self.assertEqual([], node1.attributes) | ||||
self.assertIs(attrs, node2.attributes) | self.assertIs(attrs, node2.attributes) | ||||
def test_showtag(self): | |||||
"""test getter/setter for the showtag attribute""" | |||||
def test_wiki_markup(self): | |||||
"""test getter/setter for the wiki_markup attribute""" | |||||
node = Tag(wraptext("i"), wraptext("italic text")) | node = Tag(wraptext("i"), wraptext("italic text")) | ||||
self.assertTrue(node.showtag) | |||||
node.showtag = False | |||||
self.assertFalse(node.showtag) | |||||
self.assertIs(None, node.wiki_markup) | |||||
node.wiki_markup = "''" | |||||
self.assertEqual("''", node.wiki_markup) | |||||
self.assertEqual("''italic text''", node) | self.assertEqual("''italic text''", node) | ||||
node.showtag = 1 | |||||
self.assertTrue(node.showtag) | |||||
node.wiki_markup = False | |||||
self.assertFalse(node.wiki_markup) | |||||
self.assertEqual("<i>italic text</i>", node) | self.assertEqual("<i>italic text</i>", node) | ||||
def test_self_closing(self): | def test_self_closing(self): | ||||
@@ -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): | ||||
@@ -0,0 +1,516 @@ | |||||
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--")] |