
org.structr.web.entity.dom.Content Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of structr-ui Show documentation
Show all versions of structr-ui Show documentation
Structr is an open source framework based on the popular Neo4j graph database.
The newest version!
/**
* Copyright (C) 2010-2016 Structr GmbH
*
* This file is part of Structr .
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Structr. If not, see .
*/
package org.structr.web.entity.dom;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.java.textilej.parser.MarkupParser;
import net.java.textilej.parser.markup.confluence.ConfluenceDialect;
import net.java.textilej.parser.markup.mediawiki.MediaWikiDialect;
import net.java.textilej.parser.markup.textile.TextileDialect;
import net.java.textilej.parser.markup.trac.TracWikiDialect;
import org.apache.commons.lang3.StringUtils;
import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Asciidoctor.Factory;
import org.pegdown.Parser;
import org.pegdown.PegDownProcessor;
import org.structr.common.Permission;
import org.structr.common.PropertyView;
import org.structr.common.SecurityContext;
import org.structr.common.error.ErrorBuffer;
import org.structr.common.error.FrameworkException;
import org.structr.core.Adapter;
import org.structr.core.property.ConstantBooleanProperty;
import org.structr.core.property.Property;
import org.structr.core.property.PropertyKey;
import org.structr.core.property.StringProperty;
import org.structr.web.common.AsyncBuffer;
import org.structr.web.common.RenderContext;
import org.structr.web.common.RenderContext.EditMode;
import static org.structr.web.entity.dom.DOMNode.hideOnDetail;
import static org.structr.web.entity.dom.DOMNode.hideOnIndex;
import org.structr.web.entity.html.Textarea;
import org.structr.web.entity.relation.Sync;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
//~--- classes ----------------------------------------------------------------
/**
* Represents a content node. This class implements the org.w3c.dom.Text interface.
* All methods in the W3C Text interface are based on the raw database content.
*
*
*/
public class Content extends DOMNode implements Text {
private static final Logger logger = Logger.getLogger(Content.class.getName());
public static final Property contentType = new StringProperty("contentType").indexed();
public static final Property content = new StringProperty("content").indexed();
public static final Property isContent = new ConstantBooleanProperty("isContent", true);
private static final Map> contentConverters = new LinkedHashMap<>();
private static final ThreadLocalAsciiDocProcessor asciiDocProcessor = new ThreadLocalAsciiDocProcessor();
private static final ThreadLocalTracWikiProcessor tracWikiProcessor = new ThreadLocalTracWikiProcessor();
private static final ThreadLocalTextileProcessor textileProcessor = new ThreadLocalTextileProcessor();
private static final ThreadLocalPegDownProcessor pegDownProcessor = new ThreadLocalPegDownProcessor();
private static final ThreadLocalMediaWikiProcessor mediaWikiProcessor = new ThreadLocalMediaWikiProcessor();
private static final ThreadLocalConfluenceProcessor confluenceProcessor = new ThreadLocalConfluenceProcessor();
public static final org.structr.common.View uiView = new org.structr.common.View(Content.class, PropertyView.Ui,
content, contentType, parent, pageId, syncedNodes, sharedComponent, dataKey, restQuery, cypherQuery, xpathQuery, functionQuery,
hideOnDetail, hideOnIndex, showForLocales, hideForLocales, showConditions, hideConditions, isContent, isDOMNode
);
public static final org.structr.common.View publicView = new org.structr.common.View(Content.class, PropertyView.Public,
content, contentType, parent, pageId, syncedNodes, sharedComponent, dataKey, restQuery, cypherQuery, xpathQuery, functionQuery,
hideOnDetail, hideOnIndex, showForLocales, hideForLocales, showConditions, hideConditions, isContent, isDOMNode
);
//~--- static initializers --------------------------------------------
static {
contentConverters.put("text/markdown", new Adapter() {
@Override
public String adapt(String s) throws FrameworkException {
if (s != null) {
return pegDownProcessor.get().markdownToHtml(s);
}
return "";
}
});
contentConverters.put("text/textile", new Adapter() {
@Override
public String adapt(String s) throws FrameworkException {
if (s != null) {
return textileProcessor.get().parseToHtml(s);
}
return "";
}
});
contentConverters.put("text/mediawiki", new Adapter() {
@Override
public String adapt(String s) throws FrameworkException {
if (s != null) {
return mediaWikiProcessor.get().parseToHtml(s);
}
return "";
}
});
contentConverters.put("text/tracwiki", new Adapter() {
@Override
public String adapt(String s) throws FrameworkException {
if (s != null) {
return tracWikiProcessor.get().parseToHtml(s);
}
return "";
}
});
contentConverters.put("text/confluence", new Adapter() {
@Override
public String adapt(String s) throws FrameworkException {
if (s != null) {
return confluenceProcessor.get().parseToHtml(s);
}
return "";
}
});
contentConverters.put("text/asciidoc", new Adapter() {
@Override
public String adapt(String s) throws FrameworkException {
if (s != null) {
return asciiDocProcessor.get().render(s, new HashMap());
}
return "";
}
});
// contentConverters.put("text/plain", new Adapter() {
//
// @Override
// public String adapt(String s) throws FrameworkException {
//
// return StringEscapeUtils.escapeHtml(s);
//
// }
//
// });
}
@Override
public boolean onModification(SecurityContext securityContext, ErrorBuffer errorBuffer) throws FrameworkException {
for (final Sync rel : getOutgoingRelationships(Sync.class)) {
Content syncedNode = (Content) rel.getTargetNode();
// sync content only
syncedNode.setProperty(content, getProperty(content));
syncedNode.setProperty(contentType, getProperty(contentType));
syncedNode.setProperty(name, getProperty(name));
}
final Sync rel = getIncomingRelationship(Sync.class);
if (rel != null) {
final Content otherNode = (Content) rel.getSourceNode();
if (otherNode != null) {
// sync both ways
otherNode.setProperty(content, getProperty(content));
otherNode.setProperty(contentType, getProperty(contentType));
otherNode.setProperty(name, getProperty(name));
}
}
try {
increasePageVersion();
} catch (FrameworkException ex) {
logger.log(Level.WARNING, "Updating page version failed", ex);
}
return true;
}
@Override
public boolean contentEquals(DOMNode otherNode) {
if (otherNode instanceof Content) {
final String content1 = getTextContent();
final String content2 = ((Content)otherNode).getTextContent();
if (content1 == null && content2 == null) {
return true;
}
if (content1 != null && content2 != null) {
return content1.equals(content2);
}
}
return false;
}
@Override
public void updateFromNode(final DOMNode newNode) throws FrameworkException {
if (newNode instanceof Content) {
final Map properties = new HashMap<>();
properties.put("content", newNode.getProperty(Content.content));
updateFromPropertyMap(properties);
}
}
// @Override
// public void updateFromPropertyMap(final Map properties) throws FrameworkException {
//
// this.setProperty(Content.content, properties.get(Content.content));
//
// // update visibility as well
// this.setProperty(Content.visibleToPublicUsers, properties.get(Content.visibleToPublicUsers));
// this.setProperty(Content.visibleToAuthenticatedUsers, properties.get(Content.visibleToAuthenticatedUsers));
// }
@Override
public String getIdHash() {
final DOMNode _parent = getProperty(DOMNode.parent);
if (_parent != null) {
String dataHash = _parent.getProperty(DOMNode.dataHashProperty);
if (dataHash == null) {
dataHash = _parent.getIdHash();
}
return dataHash + "Content" + treeGetChildPosition(this);
}
return super.getIdHash();
}
@Override
public java.lang.Object getPropertyForIndexing(final PropertyKey key) {
if (key.equals(Content.content)) {
String value = getProperty(Content.content);
if (value != null) {
return escape(value);
}
}
return super.getPropertyForIndexing(key);
}
@Override
public void renderContent(final RenderContext renderContext, final int depth) throws FrameworkException {
try {
if (isDeleted() || isHidden() || !displayForLocale(renderContext) || !displayForConditions(renderContext)) {
return;
}
final String id = getUuid();
final EditMode edit = renderContext.getEditMode(securityContext.getUser(false));
final boolean inBody = renderContext.inBody();
final AsyncBuffer out = renderContext.getBuffer();
String _contentType = getProperty(contentType);
// fetch content with variable replacement
String _content = getPropertyWithVariableReplacement(renderContext, Content.content);
if (!(EditMode.RAW.equals(edit) || EditMode.WIDGET.equals(edit)) && (_contentType == null || ("text/plain".equals(_contentType)))) {
_content = escapeForHtml(_content);
}
if (EditMode.CONTENT.equals(edit) && inBody && this.isGranted(Permission.write, securityContext)) {
if ("text/javascript".equals(_contentType)) {
// Javascript will only be given some local vars
// TODO: Is this neccessary?
out.append("// data-structr-type='").append(getType()).append("'\n// data-structr-id='").append(id).append("'\n");
} else if ("text/css".equals(_contentType)) {
// CSS will only be given some local vars
// TODO: Is this neccessary?
out.append("/* data-structr-type='").append(getType()).append("'*/\n/* data-structr-id='").append(id).append("'*/\n");
} else {
// // In edit mode, add an artificial comment tag around content nodes within body to make them editable
final String cleanedContent = StringUtils.remove(StringUtils.remove(org.apache.commons.lang3.StringUtils.replace(getProperty(Content.content), "\n", "\\\\n"), "");
out.append(""));
}
}
// No contentType-specific rendering in DATA edit mode
//if (!edit.equals(EditMode.DATA)) {
// examine content type and apply converter
if (_contentType != null) {
final Adapter converter = contentConverters.get(_contentType);
if (converter != null) {
try {
// apply adapter
_content = converter.adapt(_content);
} catch (FrameworkException fex) {
logger.log(Level.WARNING, "Unable to convert content: {0}", fex.getMessage());
}
}
}
// replace newlines with
for rendering
if (((_contentType == null) || _contentType.equals("text/plain")) && (_content != null) && !_content.isEmpty()) {
final DOMNode _parent = getProperty(Content.parent);
if (_parent == null || !(_parent instanceof Textarea)) {
_content = _content.replaceAll("[\\n]{1}", "
");
}
}
//}
if (_content != null) {
//buffer.append(indent(depth, true)).append(_content);
// insert whitespace to make element clickable
if (EditMode.CONTENT.equals(edit) && _content.length() == 0) {
_content = "--- empty ---";
}
out.append(_content);
}
if (EditMode.CONTENT.equals(edit) && inBody && !("text/javascript".equals(getProperty(contentType))) && !("text/css".equals(getProperty(contentType)))) {
// buffer.append("");
out.append("");
}
} catch (Throwable t) {
// catch exception to prevent ugly status 500 error pages in frontend.
logger.log(Level.SEVERE, "", t);
}
}
// @Override
// protected Object getEditModeValue(final SecurityContext securityContext, final RenderContext renderContext, final GraphObject dataObject, final PropertyKey referenceKeyProperty, final Object defaultValue) {
//
// Object value = dataObject.getProperty(StructrApp.getConfiguration().getPropertyKeyForJSONName(dataObject.getClass(), referenceKeyProperty.jsonName()));
// boolean canWrite = dataObject instanceof AbstractNode ? securityContext.isAllowed((AbstractNode) dataObject, Permission.write) : true;
//
// if (getProperty(Content.editable) && EditMode.DATA.equals(renderContext.getEditMode(securityContext.getUser(false))) && renderContext.inBody() && canWrite && !referenceKeyProperty.isReadOnly()) {
//
// String editModeValue = "" + value + "";
//
// logger.log(Level.FINEST, "Edit mode value: {0}", editModeValue);
//
// return editModeValue;
//
// } else {
//
// return value != null ? value : defaultValue;
//
// }
//
// }
@Override
public boolean isSynced() {
return false;
}
// ----- interface org.w3c.dom.Text -----
@Override
public Text splitText(int offset) throws DOMException {
checkWriteAccess();
String text = getProperty(content);
if (text != null) {
int len = text.length();
if (offset < 0 || offset > len) {
throw new DOMException(DOMException.INDEX_SIZE_ERR, INDEX_SIZE_ERR_MESSAGE);
} else {
final String firstPart = text.substring(0, offset);
final String secondPart = text.substring(offset);
final Document document = getOwnerDocument();
final Node parent = getParentNode();
if (document != null && parent != null) {
try {
// first part goes into existing text element
setProperty(content, firstPart);
// second part goes into new text element
Text newNode = document.createTextNode(secondPart);
// make new node a child of old parent
parent.appendChild(newNode);
return newNode;
} catch (FrameworkException fex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());
}
} else {
throw new DOMException(DOMException.INVALID_STATE_ERR, CANNOT_SPLIT_TEXT_WITHOUT_PARENT);
}
}
}
throw new DOMException(DOMException.INDEX_SIZE_ERR, INDEX_SIZE_ERR_MESSAGE);
}
@Override
public boolean isElementContentWhitespace() {
checkReadAccess();
String text = getProperty(content);
if (text != null) {
return !text.matches("[\\S]*");
}
return false;
}
@Override
public String getWholeText() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Text replaceWholeText(String string) throws DOMException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public String getData() throws DOMException {
checkReadAccess();
return getProperty(content);
}
@Override
public void setData(final String data) throws DOMException {
checkWriteAccess();
try {
setProperty(content, data);
} catch (FrameworkException fex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());
}
}
@Override
public int getLength() {
String text = getProperty(content);
if (text != null) {
return text.length();
}
return 0;
}
@Override
public String substringData(int offset, int count) throws DOMException {
checkReadAccess();
String text = getProperty(content);
if (text != null) {
try {
return text.substring(offset, offset + count);
} catch (IndexOutOfBoundsException iobex) {
throw new DOMException(DOMException.INDEX_SIZE_ERR, INDEX_SIZE_ERR_MESSAGE);
}
}
return "";
}
@Override
public void appendData(final String data) throws DOMException {
checkWriteAccess();
try {
String text = getProperty(content);
setProperty(content, text.concat(data));
} catch (FrameworkException fex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());
}
}
@Override
public void insertData(final int offset, final String data) throws DOMException {
checkWriteAccess();
try {
String text = getProperty(content);
String leftPart = text.substring(0, offset);
String rightPart = text.substring(offset);
StringBuilder buf = new StringBuilder(text.length() + data.length() + 1);
buf.append(leftPart);
buf.append(data);
buf.append(rightPart);
// finally, set content to concatenated left, data and right parts
setProperty(content, buf.toString());
} catch (FrameworkException fex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());
}
}
@Override
public void deleteData(final int offset, final int count) throws DOMException {
checkWriteAccess();
// finally, set content to concatenated left and right parts
try {
String text = getProperty(content);
String leftPart = text.substring(0, offset);
String rightPart = text.substring(offset + count);
setProperty(content, leftPart.concat(rightPart));
} catch (FrameworkException fex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());
}
}
@Override
public void replaceData(final int offset, final int count, final String data) throws DOMException {
checkWriteAccess();
// finally, set content to concatenated left and right parts
try {
String text = getProperty(content);
String leftPart = text.substring(0, offset);
String rightPart = text.substring(offset + count);
StringBuilder buf = new StringBuilder(leftPart.length() + data.length() + rightPart.length());
buf.append(leftPart);
buf.append(data);
buf.append(rightPart);
setProperty(content, buf.toString());
} catch (FrameworkException fex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, fex.toString());
}
}
// ----- interface org.w3c.dom.Node -----
@Override
public String getTextContent() throws DOMException {
return getData();
}
@Override
public void setTextContent(String textContent) throws DOMException {
setData(textContent);
}
@Override
public String getLocalName() {
return null;
}
@Override
public short getNodeType() {
return TEXT_NODE;
}
@Override
public String getNodeName() {
return "#text";
}
@Override
public String getNodeValue() throws DOMException {
return getData();
}
@Override
public void setNodeValue(String data) throws DOMException {
setData(data);
}
@Override
public NamedNodeMap getAttributes() {
return null;
}
@Override
public boolean hasAttributes() {
return false;
}
// ----- interface DOMImportable -----
@Override
public Node doImport(Page newPage) throws DOMException {
// for #text elements, importing is basically a clone operation
return newPage.createTextNode(getData());
}
//~--- inner classes --------------------------------------------------
private static class ThreadLocalConfluenceProcessor extends ThreadLocal {
@Override
protected MarkupParser initialValue() {
return new MarkupParser(new ConfluenceDialect());
}
}
private static class ThreadLocalMediaWikiProcessor extends ThreadLocal {
@Override
protected MarkupParser initialValue() {
return new MarkupParser(new MediaWikiDialect());
}
}
private static class ThreadLocalPegDownProcessor extends ThreadLocal {
@Override
protected PegDownProcessor initialValue() {
return new PegDownProcessor(Parser.ALL);
}
}
private static class ThreadLocalTextileProcessor extends ThreadLocal {
@Override
protected MarkupParser initialValue() {
return new MarkupParser(new TextileDialect());
}
}
private static class ThreadLocalTracWikiProcessor extends ThreadLocal {
@Override
protected MarkupParser initialValue() {
return new MarkupParser(new TracWikiDialect());
}
}
private static class ThreadLocalAsciiDocProcessor extends ThreadLocal {
@Override
protected Asciidoctor initialValue() {
return Factory.create();
}
}
private static final Set SPECIAL_CHARS = new LinkedHashSet<>();
static {
SPECIAL_CHARS.add('\\');
SPECIAL_CHARS.add('+');
SPECIAL_CHARS.add('-');
SPECIAL_CHARS.add('!');
SPECIAL_CHARS.add('(');
SPECIAL_CHARS.add(')');
SPECIAL_CHARS.add(':');
SPECIAL_CHARS.add('^');
SPECIAL_CHARS.add('[');
SPECIAL_CHARS.add(']');
SPECIAL_CHARS.add('"');
SPECIAL_CHARS.add('{');
SPECIAL_CHARS.add('}');
SPECIAL_CHARS.add('~');
SPECIAL_CHARS.add('*');
SPECIAL_CHARS.add('?');
SPECIAL_CHARS.add('|');
SPECIAL_CHARS.add('&');
SPECIAL_CHARS.add(';');
}
private String escape(String input) {
StringBuilder output = new StringBuilder();
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if (SPECIAL_CHARS.contains(c) || Character.isWhitespace(c)) {
output.append('\\');
}
output.append(c);
}
return output.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy