package org.objectstyle.wolips.templateeditor;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jp.aonir.fuzzyxml.FuzzyXMLDocument;
import jp.aonir.fuzzyxml.FuzzyXMLElement;
import jp.aonir.fuzzyxml.FuzzyXMLNode;
import jp.aonir.fuzzyxml.FuzzyXMLParser;
import jp.aonir.fuzzyxml.FuzzyXMLText;
import jp.aonir.fuzzyxml.internal.RenderContext;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationModelEvent;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelListener;
import org.eclipse.jface.text.source.IAnnotationModelListenerExtension;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.StatusTextEvent;
import org.eclipse.swt.browser.StatusTextListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.objectstyle.wolips.bindings.wod.IWodBinding;
import org.objectstyle.wolips.bindings.wod.IWodElement;
import org.objectstyle.wolips.variables.BuildProperties;
import org.objectstyle.wolips.wodclipse.core.completion.WodParserCache;
import org.objectstyle.wolips.wodclipse.core.util.WodHtmlUtils;
import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.editors.IHTMLOutlinePage;
/**
* An implementaion of IContentOutlinePage for the HTML editor.
* This shows the outline of HTML document.
*/
public class TemplateOutlinePage extends Page implements IContentOutlinePage, IHTMLOutlinePage, StatusTextListener, IAnnotationModelListener, IAnnotationModelListenerExtension {
private static final String COMPACT_VIEW_PREFERENCE_KEY = "org.objectstyle.wolips.templateEditor.compactView";
private static final String COLLAPSE_STRING = "–";
private static final String EXPAND_STRING = "+";
private TemplateSourceEditor _editor;
private FuzzyXMLDocument _doc;
private Browser _browser;
private int _counter;
private Map<String, FuzzyXMLNode> _idToNodeMap;
private Map<FuzzyXMLNode, String> _nodeToIDMap;
private List<ISelectionChangedListener> _selectionChangedListeners;
private ISelection _selection;
private Set<String> _collapsedIDs;
private boolean _compactView;
private int _lastPageOffset;
public TemplateOutlinePage(TemplateSourceEditor editor) {
_editor = editor;
_selectionChangedListeners = new LinkedList<ISelectionChangedListener>();
_collapsedIDs = new HashSet<String>();
_compactView = HTMLPlugin.getDefault().getPreferenceStore().getBoolean(HTMLPlugin.PREF_TEMPLATE_COMPACT_VIEW);
}
public FuzzyXMLDocument getDoc() {
return _doc;
}
@Override
public void createControl(Composite parent) {
try {
_browser = new Browser(parent, SWT.NONE);
_browser.addStatusTextListener(this);
}
catch (Throwable t) {
HTMLPlugin.logException(t);
}
update();
}
protected boolean isHTML() {
return true;
}
protected FuzzyXMLParser createParser(IProject project) {
BuildProperties buildProperties = (BuildProperties)project.getAdapter(BuildProperties.class);
FuzzyXMLParser parser = new FuzzyXMLParser(buildProperties != null ? buildProperties.isWellFormedTemplateRequired() : false, isHTML());
return parser;
}
@Override
public Control getControl() {
return _browser;
}
@Override
public void setFocus() {
// MS: This might be a little bit weird on the interaction side. We refresh
// the view when it gets focus, to prevent you from clicking on something that
// is now out of date.
if (_editor.isDirty()) {
update();
}
if (_browser != null) {
_browser.setFocus();
}
}
/**
* The protocol that we use to communicate between the Browser preview component
* and TBLips is via the Browser status text. Changing status text in Javascript
* fires an SWT event of the form 'command:target' that we parse on the Java side
* to determine what action the user performed inside the Browser. This method
* handles each of the corresponding event types.
*
* @param event the status text event
*/
public void changed(StatusTextEvent event) {
String text = event.text;
int colonIndex = text.indexOf(':');
if (colonIndex == -1) {
//wodeditor active + hit command+s
return;
}
String command = text.substring(0, colonIndex);
String target = text.substring(colonIndex + 1);
if ("select".equals(command)) {
outlineNodeSelected(target);
}
else if ("expand".equals(command)) {
outlineNodeExpanded(target);
}
else if ("collapse".equals(command)) {
outlineNodeCollapsed(target);
}
else if ("toggleCompact".equals(command)) {
_compactView = !_compactView;
HTMLPlugin.getDefault().getPreferenceStore().setValue(HTMLPlugin.PREF_TEMPLATE_COMPACT_VIEW, _compactView);
}
else if ("pageYOffset".equals(command)) {
try {
_lastPageOffset = Integer.parseInt(target);
}
catch (NumberFormatException e) {
e.printStackTrace();
_lastPageOffset = 0;
}
}
}
/**
* Called when the user collapses an outline node
*
* @param target the id of the target node
*/
public void outlineNodeCollapsed(String target) {
if (!_collapsedIDs.contains(target)) {
_collapsedIDs.add(target);
FuzzyXMLNode selectedNode = _idToNodeMap.get(target);
ProjectionAnnotationModel model = ((ProjectionViewer) _editor.getViewer()).getProjectionAnnotationModel();
ProjectionAnnotation lastAnnotation = getAnnotationForNode(selectedNode, model);
if (lastAnnotation != null) {
model.collapse(lastAnnotation);
}
}
}
/**
* Called when the user expands an outline node
*
* @param target the id of the target node
*/
public void outlineNodeExpanded(String target) {
if (_collapsedIDs.contains(target)) {
_collapsedIDs.remove(target);
FuzzyXMLNode selectedNode = _idToNodeMap.get(target);
ProjectionAnnotationModel model = ((ProjectionViewer) _editor.getViewer()).getProjectionAnnotationModel();
ProjectionAnnotation lastAnnotation = getAnnotationForNode(selectedNode, model);
if (lastAnnotation != null) {
model.expand(lastAnnotation);
}
}
}
/**
* Called when the user clicks on an outline node.
*
* @param target the id of the target node
*/
public void outlineNodeSelected(String target) {
FuzzyXMLNode selectedNode = _idToNodeMap.get(target);
_selection = new StructuredSelection(selectedNode);
SelectionChangedEvent selectionChangedEvent = new SelectionChangedEvent(this, _selection);
for (ISelectionChangedListener listener : _selectionChangedListeners) {
listener.selectionChanged(selectionChangedEvent);
}
_editor.selectAndReveal(selectedNode.getOffset(), selectedNode.getLength());
_editor.getViewer().getTextWidget().setFocus();
}
/**
* Returns the annotation whose position is equal the given node's offset.
*
* @param node the node to lookup annotations for
* @param model the annotation model
* @return the matching annotation (or null if not found)
*/
@SuppressWarnings("unchecked")
protected ProjectionAnnotation getAnnotationForNode(FuzzyXMLNode node, ProjectionAnnotationModel model) {
ProjectionAnnotation matchingAnnotation = null;
if (model != null) {
int index = node.getOffset();
Iterator<ProjectionAnnotation> annotationsIter = model.getAnnotationIterator();
while (annotationsIter.hasNext()) {
ProjectionAnnotation annotation = annotationsIter.next();
if (model.getPosition(annotation).getOffset() == index) {
matchingAnnotation = annotation;
}
}
}
return matchingAnnotation;
}
/**
* Called when the projection annotation model changes. This allows
* us to sync expanding/collapsing of editor folding with expanding/collapsing
* of the outline view.
*
* @param event the event
*/
public void modelChanged(AnnotationModelEvent event) {
if (_browser != null) {
IAnnotationModel model = event.getAnnotationModel();
Annotation[] annotations = event.getChangedAnnotations();
if (annotations != null) {
for (Annotation annotation : annotations) {
if (annotation instanceof ProjectionAnnotation) {
ProjectionAnnotation projectionAnnotation = (ProjectionAnnotation) annotation;
Position annotationPosition = model.getPosition(annotation);
FuzzyXMLNode node = _doc.getElementByOffset(annotationPosition.getOffset());
String nodeID = _nodeToIDMap.get(node);
if (nodeID != null) {
if (projectionAnnotation.isCollapsed()) {
_browser.execute("collapse('" + nodeID + "');");
}
else {
_browser.execute("expand('" + nodeID + "');");
}
_browser.execute("window.scrollTo(getPageXOffset(), getPageYOffset());");
}
}
}
}
}
}
public void modelChanged(IAnnotationModel model) {
// DO NOTHING
}
public void addSelectionChangedListener(ISelectionChangedListener listener) {
_selectionChangedListeners.add(listener);
}
public ISelection getSelection() {
return _selection;
}
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
_selectionChangedListeners.remove(listener);
}
public void setSelection(ISelection selection) {
// System.out.println("TemplateOutlinePage.setSelection: " + selection);
}
@Override
public void dispose() {
_nodeToIDMap.clear();
_idToNodeMap.clear();
_collapsedIDs.clear();
_selectionChangedListeners.clear();
super.dispose();
}
/**
* Called when the document changes.
*/
public void update() {
if (getControl() == null || getControl().isDisposed()) {
return;
}
ProjectionAnnotationModel model = ((ProjectionViewer) _editor.getViewer()).getProjectionAnnotationModel();
model.removeAnnotationModelListener(this);
model.addAnnotationModelListener(this);
try {
_doc = createParser(_editor.getParserCache().getProject()).parse(_editor.getHTMLSource());
}
catch (Exception e) {
e.printStackTrace();
}
_counter = 0;
_idToNodeMap = new HashMap<String, FuzzyXMLNode>();
_nodeToIDMap = new HashMap<FuzzyXMLNode, String>();
try {
WodParserCache cache = _editor.getParserCache();
RenderContext renderContext = new RenderContext(true);
FuzzyXMLElement documentElement = _doc.getDocumentElement();
StringBuffer documentContentsBuffer = new StringBuffer();
renderHeader(documentContentsBuffer);
renderElement(documentElement, renderContext, documentContentsBuffer, cache);
renderFooter(documentContentsBuffer);
if (_browser != null) {
_browser.execute("updatePageYOffset()");
}
if (_lastPageOffset > 0) {
documentContentsBuffer.append("<script>window.scrollTo(100, " + _lastPageOffset + ");</script>");
}
String documentContents = documentContentsBuffer.toString();
boolean debug = false;
if (debug) {
FileWriter fw = new FileWriter("/tmp/TemplateOutlinePage-" + System.currentTimeMillis() + ".html");
try {
fw.write(documentContents);
}
finally {
fw.close();
}
}
if (_browser != null) {
boolean rendered = _browser.setText(documentContents);
if (!rendered) {
HTMLPlugin.logError("Can't create preview of component HTML.");
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Renders the page header and embedded CSS.
*
* @param renderBuffer the render buffer
*/
protected void renderHeader(StringBuffer renderBuffer) {
renderBuffer.append("<html>\n");
renderBuffer.append("<head>\n");
renderBuffer.append("<style>\n");
renderBuffer.append("body { font-family: Helvetica; font-size: 8pt; margin: 5px; margin-top: 2px; }\n");
renderBuffer.append("a { text-decoration: none; }\n");
renderBuffer.append("div.viewControls { right: 8px; margin: 0px; margin-bottom: 2px; text-align: right; }\n");
renderBuffer.append("div.viewControls a { color: rgb(0, 0, 200); }\n");
renderBuffer.append("div.elements { margin-top: 0px; }\n");
renderBuffer.append("div.element { overflow: hidden; margin-top: 5px; margin-bottom: 5px; margin-right: 0px; padding: 0px; border: 1px solid rgb(230, 230, 230); border-right: none; }\n");
renderBuffer.append("div.element div.summary { cursor: pointer; white-space: nowrap; background-color: rgb(240, 240, 240); padding: 3px; border-bottom: 1px solid rgb(230, 230, 230); }\n");
renderBuffer.append("div.element.empty div.summary { border-bottom: none; }\n");
renderBuffer.append("div.element div.summary:hover { background-color: rgb(220, 220, 220); border-color: rgb(210, 210, 210); }\n");
renderBuffer.append("div.element div.expandcollapse { cursor: pointer; float: right; background-color: rgb(255, 255, 255); width: 10px; border: 1px solid rgb(230, 230, 230); border-top: none; padding-left: 3px; padding-right: 3px; text-align: center; }\n");
renderBuffer.append("div.element div.expandcollapse:hover { font-weight: bold; border-width: 2px; border-right-width: 1px; background-color: rgb(245, 245, 245); }\n");
renderBuffer.append("div.element div.expandcollapse:active { font-weight: bold; border-width: 2px; border-right-width: 1px; background-color: rgb(230, 230, 230); }\n");
renderBuffer.append("div.element div.contents { background-color: rgb(255, 255, 255); padding-left: 10px; padding-right: 0px; padding-top: 5px; padding-bottom: 5px; }\n");
renderBuffer.append("div.element.wo { border-color: rgb(200, 200, 255); }\n");
renderBuffer.append("div.element.wo > div.summary { background-color: rgb(240, 240, 255); border-bottom: 1px solid rgb(200, 200, 255); }\n");
renderBuffer.append("div.element.wo.empty > div.summary { border-bottom: none; }\n");
renderBuffer.append("div.element.wo > div.summary:hover { background-color: rgb(210, 210, 255); border-color: rgb(210, 210, 255); }\n");
renderBuffer.append("div.element.wo > div.summary div.title span.type { font-weight: normal; font-size: 0.80em; color: rgb(150, 150, 150); }\n");
renderBuffer.append("div.element.wo > div.expandcollapse { border-color: rgb(200, 200, 255); }\n");
renderBuffer.append("div.element.wo > div.expandcollapse:hover { background-color: rgb(245, 245, 255); }\n");
renderBuffer.append("div.element.wo > div.expandcollapse:active { background-color: rgb(200, 200, 255); }\n");
renderBuffer.append("div.element.wo > div.contents { background-color: rgb(250, 250, 255); border-color: rgb(200, 200, 255); }\n");
renderBuffer.append("div.element div.summary div.title { font-weight: bold; }\n");
renderBuffer.append("div.element div.summary div.title.nonwo { color: rgb(180, 180, 180); }\n");
renderBuffer.append("div.element div.summary div.title.missing { font-style: italic; }\n");
renderBuffer.append("div.element div.summary div.title.nonwo span.idName { font-weight: bold; color: rgb(180, 180, 180); padding-left: 10px; }\n");
renderBuffer.append("div.element div.summary div.title.nonwo span.className { font-weight: bold; color: rgb(180, 180, 180); padding-left: 10px; }\n");
renderBuffer.append("div.element div.summary table.bindings { font-family: Helvetica; font-size: 8pt; margin: 0px; padding: 0px; }\n");
renderBuffer.append("div.element div.summary table.bindings th { text-align: right; font-weight: normal; color: rgb(220, 0, 0); padding-right: 3px; }\n");
renderBuffer.append("div.element div.summary table.bindings td.literal { color: rgb(0, 0, 200); }\n");
renderBuffer.append("div.element div.summary table.bindings td.ognl { color: rgb(180, 0, 0); }\n");
renderBuffer.append("div.element div.summary table.bindings td.keypath { color: rgb(180, 0, 0); }\n");
renderBuffer.append("div.text { display: inline; }\n");
renderBuffer.append("span.negate { font-weight: bold; }\n");
renderBuffer.append("div.element.wo.TBString.simple { display: inline; border: none; }\n");
renderBuffer.append("div.element.wo.TBString.simple div.summary { display: inline; border: 1px solid rgb(200, 200, 255); border: none; background-color: transparent; padding: 0px; }\n");
renderBuffer.append("div.element.wo.TBString.simple div.summary div.title { display: inline; }\n");
renderBuffer.append("div.element.wo.TBString.simple div.text.literal { color: rgb(0, 0, 200); }\n");
renderBuffer.append("div.element.wo.TBString.simple div.text.ognl { color: rgb(180, 0, 0); }\n");
renderBuffer.append("div.element.wo.TBString.simple div.text.keypath { color: rgb(180, 0, 0); }\n");
// renderBuffer.append("div.element.wo.TBString div.summary { background-color: rgb(240, 240, 255); }\n");
// renderBuffer.append("div.element.wo.TBString div.summary:hover { background-color: rgb(240, 240, 255); border-color: rgb(170, 225, 170); }\n");
// renderBuffer.append("div.element.wo.TBString div.contents { background-color: rgb(250, 255, 250); border-color: rgb(200, 255, 200); }\n");
// renderBuffer.append("div.element.wo.TBString:hover div.contents { display: block; }\n");
renderBuffer.append("div.element.wo.TBWLocalizedString.simple { display: inline; border: none; }\n");
renderBuffer.append("div.element.wo.TBWLocalizedString.simple div.summary { display: inline; border: 1px solid rgb(200, 200, 255); border: none; background-color: transparent; padding: 0px; }\n");
renderBuffer.append("div.element.wo.TBWLocalizedString.simple div.summary div.title { display: inline; }\n");
renderBuffer.append("div.element.wo.TBWLocalizedString.simple div.text.literal { color: rgb(0, 0, 200); }\n");
renderBuffer.append("div.element.wo.TBWLocalizedString.simple div.text.ognl { color: rgb(180, 0, 0); }\n");
renderBuffer.append("div.element.wo.TBWLocalizedString.simple div.text.keypath { color: rgb(180, 0, 0); }\n");
renderBuffer.append("div.element.wo.TBConditional { border-color: rgb(190, 250, 190); }\n");
renderBuffer.append("div.element.wo.TBConditional > div.summary { background-color: rgb(230, 250, 230); }\n");
renderBuffer.append("div.element.wo.TBConditional > div.summary:hover { background-color: rgb(200, 250, 200); border-color: rgb(200, 255, 200); }\n");
renderBuffer.append("div.element.wo.TBConditional > div.expandcollapse { border-color: rgb(200, 255, 200); }\n");
renderBuffer.append("div.element.wo.TBConditional > div.expandcollapse:hover { background-color: rgb(245, 255, 245); }\n");
renderBuffer.append("div.element.wo.TBConditional > div.expandcollapse:active { background-color: rgb(200, 255, 200); }\n");
renderBuffer.append("div.element.wo.TBConditional > div.contents { background-color: rgb(250, 255, 250); border-color: rgb(190, 250, 190); }\n");
renderBuffer.append("div.element.wo.TBConditional > div.summary div.title span.type { display: none; }\n");
renderBuffer.append("div.element.wo.TBConditional > div.summary table.bindings th { text-align: right; font-weight: normal; color: rgb(220, 0, 0); padding-right: 3px; }\n");
renderBuffer.append("div.element.wo.TBConditional > div.summary table.bindings td.literal { color: rgb(0, 0, 200); }\n");
renderBuffer.append("div.element.wo.TBConditional > div.summary table.bindings td.ognl { color: rgb(180, 0, 0); }\n");
renderBuffer.append("div.element.wo.TBConditional > div.summary table.bindings td.keypath { color: rgb(180, 0, 0); }\n");
renderBuffer.append("div.element.wo.TBElse { border-color: rgb(190, 250, 190); }\n");
renderBuffer.append("div.element.wo.TBElse > div.summary { background-color: rgb(230, 250, 230); }\n");
renderBuffer.append("div.element.wo.TBElse > div.summary:hover { background-color: rgb(200, 250, 200); border-color: rgb(200, 255, 200); }\n");
renderBuffer.append("div.element.wo.TBElse > div.expandcollapse { border-color: rgb(200, 255, 200); }\n");
renderBuffer.append("div.element.wo.TBElse > div.expandcollapse:hover { background-color: rgb(245, 255, 245); }\n");
renderBuffer.append("div.element.wo.TBElse > div.expandcollapse:active { background-color: rgb(200, 255, 200); }\n");
renderBuffer.append("div.element.wo.TBElse > div.contents { background-color: rgb(250, 255, 250); border-color: rgb(190, 250, 190); }\n");
renderBuffer.append("div.element.wo.TBElse > div.summary div.title span.type { display: none; }\n");
renderBuffer.append("div.element.wo.TBElse > div.summary table.bindings th { text-align: right; font-weight: normal; color: rgb(220, 0, 0); padding-right: 3px; }\n");
renderBuffer.append("div.element.wo.TBElse > div.summary table.bindings td.literal { color: rgb(0, 0, 200); }\n");
renderBuffer.append("div.element.wo.TBElse > div.summary table.bindings td.ognl { color: rgb(180, 0, 0); }\n");
renderBuffer.append("div.element.wo.TBElse > div.summary table.bindings td.keypath { color: rgb(180, 0, 0); }\n");
// renderBuffer.append("div.element.wo.WORepetition { border-color: rgb(255, 200, 200); }\n");
// renderBuffer.append("div.element.wo.WORepetition div.summary { background-color: rgb(255, 230, 230); }\n");
// renderBuffer.append("div.element.wo.WORepetition div.summary:hover { background-color: rgb(255, 210, 210); border-color: rgb(255, 200, 200); }\n");
// renderBuffer.append("div.element.wo.WORepetition div.expandcollapse { border-color: rgb(255, 200, 200); }\n");
// renderBuffer.append("div.element.wo.WORepetition div.expandcollapse:hover { background-color: rgb(255, 250, 250); }\n");
// renderBuffer.append("div.element.wo.WORepetition div.expandcollapse:active { background-color: rgb(255, 200, 200); }\n");
// renderBuffer.append("div.element.wo.WORepetition div.contents { background-color: rgb(255, 250, 250); border-color: rgb(255, 200, 200); }\n");
// renderBuffer.append("div.element.wo.WORepetition div.summary div.title span.type { display: none; }\n");
// renderBuffer.append("div.element.wo.WORepetition div.summary table.bindings th { color: rgb(220, 0, 0); }\n");
// renderBuffer.append("div.element.wo.WORepetition div.summary table.bindings td { color: rgb(150, 0, 0); }\n");
renderBuffer.append("body.verbose div.element div.summary div.title span.nodeName.verbose { display: block; }\n");
renderBuffer.append("body.verbose div.element div.summary div.title span.nodeName.compact { display: none; }\n");
renderBuffer.append("body.compact { font-size: 7.5pt; }\n");
renderBuffer.append("body.compact div.element { margin-top: 2px; margin-bottom: 3px; }\n");
renderBuffer.append("body.compact div.element div.expandcollapse { width: 5px; padding-left: 4px; padding-right: 4px; padding-top: 1px; padding-bottom: 1px; }\n");
renderBuffer.append("body.compact div.element div.expandcollapse:hover { border-width: 1px; }\n");
renderBuffer.append("body.compact div.element div.summary { padding: 1px; padding-left: 2px; }\n");
renderBuffer.append("body.compact div.element div.summary table.bindings { display: none; }\n");
renderBuffer.append("body.compact div.element div.summary div.title { font-weight: normal; }\n");
renderBuffer.append("body.compact div.element div.summary div.title span.nodeName.verbose { display: none; }\n");
renderBuffer.append("body.compact div.element div.summary div.title span.nodeName.compact { display: block; }\n");
renderBuffer.append("body.compact div.element div.summary div.title span.type { display: none; }\n");
renderBuffer.append("body.compact div.element div.contents { padding-left: 8px; padding-top: 2px; padding-bottom: 2px; }\n");
renderBuffer.append("body.compact div.element.wo.TBString { display: inline; border: none; }\n");
renderBuffer.append("body.compact div.element.wo.TBString div.summary { display: inline; border: 1px solid rgb(200, 200, 255); }\n");
renderBuffer.append("body.compact div.element.wo.TBString div.summary div.title { display: inline; }\n");
renderBuffer.append("body.compact div.element.wo.TBString.simple { border: none; }\n");
renderBuffer.append("body.compact div.element.wo.TBString.simple div.summary { border: none; background-color: transparent; padding: 0px; }\n");
renderBuffer.append("body.compact div.element.wo.TBString.simple div.summary div.title { display: inline; }\n");
renderBuffer.append("body.compact div.element.wo.TBWLocalizedString { display: inline; border: none; }\n");
renderBuffer.append("body.compact div.element.wo.TBWLocalizedString div.summary { display: inline; border: 1px solid rgb(200, 200, 255); }\n");
renderBuffer.append("body.compact div.element.wo.TBWLocalizedString div.summary div.title { display: inline; }\n");
renderBuffer.append("body.compact div.element.wo.TBWLocalizedString.simple { border: none; }\n");
renderBuffer.append("body.compact div.element.wo.TBWLocalizedString.simple div.summary { border: none; background-color: transparent; padding: 0px; }\n");
renderBuffer.append("body.compact div.element.wo.TBWLocalizedString.simple div.summary div.title { display: inline; }\n");
renderBuffer.append("body div.element.document { margin: 0px; padding: 0px; border: none; }\n");
renderBuffer.append("body div.element.document > div.summary { margin: 0px; padding: 0px; border: none; display: none; }\n");
renderBuffer.append("body div.element.document > div.expandcollapse { display: none; }\n");
renderBuffer.append("body div.element.document > div.contents { margin: 0px; padding: 0px; border: none; }\n");
renderBuffer.append("</style>\n");
renderBuffer.append("<script>\n");
renderBuffer.append("function expandCollapse(id) { if ('none' == document.getElementById(id + '_contents').style.display) { expand(id); } else { collapse(id); } }\n");
renderBuffer.append("function expand(id) { document.getElementById(id + '_contents').style.display = 'block'; document.getElementById(id + '_toggle').innerHTML = '" + TemplateOutlinePage.COLLAPSE_STRING + "'; window.status = 'expand:' + id; }\n");
renderBuffer.append("function collapse(id) { document.getElementById(id + '_contents').style.display = 'none'; document.getElementById(id + '_toggle').innerHTML = '" + TemplateOutlinePage.EXPAND_STRING + "'; window.status = 'collapse:' + id; }\n");
renderBuffer.append("function toggleCompact() { if ('compact' == document.getElementById('outline').className) { document.getElementById('outline').className = 'verbose'; } else { document.getElementById('outline').className = 'compact'; } window.status = 'toggleCompact:'; }\n");
renderBuffer.append("function getPageYOffset() { var scrollY; if (document.all) { if (!document.documentElement.scrollTop) { scrollY = document.body.scrollTop; } else { scrollY = document.documentElement.scrollTop; } } else { scrollY = window.pageYOffset; } return scrollY; }\n");
renderBuffer.append("function getPageXOffset() { var scrollX; if (document.all) { if (!document.documentElement.scrollLeft) { scrollX = document.body.scrollLeft; } else { scrollX = document.documentElement.scrollLeft; } } else { scrollX = window.pageXOffset; } return scrollX; }\n");
renderBuffer.append("function updatePageYOffset() { window.status = 'pageYOffset:' + getPageYOffset(); }\n");
renderBuffer.append("</script>\n");
renderBuffer.append("</head>\n");
if (_compactView) {
renderBuffer.append("<body id = \"outline\" class = \"compact\">\n");
}
else {
renderBuffer.append("<body id = \"outline\" class = \"verbose\">\n");
}
renderBuffer.append("<div class = \"viewControls\"><a href = \"javascript:void(0)\" onclick = \"toggleCompact()\">toggle compact view</a></div>\n");
renderBuffer.append("<div class = \"elements\">\n");
}
/**
* Renders the page footer.
*
* @param renderBuffer the render buffer
*/
protected void renderFooter(StringBuffer renderBuffer) {
renderBuffer.append("</div></body></html>");
}
/**
* All the magic happens here. This method renders a single document node (and recursively calls itself).
*
* @param node the node to render
* @param renderContext the current render context
* @param renderBuffer the render buffer
* @param cache the WodParserCache for the current editor
*/
protected void renderElement(FuzzyXMLNode node, RenderContext renderContext, StringBuffer renderBuffer, WodParserCache cache) {
String nodeID = "node" + (_counter++);
_idToNodeMap.put(nodeID, node);
_nodeToIDMap.put(node, nodeID);
if (node instanceof FuzzyXMLElement) {
FuzzyXMLElement element = (FuzzyXMLElement) node;
boolean empty = element.isEmpty();
String nodeName = element.getName();
String className = "element";
boolean woTag = WodHtmlUtils.isWOTag(nodeName);
boolean woSimpleString = false;
IWodElement wodElement = null;
if (woTag) {
className = className + " wo";
try {
BuildProperties buildProperties = (BuildProperties)_editor.getParserCache().getProject().getAdapter(BuildProperties.class);
wodElement = WodHtmlUtils.getWodElement(element, buildProperties, true, cache);
}
catch (Throwable t) {
// IGNORE
t.printStackTrace();
}
if (wodElement != null) {
className = className + " " + wodElement.getElementType();
if ("TBString".equals(wodElement.getElementType()) || "TBWLocalizedString".equals(wodElement.getElementType())) {
if (wodElement.getBindingNamed("value") != null) {
if (wodElement.getBindingNamed("escapeHTML") != null && wodElement.getBindings().size() == 2) {
woSimpleString = true;
}
else if (wodElement.getBindings().size() == 1) {
woSimpleString = true;
}
}
// MS: FORCE OFF FOR NOW
//woSimpleString = false;
if (woSimpleString) {
className = className + " simple";
}
}
}
}
else {
className = className + " " + nodeName.toLowerCase();
}
boolean showExpandCollapse = !empty;
if ("script".equalsIgnoreCase(nodeName)) {
// don't show script
showExpandCollapse = false;
}
else if ("style".equalsIgnoreCase(nodeName)) {
// don't show style
showExpandCollapse = false;
}
if (!showExpandCollapse) {
className += " empty";
}
//renderBuffer.append("<div id = \"" + nodeID + "\" class = \"" + className + "\" onmouseover = \"window.status = 'over:" + nodeID + "';\" onmouseout = \"window.status = 'out:" + nodeID + "';\" >");
renderBuffer.append("<div id = \"" + nodeID + "\" class = \"" + className + "\">");
if (showExpandCollapse) {
renderBuffer.append("<div id = \"" + nodeID + "_toggle\" class = \"expandcollapse\" onclick = \"expandCollapse('" + nodeID + "')\">");
if (_collapsedIDs.contains(nodeID)) {
renderBuffer.append(TemplateOutlinePage.EXPAND_STRING);
}
else {
renderBuffer.append(TemplateOutlinePage.COLLAPSE_STRING);
}
renderBuffer.append("</div>");
}
renderBuffer.append("<div class = \"summary\" onclick = \"window.status = 'select:" + nodeID + "'\">");
// ... a simple string tag
if (woSimpleString) {
IWodBinding valueBinding = wodElement.getBindingNamed("value");
String text = valueBinding.getValue();
String textClassName;
if (valueBinding.isLiteral()) {
//text = text.replaceAll("^\"([^\"]+)\"", "$1");
textClassName = "text literal";
}
else if (valueBinding.isOGNL()) {
textClassName = "text ognl";
}
else {
textClassName = "text keypath";
}
renderBuffer.append("<div class = \"title\"></div> <div class = \"" + textClassName + "\">[" + text + "]</div>");
}
// ... a WO tag
else if (woTag) {
if (wodElement != null) {
if (WodHtmlUtils.isInline(nodeName)) {
String summaryName = wodElement.getElementType();
renderBuffer.append("<div class = \"title\"><span class = \"nodeName verbose\">" + summaryName + "</span></div>");
// Special case for the compact display of a conditional
if ("TBConditional".equals(summaryName)) {
String conditionValue = wodElement.getBindingValue("condition");
if (conditionValue != null) {
summaryName = conditionValue;
boolean negated = false;
IWodBinding negateBinding = wodElement.getBindingNamed("negate");
if (negateBinding != null) {
if (negateBinding.isTrueValue()) {
negated = true;
}
}
else if (nodeName.equals("tb:not") || nodeName.equals("wo:not")) {
negated = true;
}
if (negated) {
summaryName = "<span class = \"negate\">not</span> " + summaryName;
}
else if (negateBinding != null) {
summaryName = summaryName + " <span class = \"negate\">not = " + negateBinding.getValue() + "</span>";
}
}
}
// Special case for the compact display of a string
else if ("TBString".equals(summaryName)) {
String value = wodElement.getBindingValue("value");
if (value != null) {
summaryName = "TBString: " + value;
}
}
// Special case for the compact display of a localized string
else if ("TBWLocalizedString".equals(summaryName)) {
String value = wodElement.getBindingValue("value");
if (value != null) {
summaryName = "TBWLocalizedString: " + value;
}
}
// Special case for the compact display of links, buttons, etc
else if (wodElement.getBindingNamed("action") != null) {
String action = wodElement.getBindingValue("action");
summaryName = wodElement.getElementType() + ": " + action;
}
// Special case for the compact display of form fields
else if (wodElement.getBindingNamed("value") != null) {
String value = wodElement.getBindingValue("value");
summaryName = wodElement.getElementType() + ": " + value;
}
// Special case for the compact display of checkboxes
else if (wodElement.getBindingNamed("checked") != null) {
String checked = wodElement.getBindingValue("checked");
summaryName = wodElement.getElementType() + ": " + checked;
}
// Special case for the compact display of repetitions and popup buttons
else if (wodElement.getBindingNamed("list") != null) {
String list = wodElement.getBindingValue("list");
summaryName = wodElement.getElementType() + ": " + list;
}
renderBuffer.append("<div class = \"title\"><span class = \"nodeName compact\">" + summaryName + "</span></div>");
}
else {
renderBuffer.append("<div class = \"title\"><span class = \"nodeName\">" + wodElement.getElementName() + "</span> <span class = \"type\">: " + wodElement.getElementType() + "</span></div>");
}
// WO bindings
List<IWodBinding> wodBindings = wodElement.getBindings();
if (wodBindings.size() > 0) {
renderBuffer.append("<table class = \"bindings\">");
for (IWodBinding wodBinding : wodBindings) {
renderBuffer.append("<tr>");
renderBuffer.append("<th>" + wodBinding.getName() + "</th>");
String bindingClass;
if (wodBinding.isLiteral()) {
bindingClass = "literal";
}
else if (wodBinding.isOGNL()) {
bindingClass = "ognl";
}
else {
bindingClass = "keypath";
}
renderBuffer.append("<td class = \"" + bindingClass + "\">" + wodBinding.getValue() + "</td>");
renderBuffer.append("</tr>");
}
renderBuffer.append("</table>");
}
}
// ... if the WO tag doesn't have a WOD entry, show it as "missing"
else {
String missingName = element.getAttributeValue("name");
if (missingName == null) {
missingName = nodeName;
}
renderBuffer.append("<div class = \"title missing\">" + missingName + "</div>");
}
}
// ... an html node
else {
renderBuffer.append("<div class = \"title nonwo\"><span class = \"nodeName\">");
renderBuffer.append(nodeName);
renderBuffer.append("</span>");
String elementID = element.getAttributeValue("id");
if (elementID != null) {
renderBuffer.append("<span class = \"idName\">#" + elementID + "</span>");
}
String elementClass = element.getAttributeValue("class");
if (elementClass != null) {
elementClass = elementClass.replace(' ', '.');
renderBuffer.append("<span class = \"className\">." + elementClass + "</span>");
}
renderBuffer.append("</div>");
}
renderBuffer.append("</div>");
// ... if there are children, show the contents
if (showExpandCollapse) {
renderBuffer.append("<div id = \"" + nodeID + "_contents\" class = \"contents\"");
if (_collapsedIDs.contains(nodeID)) {
renderBuffer.append(" style = \"display: none\"");
}
renderBuffer.append(">");
FuzzyXMLNode[] children = element.getChildren();
for (FuzzyXMLNode child : children) {
renderElement(child, renderContext, renderBuffer, cache);
}
renderBuffer.append("</div>");
}
renderBuffer.append("</div>");
}
else {
StringBuffer nodeBuffer = new StringBuffer();
node.toXMLString(renderContext, nodeBuffer);
String nodeStr = nodeBuffer.toString();
boolean isText = (node instanceof FuzzyXMLText);
if (isText) {
renderBuffer.append("<div class = \"text\">" + nodeStr + "</div>");
}
else {
renderBuffer.append(nodeStr);
}
}
}
}