/**
  * {@inheritDoc}
  *
  * @see AbstractSelectionExecutable#execute(String)
  */
 public boolean execute(String param) {
   // Get the selected anchor
   AnchorElement selectedAnchor = LinkExecutableUtils.getSelectedAnchor(rta);
   if (selectedAnchor == null) {
     return false;
   }
   Range range = rta.getDocument().getSelection().getRangeAt(0);
   // unlink
   // but first check where is the selection. If the selection is a caret and is at one side of the
   // anchor, just
   // move the caret out instead of removing the link
   boolean moveSelection = range.isCollapsed();
   boolean isBeginning = false;
   boolean isEnd = false;
   if (moveSelection) {
     // check if it's at the beginning or at the end
     isBeginning =
         (domUtils.getFirstAncestor(
                     domUtils.getPreviousLeaf(range), LinkExecutableUtils.ANCHOR_TAG_NAME)
                 != selectedAnchor)
             && range.getStartOffset() == 0;
     isEnd =
         (domUtils.getFirstAncestor(
                     domUtils.getNextLeaf(range), LinkExecutableUtils.ANCHOR_TAG_NAME)
                 != selectedAnchor)
             && range.getEndOffset() == domUtils.getLength(range.getEndContainer());
   }
   if (moveSelection && (isEnd || isBeginning) && selectedAnchor.getOffsetWidth() > 0) {
     // cursor it's at the beginning or at the end, move it out of the anchor
     moveCaretOuside(rta, Element.as(selectedAnchor), isEnd);
   } else {
     Element.as(selectedAnchor).unwrap();
   }
   return true;
 }
 /**
  * Updates the meta data of the given anchor.
  *
  * @param anchor the anchor whose meta data will be updated
  * @param reference the new link reference
  * @param linkType the new link type
  */
 private void updateMetaData(AnchorElement anchor, String reference, LinkType linkType) {
   Document document = (Document) anchor.getOwnerDocument();
   DocumentFragment metaData = document.createDocumentFragment();
   metaData.appendChild(
       document.createComment("startwikilink:" + EscapeUtils.escapeComment(reference)));
   metaData.appendChild(document.createSpanElement());
   if (CLASS_NAME_MAPPING.containsKey(linkType)) {
     Element.as(metaData.getChild(1)).setClassName(CLASS_NAME_MAPPING.get(linkType));
   }
   metaData.getChild(1).appendChild(document.createTextNode(Element.INNER_HTML_PLACEHOLDER));
   metaData.appendChild(document.createComment("stopwikilink"));
   Element.as(anchor).setMetaData(metaData);
 }
  /**
   * Create a table from TableConfig configuration.
   *
   * <p>We create the table using innerHTML instead of creating each DOM node in order to improve
   * the speed. In most of the browsers setting the innerHTML is faster than creating the DOM nodes
   * and appending them.
   *
   * @param doc currently edited document.
   * @param config table configuration (row number, etc).
   * @return the newly created table.
   */
  public Element createTable(Document doc, TableConfig config) {
    StringBuffer table = new StringBuffer("<table>");

    StringBuffer row = new StringBuffer("<tr>");
    for (int i = 0; i < config.getColNumber(); i++) {
      row.append("<td>");
      // The default cell content depends on the browser. In Firefox the best option is to use a BR.
      // Firefox
      // itself uses BRs in order to allow the user to place the caret inside empty block elements.
      // In Internet
      // Explorer the best option is to set the inner HTML of each cell to the empty string, after
      // creation. For
      // now lets keep the non-breaking space. At some point we should have browser specific
      // implementations for
      // FF and IE. Each will overwrite this method and add specific initialization.
      row.append(TableUtils.CELL_DEFAULTHTML);
      row.append("</td>");
    }
    row.append("</tr>");

    if (config.hasHeader()) {
      table.append("<thead>");
      if (config.getRowNumber() > 0) {
        table.append(row.toString().replace("td", "th"));
      }
      table.append("</thead>");
    }

    table.append("<tbody>");
    for (int i = config.hasHeader() ? 1 : 0; i < config.getRowNumber(); i++) {
      table.append(row.toString());
    }
    table.append("</tbody></table>");

    Element container = doc.createDivElement().cast();
    container.setInnerHTML(table.toString());
    Element tableElement = (Element) container.getFirstChild();
    container.removeChild(tableElement);
    return tableElement;
  }
 /**
  * {@inheritDoc}
  *
  * @see ConfigDOMWriter#write(Object, com.google.gwt.dom.client.Element)
  */
 public void write(LinkConfig config, AnchorElement anchor) {
   // Required attributes.
   updateMetaData(anchor, config.getReference(), config.getType());
   anchor.setHref(config.getUrl());
   // Optional attributes.
   updateAttribute(anchor, "title", config.getTooltip());
   if (config.isOpenInNewWindow()) {
     anchor.setRel(TARGET_BLANK);
   } else if (TARGET_BLANK.equalsIgnoreCase(anchor.getRel())) {
     anchor.removeAttribute("rel");
   }
   // Update the content.
   if (!anchor.getInnerHTML().equals(config.getLabel())) {
     // Inner HTML listeners have to be notified in order to extract the meta data.
     Element.as(anchor).xSetInnerHTML(config.getLabel());
   }
 }