summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/ui
diff options
context:
space:
mode:
authorMaciej Przepióra <matthew@vaadin.com>2015-09-16 10:48:31 +0200
committerVaadin Code Review <review@vaadin.com>2015-09-24 07:46:38 +0000
commit5c875228d9025f4c779ffc26c3eb0f7b561061e5 (patch)
tree2e3a0d230478ef7d9090db31597e440f3f1738e7 /server/src/com/vaadin/ui
parent8006d359acce4fb31e7210ed2f20f1da3d7a1cd0 (diff)
downloadvaadin-framework-5c875228d9025f4c779ffc26c3eb0f7b561061e5.tar.gz
vaadin-framework-5c875228d9025f4c779ffc26c3eb0f7b561061e5.zip
Support HTML entities when reading/writing declarative format #18882
Change-Id: I08099673c5dd0d688d3243e5fd169edb05f046f3
Diffstat (limited to 'server/src/com/vaadin/ui')
-rw-r--r--server/src/com/vaadin/ui/AbstractSelect.java10
-rw-r--r--server/src/com/vaadin/ui/Button.java16
-rw-r--r--server/src/com/vaadin/ui/Grid.java10
-rw-r--r--server/src/com/vaadin/ui/Label.java14
-rw-r--r--server/src/com/vaadin/ui/OptionGroup.java9
-rw-r--r--server/src/com/vaadin/ui/Table.java12
-rw-r--r--server/src/com/vaadin/ui/TextArea.java5
-rw-r--r--server/src/com/vaadin/ui/declarative/DesignFormatter.java51
8 files changed, 107 insertions, 20 deletions
diff --git a/server/src/com/vaadin/ui/AbstractSelect.java b/server/src/com/vaadin/ui/AbstractSelect.java
index fdf819f031..bbd486a8f9 100644
--- a/server/src/com/vaadin/ui/AbstractSelect.java
+++ b/server/src/com/vaadin/ui/AbstractSelect.java
@@ -55,6 +55,7 @@ import com.vaadin.shared.ui.dd.VerticalDropLocation;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.declarative.DesignException;
+import com.vaadin.ui.declarative.DesignFormatter;
/**
* <p>
@@ -2232,12 +2233,13 @@ public abstract class AbstractSelect extends AbstractField<Object> implements
}
String itemId;
+ String caption = DesignFormatter.unencodeFromTextNode(child.html());
if (child.hasAttr("item-id")) {
itemId = child.attr("item-id");
addItem(itemId);
- setItemCaption(itemId, child.html());
+ setItemCaption(itemId, caption);
} else {
- addItem(itemId = child.html());
+ addItem(itemId = caption);
}
if (child.hasAttr("icon")) {
@@ -2300,10 +2302,10 @@ public abstract class AbstractSelect extends AbstractField<Object> implements
String caption = getItemCaption(itemId);
if (caption != null && !caption.equals(itemId.toString())) {
- element.html(caption);
+ element.html(DesignFormatter.encodeForTextNode(caption));
element.attr("item-id", itemId.toString());
} else {
- element.html(itemId.toString());
+ element.html(DesignFormatter.encodeForTextNode(itemId.toString()));
}
Resource icon = getItemIcon(itemId);
diff --git a/server/src/com/vaadin/ui/Button.java b/server/src/com/vaadin/ui/Button.java
index a918780a60..e5ea05a1d3 100644
--- a/server/src/com/vaadin/ui/Button.java
+++ b/server/src/com/vaadin/ui/Button.java
@@ -22,9 +22,9 @@ import java.util.Collection;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
+import org.jsoup.parser.Parser;
import com.vaadin.event.Action;
-import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
import com.vaadin.event.ShortcutAction;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.ShortcutAction.ModifierKey;
@@ -35,6 +35,7 @@ import com.vaadin.shared.ui.button.ButtonServerRpc;
import com.vaadin.shared.ui.button.ButtonState;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignFormatter;
import com.vaadin.util.ReflectTools;
/**
@@ -573,14 +574,19 @@ public class Button extends AbstractFocusable implements
public void readDesign(Element design, DesignContext designContext) {
super.readDesign(design, designContext);
Attributes attr = design.attributes();
- String content = design.html();
- setCaption(content);
+ String content;
// plain-text (default is html)
Boolean plain = DesignAttributeHandler.readAttribute(
DESIGN_ATTR_PLAIN_TEXT, attr, Boolean.class);
if (plain == null || !plain) {
setHtmlContentAllowed(true);
+ content = design.html();
+ } else {
+ // content is not intended to be interpreted as HTML,
+ // so html entities need to be decoded
+ content = DesignFormatter.unencodeFromTextNode(design.html());
}
+ setCaption(content);
if (attr.hasKey("icon-alt")) {
setIconAlternateText(DesignAttributeHandler.readAttribute(
"icon-alt", attr, String.class));
@@ -630,6 +636,10 @@ public class Button extends AbstractFocusable implements
// plain-text (default is html)
if (!isHtmlContentAllowed()) {
design.attr(DESIGN_ATTR_PLAIN_TEXT, "");
+ // encode HTML entities
+ if (content != null) {
+ design.html(DesignFormatter.encodeForTextNode(content));
+ }
}
// icon-alt
DesignAttributeHandler.writeAttribute("icon-alt", attr,
diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java
index f762fe0664..ab3708ee28 100644
--- a/server/src/com/vaadin/ui/Grid.java
+++ b/server/src/com/vaadin/ui/Grid.java
@@ -108,6 +108,7 @@ import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.declarative.DesignException;
+import com.vaadin.ui.declarative.DesignFormatter;
import com.vaadin.ui.renderers.HtmlRenderer;
import com.vaadin.ui.renderers.Renderer;
import com.vaadin.ui.renderers.TextRenderer;
@@ -2273,7 +2274,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
setHtml(cellElement.html());
}
} else {
- setText(cellElement.html());
+ // text – need to unescape HTML entities
+ setText(DesignFormatter.unencodeFromTextNode(cellElement
+ .html()));
}
}
}
@@ -4981,7 +4984,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
/*
* This method is a workaround for the fact that Vaadin re-applies
* widget dimensions (height/width) on each state change event. The
- * original design was to have setHeight an setHeightByRow be equals,
+ * original design was to have setHeight and setHeightByRow be equals,
* and whichever was called the latest was considered in effect.
*
* But, because of Vaadin always calling setHeight on the widget, this
@@ -6752,7 +6755,8 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
Object value = datasource.getItem(itemId)
.getItemProperty(c.getPropertyId()).getValue();
tableRow.appendElement("td").append(
- (value != null ? value.toString() : ""));
+ (value != null ? DesignFormatter
+ .encodeForTextNode(value.toString()) : ""));
}
}
}
diff --git a/server/src/com/vaadin/ui/Label.java b/server/src/com/vaadin/ui/Label.java
index a6ee11bdd5..0ead70933b 100644
--- a/server/src/com/vaadin/ui/Label.java
+++ b/server/src/com/vaadin/ui/Label.java
@@ -31,6 +31,7 @@ import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.shared.ui.label.LabelState;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignFormatter;
/**
* Label component for showing non-editable short texts.
@@ -585,14 +586,18 @@ public class Label extends AbstractComponent implements Property<String>,
public void readDesign(Element design, DesignContext designContext) {
super.readDesign(design, designContext);
String innerHtml = design.html();
- if (innerHtml != null && !"".equals(innerHtml)) {
- setValue(innerHtml);
- }
- if (design.hasAttr(DESIGN_ATTR_PLAIN_TEXT)) {
+ boolean plainText = design.hasAttr(DESIGN_ATTR_PLAIN_TEXT);
+ if (plainText) {
setContentMode(ContentMode.TEXT);
} else {
setContentMode(ContentMode.HTML);
}
+ if (innerHtml != null && !"".equals(innerHtml)) {
+ if (plainText) {
+ innerHtml = DesignFormatter.unencodeFromTextNode(innerHtml);
+ }
+ setValue(innerHtml);
+ }
}
/*
@@ -625,6 +630,7 @@ public class Label extends AbstractComponent implements Property<String>,
// plain-text (default is html)
if (getContentMode() == ContentMode.TEXT) {
design.attr(DESIGN_ATTR_PLAIN_TEXT, "");
+ design.html(DesignFormatter.encodeForTextNode(getValue()));
}
}
}
diff --git a/server/src/com/vaadin/ui/OptionGroup.java b/server/src/com/vaadin/ui/OptionGroup.java
index 10c263aca0..183fc36500 100644
--- a/server/src/com/vaadin/ui/OptionGroup.java
+++ b/server/src/com/vaadin/ui/OptionGroup.java
@@ -33,6 +33,7 @@ import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.shared.ui.optiongroup.OptionGroupConstants;
import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignFormatter;
/**
* Configures select to be used as an option group.
@@ -276,6 +277,14 @@ public class OptionGroup extends AbstractSelect implements
if (!isItemEnabled(itemId)) {
elem.attr("disabled", "");
}
+ if (isHtmlContentAllowed()) {
+ // need to unencode HTML entities. AbstractSelect.writeDesign can't
+ // check if HTML content is allowed, so it always encodes entities
+ // like '>', '<' and '&'; in case HTML content is allowed this is
+ // undesirable so we need to unencode entities. Entities other than
+ // '<' and '>' will be taken care by Jsoup.
+ elem.html(DesignFormatter.unencodeFromTextNode(elem.html()));
+ }
return elem;
}
diff --git a/server/src/com/vaadin/ui/Table.java b/server/src/com/vaadin/ui/Table.java
index 10d1c45ab6..5f22ec46ab 100644
--- a/server/src/com/vaadin/ui/Table.java
+++ b/server/src/com/vaadin/ui/Table.java
@@ -69,6 +69,7 @@ import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.declarative.DesignException;
+import com.vaadin.ui.declarative.DesignFormatter;
import com.vaadin.util.ReflectTools;
/**
@@ -279,7 +280,7 @@ public class Table extends AbstractSelect implements Action.Container,
ICON_ONLY(ItemCaptionMode.ICON_ONLY),
/**
* Row caption mode: Item captions are read from property specified with
- * {@link #setItemCaptionPropertyId(Object)}.
+ * {@link #setItemCaptionPropertyId(Object)} .
*/
PROPERTY(ItemCaptionMode.PROPERTY);
@@ -2055,7 +2056,9 @@ public class Table extends AbstractSelect implements Action.Container,
} else if (minPageBufferIndex < pageBufferFirstIndex) {
newCachedRowCount -= pageBufferFirstIndex - minPageBufferIndex;
}
- /* calculate the internal location of the new rows within the new cache */
+ /*
+ * calculate the internal location of the new rows within the new cache
+ */
int firstIndexInNewPageBuffer = firstIndex - pageBufferFirstIndex
- rowsFromBeginning;
@@ -6199,7 +6202,8 @@ public class Table extends AbstractSelect implements Action.Container,
}
Iterator<?> propertyIt = propertyIds.iterator();
for (Element e : elems) {
- String columnValue = e.html();
+ String columnValue = DesignFormatter.unencodeFromTextNode(e
+ .html());
Object propertyId = propertyIt.next();
if (header) {
setColumnHeader(propertyId, columnValue);
@@ -6240,7 +6244,7 @@ public class Table extends AbstractSelect implements Action.Container,
}
Object[] data = new String[cells.size()];
for (int c = 0; c < cells.size(); ++c) {
- data[c] = cells.get(c).html();
+ data[c] = DesignFormatter.unencodeFromTextNode(cells.get(c).html());
}
Object itemId = addItem(data,
diff --git a/server/src/com/vaadin/ui/TextArea.java b/server/src/com/vaadin/ui/TextArea.java
index b4dfb209e8..75ecc19d40 100644
--- a/server/src/com/vaadin/ui/TextArea.java
+++ b/server/src/com/vaadin/ui/TextArea.java
@@ -21,6 +21,7 @@ import org.jsoup.nodes.Element;
import com.vaadin.data.Property;
import com.vaadin.shared.ui.textarea.TextAreaState;
import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignFormatter;
/**
* A text field that supports multi line editing.
@@ -145,7 +146,7 @@ public class TextArea extends AbstractTextField {
@Override
public void readDesign(Element design, DesignContext designContext) {
super.readDesign(design, designContext);
- setValue(design.html());
+ setValue(DesignFormatter.unencodeFromTextNode(design.html()));
}
/*
@@ -157,7 +158,7 @@ public class TextArea extends AbstractTextField {
@Override
public void writeDesign(Element design, DesignContext designContext) {
super.writeDesign(design, designContext);
- design.html(getValue());
+ design.html(DesignFormatter.encodeForTextNode(getValue()));
}
@Override
diff --git a/server/src/com/vaadin/ui/declarative/DesignFormatter.java b/server/src/com/vaadin/ui/declarative/DesignFormatter.java
index 73c45caed4..db901329cb 100644
--- a/server/src/com/vaadin/ui/declarative/DesignFormatter.java
+++ b/server/src/com/vaadin/ui/declarative/DesignFormatter.java
@@ -28,12 +28,15 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
+import org.jsoup.parser.Parser;
+
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.StringToBigDecimalConverter;
import com.vaadin.data.util.converter.StringToDoubleConverter;
import com.vaadin.data.util.converter.StringToFloatConverter;
import com.vaadin.event.ShortcutAction;
import com.vaadin.server.Resource;
+import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.declarative.converters.DesignDateConverter;
import com.vaadin.ui.declarative.converters.DesignEnumConverter;
import com.vaadin.ui.declarative.converters.DesignObjectConverter;
@@ -360,4 +363,52 @@ public class DesignFormatter implements Serializable {
return findConverterFor(sourceType, false);
}
+ /**
+ * <p>
+ * Encodes <em>some</em> special characters in a given input String to make
+ * it ready to be written as contents of a text node. WARNING: this will
+ * e.g. encode "&lt;someTag&gt;" to "&amp;lt;someTag&amp;gt;" as this method
+ * doesn't do any parsing and assumes that there are no intended HTML
+ * elements in the input. Only some entities are actually encoded:
+ * &amp;,&lt;, &gt; It's assumed that other entities are taken care of by
+ * Jsoup.
+ * </p>
+ * <p>
+ * Typically, this method will be used by components to encode data (like
+ * option items in {@link AbstractSelect}) when dumping to HTML format
+ * </p>
+ *
+ * @since
+ * @param input
+ * String to be encoded
+ * @return String with &amp;,&lt; and &gt; replaced with their HTML entities
+ */
+ public static String encodeForTextNode(String input) {
+ if (input == null) {
+ return null;
+ }
+ return input.replace("&", "&amp;").replace(">", "&gt;")
+ .replace("<", "&lt;");
+ }
+
+ /**
+ * <p>
+ * Decodes HTML entities in a text from text node and replaces them with
+ * actual characters.
+ * </p>
+ *
+ * <p>
+ * Typically this method will be used by components to read back data (like
+ * option items in {@link AbstractSelect}) from HTML. Note that this method
+ * unencodes more characters than {@link #encodeForTextNode(String)} encodes
+ * </p>
+ *
+ * @since
+ * @param input
+ * @return
+ */
+ public static String unencodeFromTextNode(String input) {
+ return Parser.unescapeEntities(input, false);
+ }
+
}