Define state for justify*
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Mon, 27 Jun 2011 14:11:11 -0600
changeset 324 3d6fc8f9a040
parent 323 31db5f77c92d
child 325 13272d002cb2
Define state for justify*

Also fix two bugs, where <span align=right> or such would be converted
to <span> instead of removed, and things would be wrapped in divs that
aren't allowed children of divs.
editcommands.html
implementation.js
source.html
tests.js
--- a/editcommands.html	Mon Jun 27 12:56:46 2011 -0600
+++ b/editcommands.html	Mon Jun 27 14:11:11 2011 -0600
@@ -3106,6 +3106,66 @@
   </ol>
 </ol>
 
+<p>The <dfn id=current-alignment-state>current alignment state</dfn> is returned by the following
+algorithm:
+<!--
+IE9 throws exceptions in almost every case when querying the state of justify*,
+and Opera 11.11 returns false in every case except some seemingly random crazy
+ones.
+
+Firefox 6.0a2 returns true for the state of justify* if anything in the range
+has the right alignment, not if everything does.  This isn't consistent with
+how state works for the inline commands, nor with WebKit.
+
+Chrome 14 dev counts text-align on inline elements, which is wrong, because the
+property has no effect.  It also counts it on non-editable elements, which is
+wrong, because then the state for justify* wouldn't necessarily be true after
+executing it.  (Chrome actually does align the non-editable elements, but
+that's just a bug.)  Chrome further returns false for justify* if the
+justification is just the default inherited justification, e.g., left for LTR.
+This doesn't seem to make sense either.
+
+It's not actually clear how much sense state makes here.  Most commands that
+have a state that's not always false work differently depending on what the
+state is, generally because they toggle something (bold, lists, etc.).  The
+justify commands behave the same regardless of state.  Still, I'll follow
+Gecko/WebKit in supporting the command.
+-->
+<ol>
+  <li><a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>, and let <var title="">new
+  range</var> be the result.
+
+  <li>Let <var title="">node list</var> be a list of all <a href=#editable>editable</a> <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new range</var>.
+
+  <li>If <var title="">node list</var> is empty, return "none".
+
+  <li>Let <var title="">state</var> be "none".
+
+  <li>For each <var title="">node</var> in <var title="">node list</var>:
+
+  <ol>
+    <li>While <var title="">node</var> is neither null nor an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>, or it is an
+    <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> but its "display" property has <a href=http://www.w3.org/TR/CSS21/cascade.html#computed-value>computed value</a> "inline" or "none",
+    set <var title="">node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>If <var title="">node</var> is not an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> or its "display" property has
+    <a href=http://www.w3.org/TR/CSS21/cascade.html#computed-value>computed value</a> "inline" or "none", continue with the next <var title="">node</var>.
+
+    <li>If <var title="">state</var> is "none", set <var title="">state</var> to either "center",
+    "justify", "left", or "right", depending on the <a href=http://www.w3.org/TR/CSS21/cascade.html#computed-value>computed value</a> of
+    <var title="">node</var>'s "text-align" property.
+
+    <p class=XXX>What to do for "start" "auto" etc. currently undefined.  We
+    assume something CSS 2.1-ish.
+
+    <li>If <var title="">state</var> is different from the <a href=http://www.w3.org/TR/CSS21/cascade.html#computed-value>computed value</a> of
+    <var title="">node</var>'s "text-align" property, return "none".
+  </ol>
+
+  <li>Return <var title="">state</var>.
+</ol>
+
 
 <h3 id=allowed-children><span class=secno>7.3 </span>Allowed children</h3>
 
@@ -4470,7 +4530,7 @@
     <li>Unset the CSS property "text-align" on <var title="">element</var>, if it's set
     by a <code class=external data-anolis-spec=html title="the style attribute"><a href=http://www.whatwg.org/html/#the-style-attribute>style</a></code> attribute.
 
-    <li>If <var title="">element</var> is a <code class=external data-anolis-spec=html title="the div element"><a href=http://www.whatwg.org/html/#the-div-element>div</a></code> or <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#center>center</a></code> with no attributes, remove it,
+    <li>If <var title="">element</var> is a <code class=external data-anolis-spec=html title="the div element"><a href=http://www.whatwg.org/html/#the-div-element>div</a></code> or <code class=external data-anolis-spec=html title="the span element"><a href=http://www.whatwg.org/html/#the-span-element>span</a></code> or <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#center>center</a></code> with no attributes, remove it,
     <a href=#preserving-its-descendants>preserving its descendants</a>.
 
     <li>If <var title="">element</var> is a <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#center>center</a></code>
@@ -4489,11 +4549,12 @@
   <li>For each <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> <var title="">node</var> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new range</var>,
   append <var title="">node</var> to <var title="">node list</var> if the last member of
   <var title="">node list</var> (if any) is not an <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of <var title="">node</var>;
-  <var title="">node</var> is <a href=#editable>editable</a>; and either <var title="">node</var> is an
-  <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> and the CSS property "text-align" does not compute to
-  <var title="">alignment</var> on it, or it is not an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>, but its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>
-  is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>, and the CSS property "text-align" does not compute to
-  <var title="">alignment</var> on its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+  <var title="">node</var> is <a href=#editable>editable</a>; <var title="">node</var> is an <a href=#allowed-child>allowed
+  child</a> of "div"; and either <var title="">node</var> is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> and the
+  CSS property "text-align" does not compute to <var title="">alignment</var> on it, or
+  it is not an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>, but its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>, and the CSS
+  property "text-align" does not compute to <var title="">alignment</var> on its
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
   <!-- Of tested browsers, only Chrome 13 dev seems to not apply the alignment
   to nodes that are already aligned.  Even then, it does apply it if the
   alignment is just inherited from the root. -->
@@ -6018,24 +6079,36 @@
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "center".
 
+<p><a href=#state>State</a>: True if the <a href=#current-alignment-state>current alignment state</a> is
+"center", otherwise false.
+
 
 <h3 id=the-justifyfull-command><span class=secno>7.22 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "justify".
 
+<p><a href=#state>State</a>: True if the <a href=#current-alignment-state>current alignment state</a> is
+"justify", otherwise false.
+
 
 <h3 id=the-justifyleft-command><span class=secno>7.23 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "left".
 
+<p><a href=#state>State</a>: True if the <a href=#current-alignment-state>current alignment state</a> is
+"left", otherwise false.
+
 
 <h3 id=the-justifyright-command><span class=secno>7.24 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "right".
 
+<p><a href=#state>State</a>: True if the <a href=#current-alignment-state>current alignment state</a> is
+"right", otherwise false.
+
 
 <h3 id=the-outdent-command><span class=secno>7.25 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
 
--- a/implementation.js	Mon Jun 27 12:56:46 2011 -0600
+++ b/implementation.js	Mon Jun 27 14:11:11 2011 -0600
@@ -233,6 +233,26 @@
 	return atLeastOne;
 }
 
+// Returns a standard CSS 2.1 computed value for text-align, adapting
+// nonstandard values and things like "start" and "auto".
+function getRealTextAlign(element) {
+	var computedAlign = getComputedStyle(element).textAlign
+		.replace(/^-(moz|webkit)-/, "");
+	if (computedAlign == "auto" || computedAlign == "start") {
+		// Depends on directionality.  Note: this is a serious hack.
+		do {
+			var dir = element.dir.toLowerCase();
+			element = element.parentNode;
+		} while (element && element.nodeType == Node.ELEMENT_NODE && dir != "ltr" && dir != "rtl");
+		if (dir == "rtl") {
+			computedAlign = "right";
+		} else {
+			computedAlign = "left";
+		}
+	}
+	return computedAlign;
+}
+
 //@}
 
 
@@ -3531,6 +3551,60 @@
 	}
 }
 
+function getCurrentAlignmentState() {
+	// "Block-extend the active range, and let new range be the result."
+	var newRange = blockExtendRange(getActiveRange());
+
+	// "Let node list be a list of all editable nodes contained in new
+	// range."
+	var nodeList = collectAllContainedNodes(newRange, isEditable);
+
+	// "If node list is empty, return "none"."
+	if (nodeList.length == 0) {
+		return "none";
+	}
+
+	// "Let state be "none"."
+	var state = "none";
+
+	// "For each node in node list:"
+	for (var i = 0; i < nodeList.length; i++) {
+		var node = nodeList[i];
+
+		// "While node is neither null nor an Element, or it is an Element
+		// but its "display" property has computed value "inline" or
+		// "none", set node to its parent."
+		while ((node && node.nodeType != Node.ELEMENT_NODE)
+		|| (node.nodeType == Node.ELEMENT_NODE && getComputedStyle(node).display == "inline")
+		|| (node.nodeType == Node.ELEMENT_NODE && getComputedStyle(node).display == "none")) {
+			node = node.parentNode;
+		}
+
+		// "If node is not an Element or its "display" property has computed
+		// value "inline" or "none", continue with the next node."
+		if (node.nodeType != Node.ELEMENT_NODE
+		|| getComputedStyle(node).display == "inline"
+		|| getComputedStyle(node).display == "none") {
+			continue;
+		}
+
+		// "If state is "none", set state to either "center", "justify",
+		// "left", or "right", depending on the computed value of node's
+		// "text-align" property."
+		if (state == "none") {
+			state = getRealTextAlign(node);
+		}
+
+		// "If state is different from the computed value of node's
+		// "text-align" property, return "none"."
+		if (state != getRealTextAlign(node)) {
+			return "none";
+		}
+	}
+
+	// "Return state."
+	return state;
+}
 //@}
 
 ///// Allowed children /////
@@ -4720,9 +4794,9 @@
 			element.removeAttribute("style");
 		}
 
-		// "If element is a div or center with no attributes, remove it,
-		// preserving its descendants."
-		if (isHtmlElement(element, ["div", "center"])
+		// "If element is a div or span or center with no attributes, remove
+		// it, preserving its descendants."
+		if (isHtmlElement(element, ["div", "span", "center"])
 		&& !element.attributes.length) {
 			removePreservingDescendants(element);
 		}
@@ -4743,12 +4817,12 @@
 
 	// "For each node node contained in new range, append node to node list if
 	// the last member of node list (if any) is not an ancestor of node; node
-	// is editable; and either node is an Element and the CSS property
-	// "text-align" does not compute to alignment on it, or it is not an
-	// Element, but its parent is an Element, and the CSS property "text-align"
-	// does not compute to alignment on its parent."
+	// is editable; node is an allowed child of div; and either node is an
+	// Element and the CSS property "text-align" does not compute to alignment
+	// on it, or it is not an Element, but its parent is an Element, and the
+	// CSS property "text-align" does not compute to alignment on its parent."
 	nodeList = collectContainedNodes(newRange, function(node) {
-		if (!isEditable(node)) {
+		if (!isEditable(node) || !isAllowedChild(node, "div")) {
 			return false;
 		}
 		// Gecko and WebKit have lots of fun here confusing us with
@@ -4759,21 +4833,7 @@
 		if (!element || element.nodeType != Node.ELEMENT_NODE) {
 			return false;
 		}
-		var computedAlign = getComputedStyle(element).textAlign
-			.replace(/^-(moz|webkit)-/, "");
-		if (computedAlign == "auto" || computedAlign == "start") {
-			// Depends on directionality.  Note: this is a serious hack.
-			do {
-				var dir = element.dir.toLowerCase();
-				element = element.parentNode;
-			} while (element && element.nodeType == Node.ELEMENT_NODE && dir != "ltr" && dir != "rtl");
-			if (dir == "rtl") {
-				computedAlign = "right";
-			} else {
-				computedAlign = "left";
-			}
-		}
-		return computedAlign != alignment;
+		return getRealTextAlign(element) != alignment;
 	});
 
 	// "While node list is not empty:"
@@ -6179,7 +6239,9 @@
 //@{
 commands.justifycenter = {
 	// "Justify the selection with alignment "center"."
-	action: function() { justifySelection("center") }
+	action: function() { justifySelection("center") },
+	// "True if the current alignment state is "center", otherwise false."
+	state: function() { return getCurrentAlignmentState() == "center" },
 };
 //@}
 
@@ -6187,7 +6249,9 @@
 //@{
 commands.justifyfull = {
 	// "Justify the selection with alignment "justify"."
-	action: function() { justifySelection("justify") }
+	action: function() { justifySelection("justify") },
+	// "True if the current alignment state is "justify", otherwise false."
+	state: function() { return getCurrentAlignmentState() == "justify" },
 };
 //@}
 
@@ -6195,7 +6259,9 @@
 //@{
 commands.justifyleft = {
 	// "Justify the selection with alignment "left"."
-	action: function() { justifySelection("left") }
+	action: function() { justifySelection("left") },
+	// "True if the current alignment state is "left", otherwise false."
+	state: function() { return getCurrentAlignmentState() == "left" },
 };
 //@}
 
@@ -6203,7 +6269,9 @@
 //@{
 commands.justifyright = {
 	// "Justify the selection with alignment "right"."
-	action: function() { justifySelection("right") }
+	action: function() { justifySelection("right") },
+	// "True if the current alignment state is "right", otherwise false."
+	state: function() { return getCurrentAlignmentState() == "right" },
 };
 //@}
 
--- a/source.html	Mon Jun 27 12:56:46 2011 -0600
+++ b/source.html	Mon Jun 27 14:11:11 2011 -0600
@@ -3090,6 +3090,66 @@
     <li>Add one to <var>start offset</var>.
   </ol>
 </ol>
+
+<p>The <dfn>current alignment state</dfn> is returned by the following
+algorithm:
+<!--
+IE9 throws exceptions in almost every case when querying the state of justify*,
+and Opera 11.11 returns false in every case except some seemingly random crazy
+ones.
+
+Firefox 6.0a2 returns true for the state of justify* if anything in the range
+has the right alignment, not if everything does.  This isn't consistent with
+how state works for the inline commands, nor with WebKit.
+
+Chrome 14 dev counts text-align on inline elements, which is wrong, because the
+property has no effect.  It also counts it on non-editable elements, which is
+wrong, because then the state for justify* wouldn't necessarily be true after
+executing it.  (Chrome actually does align the non-editable elements, but
+that's just a bug.)  Chrome further returns false for justify* if the
+justification is just the default inherited justification, e.g., left for LTR.
+This doesn't seem to make sense either.
+
+It's not actually clear how much sense state makes here.  Most commands that
+have a state that's not always false work differently depending on what the
+state is, generally because they toggle something (bold, lists, etc.).  The
+justify commands behave the same regardless of state.  Still, I'll follow
+Gecko/WebKit in supporting the command.
+-->
+<ol>
+  <li><span>Block-extend</span> the <span>active range</span>, and let <var>new
+  range</var> be the result.
+
+  <li>Let <var>node list</var> be a list of all <span>editable</span> [[nodes]]
+  [[contained]] in <var>new range</var>.
+
+  <li>If <var>node list</var> is empty, return "none".
+
+  <li>Let <var>state</var> be "none".
+
+  <li>For each <var>node</var> in <var>node list</var>:
+
+  <ol>
+    <li>While <var>node</var> is neither null nor an [[element]], or it is an
+    [[element]] but its "display" property has [[compval]] "inline" or "none",
+    set <var>node</var> to its [[parent]].
+
+    <li>If <var>node</var> is not an [[element]] or its "display" property has
+    [[compval]] "inline" or "none", continue with the next <var>node</var>.
+
+    <li>If <var>state</var> is "none", set <var>state</var> to either "center",
+    "justify", "left", or "right", depending on the [[compval]] of
+    <var>node</var>'s "text-align" property.
+
+    <p class=XXX>What to do for "start" "auto" etc. currently undefined.  We
+    assume something CSS 2.1-ish.
+
+    <li>If <var>state</var> is different from the [[compval]] of
+    <var>node</var>'s "text-align" property, return "none".
+  </ol>
+
+  <li>Return <var>state</var>.
+</ol>
 <!-- @} -->
 
 <h3>Allowed children</h3>
@@ -4471,7 +4531,7 @@
     <li>Unset the CSS property "text-align" on <var>element</var>, if it's set
     by a [[style]] attribute.
 
-    <li>If <var>element</var> is a [[div]] or <code
+    <li>If <var>element</var> is a [[div]] or [[span]] or <code
     data-anolis-spec=html>center</code> with no attributes, remove it,
     <span>preserving its descendants</span>.
 
@@ -4491,11 +4551,12 @@
   <li>For each [[node]] <var>node</var> [[contained]] in <var>new range</var>,
   append <var>node</var> to <var>node list</var> if the last member of
   <var>node list</var> (if any) is not an [[ancestor]] of <var>node</var>;
-  <var>node</var> is <span>editable</span>; and either <var>node</var> is an
-  [[element]] and the CSS property "text-align" does not compute to
-  <var>alignment</var> on it, or it is not an [[element]], but its [[parent]]
-  is an [[element]], and the CSS property "text-align" does not compute to
-  <var>alignment</var> on its [[parent]].
+  <var>node</var> is <span>editable</span>; <var>node</var> is an <span>allowed
+  child</span> of "div"; and either <var>node</var> is an [[element]] and the
+  CSS property "text-align" does not compute to <var>alignment</var> on it, or
+  it is not an [[element]], but its [[parent]] is an [[element]], and the CSS
+  property "text-align" does not compute to <var>alignment</var> on its
+  [[parent]].
   <!-- Of tested browsers, only Chrome 13 dev seems to not apply the alignment
   to nodes that are already aligned.  Even then, it does apply it if the
   alignment is just inherited from the root. -->
@@ -6037,24 +6098,36 @@
 <!-- @{ -->
 <p><span>Action</span>: <span>Justify the selection</span> with
 <var>alignment</var> "center".
+
+<p><span>State</span>: True if the <span>current alignment state</span> is
+"center", otherwise false.
 <!-- @} -->
 
 <h3><dfn>The <code title>justifyFull</code> command</dfn></h3>
 <!-- @{ -->
 <p><span>Action</span>: <span>Justify the selection</span> with
 <var>alignment</var> "justify".
+
+<p><span>State</span>: True if the <span>current alignment state</span> is
+"justify", otherwise false.
 <!-- @} -->
 
 <h3><dfn>The <code title>justifyLeft</code> command</dfn></h3>
 <!-- @{ -->
 <p><span>Action</span>: <span>Justify the selection</span> with
 <var>alignment</var> "left".
+
+<p><span>State</span>: True if the <span>current alignment state</span> is
+"left", otherwise false.
 <!-- @} -->
 
 <h3><dfn>The <code title>justifyRight</code> command</dfn></h3>
 <!-- @{ -->
 <p><span>Action</span>: <span>Justify the selection</span> with
 <var>alignment</var> "right".
+
+<p><span>State</span>: True if the <span>current alignment state</span> is
+"right", otherwise false.
 <!-- @} -->
 
 <h3><dfn>The <code title>outdent</code> command</dfn></h3>
--- a/tests.js	Mon Jun 27 12:56:46 2011 -0600
+++ b/tests.js	Mon Jun 27 14:11:11 2011 -0600
@@ -2197,10 +2197,37 @@
 		'<h1>foo[bar]baz</h1><p>extra',
 		'<pre>foo[bar]baz</pre><p>extra',
 		'<xmp>foo[bar]baz</xmp><p>extra',
-		'{<table><tr><td>foo<td>bar</table>}<p>extra',
 		'<center><p>[foo]<p>bar</center><p>extra',
 		'<center><p>[foo<p>bar]</center><p>extra',
 
+		'<table><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table align=center><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table align=center><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=center><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=center><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=center data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table align=center><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table><tbody align=center><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody align=center><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody align=center><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody align=center data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody align=center><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tbody align=center><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table><tbody><tr align=center><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody><tr align=center data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody><tr align=center data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody data-start=0 data-end=1><tr align=center><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody><tr align=center><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tr align=center><td>foo<td>bar<td>baz</table>}<p>extra',
+
 		'<div align=center><p>[foo]<p>bar</div><p>extra',
 		'<div align=center><p>[foo<p>bar}</div><p>extra',
 		'<div style=text-align:center><p>[foo]<p>bar</div><p>extra',
@@ -2240,6 +2267,17 @@
 		'<p>[foo]<p align=center>bar<p>extra',
 		'<p align=center>foo<p>[bar]<p align=center>baz<p>extra',
 
+		'<center>[foo</center>bar]<p>extra',
+		'<center>fo[o</center>b]ar<p>extra',
+		'<div align=center>[foo</div>bar]<p>extra',
+		'<div align=center>fo[o</div>b]ar<p>extra',
+		'<div style=text-align:center>[foo</div>bar]<p>extra',
+		'<div style=text-align:center>fo[o</div>b]ar<p>extra',
+		'<span style=text-align:center>[foo]</span><p>extra',
+		'<span style=text-align:center>f[o]o</span><p>extra',
+
+		'<div style=text-align:center>[foo<div style=text-align:left contenteditable=false>bar</div>baz]</div><p>extra',
+
 		'<div align=nonsense><p>[foo]</div><p>extra',
 		'<div style=text-align:inherit><p>[foo]</div><p>extra',
 		'<quasit align=right><p>[foo]</p></quasit><p>extra',
@@ -2257,10 +2295,37 @@
 		'<h1>foo[bar]baz</h1><p>extra',
 		'<pre>foo[bar]baz</pre><p>extra',
 		'<xmp>foo[bar]baz</xmp><p>extra',
-		'{<table><tr><td>foo<td>bar</table>}<p>extra',
 		'<center><p>[foo]<p>bar</center><p>extra',
 		'<center><p>[foo<p>bar]</center><p>extra',
 
+		'<table><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table align=justify><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table align=justify><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=justify><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=justify><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=justify data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table align=justify><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table><tbody align=justify><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody align=justify><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody align=justify><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody align=justify data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody align=justify><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tbody align=justify><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table><tbody><tr align=justify><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody><tr align=justify data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody><tr align=justify data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody data-start=0 data-end=1><tr align=justify><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody><tr align=justify><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tr align=justify><td>foo<td>bar<td>baz</table>}<p>extra',
+
 		'<div align=center><p>[foo]<p>bar</div><p>extra',
 		'<div align=center><p>[foo<p>bar}</div><p>extra',
 		'<div style=text-align:center><p>[foo]<p>bar</div><p>extra',
@@ -2297,6 +2362,15 @@
 		'<p>[foo]<p align=justify>bar<p>extra',
 		'<p align=justify>foo<p>[bar]<p align=justify>baz<p>extra',
 
+		'<div align=justify>[foo</div>bar]<p>extra',
+		'<div align=justify>fo[o</div>b]ar<p>extra',
+		'<div style=text-align:justify>[foo</div>bar]<p>extra',
+		'<div style=text-align:justify>fo[o</div>b]ar<p>extra',
+		'<span style=text-align:justify>[foo]</span><p>extra',
+		'<span style=text-align:justify>f[o]o</span><p>extra',
+
+		'<div style=text-align:justify>[foo<div style=text-align:left contenteditable=false>bar</div>baz]</div><p>extra',
+
 		'<div align=nonsense><p>[foo]</div><p>extra',
 		'<div style=text-align:inherit><p>[foo]</div><p>extra',
 		'<quasit align=center><p>[foo]</p></quasit><p>extra',
@@ -2314,10 +2388,37 @@
 		'<h1>foo[bar]baz</h1><p>extra',
 		'<pre>foo[bar]baz</pre><p>extra',
 		'<xmp>foo[bar]baz</xmp><p>extra',
-		'{<table><tr><td>foo<td>bar</table>}<p>extra',
 		'<center><p>[foo]<p>bar</center><p>extra',
 		'<center><p>[foo<p>bar]</center><p>extra',
 
+		'<table><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table align=left><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table align=left><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=left><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=left><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=left data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table align=left><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table><tbody align=left><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody align=left><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody align=left><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody align=left data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody align=left><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tbody align=left><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table><tbody><tr align=left><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody><tr align=left data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody><tr align=left data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody data-start=0 data-end=1><tr align=left><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody><tr align=left><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tr align=left><td>foo<td>bar<td>baz</table>}<p>extra',
+
 		'<div align=center><p>[foo]<p>bar</div><p>extra',
 		'<div align=center><p>[foo<p>bar}</div><p>extra',
 		'<div style=text-align:center><p>[foo]<p>bar</div><p>extra',
@@ -2354,6 +2455,15 @@
 		'<p>[foo]<p align=left>bar<p>extra',
 		'<p align=left>foo<p>[bar]<p align=left>baz<p>extra',
 
+		'<div align=left>[foo</div>bar]<p>extra',
+		'<div align=left>fo[o</div>b]ar<p>extra',
+		'<div style=text-align:left>[foo</div>bar]<p>extra',
+		'<div style=text-align:left>fo[o</div>b]ar<p>extra',
+		'<span style=text-align:left>[foo]</span><p>extra',
+		'<span style=text-align:left>f[o]o</span><p>extra',
+
+		'<div style=text-align:left>[foo<div style=text-align:left contenteditable=false>bar</div>baz]</div><p>extra',
+
 		'<div align=nonsense><p>[foo]</div><p>extra',
 		'<div style=text-align:inherit><p>[foo]</div><p>extra',
 		'<quasit align=center><p>[foo]</p></quasit><p>extra',
@@ -2371,10 +2481,37 @@
 		'<h1>foo[bar]baz</h1><p>extra',
 		'<pre>foo[bar]baz</pre><p>extra',
 		'<xmp>foo[bar]baz</xmp><p>extra',
-		'{<table><tr><td>foo<td>bar</table>}<p>extra',
 		'<center><p>[foo]<p>bar</center><p>extra',
 		'<center><p>[foo<p>bar]</center><p>extra',
 
+		'<table><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table align=right><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table align=right><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=right><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=right><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table align=right data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table align=right><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table><tbody align=right><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody align=right><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody align=right><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody align=right data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody align=right><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tbody align=right><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<table><tbody><tr align=right><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody><tr align=right data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody><tr align=right data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody data-start=0 data-end=1><tr align=right><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody><tr align=right><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tr align=right><td>foo<td>bar<td>baz</table>}<p>extra',
+
 		'<div align=center><p>[foo]<p>bar</div><p>extra',
 		'<div align=center><p>[foo<p>bar}</div><p>extra',
 		'<div style=text-align:center><p>[foo]<p>bar</div><p>extra',
@@ -2411,6 +2548,15 @@
 		'<p>[foo]<p align=right>bar<p>extra',
 		'<p align=right>foo<p>[bar]<p align=right>baz<p>extra',
 
+		'<div align=right>[foo</div>bar]<p>extra',
+		'<div align=right>fo[o</div>b]ar<p>extra',
+		'<div style=text-align:right>[foo</div>bar]<p>extra',
+		'<div style=text-align:right>fo[o</div>b]ar<p>extra',
+		'<span style=text-align:right>[foo]</span><p>extra',
+		'<span style=text-align:right>f[o]o</span><p>extra',
+
+		'<div style=text-align:right>[foo<div style=text-align:left contenteditable=false>bar</div>baz]</div><p>extra',
+
 		'<div align=nonsense><p>[foo]</div><p>extra',
 		'<div style=text-align:inherit><p>[foo]</div><p>extra',
 		'<quasit align=center><p>[foo]</p></quasit><p>extra',