/*
* Freeplane - mind map editor
* Copyright (C) 2008 Dimitry Polivaev
*
* This file author is Dimitry Polivaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeplane.features.text;
import java.io.IOException;
import org.freeplane.core.extension.IExtension;
import org.freeplane.core.io.IAttributeHandler;
import org.freeplane.core.io.IAttributeWriter;
import org.freeplane.core.io.IElementContentHandler;
import org.freeplane.core.io.IElementWriter;
import org.freeplane.core.io.IExtensionAttributeWriter;
import org.freeplane.core.io.IExtensionElementWriter;
import org.freeplane.core.io.ITreeWriter;
import org.freeplane.core.io.ReadManager;
import org.freeplane.core.io.WriteManager;
import org.freeplane.core.resources.NamedObject;
import org.freeplane.core.util.HtmlUtils;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.TypeReference;
import org.freeplane.features.format.IFormattedObject;
import org.freeplane.features.map.MapWriter;
import org.freeplane.features.map.NodeBuilder;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.styles.StyleFactory;
import org.freeplane.features.styles.StyleNamedObject;
import org.freeplane.features.styles.StyleString;
import org.freeplane.n3.nanoxml.XMLElement;
public class NodeTextBuilder implements IElementContentHandler, IElementWriter, IAttributeWriter, IExtensionElementWriter, IExtensionAttributeWriter {
public static final String XML_NODE_TEXT = "TEXT";
public static final String XML_NODE_LOCALIZED_TEXT = "LOCALIZED_TEXT";
public static final String XML_NODE_XHTML_CONTENT_TAG = "richcontent";
public static final String XML_NODE_XHTML_TYPE_NODE = "NODE";
public static final String XML_NODE_XHTML_TYPE_NOTE = "NOTE";
public static final String XML_NODE_XHTML_TYPE_DETAILS = "DETAILS";
public static final String XML_NODE_XHTML_TYPE_TAG = "TYPE";
public static final String XML_NODE_OBJECT = "OBJECT";
private static final String XML_NODE_TEXT_SHORTENED = "TEXT_SHORTENED";
public Object createElement(final Object parent, final String tag, final XMLElement attributes) {
if (attributes == null) {
return null;
}
final Object typeAttribute = attributes.getAttribute(NodeTextBuilder.XML_NODE_XHTML_TYPE_TAG, null);
if (NodeTextBuilder.XML_NODE_XHTML_TYPE_NODE.equals(typeAttribute)
|| NodeTextBuilder.XML_NODE_XHTML_TYPE_DETAILS.equals(typeAttribute)) {
return parent;
}
return null;
}
public void endElement(final Object parent, final String tag, final Object obj, final XMLElement attributes,
final String content) {
assert tag.equals("richcontent");
final String xmlText = content.trim();
final Object typeAttribute = attributes.getAttribute(NodeTextBuilder.XML_NODE_XHTML_TYPE_TAG, null);
final NodeModel nodeModel = (NodeModel) obj;
if (NodeTextBuilder.XML_NODE_XHTML_TYPE_NODE.equals(typeAttribute)) {
nodeModel.setXmlText(xmlText);
}
else if (NodeTextBuilder.XML_NODE_XHTML_TYPE_DETAILS.equals(typeAttribute)) {
final DetailTextModel note = new DetailTextModel("true".equals(attributes.getAttribute("HIDDEN", "false")));
note.setXml(xmlText);
nodeModel.addExtension((IExtension) note);
}
}
private void registerAttributeHandlers(final ReadManager reader) {
reader.addAttributeHandler(NodeBuilder.XML_NODE, NodeTextBuilder.XML_NODE_TEXT, new IAttributeHandler() {
public void setAttribute(final Object userObject, final String value) {
final NodeModel node = ((NodeModel) userObject);
final Object nodeContent = node.getUserObject();
if(nodeContent == null || nodeContent.equals("")){
node.setText(value);
}
}
});
reader.addAttributeHandler(NodeBuilder.XML_NODE, NodeTextBuilder.XML_NODE_OBJECT, new IAttributeHandler() {
public void setAttribute(final Object userObject, final String value) {
final NodeModel node = ((NodeModel) userObject);
final Object newInstance = TypeReference.create(value);
// work around for old maps :
// actually we do not need IFormattedObject as user objects
// because formatting is saved as an extra attribute
if(newInstance instanceof IFormattedObject)
node.setUserObject(((IFormattedObject) newInstance).getObject());
else
node.setUserObject(newInstance);
}
});
reader.addAttributeHandler(NodeBuilder.XML_NODE, NodeTextBuilder.XML_NODE_TEXT_SHORTENED, new IAttributeHandler() {
public void setAttribute(final Object userObject, final String value) {
final NodeModel node = ((NodeModel) userObject);
try {
if(Boolean.valueOf(value)){
node.addExtension(new ShortenedTextModel());
}
}
catch (Exception e) {
LogUtils.warn(e);
}
}
});
reader.addAttributeHandler(NodeBuilder.XML_STYLENODE, NodeTextBuilder.XML_NODE_TEXT, new IAttributeHandler() {
public void setAttribute(final Object userObject, final String value) {
final NodeModel node = ((NodeModel) userObject);
node.setUserObject(StyleFactory.create(value));
}
});
reader.addAttributeHandler(NodeBuilder.XML_NODE, NodeTextBuilder.XML_NODE_LOCALIZED_TEXT, new IAttributeHandler() {
public void setAttribute(final Object userObject, final String value) {
final NodeModel node = ((NodeModel) userObject);
node.setUserObject(StyleFactory.create(NamedObject.format(value)));
}
});
reader.addAttributeHandler(NodeBuilder.XML_STYLENODE, NodeTextBuilder.XML_NODE_LOCALIZED_TEXT, new IAttributeHandler() {
public void setAttribute(final Object userObject, final String value) {
final NodeModel node = ((NodeModel) userObject);
node.setUserObject(StyleFactory.create(NamedObject.format(value)));
}
});
}
/**
* @param writeManager
*/
public void registerBy(final ReadManager reader, final WriteManager writeManager) {
registerAttributeHandlers(reader);
reader.addElementHandler("richcontent", this);
writeManager.addElementWriter(NodeBuilder.XML_NODE, this);
writeManager.addElementWriter(NodeBuilder.XML_STYLENODE, this);
writeManager.addAttributeWriter(NodeBuilder.XML_NODE, this);
writeManager.addAttributeWriter(NodeBuilder.XML_STYLENODE, this);
}
private static class TransformedXMLExtension implements IExtension{
final String xml;
public TransformedXMLExtension(String html) {
super();
this.xml = HtmlUtils.toXhtml(html);
}
}
public void writeAttributes(final ITreeWriter writer, final Object userObject, final String tag) {
final NodeModel node = (NodeModel) userObject;
final Object data = node.getUserObject();
if(data == null)
return;
final Class<? extends Object> dataClass = data.getClass();
if (dataClass.equals(StyleNamedObject.class)) {
writer.addAttribute(NodeTextBuilder.XML_NODE_LOCALIZED_TEXT, ((StyleNamedObject) data).getObject().toString());
return;
}
if (dataClass.equals(NamedObject.class)) {
writer.addAttribute(NodeTextBuilder.XML_NODE_LOCALIZED_TEXT, ((NamedObject) data).getObject().toString());
return;
}
final boolean forceFormatting = Boolean.TRUE.equals(writer.getHint(MapWriter.WriterHint.FORCE_FORMATTING));
if (forceFormatting) {
final String text = TextController.getController().getTransformedTextNoThrow(data, node, data);
if (!HtmlUtils.isHtmlNode(text)) {
writer.addAttribute(NodeTextBuilder.XML_NODE_TEXT, text.replace('\0', ' '));
}
else{
node.addExtension(new TransformedXMLExtension(text));
}
}
else{
final String text = data.toString();
if (node.getXmlText() == null) {
writer.addAttribute(NodeTextBuilder.XML_NODE_TEXT, text.replace('\0', ' '));
}
if(! (data instanceof String || data instanceof StyleString)){
writer.addAttribute(XML_NODE_OBJECT, TypeReference.toSpec(data));
}
}
}
public void writeContent(final ITreeWriter writer, final Object element, final String tag) throws IOException {
final NodeModel node = (NodeModel) element;
final TransformedXMLExtension transformedXML = (TransformedXMLExtension) node.getExtension(TransformedXMLExtension.class);
if (transformedXML != null || node.getXmlText() != null) {
final XMLElement htmlElement = new XMLElement();
htmlElement.setName(NodeTextBuilder.XML_NODE_XHTML_CONTENT_TAG);
htmlElement.setAttribute(NodeTextBuilder.XML_NODE_XHTML_TYPE_TAG, NodeTextBuilder.XML_NODE_XHTML_TYPE_NODE);
final String xmlText;
if (transformedXML != null){
xmlText = transformedXML.xml;
node.removeExtension(transformedXML);
}
else
xmlText = node.getXmlText();
final String content = xmlText.replace('\0', ' ');
writer.addElement('\n' + content + '\n', htmlElement);
}
}
/*
* (non-Javadoc)
* @see freeplane.io.INodeWriter#saveContent(freeplane.io.ITreeWriter,
* java.lang.Object, java.lang.String)
*/
public void writeContent(final ITreeWriter writer, final Object element, final IExtension note) throws IOException {
DetailTextModel model = (DetailTextModel) note;
if (model.getXml() != null) {
final XMLElement htmlElement = new XMLElement();
htmlElement.setName(NodeTextBuilder.XML_NODE_XHTML_CONTENT_TAG);
htmlElement.setAttribute(NodeTextBuilder.XML_NODE_XHTML_TYPE_TAG, NodeTextBuilder.XML_NODE_XHTML_TYPE_DETAILS);
if(model.isHidden()){
htmlElement.setAttribute("HIDDEN", "true");
}
final String content = model.getXml().replace('\0', ' ');
writer.addElement('\n' + content + '\n', htmlElement);
}
return;
}
public void writeAttributes(ITreeWriter writer, Object userObject, IExtension extension) {
writer.addAttribute(XML_NODE_TEXT_SHORTENED, Boolean.TRUE.toString());
}
}