/*
 * Copyright (C) oStorybook Team
 *
 * This program is free software, see <http://www.gnu.org/licenses/>.
 *
 * Parts of this code are from the NovaWorx project. Other parts are
 * from the SHEF project developed and published by Bob Tantlinger.
 */
package api.shef.editors.wys;

import api.shef.actions.CompoundManager;
import api.shef.actions.DecoratedTextAction;
import api.shef.actions.EditPasteAction;
import api.shef.actions.EditPasteFormattedAction;
import api.shef.actions.EnterKeyAction;
import api.shef.actions.RemoveAction;
import api.shef.actions.TabAction;
import api.shef.tools.DelegateView;
import api.shef.tools.HtmlUtils;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.MouseInputAdapter;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.ComponentView;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.ObjectView;
import resources.icons.ICONS;
import resources.icons.IconUtil;
import storybook.tools.swing.js.JSLabel;

/**
 * An HTML Wysiwyg editor kit which can properly draw borderless tables and allows for resizing of
 * tables and images.
 *
 * @author Bob Tantlinger
 *
 */
public class WysiwygEditorKit extends HTMLEditorKit {

	private final ViewFactory wysFactory = new WysiwygHTMLFactory();
	private final ArrayList monitoredViews = new ArrayList();
	private final MouseInputAdapter resizeHandler = new ResizeHandler();
	private final Map editorToActionsMap = new HashMap();
	private final KeyStroke KEY_TAB_BACKWARD
		= KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK);
	private final WysiwygEditor editor;

	public WysiwygEditorKit(WysiwygEditor editor) {
		super();
		this.editor = editor;
	}

	@Override
	public Document createDefaultDocument() {
		HTMLDocument doc = (HTMLDocument) super.createDefaultDocument();
		//Unless the following property is set, the HTML parser will throw a
		//ChangedCharSetException every time a char set tag is encountered.
		doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE);
		return doc;
	}

	@Override
	@SuppressWarnings("unchecked")
	public void install(JEditorPane ed) {
		super.install(ed);
		if (editorToActionsMap.containsKey(ed)) {
			return;
		}
		ed.addMouseListener(resizeHandler);
		ed.addMouseMotionListener(resizeHandler);

		//install wysiwyg actions into the ActionMap for the editor being installed
		Map actions = new HashMap();
		InputMap inputMap = ed.getInputMap(JComponent.WHEN_FOCUSED);
		ActionMap actionMap = ed.getActionMap();
		Action delegate;
		Action action;

		delegate = actionMap.get("insert-break");
		addAction(actions, actionMap, "insert-break",
			new EnterKeyAction(delegate));

		delegate = actionMap.get("delete-previous");
		addAction(actions, actionMap, "delete-previous",
			new RemoveAction(RemoveAction.BACKSPACE, delegate));

		delegate = actionMap.get("delete-next");
		addAction(actions, actionMap, "delete-next",
			new RemoveAction(RemoveAction.DELETE, delegate));

		delegate = actionMap.get("insert-tab");
		addAction(actions, actionMap, "insert-tab",
			new TabAction(TabAction.FORWARD, delegate));
		addAction(actions, actionMap, "paste-from-clipboard",
			new EditPasteAction(editor));
		addAction(actions, actionMap, "pasteformatted-from-clipboard",
			new EditPasteFormattedAction(editor));
		addAction(actions, actionMap, "edit-undo",
			CompoundManager.ActionUndo);
		addAction(actions, actionMap, "edit-redo",
			CompoundManager.ActionRedo);

		inputMap.put(KEY_TAB_BACKWARD, "tab-backward");
		delegate = actionMap.get("tab-backward");
		action = new TabAction(TabAction.BACKWARD, delegate);
		actions.put("tab-backward", action);
		actionMap.put("tab-backward", action);

		editorToActionsMap.put(ed, actions);
	}

	@Override
	public void deinstall(JEditorPane ed) {
		super.deinstall(ed);
		if (!editorToActionsMap.containsKey(ed)) {
			return;
		}
		ed.removeMouseListener(resizeHandler);
		ed.removeMouseMotionListener(resizeHandler);

		//restore actions to their original state
		ActionMap actionMap = ed.getActionMap();
		Map actions = (Map) editorToActionsMap.get(ed);
		Action curAct = actionMap.get("insert-break");
		if (curAct == actions.get("insert-break")) {
			actionMap.put("insert-break", ((DecoratedTextAction) curAct).getDelegate());
		}

		curAct = actionMap.get("delete-previous");
		if (curAct == actions.get("delete-previous")) {
			actionMap.put("delete-previous", ((DecoratedTextAction) curAct).getDelegate());
		}

		curAct = actionMap.get("delete-next");
		if (curAct == actions.get("delete-next")) {
			actionMap.put("delete-next", ((DecoratedTextAction) curAct).getDelegate());
		}

		curAct = actionMap.get("insert-tab");
		if (curAct == actions.get("insert-tab")) {
			actionMap.put("insert-tab", ((DecoratedTextAction) curAct).getDelegate());
		}

		curAct = actionMap.get("paste-from-clipboard");
		if (curAct instanceof HTMLEditPasteAction) {
			actionMap.put("paste-from-clipboard", (Action) actions.get("paste-from-clipboard"));
		}

		curAct = actionMap.get("tab-backward");
		if (curAct == actions.get("insert-tab")) {
			actionMap.remove("tab-backward");
		}
		editorToActionsMap.remove(ed);
	}

	/**
	 * Fetch a factory that is suitable for producing views of any models that are produced by this
	 * kit.
	 *
	 * @return the factory
	 */
	@Override
	public ViewFactory getViewFactory() {
		return wysFactory;
	}

	@SuppressWarnings("unchecked")
	private void addAction(Map actions, ActionMap actionMap, String lib, Action action) {
		Action delegate = actionMap.get(lib);
		actions.put(lib, delegate);
		actionMap.put(lib, action);
	}

	/**
	 * Factory to build views of the html elements.
	 *
	 * This simply extends the behavior of the default html factory to draw borderless tables, etc
	 */
	public class WysiwygHTMLFactory extends HTMLFactory {

		@Override
		@SuppressWarnings("unchecked")
		public View create(Element elem) {
			Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
			if (o instanceof HTML.Tag) {
				HTML.Tag kind = (HTML.Tag) o;
				if (kind == HTML.Tag.TABLE) {
					ResizableView v = new ResizableView(
						new BorderlessTableView(super.create(elem)));
					monitoredViews.add(v);
					return v;
				} else if (kind == HTML.Tag.IMG) {
					ResizableView v = new ResizableView(super.create(elem));
					monitoredViews.add(v);
					return v;
				} else if (kind == HTML.Tag.COMMENT) {
					return new UnknownElementView((elem));
				} else if (kind == HTML.Tag.OBJECT) {
					ObjectView ov = new ObjectView(elem) {
						//make a nicer looking representation for <object>.
						//The default is a crappy red JSLabel with "??" as the text
						@Override
						protected Component createComponent() {
							Component comp = super.createComponent();
							if (comp instanceof JSLabel) {
								JSLabel l = (JSLabel) comp;
								if (l.getText().equals("??")
									&& l.getForeground().equals(Color.red)) {
									l.setIcon(IconUtil.getIconSmall(ICONS.K.COGS));
									l.setText(null);
									l.setBackground(Color.YELLOW);
									l.setOpaque(true);
									l.setBorder(BorderFactory.createRaisedBevelBorder());
									l.setToolTipText("<object></object>");
								}
							}
							return comp;
						}
					};
					return ov;
				} else if ((kind instanceof HTML.UnknownTag)
					|| (kind == HTML.Tag.TITLE)
					|| (kind == HTML.Tag.META)
					|| (kind == HTML.Tag.LINK)
					|| (kind == HTML.Tag.STYLE)
					|| (kind == HTML.Tag.SCRIPT)
					|| (kind == HTML.Tag.AREA)
					|| (kind == HTML.Tag.MAP)
					|| (kind == HTML.Tag.PARAM)
					|| (kind == HTML.Tag.APPLET)) {
					return new UnknownElementView(elem);
				}
			}
			return super.create(elem);
		}
	}

	/**
	 * Handle the resizing of images and tables
	 */
	private class ResizeHandler extends MouseInputAdapter {

		boolean dragStarted;
		//need down flag for Java 1.4 - mouseMoved gets called during a drag
		boolean mouseDown;
		int dragDir = -1;

		@Override
		public void mousePressed(MouseEvent e) {
			mouseDown = true;
			boolean selected = false;
			//iterate thru the list backwards to select
			//most recently added views so nested tables get selected properly
			for (int i = monitoredViews.size() - 1; i >= 0; i--) {
				ResizableView v = (ResizableView) monitoredViews.get(i);
				Rectangle r = v.getBounds();
				if (r != null && r.contains(e.getPoint()) && !selected) {
					v.setSelectionEnabled(true);
					dragDir = v.getHandleForPoint(e.getPoint());
					setCursorForDir(dragDir, e.getComponent());
					selected = true;
				} else {
					v.setSelectionEnabled(false);
				}
			}
			e.getComponent().repaint();
		}

		@Override
		public void mouseMoved(MouseEvent e) {
			if (!mouseDown) {
				ResizableView v = getSelectedView();
				if (v == null) {
					return;
				}
				Component c = e.getComponent();
				setCursorForDir(v.getHandleForPoint(e.getPoint()), c);
			}
		}

		@Override
		public void mouseDragged(MouseEvent e) {
			dragStarted = dragDir != -1;
			ResizableView v = getSelectedView();
			if (v == null || !dragStarted) {
				return;
			}
			Rectangle r = v.getSelectionBounds();
			switch (dragDir) {
				case ResizableView.SE:
					r.width = e.getX() - r.x;
					r.height = e.getY() - r.y;
					break;
				case ResizableView.NE:
					r.width = e.getX() - r.x;
					r.height = (r.y + r.height) - e.getY();
					r.y = e.getY();
					break;
				case ResizableView.SW:
					r.width = (r.x + r.width) - e.getX();
					r.height = e.getY() - r.y;
					r.x = e.getX();
					break;
				case ResizableView.NW:
					r.width = (r.x + r.width) - e.getX();
					r.height = (r.y + r.height) - e.getY();
					r.x = e.getX();
					r.y = e.getY();
					break;
				case ResizableView.N:
					r.height = (r.y + r.height) - e.getY();
					r.y = e.getY();
					break;
				case ResizableView.S:
					r.height = e.getY() - r.y;
					break;
				case ResizableView.E:
					r.width = e.getX() - r.x;
					break;
				case ResizableView.W:
					r.width = (r.x + r.width) - e.getX();
					r.x = e.getX();
					break;
				default:
					break;
			}
			e.getComponent().repaint();
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			mouseDown = false;
			ResizableView v = getSelectedView();
			if (v != null && dragStarted) {
				Element elem = v.getElement();
				SimpleAttributeSet sas = new SimpleAttributeSet(elem.getAttributes());
				Integer w = v.getSelectionBounds().width;
				Integer h = v.getSelectionBounds().height;
				if (elem.getName().equals("table")) {
					//resize the table
					//currently jEditorPane only supports the width attrib for tables
					sas.addAttribute(HTML.Attribute.WIDTH, w);
					String html = HtmlUtils.getElementHTML(elem, false);
					html = HtmlUtils.createTag(HTML.Tag.TABLE, sas, html);
					replace(elem, html);
				} else if (elem.getName().equals("img")) {
					//resize the img
					sas.addAttribute(HTML.Attribute.WIDTH, w);
					sas.addAttribute(HTML.Attribute.HEIGHT, h);
					String html = "<img";
					for (Enumeration ee = sas.getAttributeNames(); ee.hasMoreElements();) {
						Object name = ee.nextElement();
						if (!(name.toString().equals("name") || name.toString().equals("a"))) {
							Object val = sas.getAttribute(name);
							html += " " + name + "=\"" + val + "\"";
						}
					}
					html += ">";
					if (sas.isDefined(HTML.Tag.A)) {
						html = "<a " + sas.getAttribute(HTML.Tag.A) + ">" + html + "</a>";
					}
					replace(elem, html);
				}
				//remove views not appearing in the doc
				updateMonitoredViews((HTMLDocument) v.getDocument());
			}
			dragStarted = false;
		}

		private void setCursorForDir(int d, Component c) {
			if (d == ResizableView.NW) {
				c.setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
			} else if (d == ResizableView.SW) {
				c.setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
			} else if (d == ResizableView.NE) {
				c.setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
			} else if (d == ResizableView.SE) {
				c.setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
			} else if (d == ResizableView.N) {
				c.setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
			} else if (d == ResizableView.S) {
				c.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
			} else if (d == ResizableView.E) {
				c.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
			} else if (d == ResizableView.W) {
				c.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
			} else if (c.getCursor().getType() != Cursor.DEFAULT_CURSOR) {
				c.setCursor(Cursor.getDefaultCursor());
			}
		}

		/**
		 * Updates the list of monitored ResizeableViews. If they don't exist in the document,
		 * they're removed from the list.
		 *
		 * @param doc
		 */
		private void updateMonitoredViews(HTMLDocument doc) {
			for (Iterator it = monitoredViews.iterator(); it.hasNext();) {
				View v = (View) it.next();
				Element vElem = v.getElement();
				if (vElem.getName().equals("img")) {
					Element el = doc.getCharacterElement(vElem.getStartOffset());
					if (el != vElem) {
						it.remove();
					}
				} else if (vElem.getName().equals("table")) {
					Element el = doc.getParagraphElement(vElem.getStartOffset());
					//get the parent and check if it's the same element
					el = HtmlUtils.getParent(el, HTML.Tag.TABLE);
					if (el != vElem) {
						it.remove();
					}
				}
			}
		}

		/**
		 * Get the currently selected view. Only one view at a time can be selected
		 *
		 * @return
		 */
		private ResizableView getSelectedView() {
			for (Iterator it = monitoredViews.iterator(); it.hasNext();) {
				ResizableView v = (ResizableView) it.next();
				if (v.isSelectionEnabled()) {
					return v;
				}
			}
			return null;
		}

		/**
		 * Replaced the element with the specified html.
		 *
		 * @param elem
		 * @param html
		 */
		private void replace(Element elem, String html) {
			HTMLDocument document = (HTMLDocument) elem.getDocument();
			CompoundManager.beginCompoundEdit(document);
			try {
				document.setOuterHTML(elem, html);
			} catch (IOException | BadLocationException ex) {
				ex.printStackTrace(System.err);
			}
			CompoundManager.endCompoundEdit(document);
		}
	}

	/**
	 * View which can draw resize handles around its delegate
	 *
	 */
	private class ResizableView extends DelegateView {

		public static final int NW = 0;
		public static final int NE = 1;
		public static final int SW = 2;
		public static final int SE = 3;
		public static final int N = 4;
		public static final int S = 5;
		public static final int E = 6;
		public static final int W = 7;
		private Rectangle curBounds;
		private Rectangle selBounds;

		public ResizableView(View delegate) {
			super(delegate);
		}

		@Override
		public void paint(Graphics g, Shape allocation) {
			curBounds = new Rectangle(allocation.getBounds());
			delegate.paint(g, allocation);
			drawSelectionHandles(g);
		}

		@Override
		public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
			setSelectionEnabled(false);
			super.insertUpdate(e, a, f);
		}

		@Override
		public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
			setSelectionEnabled(false);
			super.changedUpdate(e, a, f);
		}

		@Override
		public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
			setSelectionEnabled(false);
			super.removeUpdate(e, a, f);
		}

		/**
		 * Gets the current bounds of the view
		 *
		 * @return
		 */
		public Rectangle getBounds() {
			return curBounds;
		}

		/**
		 * Gets the Rectangle from which the selection handles are drawn
		 *
		 * @return
		 */
		public Rectangle getSelectionBounds() {
			return selBounds;
		}

		/**
		 * Draw the selection if true
		 *
		 * @param b
		 */
		public void setSelectionEnabled(boolean b) {
			if (b && curBounds != null) {
				selBounds = new Rectangle(curBounds);
			} else {
				selBounds = null;
			}
		}

		public boolean isSelectionEnabled() {
			return selBounds != null;
		}

		/**
		 * Gets the selection handle at the specified point.
		 *
		 * @param p
		 * @return one of NW, SW, NE, SE, N, S, E, W or -1 if a handle is not at the point
		 */
		public int getHandleForPoint(Point p) {
			if (isSelectionEnabled()) {
				Rectangle r[] = computeHandles(selBounds);
				for (int i = 0; i < r.length; i++) {
					if (r[i].contains(p)) {
						return i;
					}
				}
			}
			return -1;
		}

		private void drawSelectionHandles(Graphics g) {
			if (!isSelectionEnabled()) {
				return;
			}
			Color cached = g.getColor();
			g.setColor(Color.DARK_GRAY);
			g.drawRect(selBounds.x, selBounds.y, selBounds.width, selBounds.height);
			Rectangle h[] = computeHandles(selBounds);
			for (Rectangle h1 : h) {
				g.fillRect(h1.x, h1.y, h1.width, h1.height);
			}
			g.setColor(cached);
		}

		private Rectangle[] computeHandles(Rectangle sel) {
			Rectangle r[] = new Rectangle[8];
			int sq = 8;
			r[NW] = new Rectangle(sel.x, sel.y, sq, sq);
			r[NE] = new Rectangle(sel.x + sel.width - sq, sel.y, sq, sq);
			r[SW] = new Rectangle(sel.x, sel.y + sel.height - sq, sq, sq);
			r[SE] = new Rectangle(sel.x + sel.width - sq, sel.y + sel.height - sq, sq, sq);
			int midX = sel.x + (sel.width / 2 - sq / 2);
			int midY = sel.y + (sel.height / 2 - sq / 2);
			r[N] = new Rectangle(midX, sel.y, sq, sq);
			r[S] = new Rectangle(midX, sel.y + sel.height - sq, sq, sq);
			r[E] = new Rectangle(sel.x + sel.width - sq, midY, sq, sq);
			r[W] = new Rectangle(sel.x, midY, sq, sq);
			return r;
		}
	}

	/**
	 * Delegate view which draws borderless tables.
	 *
	 * This class is a delegate view because javax.swing.text.html.TableView is not public...
	 *
	 * @author Bob Tantlinger
	 *
	 */
	private class BorderlessTableView extends DelegateView {

		public BorderlessTableView(View delegate) {
			super(delegate);
		}

		@Override
		public void paint(Graphics g, Shape allocation) {
			//if the table element has no border,
			//then draw the table outline with a dotted line
			if (shouldDrawDottedBorder()) {
				//draw the table background color if set
				//we need to do this for Java 1.5, otherwise
				//the bgcolor doesnt get painted for some reason
				Color bgColor = getTableBgcolor();
				if (bgColor != null) {
					Color cachedColor = g.getColor();
					g.setColor(bgColor);
					Rectangle tr = allocation.getBounds();
					g.fillRect(tr.x, tr.y, tr.width, tr.height);
					g.setColor(cachedColor);
				}
				delegate.paint(g, allocation);
				//set up the graphics object to draw dotted lines
				Graphics2D g2 = (Graphics2D) g;
				Stroke cachedStroke = g2.getStroke();
				float dash[] = {3.0f};
				BasicStroke stroke = new BasicStroke(
					1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
					10f, dash, 0.0f);
				g2.setStroke(stroke);
				g2.setColor(Color.DARK_GRAY);
				int rows = getViewCount();
				for (int r = 0; r < rows; r++) {
					Shape rowShape = getChildAllocation(r, allocation);
					View rowView = getView(r);
					int cells = rowView.getViewCount();
					//draw each cell with a dotted border
					for (int c = 0; c < cells; c++) {
						Shape cellShape = rowView.getChildAllocation(c, rowShape);
						Rectangle cr = cellShape.getBounds();
						g2.drawRect(cr.x, cr.y, cr.width, cr.height);
					}
				}
				g2.setStroke(cachedStroke);
			} else {
				delegate.paint(g, allocation);
			}
		}

		private Color getTableBgcolor() {
			AttributeSet atr = getElement().getAttributes();
			Object o = atr.getAttribute(HTML.Attribute.BGCOLOR);
			if (o != null) {
				Color c = HtmlUtils.stringToColor(o.toString());
				return c;
			}
			return null;
		}

		private boolean shouldDrawDottedBorder() {
			AttributeSet atr = getElement().getAttributes();
			boolean isBorderAttr = hasBorderAttr(atr);
			return (!isBorderAttr) || isBorderAttr
				&& atr.getAttribute(HTML.Attribute.BORDER).toString().equals("0");
		}

		private boolean hasBorderAttr(AttributeSet atr) {
			for (Enumeration e = atr.getAttributeNames(); e.hasMoreElements();) {
				if (e.nextElement().toString().equals("border")) {
					return true;
				}
			}
			return false;
		}
	}

	/**
	 * A view for {@link Element}s that are uneditable.
	 * <p>
	 * The default view for such {@link Element}s are big ugly blocky JTextFields, which doesn't
	 * really work well in practice and tends to confuse users. This view replaces the default and
	 * simply draws the elements as blocks that indicates the presence of an uneditable
	 * {@link Element}.
	 * <p>
	 *
	 * @author Bob Tantlinger
	 *
	 */
	private class UnknownElementView extends ComponentView {

		public UnknownElementView(Element e) {
			super(e);
		}

		@Override
		protected Component createComponent() {
			JSLabel p = new JSLabel();
			if (getElement().getAttributes().getAttribute(StyleConstants.NameAttribute) == HTML.Tag.COMMENT) {
				p.setText("<!-- -->");
				AttributeSet as = getElement().getAttributes();
				if (as != null) {
					Object comment = as.getAttribute(HTML.Attribute.COMMENT);
					if (comment instanceof String) {
						p.setToolTipText((String) comment);
					}
				}
			} else {
				String text = this.getElement().getName();
				if (text == null || text.equals("")) {
					text = "??";
				}
				if (isEndTag()) {
					text = "</" + text + ">";
				} else {
					text = "<" + text + ">";
				}
				p.setText(text);
			}
			p.setBorder(BorderFactory.createRaisedBevelBorder());
			p.setBackground(Color.YELLOW);
			p.setForeground(Color.BLUE);
			p.setOpaque(true);
			return p;
		}

		boolean isEndTag() {
			AttributeSet as = getElement().getAttributes();
			if (as != null) {
				Object end = as.getAttribute(HTML.Attribute.ENDTAG);
				if (end != null && (end instanceof String) && ((String) end).equals("true")) {
					return true;
				}
			}
			return false;
		}
	}
}
