@@ -10,6 +10,10 @@ v0.4 (unreleased): | |||||
option, RECURSE_OTHERS, which recurses over all children except instances of | option, RECURSE_OTHERS, which recurses over all children except instances of | ||||
'forcetype' (for example, `code.filter_templates(code.RECURSE_OTHERS)` | 'forcetype' (for example, `code.filter_templates(code.RECURSE_OTHERS)` | ||||
returns all un-nested templates). | returns all un-nested templates). | ||||
- The parser now understands HTML tag attributes quoted with single quotes. | |||||
When setting a tag attribute's value, quotes will be added if necessary. As | |||||
part of this, Attribute's 'quoted' attribute has been changed to 'quotes', | |||||
and is now either a string or None. | |||||
- Calling Template.remove() with a Parameter object that is not part of the | - Calling Template.remove() with a Parameter object that is not part of the | ||||
template now raises ValueError instead of doing nothing. | template now raises ValueError instead of doing nothing. | ||||
- Parameters with non-integer keys can no longer be created with | - Parameters with non-integer keys can no longer be created with | ||||
@@ -18,6 +18,11 @@ Unreleased | |||||
which recurses over all children except instances of *forcetype* (for | which recurses over all children except instances of *forcetype* (for | ||||
example, ``code.filter_templates(code.RECURSE_OTHERS)`` returns all un-nested | example, ``code.filter_templates(code.RECURSE_OTHERS)`` returns all un-nested | ||||
templates). | templates). | ||||
- The parser now understands HTML tag attributes quoted with single quotes. | |||||
When setting a tag attribute's value, quotes will be added if necessary. As | |||||
part of this, :py:class:`.Attribute`\ 's :py:attr:`~.Attribute.quoted` | |||||
attribute has been changed to :py:attr:`~.Attribute.quotes`, and is now | |||||
either a string or ``None``. | |||||
- Calling :py:meth:`.Template.remove` with a :py:class:`.Parameter` object that | - Calling :py:meth:`.Template.remove` with a :py:class:`.Parameter` object that | ||||
is not part of the template now raises :py:exc:`ValueError` instead of doing | is not part of the template now raises :py:exc:`ValueError` instead of doing | ||||
nothing. | nothing. | ||||
@@ -36,12 +36,14 @@ class Attribute(StringMixIn): | |||||
whose value is ``"foo"``. | whose value is ``"foo"``. | ||||
""" | """ | ||||
def __init__(self, name, value=None, quoted=True, pad_first=" ", | |||||
def __init__(self, name, value=None, quotes='"', pad_first=" ", | |||||
pad_before_eq="", pad_after_eq=""): | pad_before_eq="", pad_after_eq=""): | ||||
super(Attribute, self).__init__() | super(Attribute, self).__init__() | ||||
if not quotes and self._value_needs_quotes(value): | |||||
raise ValueError("given value {0!r} requires quotes".format(value)) | |||||
self._name = name | self._name = name | ||||
self._value = value | self._value = value | ||||
self._quoted = quoted | |||||
self._quotes = quotes | |||||
self._pad_first = pad_first | self._pad_first = pad_first | ||||
self._pad_before_eq = pad_before_eq | self._pad_before_eq = pad_before_eq | ||||
self._pad_after_eq = pad_after_eq | self._pad_after_eq = pad_after_eq | ||||
@@ -50,11 +52,18 @@ class Attribute(StringMixIn): | |||||
result = self.pad_first + str(self.name) + self.pad_before_eq | result = self.pad_first + str(self.name) + self.pad_before_eq | ||||
if self.value is not None: | if self.value is not None: | ||||
result += "=" + self.pad_after_eq | result += "=" + self.pad_after_eq | ||||
if self.quoted: | |||||
return result + '"' + str(self.value) + '"' | |||||
if self.quotes: | |||||
return result + self.quotes + str(self.value) + self.quotes | |||||
return result + str(self.value) | return result + str(self.value) | ||||
return result | return result | ||||
@staticmethod | |||||
def _value_needs_quotes(val): | |||||
"""Return the preferred quotes for the given value, or None.""" | |||||
if val and any(char.isspace() for char in val): | |||||
return ('"' in val and "'" in val) or ("'" if '"' in val else '"') | |||||
return None | |||||
def _set_padding(self, attr, value): | def _set_padding(self, attr, value): | ||||
"""Setter for the value of a padding attribute.""" | """Setter for the value of a padding attribute.""" | ||||
if not value: | if not value: | ||||
@@ -65,6 +74,14 @@ class Attribute(StringMixIn): | |||||
raise ValueError("padding must be entirely whitespace") | raise ValueError("padding must be entirely whitespace") | ||||
setattr(self, attr, value) | setattr(self, attr, value) | ||||
@staticmethod | |||||
def coerce_quotes(quotes): | |||||
"""Coerce a quote type into an acceptable value, or raise an error.""" | |||||
orig, quotes = quotes, str(quotes) if quotes else None | |||||
if quotes not in [None, '"', "'"]: | |||||
raise ValueError("{0!r} is not a valid quote type".format(orig)) | |||||
return quotes | |||||
@property | @property | ||||
def name(self): | def name(self): | ||||
"""The name of the attribute as a :py:class:`~.Wikicode` object.""" | """The name of the attribute as a :py:class:`~.Wikicode` object.""" | ||||
@@ -76,9 +93,9 @@ class Attribute(StringMixIn): | |||||
return self._value | return self._value | ||||
@property | @property | ||||
def quoted(self): | |||||
"""Whether the attribute's value is quoted with double quotes.""" | |||||
return self._quoted | |||||
def quotes(self): | |||||
"""How to enclose the attribute value. ``"``, ``'``, or ``None``.""" | |||||
return self._quotes | |||||
@property | @property | ||||
def pad_first(self): | def pad_first(self): | ||||
@@ -101,11 +118,21 @@ class Attribute(StringMixIn): | |||||
@value.setter | @value.setter | ||||
def value(self, newval): | def value(self, newval): | ||||
self._value = None if newval is None else parse_anything(newval) | |||||
@quoted.setter | |||||
def quoted(self, value): | |||||
self._quoted = bool(value) | |||||
if newval is None: | |||||
self._value = None | |||||
else: | |||||
code = parse_anything(newval) | |||||
quotes = self._value_needs_quotes(code) | |||||
if quotes in ['"', "'"] or (quotes is True and not self.quotes): | |||||
self._quotes = quotes | |||||
self._value = code | |||||
@quotes.setter | |||||
def quotes(self, value): | |||||
value = self.coerce_quotes(value) | |||||
if not value and self._value_needs_quotes(self.value): | |||||
raise ValueError("attribute value requires quotes") | |||||
self._quotes = value | |||||
@pad_first.setter | @pad_first.setter | ||||
def pad_first(self, value): | def pad_first(self, value): | ||||
@@ -236,21 +236,24 @@ class Tag(Node): | |||||
return attr | return attr | ||||
raise ValueError(name) | raise ValueError(name) | ||||
def add(self, name, value=None, quoted=True, pad_first=" ", | |||||
def add(self, name, value=None, quotes='"', pad_first=" ", | |||||
pad_before_eq="", pad_after_eq=""): | pad_before_eq="", pad_after_eq=""): | ||||
"""Add an attribute with the given *name* and *value*. | """Add an attribute with the given *name* and *value*. | ||||
*name* and *value* can be anything parsable by | *name* and *value* can be anything parsable by | ||||
:py:func:`.utils.parse_anything`; *value* can be omitted if the | :py:func:`.utils.parse_anything`; *value* can be omitted if the | ||||
attribute is valueless. *quoted* is a bool telling whether to wrap the | |||||
*value* in double quotes (this is recommended). *pad_first*, | |||||
*pad_before_eq*, and *pad_after_eq* are whitespace used as padding | |||||
before the name, before the equal sign (or after the name if no value), | |||||
and after the equal sign (ignored if no value), respectively. | |||||
attribute is valueless. If *quotes* is not ``None``, it should be a | |||||
string (either ``"`` or ``'``) that *value* will be wrapped in (this is | |||||
recommended). ``None`` is only legal if *value* contains no spacing. | |||||
*pad_first*, *pad_before_eq*, and *pad_after_eq* are whitespace used as | |||||
padding before the name, before the equal sign (or after the name if no | |||||
value), and after the equal sign (ignored if no value), respectively. | |||||
""" | """ | ||||
if value is not None: | if value is not None: | ||||
value = parse_anything(value) | value = parse_anything(value) | ||||
attr = Attribute(parse_anything(name), value, quoted) | |||||
quotes = Attribute.coerce_quotes(quotes) | |||||
attr = Attribute(parse_anything(name), value, quotes) | |||||
attr.pad_first = pad_first | attr.pad_first = pad_first | ||||
attr.pad_before_eq = pad_before_eq | attr.pad_before_eq = pad_before_eq | ||||
attr.pad_after_eq = pad_after_eq | attr.pad_after_eq = pad_after_eq | ||||
@@ -193,7 +193,7 @@ class Builder(object): | |||||
def _handle_attribute(self, start): | def _handle_attribute(self, start): | ||||
"""Handle a case where a tag attribute is at the head of the tokens.""" | """Handle a case where a tag attribute is at the head of the tokens.""" | ||||
name, quoted = None, False | |||||
name = quotes = None | |||||
self._push() | self._push() | ||||
while self._tokens: | while self._tokens: | ||||
token = self._tokens.pop() | token = self._tokens.pop() | ||||
@@ -201,7 +201,7 @@ class Builder(object): | |||||
name = self._pop() | name = self._pop() | ||||
self._push() | self._push() | ||||
elif isinstance(token, tokens.TagAttrQuote): | elif isinstance(token, tokens.TagAttrQuote): | ||||
quoted = True | |||||
quotes = token.char | |||||
elif isinstance(token, (tokens.TagAttrStart, tokens.TagCloseOpen, | elif isinstance(token, (tokens.TagAttrStart, tokens.TagCloseOpen, | ||||
tokens.TagCloseSelfclose)): | tokens.TagCloseSelfclose)): | ||||
self._tokens.append(token) | self._tokens.append(token) | ||||
@@ -209,7 +209,7 @@ class Builder(object): | |||||
value = self._pop() | value = self._pop() | ||||
else: | else: | ||||
name, value = self._pop(), None | name, value = self._pop(), None | ||||
return Attribute(name, value, quoted, start.pad_first, | |||||
return Attribute(name, value, quotes, start.pad_first, | |||||
start.pad_before_eq, start.pad_after_eq) | start.pad_before_eq, start.pad_after_eq) | ||||
else: | else: | ||||
self._write(self._handle_token(token)) | self._write(self._handle_token(token)) | ||||
@@ -173,7 +173,7 @@ static TagData* TagData_new(void) | |||||
ALLOC_BUFFER(self->pad_first) | ALLOC_BUFFER(self->pad_first) | ||||
ALLOC_BUFFER(self->pad_before_eq) | ALLOC_BUFFER(self->pad_before_eq) | ||||
ALLOC_BUFFER(self->pad_after_eq) | ALLOC_BUFFER(self->pad_after_eq) | ||||
self->reset = 0; | |||||
self->quoter = self->reset = 0; | |||||
return self; | return self; | ||||
} | } | ||||
@@ -1566,10 +1566,18 @@ static int Tokenizer_parse_comment(Tokenizer* self) | |||||
*/ | */ | ||||
static int Tokenizer_push_tag_buffer(Tokenizer* self, TagData* data) | static int Tokenizer_push_tag_buffer(Tokenizer* self, TagData* data) | ||||
{ | { | ||||
PyObject *tokens, *kwargs, *pad_first, *pad_before_eq, *pad_after_eq; | |||||
PyObject *tokens, *kwargs, *tmp, *pad_first, *pad_before_eq, *pad_after_eq; | |||||
if (data->context & TAG_QUOTED) { | if (data->context & TAG_QUOTED) { | ||||
if (Tokenizer_emit_first(self, TagAttrQuote)) | |||||
kwargs = PyDict_New(); | |||||
if (!kwargs) | |||||
return -1; | |||||
tmp = PyUnicode_FromUnicode(&data->quoter, 1); | |||||
if (!tmp) | |||||
return -1; | |||||
PyDict_SetItemString(kwargs, "char", tmp); | |||||
Py_DECREF(tmp); | |||||
if (Tokenizer_emit_first_kwargs(self, TagAttrQuote, kwargs)) | |||||
return -1; | return -1; | ||||
tokens = Tokenizer_pop(self); | tokens = Tokenizer_pop(self); | ||||
if (!tokens) | if (!tokens) | ||||
@@ -1721,16 +1729,17 @@ Tokenizer_handle_tag_data(Tokenizer* self, TagData* data, Py_UNICODE chunk) | |||||
Tokenizer_READ_BACKWARDS(self, 2) != '\\'); | Tokenizer_READ_BACKWARDS(self, 2) != '\\'); | ||||
if (data->context & TAG_NOTE_QUOTE) { | if (data->context & TAG_NOTE_QUOTE) { | ||||
data->context ^= TAG_NOTE_QUOTE; | data->context ^= TAG_NOTE_QUOTE; | ||||
if (chunk == '"' && !escaped) { | |||||
if ((chunk == '"' || chunk == '\'') && !escaped) { | |||||
data->context |= TAG_QUOTED; | data->context |= TAG_QUOTED; | ||||
data->quoter = chunk; | |||||
data->reset = self->head; | |||||
if (Tokenizer_push(self, self->topstack->context)) | if (Tokenizer_push(self, self->topstack->context)) | ||||
return -1; | return -1; | ||||
data->reset = self->head; | |||||
return 0; | return 0; | ||||
} | } | ||||
} | } | ||||
else if (data->context & TAG_QUOTED) { | else if (data->context & TAG_QUOTED) { | ||||
if (chunk == '"' && !escaped) { | |||||
if (chunk == data->quoter && !escaped) { | |||||
data->context |= TAG_NOTE_SPACE; | data->context |= TAG_NOTE_SPACE; | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -206,6 +206,7 @@ typedef struct { | |||||
struct Textbuffer* pad_first; | struct Textbuffer* pad_first; | ||||
struct Textbuffer* pad_before_eq; | struct Textbuffer* pad_before_eq; | ||||
struct Textbuffer* pad_after_eq; | struct Textbuffer* pad_after_eq; | ||||
Py_UNICODE quoter; | |||||
Py_ssize_t reset; | Py_ssize_t reset; | ||||
} TagData; | } TagData; | ||||
@@ -53,6 +53,7 @@ class _TagOpenData(object): | |||||
def __init__(self): | def __init__(self): | ||||
self.context = self.CX_NAME | self.context = self.CX_NAME | ||||
self.padding_buffer = {"first": "", "before_eq": "", "after_eq": ""} | self.padding_buffer = {"first": "", "before_eq": "", "after_eq": ""} | ||||
self.quoter = None | |||||
self.reset = 0 | self.reset = 0 | ||||
@@ -66,7 +67,7 @@ class Tokenizer(object): | |||||
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): | ||||
self._text = None | self._text = None | ||||
@@ -612,7 +613,7 @@ class Tokenizer(object): | |||||
def _push_tag_buffer(self, data): | def _push_tag_buffer(self, data): | ||||
"""Write a pending tag attribute from *data* to the stack.""" | """Write a pending tag attribute from *data* to the stack.""" | ||||
if data.context & data.CX_QUOTED: | if data.context & data.CX_QUOTED: | ||||
self._emit_first(tokens.TagAttrQuote()) | |||||
self._emit_first(tokens.TagAttrQuote(char=data.quoter)) | |||||
self._emit_all(self._pop()) | self._emit_all(self._pop()) | ||||
buf = data.padding_buffer | buf = data.padding_buffer | ||||
self._emit_first(tokens.TagAttrStart(pad_first=buf["first"], | self._emit_first(tokens.TagAttrStart(pad_first=buf["first"], | ||||
@@ -689,13 +690,14 @@ class Tokenizer(object): | |||||
escaped = self._read(-1) == "\\" and self._read(-2) != "\\" | escaped = self._read(-1) == "\\" and self._read(-2) != "\\" | ||||
if data.context & data.CX_NOTE_QUOTE: | if data.context & data.CX_NOTE_QUOTE: | ||||
data.context ^= data.CX_NOTE_QUOTE | data.context ^= data.CX_NOTE_QUOTE | ||||
if chunk == '"' and not escaped: | |||||
if chunk in "'\"" and not escaped: | |||||
data.context |= data.CX_QUOTED | data.context |= data.CX_QUOTED | ||||
self._push(self._context) | |||||
data.quoter = chunk | |||||
data.reset = self._head | data.reset = self._head | ||||
self._push(self._context) | |||||
continue | continue | ||||
elif data.context & data.CX_QUOTED: | elif data.context & data.CX_QUOTED: | ||||
if chunk == '"' and not escaped: | |||||
if chunk == data.quoter and not escaped: | |||||
data.context |= data.CX_NOTE_SPACE | data.context |= data.CX_NOTE_SPACE | ||||
continue | continue | ||||
self._handle_tag_text(chunk) | self._handle_tag_text(chunk) | ||||
@@ -100,7 +100,7 @@ CommentEnd = make("CommentEnd") # --> | |||||
TagOpenOpen = make("TagOpenOpen") # < | TagOpenOpen = make("TagOpenOpen") # < | ||||
TagAttrStart = make("TagAttrStart") | TagAttrStart = make("TagAttrStart") | ||||
TagAttrEquals = make("TagAttrEquals") # = | TagAttrEquals = make("TagAttrEquals") # = | ||||
TagAttrQuote = make("TagAttrQuote") # " | |||||
TagAttrQuote = make("TagAttrQuote") # ", ' | |||||
TagCloseOpen = make("TagCloseOpen") # > | TagCloseOpen = make("TagCloseOpen") # > | ||||
TagCloseSelfclose = make("TagCloseSelfclose") # /> | TagCloseSelfclose = make("TagCloseSelfclose") # /> | ||||
TagOpenClose = make("TagOpenClose") # </ | TagOpenClose = make("TagOpenClose") # </ | ||||
@@ -98,7 +98,7 @@ class TreeEqualityTestCase(TestCase): | |||||
self.assertWikicodeEqual(exp_attr.name, act_attr.name) | self.assertWikicodeEqual(exp_attr.name, act_attr.name) | ||||
if exp_attr.value is not None: | if exp_attr.value is not None: | ||||
self.assertWikicodeEqual(exp_attr.value, act_attr.value) | self.assertWikicodeEqual(exp_attr.value, act_attr.value) | ||||
self.assertIs(exp_attr.quoted, act_attr.quoted) | |||||
self.assertEqual(exp_attr.quotes, act_attr.quotes) | |||||
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) | ||||
@@ -42,12 +42,14 @@ class TestAttribute(TreeEqualityTestCase): | |||||
self.assertEqual(" foo", str(node)) | self.assertEqual(" foo", str(node)) | ||||
node2 = Attribute(wraptext("foo"), wraptext("bar")) | node2 = Attribute(wraptext("foo"), wraptext("bar")) | ||||
self.assertEqual(' foo="bar"', str(node2)) | self.assertEqual(' foo="bar"', str(node2)) | ||||
node3 = Attribute(wraptext("a"), wraptext("b"), True, "", " ", " ") | |||||
node3 = Attribute(wraptext("a"), wraptext("b"), '"', "", " ", " ") | |||||
self.assertEqual('a = "b"', str(node3)) | self.assertEqual('a = "b"', str(node3)) | ||||
node3 = Attribute(wraptext("a"), wraptext("b"), False, "", " ", " ") | |||||
self.assertEqual("a = b", str(node3)) | |||||
node4 = Attribute(wraptext("a"), wrap([]), False, " ", "", " ") | |||||
self.assertEqual(" a= ", str(node4)) | |||||
node4 = Attribute(wraptext("a"), wraptext("b"), "'", "", " ", " ") | |||||
self.assertEqual("a = 'b'", str(node4)) | |||||
node5 = Attribute(wraptext("a"), wraptext("b"), None, "", " ", " ") | |||||
self.assertEqual("a = b", str(node5)) | |||||
node6 = Attribute(wraptext("a"), wrap([]), None, " ", "", " ") | |||||
self.assertEqual(" a= ", str(node6)) | |||||
def test_name(self): | def test_name(self): | ||||
"""test getter/setter for the name attribute""" | """test getter/setter for the name attribute""" | ||||
@@ -66,17 +68,35 @@ class TestAttribute(TreeEqualityTestCase): | |||||
self.assertWikicodeEqual(wrap([Template(wraptext("bar"))]), node.value) | self.assertWikicodeEqual(wrap([Template(wraptext("bar"))]), node.value) | ||||
node.value = None | node.value = None | ||||
self.assertIs(None, node.value) | self.assertIs(None, node.value) | ||||
node2 = Attribute(wraptext("id"), wraptext("foo"), None) | |||||
node2.value = "foo bar baz" | |||||
self.assertWikicodeEqual(wraptext("foo bar baz"), node2.value) | |||||
self.assertEqual('"', node2.quotes) | |||||
node2.value = 'foo "bar" baz' | |||||
self.assertWikicodeEqual(wraptext('foo "bar" baz'), node2.value) | |||||
self.assertEqual("'", node2.quotes) | |||||
node2.value = "foo 'bar' baz" | |||||
self.assertWikicodeEqual(wraptext("foo 'bar' baz"), node2.value) | |||||
self.assertEqual('"', node2.quotes) | |||||
node2.value = "fo\"o 'bar' b\"az" | |||||
self.assertWikicodeEqual(wraptext("fo\"o 'bar' b\"az"), node2.value) | |||||
self.assertEqual('"', node2.quotes) | |||||
def test_quoted(self): | |||||
"""test getter/setter for the quoted attribute""" | |||||
node1 = Attribute(wraptext("id"), wraptext("foo"), False) | |||||
def test_quotes(self): | |||||
"""test getter/setter for the quotes attribute""" | |||||
node1 = Attribute(wraptext("id"), wraptext("foo"), None) | |||||
node2 = Attribute(wraptext("id"), wraptext("bar")) | node2 = Attribute(wraptext("id"), wraptext("bar")) | ||||
self.assertFalse(node1.quoted) | |||||
self.assertTrue(node2.quoted) | |||||
node1.quoted = True | |||||
node2.quoted = "" | |||||
self.assertTrue(node1.quoted) | |||||
self.assertFalse(node2.quoted) | |||||
node3 = Attribute(wraptext("id"), wraptext("foo bar baz")) | |||||
self.assertIs(None, node1.quotes) | |||||
self.assertEqual('"', node2.quotes) | |||||
node1.quotes = "'" | |||||
node2.quotes = None | |||||
self.assertEqual("'", node1.quotes) | |||||
self.assertIs(None, node2.quotes) | |||||
self.assertRaises(ValueError, setattr, node1, "quotes", "foobar") | |||||
self.assertRaises(ValueError, setattr, node3, "quotes", None) | |||||
self.assertRaises(ValueError, Attribute, wraptext("id"), | |||||
wraptext("foo bar baz"), None) | |||||
def test_padding(self): | def test_padding(self): | ||||
"""test getter/setter for the padding attributes""" | """test getter/setter for the padding attributes""" | ||||
@@ -270,7 +270,7 @@ class TestBuilder(TreeEqualityTestCase): | |||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | ||||
pad_after_eq=""), | pad_after_eq=""), | ||||
tokens.Text(text="name"), tokens.TagAttrEquals(), | tokens.Text(text="name"), tokens.TagAttrEquals(), | ||||
tokens.TagAttrQuote(), tokens.Text(text="abc"), | |||||
tokens.TagAttrQuote(char='"'), tokens.Text(text="abc"), | |||||
tokens.TagCloseSelfclose(padding=" ")], | tokens.TagCloseSelfclose(padding=" ")], | ||||
wrap([Tag(wraptext("ref"), | wrap([Tag(wraptext("ref"), | ||||
attrs=[Attribute(wraptext("name"), wraptext("abc"))], | attrs=[Attribute(wraptext("name"), wraptext("abc"))], | ||||
@@ -298,7 +298,7 @@ class TestBuilder(TreeEqualityTestCase): | |||||
wrap([Tag(wraptext("br"), self_closing=True, invalid=True)])), | wrap([Tag(wraptext("br"), self_closing=True, invalid=True)])), | ||||
# <ref name={{abc}} foo="bar {{baz}}" abc={{de}}f ghi=j{{k}}{{l}} | # <ref name={{abc}} foo="bar {{baz}}" abc={{de}}f ghi=j{{k}}{{l}} | ||||
# mno = "{{p}} [[q]] {{r}}">[[Source]]</ref> | |||||
# mno = '{{p}} [[q]] {{r}}'>[[Source]]</ref> | |||||
([tokens.TagOpenOpen(), tokens.Text(text="ref"), | ([tokens.TagOpenOpen(), tokens.Text(text="ref"), | ||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | ||||
pad_after_eq=""), | pad_after_eq=""), | ||||
@@ -308,7 +308,7 @@ class TestBuilder(TreeEqualityTestCase): | |||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | ||||
pad_after_eq=""), | pad_after_eq=""), | ||||
tokens.Text(text="foo"), tokens.TagAttrEquals(), | tokens.Text(text="foo"), tokens.TagAttrEquals(), | ||||
tokens.TagAttrQuote(), tokens.Text(text="bar "), | |||||
tokens.TagAttrQuote(char='"'), tokens.Text(text="bar "), | |||||
tokens.TemplateOpen(), tokens.Text(text="baz"), | tokens.TemplateOpen(), tokens.Text(text="baz"), | ||||
tokens.TemplateClose(), | tokens.TemplateClose(), | ||||
tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | tokens.TagAttrStart(pad_first=" ", pad_before_eq="", | ||||
@@ -326,7 +326,7 @@ class TestBuilder(TreeEqualityTestCase): | |||||
tokens.TagAttrStart(pad_first=" \n ", pad_before_eq=" ", | tokens.TagAttrStart(pad_first=" \n ", pad_before_eq=" ", | ||||
pad_after_eq=" "), | pad_after_eq=" "), | ||||
tokens.Text(text="mno"), tokens.TagAttrEquals(), | tokens.Text(text="mno"), tokens.TagAttrEquals(), | ||||
tokens.TagAttrQuote(), tokens.TemplateOpen(), | |||||
tokens.TagAttrQuote(char="'"), tokens.TemplateOpen(), | |||||
tokens.Text(text="p"), tokens.TemplateClose(), | tokens.Text(text="p"), tokens.TemplateClose(), | ||||
tokens.Text(text=" "), tokens.WikilinkOpen(), | tokens.Text(text=" "), tokens.WikilinkOpen(), | ||||
tokens.Text(text="q"), tokens.WikilinkClose(), | tokens.Text(text="q"), tokens.WikilinkClose(), | ||||
@@ -338,17 +338,17 @@ class TestBuilder(TreeEqualityTestCase): | |||||
tokens.TagCloseClose()], | tokens.TagCloseClose()], | ||||
wrap([Tag(wraptext("ref"), wrap([Wikilink(wraptext("Source"))]), [ | wrap([Tag(wraptext("ref"), wrap([Wikilink(wraptext("Source"))]), [ | ||||
Attribute(wraptext("name"), | Attribute(wraptext("name"), | ||||
wrap([Template(wraptext("abc"))]), False), | |||||
wrap([Template(wraptext("abc"))]), None), | |||||
Attribute(wraptext("foo"), wrap([Text("bar "), | Attribute(wraptext("foo"), wrap([Text("bar "), | ||||
Template(wraptext("baz"))]), pad_first=" "), | Template(wraptext("baz"))]), pad_first=" "), | ||||
Attribute(wraptext("abc"), wrap([Template(wraptext("de")), | Attribute(wraptext("abc"), wrap([Template(wraptext("de")), | ||||
Text("f")]), False), | |||||
Text("f")]), None), | |||||
Attribute(wraptext("ghi"), wrap([Text("j"), | Attribute(wraptext("ghi"), wrap([Text("j"), | ||||
Template(wraptext("k")), | Template(wraptext("k")), | ||||
Template(wraptext("l"))]), False), | |||||
Template(wraptext("l"))]), None), | |||||
Attribute(wraptext("mno"), wrap([Template(wraptext("p")), | Attribute(wraptext("mno"), wrap([Template(wraptext("p")), | ||||
Text(" "), Wikilink(wraptext("q")), Text(" "), | Text(" "), Wikilink(wraptext("q")), Text(" "), | ||||
Template(wraptext("r"))]), True, " \n ", " ", | |||||
Template(wraptext("r"))]), "'", " \n ", " ", | |||||
" ")])])), | " ")])])), | ||||
# "''italic text''" | # "''italic text''" | ||||
@@ -34,9 +34,9 @@ from ._test_tree_equality import TreeEqualityTestCase, wrap, wraptext | |||||
agen = lambda name, value: Attribute(wraptext(name), wraptext(value)) | agen = lambda name, value: Attribute(wraptext(name), wraptext(value)) | ||||
agennv = lambda name: Attribute(wraptext(name)) | agennv = lambda name: Attribute(wraptext(name)) | ||||
agennq = lambda name, value: Attribute(wraptext(name), wraptext(value), False) | |||||
agenp = lambda name, v, a, b, c: Attribute(wraptext(name), v, True, a, b, c) | |||||
agenpnv = lambda name, a, b, c: Attribute(wraptext(name), None, True, a, b, c) | |||||
agennq = lambda name, value: Attribute(wraptext(name), wraptext(value), None) | |||||
agenp = lambda name, v, a, b, c: Attribute(wraptext(name), v, '"', a, b, c) | |||||
agenpnv = lambda name, a, b, c: Attribute(wraptext(name), None, '"', a, b, c) | |||||
class TestTag(TreeEqualityTestCase): | class TestTag(TreeEqualityTestCase): | ||||
"""Test cases for the Tag node.""" | """Test cases for the Tag node.""" | ||||
@@ -276,28 +276,33 @@ class TestTag(TreeEqualityTestCase): | |||||
"""test Tag.add()""" | """test Tag.add()""" | ||||
node = Tag(wraptext("ref"), wraptext("cite")) | node = Tag(wraptext("ref"), wraptext("cite")) | ||||
node.add("name", "value") | node.add("name", "value") | ||||
node.add("name", "value", quoted=False) | |||||
node.add("name", "value", quotes=None) | |||||
node.add("name", "value", quotes="'") | |||||
node.add("name") | node.add("name") | ||||
node.add(1, False) | node.add(1, False) | ||||
node.add("style", "{{foobar}}") | node.add("style", "{{foobar}}") | ||||
node.add("name", "value", True, "\n", " ", " ") | |||||
node.add("name", "value", '"', "\n", " ", " ") | |||||
attr1 = ' name="value"' | attr1 = ' name="value"' | ||||
attr2 = " name=value" | attr2 = " name=value" | ||||
attr3 = " name" | |||||
attr4 = ' 1="False"' | |||||
attr5 = ' style="{{foobar}}"' | |||||
attr6 = '\nname = "value"' | |||||
attr3 = " name='value'" | |||||
attr4 = " name" | |||||
attr5 = ' 1="False"' | |||||
attr6 = ' style="{{foobar}}"' | |||||
attr7 = '\nname = "value"' | |||||
self.assertEqual(attr1, node.attributes[0]) | self.assertEqual(attr1, node.attributes[0]) | ||||
self.assertEqual(attr2, node.attributes[1]) | self.assertEqual(attr2, node.attributes[1]) | ||||
self.assertEqual(attr3, node.attributes[2]) | self.assertEqual(attr3, node.attributes[2]) | ||||
self.assertEqual(attr4, node.attributes[3]) | self.assertEqual(attr4, node.attributes[3]) | ||||
self.assertEqual(attr5, node.attributes[4]) | self.assertEqual(attr5, node.attributes[4]) | ||||
self.assertEqual(attr6, node.attributes[5]) | self.assertEqual(attr6, node.attributes[5]) | ||||
self.assertEqual(attr6, node.get("name")) | |||||
self.assertEqual(attr7, node.attributes[6]) | |||||
self.assertEqual(attr7, node.get("name")) | |||||
self.assertWikicodeEqual(wrap([Template(wraptext("foobar"))]), | self.assertWikicodeEqual(wrap([Template(wraptext("foobar"))]), | ||||
node.attributes[4].value) | |||||
node.attributes[5].value) | |||||
self.assertEqual("".join(("<ref", attr1, attr2, attr3, attr4, attr5, | self.assertEqual("".join(("<ref", attr1, attr2, attr3, attr4, attr5, | ||||
attr6, ">cite</ref>")), node) | |||||
attr6, attr7, ">cite</ref>")), node) | |||||
self.assertRaises(ValueError, node.add, "name", "foo", quotes="bar") | |||||
self.assertRaises(ValueError, node.add, "name", "a bc d", quotes=None) | |||||
def test_remove(self): | def test_remove(self): | ||||
"""test Tag.remove()""" | """test Tag.remove()""" | ||||
@@ -43,7 +43,7 @@ output: [Text(text="&n"), CommentStart(), Text(text="foo"), CommentEnd(), Text(t | |||||
name: rich_tags | name: rich_tags | ||||
label: a HTML tag with tons of other things in it | label: a HTML tag with tons of other things in it | ||||
input: "{{dubious claim}}<ref name={{abc}} foo="bar {{baz}}" abc={{de}}f ghi=j{{k}}{{l}} \n mno = "{{p}} [[q]] {{r}}">[[Source]]</ref>" | input: "{{dubious claim}}<ref name={{abc}} foo="bar {{baz}}" abc={{de}}f ghi=j{{k}}{{l}} \n mno = "{{p}} [[q]] {{r}}">[[Source]]</ref>" | ||||
output: [TemplateOpen(), Text(text="dubious claim"), TemplateClose(), TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TemplateOpen(), Text(text="abc"), TemplateClose(), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="foo"), TagAttrEquals(), TagAttrQuote(), Text(text="bar "), TemplateOpen(), Text(text="baz"), TemplateClose(), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="abc"), TagAttrEquals(), TemplateOpen(), Text(text="de"), TemplateClose(), Text(text="f"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="ghi"), TagAttrEquals(), Text(text="j"), TemplateOpen(), Text(text="k"), TemplateClose(), TemplateOpen(), Text(text="l"), TemplateClose(), TagAttrStart(pad_first=" \n ", pad_before_eq=" ", pad_after_eq=" "), Text(text="mno"), TagAttrEquals(), TagAttrQuote(), TemplateOpen(), Text(text="p"), TemplateClose(), Text(text=" "), WikilinkOpen(), Text(text="q"), WikilinkClose(), Text(text=" "), TemplateOpen(), Text(text="r"), TemplateClose(), TagCloseOpen(padding=""), WikilinkOpen(), Text(text="Source"), WikilinkClose(), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
output: [TemplateOpen(), Text(text="dubious claim"), TemplateClose(), TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TemplateOpen(), Text(text="abc"), TemplateClose(), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="foo"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="bar "), TemplateOpen(), Text(text="baz"), TemplateClose(), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="abc"), TagAttrEquals(), TemplateOpen(), Text(text="de"), TemplateClose(), Text(text="f"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="ghi"), TagAttrEquals(), Text(text="j"), TemplateOpen(), Text(text="k"), TemplateClose(), TemplateOpen(), Text(text="l"), TemplateClose(), TagAttrStart(pad_first=" \n ", pad_before_eq=" ", pad_after_eq=" "), Text(text="mno"), TagAttrEquals(), TagAttrQuote(char="\""), TemplateOpen(), Text(text="p"), TemplateClose(), Text(text=" "), WikilinkOpen(), Text(text="q"), WikilinkClose(), Text(text=" "), TemplateOpen(), Text(text="r"), TemplateClose(), TagCloseOpen(padding=""), WikilinkOpen(), Text(text="Source"), WikilinkClose(), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | --- | ||||
@@ -57,7 +57,14 @@ output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before | |||||
name: attribute_quoted | name: attribute_quoted | ||||
label: a tag with a single quoted attribute | label: a tag with a single quoted attribute | ||||
input: "<ref name="foo bar"></ref>" | input: "<ref name="foo bar"></ref>" | ||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), Text(text="foo bar"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="foo bar"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | |||||
name: attribute_single_quoted | |||||
label: a tag with a single singly-quoted attribute | |||||
input: "<ref name='foo bar'></ref>" | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(char="'"), Text(text="foo bar"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | --- | ||||
@@ -71,7 +78,7 @@ output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before | |||||
name: attribute_quoted_hyphen | name: attribute_quoted_hyphen | ||||
label: a tag with a single quoted attribute, containing a hyphen | label: a tag with a single quoted attribute, containing a hyphen | ||||
input: "<ref name="foo-bar"></ref>" | input: "<ref name="foo-bar"></ref>" | ||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), Text(text="foo-bar"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="foo-bar"), TagCloseOpen(padding=""), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | --- | ||||
@@ -92,21 +99,21 @@ output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before | |||||
name: attribute_selfclosing_value_quoted | name: attribute_selfclosing_value_quoted | ||||
label: a self-closing tag with a single quoted attribute | label: a self-closing tag with a single quoted attribute | ||||
input: "<ref name="foo"/>" | input: "<ref name="foo"/>" | ||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), Text(text="foo"), TagCloseSelfclose(padding="")] | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="foo"), TagCloseSelfclose(padding="")] | |||||
--- | --- | ||||
name: nested_tag | name: nested_tag | ||||
label: a tag nested within the attributes of another | label: a tag nested within the attributes of another | ||||
input: "<ref name=<span style="color: red;">foo</span>>citation</ref>" | input: "<ref name=<span style="color: red;">foo</span>>citation</ref>" | ||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="style"), TagAttrEquals(), TagAttrQuote(), Text(text="color: red;"), TagCloseOpen(padding=""), Text(text="foo"), TagOpenClose(), Text(text="span"), TagCloseClose(), TagCloseOpen(padding=""), Text(text="citation"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="style"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="color: red;"), TagCloseOpen(padding=""), Text(text="foo"), TagOpenClose(), Text(text="span"), TagCloseClose(), TagCloseOpen(padding=""), Text(text="citation"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | --- | ||||
name: nested_tag_quoted | name: nested_tag_quoted | ||||
label: a tag nested within the attributes of another, quoted | label: a tag nested within the attributes of another, quoted | ||||
input: "<ref name="<span style="color: red;">foo</span>">citation</ref>" | input: "<ref name="<span style="color: red;">foo</span>">citation</ref>" | ||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="style"), TagAttrEquals(), TagAttrQuote(), Text(text="color: red;"), TagCloseOpen(padding=""), Text(text="foo"), TagOpenClose(), Text(text="span"), TagCloseClose(), TagCloseOpen(padding=""), Text(text="citation"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(char="\""), TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="style"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="color: red;"), TagCloseOpen(padding=""), Text(text="foo"), TagOpenClose(), Text(text="span"), TagCloseClose(), TagCloseOpen(padding=""), Text(text="citation"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | --- | ||||
@@ -120,7 +127,7 @@ output: [Text(text="<ref name=</ ><//>>citation</ref>")] | |||||
name: nested_troll_tag_quoted | name: nested_troll_tag_quoted | ||||
label: a bogus tag that appears to be nested within the attributes of another, quoted | label: a bogus tag that appears to be nested within the attributes of another, quoted | ||||
input: "<ref name="</ ><//>">citation</ref>" | input: "<ref name="</ ><//>">citation</ref>" | ||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(), Text(text="</ ><//>"), TagCloseOpen(padding=""), Text(text="citation"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="ref"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="name"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="</ ><//>"), TagCloseOpen(padding=""), Text(text="citation"), TagOpenClose(), Text(text="ref"), TagCloseClose()] | |||||
--- | --- | ||||
@@ -222,6 +229,27 @@ output: [TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_befor | |||||
--- | --- | ||||
name: quotes_in_quotes | |||||
label: singly-quoted text inside a doubly-quoted attribute | |||||
input: "<span foo="bar 'baz buzz' biz">stuff</span>" | |||||
output: [TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="foo"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="bar 'baz buzz' biz"), TagCloseOpen(padding=""), Text(text="stuff"), TagOpenClose(), Text(text="span"), TagCloseClose()] | |||||
--- | |||||
name: quotes_in_quotes_2 | |||||
label: doubly-quoted text inside a singly-quoted attribute | |||||
input: "<span foo='bar "baz buzz" biz'>stuff</span>" | |||||
output: [TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="foo"), TagAttrEquals(), TagAttrQuote(char="'"), Text(text="bar \"baz buzz\" biz"), TagCloseOpen(padding=""), Text(text="stuff"), TagOpenClose(), Text(text="span"), TagCloseClose()] | |||||
--- | |||||
name: quotes_in_quotes_3 | |||||
label: doubly-quoted text inside a singly-quoted attribute, with backslashes | |||||
input: "<span foo='bar "baz buzz\\" biz'>stuff</span>" | |||||
output: [TagOpenOpen(), Text(text="span"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="foo"), TagAttrEquals(), TagAttrQuote(char="'"), Text(text="bar \"baz buzz\\\" biz"), TagCloseOpen(padding=""), Text(text="stuff"), TagOpenClose(), Text(text="span"), TagCloseClose()] | |||||
--- | |||||
name: incomplete_lbracket | name: incomplete_lbracket | ||||
label: incomplete tags: just a left bracket | label: incomplete tags: just a left bracket | ||||
input: "<" | input: "<" | ||||
@@ -407,28 +435,28 @@ output: [Text(text="junk <></>")] | |||||
name: backslash_premature_before | name: backslash_premature_before | ||||
label: a backslash before a quote before a space | label: a backslash before a quote before a space | ||||
input: "<foo attribute="this is\\" quoted">blah</foo>" | input: "<foo attribute="this is\\" quoted">blah</foo>" | ||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this is\\\" quoted"), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="this is\\\" quoted"), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | --- | ||||
name: backslash_premature_after | name: backslash_premature_after | ||||
label: a backslash before a quote after a space | label: a backslash before a quote after a space | ||||
input: "<foo attribute="this is \\"quoted">blah</foo>" | input: "<foo attribute="this is \\"quoted">blah</foo>" | ||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this is \\\"quoted"), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="this is \\\"quoted"), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | --- | ||||
name: backslash_premature_middle | name: backslash_premature_middle | ||||
label: a backslash before a quote in the middle of a word | label: a backslash before a quote in the middle of a word | ||||
input: "<foo attribute="this i\\"s quoted">blah</foo>" | input: "<foo attribute="this i\\"s quoted">blah</foo>" | ||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this i\\\"s quoted"), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="this i\\\"s quoted"), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | --- | ||||
name: backslash_adjacent | name: backslash_adjacent | ||||
label: escaped quotes next to unescaped quotes | label: escaped quotes next to unescaped quotes | ||||
input: "<foo attribute="\\"this is quoted\\"">blah</foo>" | input: "<foo attribute="\\"this is quoted\\"">blah</foo>" | ||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="\\\"this is quoted\\\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="\\\"this is quoted\\\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | --- | ||||
@@ -442,21 +470,21 @@ output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before | |||||
name: backslash_double | name: backslash_double | ||||
label: two adjacent backslashes, which do *not* affect the quote | label: two adjacent backslashes, which do *not* affect the quote | ||||
input: "<foo attribute="this is\\\\" quoted">blah</foo>" | input: "<foo attribute="this is\\\\" quoted">blah</foo>" | ||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this is\\\\"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="quoted\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="this is\\\\"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="quoted\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | --- | ||||
name: backslash_triple | name: backslash_triple | ||||
label: three adjacent backslashes, which do *not* affect the quote | label: three adjacent backslashes, which do *not* affect the quote | ||||
input: "<foo attribute="this is\\\\\\" quoted">blah</foo>" | input: "<foo attribute="this is\\\\\\" quoted">blah</foo>" | ||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="this is\\\\\\"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="quoted\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="this is\\\\\\"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="quoted\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | --- | ||||
name: backslash_unaffecting | name: backslash_unaffecting | ||||
label: backslashes near quotes, but not immediately adjacent, thus having no effect | label: backslashes near quotes, but not immediately adjacent, thus having no effect | ||||
input: "<foo attribute="\\quote\\d" also="quote\\d\\">blah</foo>" | input: "<foo attribute="\\quote\\d" also="quote\\d\\">blah</foo>" | ||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(), Text(text="\\quote\\d"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="also"), TagAttrEquals(), Text(text="\"quote\\d\\\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
output: [TagOpenOpen(), Text(text="foo"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attribute"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="\\quote\\d"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="also"), TagAttrEquals(), Text(text="\"quote\\d\\\""), TagCloseOpen(padding=""), Text(text="blah"), TagOpenClose(), Text(text="foo"), TagCloseClose()] | |||||
--- | --- | ||||
@@ -477,7 +505,7 @@ output: [TemplateOpen(), Text(text="t1"), TemplateClose(), TagOpenOpen(), Text(t | |||||
name: unparsable_attributed | name: unparsable_attributed | ||||
label: a tag that should not be put through the normal parser; parsed attributes | label: a tag that should not be put through the normal parser; parsed attributes | ||||
input: "{{t1}}<nowiki attr=val attr2="{{val2}}">{{t2}}</nowiki>{{t3}}" | input: "{{t1}}<nowiki attr=val attr2="{{val2}}">{{t2}}</nowiki>{{t3}}" | ||||
output: [TemplateOpen(), Text(text="t1"), TemplateClose(), TagOpenOpen(), Text(text="nowiki"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr"), TagAttrEquals(), Text(text="val"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr2"), TagAttrEquals(), TagAttrQuote(), TemplateOpen(), Text(text="val2"), TemplateClose(), TagCloseOpen(padding=""), Text(text="{{t2}}"), TagOpenClose(), Text(text="nowiki"), TagCloseClose(), TemplateOpen(), Text(text="t3"), TemplateClose()] | |||||
output: [TemplateOpen(), Text(text="t1"), TemplateClose(), TagOpenOpen(), Text(text="nowiki"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr"), TagAttrEquals(), Text(text="val"), TagAttrStart(pad_first=" ", pad_before_eq="", pad_after_eq=""), Text(text="attr2"), TagAttrEquals(), TagAttrQuote(char="\""), TemplateOpen(), Text(text="val2"), TemplateClose(), TagCloseOpen(padding=""), Text(text="{{t2}}"), TagOpenClose(), Text(text="nowiki"), TagCloseClose(), TemplateOpen(), Text(text="t3"), TemplateClose()] | |||||
--- | --- | ||||
@@ -575,7 +603,7 @@ output: [Text(text="foo"), TagOpenOpen(invalid=True), Text(text="br"), TagCloseS | |||||
name: single_only_close_attribute | name: single_only_close_attribute | ||||
label: a tag that can only be single; presented as a close tag with an attribute | label: a tag that can only be single; presented as a close tag with an attribute | ||||
input: "</br id="break">" | input: "</br id="break">" | ||||
output: [TagOpenOpen(invalid=True), Text(text="br"), TagAttrStart(pad_first=" ", pad_after_eq="", pad_before_eq=""), Text(text="id"), TagAttrEquals(), TagAttrQuote(), Text(text="break"), TagCloseSelfclose(padding="", implicit=True)] | |||||
output: [TagOpenOpen(invalid=True), Text(text="br"), TagAttrStart(pad_first=" ", pad_after_eq="", pad_before_eq=""), Text(text="id"), TagAttrEquals(), TagAttrQuote(char="\""), Text(text="break"), TagCloseSelfclose(padding="", implicit=True)] | |||||
--- | --- | ||||