indicate if morphing is possible
IVexWidget.canMorph(QualifiedName) indicates if morphing the current
element into an element with the given name is possible.
Change-Id: I4a2f15124ed9014dbf00f543dbb5b4f1a8b565ca
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/validator/DTDValidatorTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/validator/DTDValidatorTest.java
index 0e7674d..76d60b1 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/validator/DTDValidatorTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/validator/DTDValidatorTest.java
@@ -28,7 +28,6 @@
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.vex.core.internal.dom.Document;
-import org.eclipse.vex.core.internal.validator.WTPVEXValidator;
import org.eclipse.vex.core.provisional.dom.AttributeDefinition;
import org.eclipse.vex.core.provisional.dom.IDocument;
import org.eclipse.vex.core.provisional.dom.IElement;
@@ -272,6 +271,11 @@
assertTrue("isRequired should be true", ad.isRequired());
}
+ @Test
+ public void givenEmptyElement_shouldBePartiallyValid() throws Exception {
+ assertTrue(validator.isValidSequence(new QualifiedName(null, "section"), Collections.<QualifiedName> emptyList(), true));
+ }
+
private Map<QualifiedName, AttributeDefinition> getAttributeMap(final IElement element) {
final List<AttributeDefinition> atts = validator.getAttributeDefinitions(element);
final Map<QualifiedName, AttributeDefinition> adMap = new HashMap<QualifiedName, AttributeDefinition>();
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SimpleEditingTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SimpleEditingTest.java
index 2d4a9db..f8b4864 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SimpleEditingTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SimpleEditingTest.java
@@ -4,7 +4,7 @@
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
- *
+ *
* Contributors:
* Florian Thienel - initial API and implementation
* Carsten Hiesserich - additional tests (bug 407827, 409032)
@@ -14,6 +14,7 @@
import static org.eclipse.vex.core.internal.widget.VexWidgetTest.PARA;
import static org.eclipse.vex.core.internal.widget.VexWidgetTest.PRE;
import static org.eclipse.vex.core.internal.widget.VexWidgetTest.TITLE;
+import static org.eclipse.vex.core.internal.widget.VexWidgetTest.assertCanMorphOnlyTo;
import static org.eclipse.vex.core.internal.widget.VexWidgetTest.createDocumentWithDTD;
import static org.eclipse.vex.core.internal.widget.VexWidgetTest.getCurrentXML;
import static org.eclipse.vex.core.tests.TestResources.TEST_DTD;
@@ -29,6 +30,7 @@
import org.eclipse.vex.core.internal.css.CssWhitespacePolicy;
import org.eclipse.vex.core.internal.css.StyleSheet;
import org.eclipse.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.vex.core.internal.io.XMLFragment;
import org.eclipse.vex.core.internal.undo.CannotRedoException;
import org.eclipse.vex.core.provisional.dom.DocumentValidationException;
import org.eclipse.vex.core.provisional.dom.IDocumentFragment;
@@ -597,6 +599,100 @@
assertEquals(expectedXml, getCurrentXML(widget));
}
+ @Test
+ public void whenReadOnly_cannotMorph() throws Exception {
+ widget.insertElement(TITLE);
+ widget.setReadOnly(true);
+ assertFalse(widget.canMorph(PARA));
+ }
+
+ @Test(expected = ReadOnlyException.class)
+ public void whenReadOnly_shouldNotMorph() throws Exception {
+ widget.insertElement(TITLE);
+ widget.setReadOnly(true);
+ widget.morph(PARA);
+ }
+
+ @Test
+ public void cannotMorphRootElement() throws Exception {
+ assertFalse(widget.canMorph(TITLE));
+ }
+
+ @Test
+ public void morphEmptyElement() throws Exception {
+ widget.insertElement(TITLE);
+
+ assertTrue(widget.canMorph(PARA));
+ assertCanMorphOnlyTo(widget, PARA);
+ widget.morph(PARA);
+ }
+
+ @Test
+ public void givenElementWithText_whenMorphing_shouldPreserveText() throws Exception {
+ widget.insertElement(TITLE);
+ widget.insertText("text");
+
+ assertTrue(widget.canMorph(PARA));
+ widget.morph(PARA);
+
+ widget.selectAll();
+ assertEquals("<section><para>text</para></section>", new XMLFragment(widget.getSelectedFragment()).getXML());
+ }
+
+ @Test
+ public void givenElementWithChildren_whenStructureIsInvalidAfterMorphing_cannotMorph() throws Exception {
+ widget.insertElement(PARA);
+ widget.insertText("before");
+ widget.insertElement(PRE);
+ widget.insertText("within");
+ widget.moveBy(1);
+ widget.insertText("after");
+
+ assertFalse(widget.canMorph(TITLE));
+ }
+
+ @Test
+ public void givenElementWithChildren_whenStructureIsInvalidAfterMorphing_shouldNotProvideElementToMorph() throws Exception {
+ widget.insertElement(PARA);
+ widget.insertText("before");
+ widget.insertElement(PRE);
+ widget.insertText("within");
+ widget.moveBy(1);
+ widget.insertText("after");
+
+ assertCanMorphOnlyTo(widget /* nothing */);
+ }
+
+ @Test(expected = CannotRedoException.class)
+ public void givenElementWithChildren_whenStructureIsInvalidAfterMorphing_shouldNotMorph() throws Exception {
+ widget.insertElement(PARA);
+ widget.insertText("before");
+ widget.insertElement(PRE);
+ widget.insertText("within");
+ widget.moveBy(1);
+ widget.insertText("after");
+
+ widget.morph(TITLE);
+ }
+
+ @Test
+ public void givenAlternativeElement_whenElementIsNotAllowedAtCurrentInsertionPosition_cannotMorph() throws Exception {
+ widget.insertElement(PARA);
+ widget.moveBy(1);
+ widget.insertElement(PARA);
+
+ assertFalse(widget.canMorph(TITLE));
+ }
+
+ @Test
+ public void givenAlternativeElement_whenElementIsNotAllowedAtCurrentInsertionPosition_shouldNotProvideElementToMorph() throws Exception {
+ widget.insertElement(PARA);
+ widget.moveBy(1);
+ widget.insertElement(PARA);
+
+ assertCanMorphOnlyTo(widget /* nothing */);
+ }
+
private static StyleSheet readTestStyleSheet() throws IOException {
return new StyleSheetReader().read(TestResources.get("test.css"));
}
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/VexWidgetTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/VexWidgetTest.java
index 474fd90..59c53fb 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/VexWidgetTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/VexWidgetTest.java
@@ -4,7 +4,7 @@
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
- *
+ *
* Contributors:
* Florian Thienel - bug 315914, initial implementation
*******************************************************************************/
@@ -143,12 +143,18 @@
return document;
}
- public static void assertCanInsertOnly(final IVexWidget widget, final String... elementNames) {
+ public static void assertCanInsertOnly(final IVexWidget widget, final Object... elementNames) {
final String[] expected = sortedCopyOf(elementNames);
final String[] actual = sortedCopyOf(widget.getValidInsertElements());
assertEquals(Arrays.toString(expected), Arrays.toString(actual));
}
+ public static void assertCanMorphOnlyTo(final IVexWidget widget, final Object... elementNames) {
+ final String[] expected = sortedCopyOf(elementNames);
+ final String[] actual = sortedCopyOf(widget.getValidMorphElements());
+ assertEquals(Arrays.toString(expected), Arrays.toString(actual));
+ }
+
public static void assertCannotInsertAnything(final IVexWidget widget) {
assertCanInsertOnly(widget /* nothing */);
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/validator/WTPVEXValidator.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/validator/WTPVEXValidator.java
index fdac850..f11f434 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/validator/WTPVEXValidator.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/validator/WTPVEXValidator.java
@@ -364,6 +364,10 @@
}
public boolean isValidSequence(final QualifiedName element, final List<QualifiedName> nodes, final boolean partial) {
+ if (partial && nodes.isEmpty()) {
+ return true;
+ }
+
final CMNode parent = getSchema(element.getQualifier()).getElements().getNamedItem(element.getLocalName());
if (!(parent instanceof CMElementDeclaration)) {
return true;
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/BaseVexWidget.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/BaseVexWidget.java
index 57c2c74..d9a220d 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/BaseVexWidget.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/BaseVexWidget.java
@@ -4,11 +4,11 @@
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
- *
+ *
* Contributors:
* John Krasnay - initial API and implementation
* Igor Jacy Lino Campista - Java 5 warnings fixed (bug 311325)
- * Holger Voormann - bug 315914: content assist should only show elements
+ * Holger Voormann - bug 315914: content assist should only show elements
* valid in the current context
* Carsten Hiesserich - handling of elements within comments (bug 407801)
* Carsten Hiesserich - allow insertion of newline into pre elements (bug 407827)
@@ -297,11 +297,10 @@
}
final IElement parent = document.getElementForInsertionAt(startOffset);
- final List<QualifiedName> seq1 = Node.getNodeNames(parent.children().before(startOffset));
- final List<QualifiedName> seq2 = nodeNames;
- final List<QualifiedName> seq3 = Node.getNodeNames(parent.children().after(endOffset));
+ final List<QualifiedName> nodesBefore = Node.getNodeNames(parent.children().before(startOffset));
+ final List<QualifiedName> nodesAfter = Node.getNodeNames(parent.children().after(endOffset));
- return validator.isValidSequence(parent.getQualifiedName(), seq1, seq2, seq3, true);
+ return validator.isValidSequence(parent.getQualifiedName(), nodesBefore, nodeNames, nodesAfter, true);
}
public boolean canPaste() {
@@ -347,11 +346,11 @@
return false;
}
- final List<QualifiedName> seq1 = Node.getNodeNames(parent.children().before(element.getStartOffset()));
- final List<QualifiedName> seq2 = Node.getNodeNames(element.children());
- final List<QualifiedName> seq3 = Node.getNodeNames(parent.children().after(element.getEndOffset()));
+ final List<QualifiedName> nodesBefore = Node.getNodeNames(parent.children().before(element.getStartOffset()));
+ final List<QualifiedName> newNodes = Node.getNodeNames(element.children());
+ final List<QualifiedName> nodesAfter = Node.getNodeNames(parent.children().after(element.getEndOffset()));
- return validator.isValidSequence(parent.getQualifiedName(), seq1, seq2, seq3, true);
+ return validator.isValidSequence(parent.getQualifiedName(), nodesBefore, newNodes, nodesAfter, true);
}
public void copySelection() {
@@ -670,11 +669,7 @@
Collections.sort(candidates, new QualifiedNameComparator());
- final ElementName[] result = new ElementName[candidates.size()];
- int i = 0;
- for (final QualifiedName candidate : candidates) {
- result[i++] = new ElementName(candidate, parent.getNamespacePrefix(candidate.getQualifier()));
- }
+ final ElementName[] result = toElementNames(parent, candidates);
return result;
}
@@ -699,7 +694,7 @@
sequence.addAll(nodesBefore);
sequence.add(candidate);
sequence.addAll(nodesAfter);
- if (!validator.isValidSequence(parent.getQualifiedName(), sequence, true)) {
+ if (!canContainContent(validator, parent.getQualifiedName(), sequence)) {
iterator.remove();
}
}
@@ -708,7 +703,7 @@
private static void filterInvalidSelectionParents(final IValidator validator, final List<QualifiedName> selectedNodes, final List<QualifiedName> candidates) {
for (final Iterator<QualifiedName> iter = candidates.iterator(); iter.hasNext();) {
final QualifiedName candidate = iter.next();
- if (!validator.isValidSequence(candidate, selectedNodes, true)) {
+ if (!canContainContent(validator, candidate, selectedNodes)) {
iter.remove();
}
}
@@ -722,48 +717,6 @@
return debugging;
}
- public ElementName[] getValidMorphElements() {
- if (readOnly) {
- return new ElementName[0];
- }
-
- if (document == null) {
- return new ElementName[0];
- }
-
- final IValidator validator = document.getValidator();
- if (validator == null) {
- return new ElementName[0];
- }
-
- final IElement element = document.getElementForInsertionAt(getCaretOffset());
- final IElement parent = element.getParentElement();
- if (parent == null) {
- // can't morph the root
- return new ElementName[0];
- }
-
- final List<QualifiedName> candidates = createCandidatesList(validator, parent, IValidator.PCDATA, element.getQualifiedName());
-
- // root out those that can't contain the current content
- final List<QualifiedName> content = Node.getNodeNames(element.children());
-
- for (final Iterator<QualifiedName> iter = candidates.iterator(); iter.hasNext();) {
- final QualifiedName candidate = iter.next();
- if (!validator.isValidSequence(candidate, content, true)) {
- iter.remove();
- }
- }
-
- Collections.sort(candidates, new QualifiedNameComparator());
- final ElementName[] result = new ElementName[candidates.size()];
- int i = 0;
- for (final QualifiedName candidate : candidates) {
- result[i++] = new ElementName(candidate, parent.getNamespacePrefix(candidate.getQualifier()));
- }
- return result;
- }
-
private int getSelectionEnd() {
return selectionEnd;
}
@@ -1043,6 +996,97 @@
}
}
+ public ElementName[] getValidMorphElements() {
+ final IElement currentElement = document.getElementForInsertionAt(getCaretOffset());
+ if (!canMorphElement(currentElement)) {
+ return new ElementName[0];
+ }
+
+ final IValidator validator = document.getValidator();
+ final IElement parent = currentElement.getParentElement();
+ final List<QualifiedName> candidates = createCandidatesList(validator, parent, IValidator.PCDATA, currentElement.getQualifiedName());
+ if (candidates.isEmpty()) {
+ return new ElementName[0];
+ }
+
+ final List<QualifiedName> content = Node.getNodeNames(currentElement.children());
+ final List<QualifiedName> nodesBefore = Node.getNodeNames(parent.children().before(currentElement.getStartOffset()));
+ final List<QualifiedName> nodesAfter = Node.getNodeNames(parent.children().after(currentElement.getEndOffset()));
+
+ for (final Iterator<QualifiedName> iter = candidates.iterator(); iter.hasNext();) {
+ final QualifiedName candidate = iter.next();
+ if (!canContainContent(validator, candidate, content)) {
+ iter.remove();
+ } else if (!isValidChild(validator, parent.getQualifiedName(), candidate, nodesBefore, nodesAfter)) {
+ iter.remove();
+ }
+ }
+
+ Collections.sort(candidates, new QualifiedNameComparator());
+ return toElementNames(parent, candidates);
+ }
+
+ private static ElementName[] toElementNames(final IElement parent, final List<QualifiedName> candidates) {
+ final ElementName[] result = new ElementName[candidates.size()];
+ int i = 0;
+ for (final QualifiedName candidate : candidates) {
+ result[i++] = new ElementName(candidate, parent.getNamespacePrefix(candidate.getQualifier()));
+ }
+ return result;
+ }
+
+ private boolean canMorphElement(final IElement element) {
+ if (readOnly) {
+ return false;
+ }
+
+ if (document == null) {
+ return false;
+ }
+
+ if (document.getValidator() == null) {
+ return false;
+ }
+
+ if (element.getParentElement() == null) {
+ return false;
+ }
+
+ if (element == document.getRootElement()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static boolean canContainContent(final IValidator validator, final QualifiedName elementName, final List<QualifiedName> content) {
+ return validator.isValidSequence(elementName, content, true);
+ }
+
+ private static boolean isValidChild(final IValidator validator, final QualifiedName parentName, final QualifiedName elementName, final List<QualifiedName> nodesBefore,
+ final List<QualifiedName> nodesAfter) {
+ return validator.isValidSequence(parentName, nodesBefore, Arrays.asList(elementName), nodesAfter, true);
+ }
+
+ public boolean canMorph(final QualifiedName elementName) {
+ final IElement currentElement = document.getElementForInsertionAt(getCaretOffset());
+ if (!canMorphElement(currentElement)) {
+ return false;
+ }
+
+ final IValidator validator = document.getValidator();
+
+ if (!canContainContent(validator, elementName, Node.getNodeNames(currentElement.children()))) {
+ return false;
+ }
+
+ final IElement parent = currentElement.getParentElement();
+ final List<QualifiedName> nodesBefore = Node.getNodeNames(parent.children().before(currentElement.getStartOffset()));
+ final List<QualifiedName> nodesAfter = Node.getNodeNames(parent.children().after(currentElement.getEndOffset()));
+
+ return isValidChild(validator, parent.getQualifiedName(), elementName, nodesBefore, nodesAfter);
+ }
+
public void morph(final QualifiedName elementName) throws DocumentValidationException, ReadOnlyException {
if (readOnly) {
throw new ReadOnlyException(MessageFormat.format("Cannot morph to element {0}, because the editor is read-only.", elementName));
@@ -1542,11 +1586,11 @@
final int startOffset = element.getStartOffset();
final int endOffset = element.getEndOffset();
- final List<QualifiedName> seq1 = Node.getNodeNames(parent.children().before(startOffset));
- final List<QualifiedName> seq2 = Arrays.asList(element.getQualifiedName(), element.getQualifiedName());
- final List<QualifiedName> seq3 = Node.getNodeNames(parent.children().after(endOffset));
+ final List<QualifiedName> nodesBefore = Node.getNodeNames(parent.children().before(startOffset));
+ final List<QualifiedName> newNodes = Arrays.asList(element.getQualifiedName(), element.getQualifiedName());
+ final List<QualifiedName> nodesAfter = Node.getNodeNames(parent.children().after(endOffset));
- return validator.isValidSequence(parent.getQualifiedName(), seq1, seq2, seq3, true);
+ return validator.isValidSequence(parent.getQualifiedName(), nodesBefore, newNodes, nodesAfter, true);
}
public void split() throws DocumentValidationException, ReadOnlyException {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IVexWidget.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IVexWidget.java
index 5f8393d..ab936e2 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IVexWidget.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IVexWidget.java
@@ -589,6 +589,15 @@
boolean canUnwrap();
/**
+ * Indicates whether the current element can be morphed into the given element.
+ *
+ * @param elementName
+ * Qualified name of the element to morph the current element into.
+ * @return true if the current element can be morphed
+ */
+ boolean canMorph(QualifiedName elementName);
+
+ /**
* Replaces the current element with an element with the given name. The content of the element is preserved.
*
* @param elementName
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/VexWidget.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/VexWidget.java
index 34b5a8f..359c5cf 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/VexWidget.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/VexWidget.java
@@ -304,6 +304,10 @@
return impl.isDebugging();
}
+ public boolean canMorph(final QualifiedName elementName) {
+ return impl.canMorph(elementName);
+ }
+
public void morph(final QualifiedName elementName) throws DocumentValidationException {
impl.morph(elementName);
}