All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.structr.web.entity.dom.Content Maven / Gradle / Ivy

Go to download

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