|
|
(7 intermediate revisions by 4 users not shown) |
Line 1: |
Line 1: |
− | This page describes a development version which will only work with preview/alpha/beta/test versions of Freeplane. If you are seeking for the stable released version see [[Scripting API]].
| + | #REDIRECT [[Scripting API]] |
− | | |
− | ==Overview over the changes==
| |
− | The development is currently driven by the implementation of the [[Formula]] feature that provides mindmappers with features you know from spreadsheet processors like Excel. This implies
| |
− | | |
− | * The syntax should allow more concise statements. For instance to convert a node text you can now write <tt>to.num</tt> instead of <tt>Double.parse(node.text)</tt>. Attributes are available as <tt>node['name']</tt> in addition to the old <tt>node.attributes.get('name')</tt>.
| |
− | * The API should provide the additional functionality that is needed for formulas.
| |
− | * The API has to be re-organized to provide a read-only API for formulas.
| |
− | | |
− | A very important usability improvement, not only for formula writers, is the introduction of the class [[Scripting: Convertible|Convertible]] that is returned now by some new methods/properties:
| |
− | | |
− | * <tt>node.to</tt> (or <tt>node.getTo())</tt>
| |
− | * <tt>node['attr_name']</tt> (or <tt>node.getAt('attr_name'))</tt>
| |
− | * <tt>node.note</tt> (or <tt>node.getNote())</tt>
| |
− | | |
− | Many "setters" on the other hand, like <tt>node.setText()</tt> have been extended to accept not only Strings but Objects. Much effort was spent to ensure that this conversion matches the conversions that <tt>Convertible</tt> performs for Strings. For example <tt>node.text = new Date()</tt> is converted to <tt>2010-10-05T22:11:03.243+0000</tt> which <tt>Convertible</tt> knows how to convert back to date (try <tt>node.to.date</tt>).
| |
− | | |
− | For formulas it's important that the formulas itself don't change the state of the map. Currently only the first step is made: All subinterfaces <tt>Xyz</tt> have a base interface <tt>XyzRO</tt> that includes only the methods that are suitable for formulas. The Proxy implementations implement the full interfaces currently and the constraint is not enforced.
| |
− | | |
− | Some controller methods were introduced mainly for testing:
| |
− | | |
− | * <tt>Controller.newMap()</tt>
| |
− | * <tt>Controller.undo()</tt>
| |
− | * <tt>Controller.redo()</tt>
| |
− | | |
− | ===Example Maps===
| |
− | * [[Media:Scripting-convertible-and-more.mm|Scripting API improvements]] (download only since the browser doesn't support formulas yet)
| |
− | | |
− | ==Entry Points==
| |
− | Each script is given two variables:
| |
− | | |
− | <groovy>
| |
− | final Proxy.Node node;
| |
− | final Proxy.Controller c;
| |
− | </groovy>
| |
− | | |
− | ''New:'' Methods and properties of the current node are directly available so you can write <tt>children.size()</tt> instead of <tt>node.children.size()</tt>.
| |
− | | |
− | ==Interface==
| |
− | <groovy>
| |
− | package org.freeplane.plugin.script.proxy;
| |
− | | |
− | import groovy.lang.Closure;
| |
− | | |
− | import java.awt.Color;
| |
− | import java.io.File;
| |
− | import java.net.URL;
| |
− | import java.util.Collection;
| |
− | import java.util.Date;
| |
− | import java.util.List;
| |
− | | |
− | import javax.swing.Icon;
| |
− | | |
− | import org.freeplane.core.util.FreeplaneIconUtils;
| |
− | import org.freeplane.core.util.FreeplaneVersion;
| |
− | import org.freeplane.features.common.edge.EdgeStyle;
| |
− | import org.freeplane.features.common.filter.condition.ICondition;
| |
− | import org.freeplane.features.common.link.ArrowType;
| |
− | import org.freeplane.features.common.styles.IStyle;
| |
− | | |
− | public interface Proxy {
| |
− | interface AttributesRO {
| |
− | /** alias for getFirst(int).
| |
− | * @deprecated before 1.1 - use get(int), getFirst(int) or getAll(String) instead. */
| |
− | @Deprecated
| |
− | String get(final String name);
| |
− | | |
− | /** returns the <em>first</em> value of an attribute with the given name or null otherwise. @since 1.2 */
| |
− | String getFirst(final String name);
| |
− | | |
− | /** returns all values for the attribute name. */
| |
− | List<String> getAll(final String name);
| |
− | | |
− | /** returns all attribute names in the proper sequence. The number of names returned
| |
− | * is equal to the number of attributes.
| |
− | * <pre>
| |
− | * // rename attribute
| |
− | * int i = 0;
| |
− | * for (String name : attributes.getAttributeNames()) {
| |
− | * if (name.equals("xy"))
| |
− | * attributes.set(i, "xyz", attributes.get(i));
| |
− | * ++i;
| |
− | * }
| |
− | * </pre> */
| |
− | List<String> getAttributeNames();
| |
− | | |
− | /** returns the attribute value at the given index.
| |
− | * @throws IndexOutOfBoundsException if index is out of range <tt>(index
| |
− | * < 0 || index >= size())</tt>.*/
| |
− | String get(final int index);
| |
− | | |
− | /** @deprecated since 1.2 - use findFirst(String) instead. */
| |
− | int findAttribute(final String name);
| |
− | | |
− | /** returns the index of the first attribute with the given name if one exists or -1 otherwise.
| |
− | * For searches for <em>all</em> attributes with a given name <code>getAttributeNames()</code>
| |
− | * must be used. @since 1.2*/
| |
− | int findFirst(final String name);
| |
− |
| |
− | /** the number of attributes. It is <code>size() == getAttributeNames().size()</code>. */
| |
− | int size();
| |
− | }
| |
− | | |
− | /** Attributes are name - value pairs assigned to a node. A node may have multiple attributes
| |
− | * with the same name. */
| |
− | interface Attributes extends AttributesRO {
| |
− | /** sets the value of the attribute at an index. This method will not create new attributes.
| |
− | * @throws IndexOutOfBoundsException if index is out of range <tt>(index
| |
− | * < 0 || index >= size())</tt>. */
| |
− | void set(final int index, final String value);
| |
− | | |
− | /** sets name and value of the attribute at the given index. This method will not create new attributes.
| |
− | * @throws IndexOutOfBoundsException if index is out of range <tt>(index
| |
− | * < 0 || index >= size())</tt>. */
| |
− | void set(final int index, final String name, final String value);
| |
− | | |
− | /** removes the <em>first</em> attribute with this name.
| |
− | * @returns true on removal of an existing attribute and false otherwise.
| |
− | * @deprecated before 1.1 - use remove(int) or removeAll(String) instead. */
| |
− | @Deprecated
| |
− | boolean remove(final String name);
| |
− | | |
− | /** removes <em>all</em> attributes with this name.
| |
− | * @returns true on removal of an existing attribute and false otherwise. */
| |
− | boolean removeAll(final String name);
| |
− | | |
− | /** removes the attribute at the given index.
| |
− | * @throws IndexOutOfBoundsException if index is out of range <tt>(index
| |
− | * < 0 || index >= size())</tt>. */
| |
− | void remove(final int index);
| |
− | | |
− | /** adds an attribute if there is no attribute with the given name or changes
| |
− | * the value <em>of the first</em> attribute with the given name. */
| |
− | void set(final String name, final String value);
| |
− | | |
− | /** adds an attribute no matter if an attribute with the given name already exists. */
| |
− | void add(final String name, final String value);
| |
− | | |
− | /** removes all attributes. @since 1.2 */
| |
− | void clear();
| |
− | }
| |
− | | |
− | interface ConnectorRO {
| |
− | Color getColor();
| |
− | | |
− | ArrowType getEndArrow();
| |
− | | |
− | String getMiddleLabel();
| |
− | | |
− | Node getSource();
| |
− | | |
− | String getSourceLabel();
| |
− | | |
− | ArrowType getStartArrow();
| |
− | | |
− | Node getTarget();
| |
− | | |
− | String getTargetLabel();
| |
− | | |
− | boolean simulatesEdge();
| |
− | }
| |
− | | |
− | interface Connector extends ConnectorRO {
| |
− | void setColor(Color color);
| |
− | | |
− | void setEndArrow(ArrowType arrowType);
| |
− | | |
− | void setMiddleLabel(String label);
| |
− | | |
− | void setSimulatesEdge(boolean simulatesEdge);
| |
− | | |
− | void setSourceLabel(String label);
| |
− | | |
− | void setStartArrow(ArrowType arrowType);
| |
− | | |
− | void setTargetLabel(String label);
| |
− | }
| |
− | | |
− | interface ControllerRO {
| |
− | /** if multiple nodes are selected returns one (arbitrarily chosen)
| |
− | * selected node or the selected node for a single node selection. */
| |
− | Node getSelected();
| |
− | | |
− | List<Node> getSelecteds();
| |
− | | |
− | /** returns List<Node> of Node objects sorted on Y
| |
− | *
| |
− | * @param differentSubtrees if true
| |
− | * children/grandchildren/grandgrandchildren/... nodes of selected
| |
− | * parent nodes are excluded from the result. */
| |
− | List<Node> getSortedSelection(boolean differentSubtrees);
| |
− | | |
− | /**
| |
− | * returns Freeplane version.
| |
− | * Use it like this:
| |
− | * <pre>
| |
− | * import org.freeplane.core.util.FreeplaneVersion
| |
− | * import org.freeplane.core.ui.components.UITools
| |
− | *
| |
− | * def required = FreeplaneVersion.getVersion("1.1.2");
| |
− | * if (c.freeplaneVersion < required)
| |
− | * UITools.errorMessage("Freeplane version " + c.freeplaneVersion
| |
− | * + " not supported - update to at least " + required);
| |
− | * </pre>
| |
− | */
| |
− | FreeplaneVersion getFreeplaneVersion();
| |
− | | |
− | /** Starting from the root node, recursively searches for nodes for which
| |
− | * <code>condition.checkNode(node)</code> returns true.
| |
− | * @see Node.find(ICondition) for searches on subtrees
| |
− | * @deprecated since 1.2 use find(Closure) instead. */
| |
− | List<Node> find(ICondition condition);
| |
− | | |
− | /**
| |
− | * Starting from the root node, recursively searches for nodes for which <code>closure.call(node)</code>
| |
− | * returns true.
| |
− | * <p>
| |
− | * A find method that uses a Groovy closure ("block") for simple custom searches. As this closure
| |
− | * will be called with a node as an argument (to be referenced by <code>it</code>) the search can
| |
− | * evaluate every node property, like attributes, icons, node text or notes.
| |
− | * <p>
| |
− | * Examples:
| |
− | * <pre>
| |
− | * def nodesWithNotes = c.find{ it.noteText != null }
| |
− | *
| |
− | * def matchingNodes = c.find{ it.text.matches(".*\\d.*") }
| |
− | * def texts = matchingNodes.collect{ it.text }
| |
− | * print "node texts containing numbers:\n " + texts.join("\n ")
| |
− | * </pre>
| |
− | * @param closure a Groovy closure that returns a boolean value. The closure will receive
| |
− | * a NodeModel as an argument which can be tested for a match.
| |
− | * @return all nodes for which <code>closure.call(NodeModel)</code> returns true.
| |
− | * @see Node.find(Closure) for searches on subtrees
| |
− | */
| |
− | List<Node> find(Closure closure);
| |
− | }
| |
− | | |
− | interface Controller extends ControllerRO {
| |
− | void centerOnNode(Node center);
| |
− | | |
− | void select(Node toSelect);
| |
− | | |
− | /** selects branchRoot and all children */
| |
− | void selectBranch(Node branchRoot);
| |
− | | |
− | /** toSelect is a List<Node> of Node objects */
| |
− | void selectMultipleNodes(List<Node> toSelect);
| |
− | | |
− | /** reset undo / redo lists and deactivate Undo for current script */
| |
− | void deactivateUndo();
| |
− | | |
− | /** invokes undo once - for testing purposes mainly. @since 1.2 */
| |
− | void undo();
| |
− | | |
− | /** invokes redo once - for testing purposes mainly. @since 1.2 */
| |
− | void redo();
| |
− | | |
− | /** The main info for the status line with key="standard", use null to remove. Removes icon if there is one. */
| |
− | void setStatusInfo(String info);
| |
− | | |
− | /** Info for status line, null to remove. Removes icon if there is one.
| |
− | * @see setStatusInfo(String, String, String) */
| |
− | void setStatusInfo(String infoPanelKey, String info);
| |
− | | |
− | /** Info for status line - text and icon - null stands for "remove" (text or icon)
| |
− | * @param infoPanelKey "standard" is the left most standard info panel. If a panel with
| |
− | * this name doesn't exist it will be created.
| |
− | * @param info Info text
| |
− | * @param iconKey key as those that are used for nodes (see {@link Icons#addIcon(String)}).
| |
− | * <pre>
| |
− | * println("all available icon keys: " + FreeplaneIconUtils.listStandardIconKeys())
| |
− | * c.setStatusInfo("standard", "hi there!", "button_ok");
| |
− | * </pre>
| |
− | * @see FreeplaneIconUtils
| |
− | * @since 1.2 */
| |
− | void setStatusInfo(String infoPanelKey, String info, String iconKey);
| |
− |
| |
− | /** @deprecated since 1.2 - use setStatusInfo(String, String, String) */
| |
− | void setStatusInfo(String infoPanelKey, Icon icon);
| |
− | | |
− | /** opens a new map with a default name in the foreground. @since 1.2 */
| |
− | Map newMap();
| |
− | | |
− | /** opens a new map for url in the foreground if it isn't opened already. @since 1.2 */
| |
− | Map newMap(URL url);
| |
− | }
| |
− | | |
− | interface EdgeRO {
| |
− | Color getColor();
| |
− | | |
− | EdgeStyle getType();
| |
− | | |
− | int getWidth();
| |
− | }
| |
− | | |
− | interface Edge extends EdgeRO {
| |
− | void setColor(Color color);
| |
− | | |
− | void setType(EdgeStyle type);
| |
− | | |
− | /** can be -1 for default, 0 for thin, >0 */
| |
− | void setWidth(int width);
| |
− | }
| |
− | | |
− | interface ExternalObjectRO {
| |
− | /** empty string means that there's no external object */
| |
− | String getURI();
| |
− | | |
− | float getZoom();
| |
− | }
| |
− | | |
− | interface ExternalObject extends ExternalObjectRO {
| |
− | /** setting empty String uri means remove external object (as for Links); */
| |
− | void setURI(String uri);
| |
− | | |
− | void setZoom(float zoom);
| |
− | }
| |
− | | |
− | interface FontRO {
| |
− | String getName();
| |
− | | |
− | int getSize();
| |
− | | |
− | boolean isBold();
| |
− | | |
− | boolean isBoldSet();
| |
− | | |
− | boolean isItalic();
| |
− | | |
− | boolean isItalicSet();
| |
− | | |
− | boolean isNameSet();
| |
− | | |
− | boolean isSizeSet();
| |
− | }
| |
− | | |
− | interface Font extends FontRO {
| |
− | void resetBold();
| |
− | | |
− | void resetItalic();
| |
− | | |
− | void resetName();
| |
− | | |
− | void resetSize();
| |
− | | |
− | void setBold(boolean bold);
| |
− | | |
− | void setItalic(boolean italic);
| |
− | | |
− | void setName(String name);
| |
− | | |
− | void setSize(int size);
| |
− | }
| |
− | | |
− | interface IconsRO {
| |
− | /** returns List<Node> of Strings (corresponding to iconID above);
| |
− | * iconID is one of "Idea","Question","Important", etc. */
| |
− | List<String> getIcons();
| |
− | }
| |
− | | |
− | interface Icons extends IconsRO {
| |
− | /**
| |
− | * adds an icon to a node if an icon for the given key can be found. The same icon can be added multiple
| |
− | * times.
| |
− | * <pre>
| |
− | * println("all available icon keys: " + FreeplaneIconUtils.listStandardIconKeys())
| |
− | * node.icons.addIcon("button_ok")
| |
− | * </pre>
| |
− | * @see FreeplaneIconUtils */
| |
− | void addIcon(String name);
| |
− | | |
− | /** deletes first occurence of icon with the given name, returns true if
| |
− | * success (icon existed); */
| |
− | boolean removeIcon(String name);
| |
− | }
| |
− | | |
− | interface LinkRO {
| |
− | String get();
| |
− | }
| |
− | | |
− | interface Link extends LinkRO {
| |
− | /** target is a URI.
| |
− | * An empty String will remove the link.
| |
− | * To get a local link (i.e. to another node) target should be: "#" + nodeId */
| |
− | boolean set(String target);
| |
− | }
| |
− | | |
− | interface MapRO {
| |
− | /** @since 1.2 */
| |
− | Node getRoot();
| |
− | | |
− | /** @deprecated since 1.2 - use getRoot() instead. */
| |
− | Node getRootNode();
| |
− | | |
− | /** returns the node if the map contains it or null otherwise. */
| |
− | Node node(String id);
| |
− | | |
− | /** returns the physical location of the map if available or null otherwise. */
| |
− | File getFile();
| |
− | | |
− | /** returns the title of the MapView. @since 1.2 */
| |
− | String getName();
| |
− | }
| |
− | | |
− | interface Map extends MapRO {
| |
− | /**
| |
− | * closes a map. Note that there is no undo for this method.
| |
− | * @param close map even if there are unsaved changes.
| |
− | * @param allowInteraction if (allowInteraction && ! force) a saveAs dialog will be opened if there are
| |
− | * unsaved changes.
| |
− | * @return false if the saveAs was cancelled by the user and true otherwise.
| |
− | * @throws RuntimeException if the map contains changes and parameter force is false.
| |
− | * @since 1.2
| |
− | */
| |
− | boolean close(boolean force, boolean allowInteraction);
| |
− | | |
− | /**
| |
− | * saves the map to disk. Note that there is no undo for this method.
| |
− | * @param allowInteraction if a saveAs dialog should be opened if the map has no assigned URL so far.
| |
− | * @return false if the saveAs was cancelled by the user and true otherwise.
| |
− | * @throws RuntimeException if the map has no assigned URL and parameter allowInteraction is false.
| |
− | * @since 1.2
| |
− | */
| |
− | boolean save(boolean allowInteraction);
| |
− | }
| |
− | | |
− | interface NodeRO {
| |
− | Attributes getAttributes();
| |
− | | |
− | /** allows to access attribute values like array elements. Note that the returned type is a
| |
− | * {@link Convertible}, not a String. Nevertheless it behaves like a String in almost all respects,
| |
− | * that is, in Groovy scripts it understands all String methods like lenght(), matches() etc.
| |
− | * <pre>
| |
− | * // standard way
| |
− | * node.attributes.set("attribute name", "12")
| |
− | * // implicitely use getAt()
| |
− | * def val = node["attribute name"]
| |
− | * // use all conversions that Convertible provides (num, date, string, ...)
| |
− | * assert val.num == new Long(12)
| |
− | * // or use it just like a string
| |
− | * assert val.startsWith("1")
| |
− | * </pre>
| |
− | * @since 1.2
| |
− | */
| |
− | Convertible getAt(String attributeName);
| |
− | | |
− | /** returns the index (0..) of this node in the (by Y coordinate sorted)
| |
− | * list of this node's children. Returns -1 if childNode is not a child
| |
− | * of this node. */
| |
− | int getChildPosition(Node childNode);
| |
− | | |
− | /** returns the children of this node ordered by Y coordinate. */
| |
− | List<Node> getChildren();
| |
− | | |
− | Collection<Connector> getConnectorsIn();
| |
− | | |
− | Collection<Connector> getConnectorsOut();
| |
− | | |
− | ExternalObject getExternalObject();
| |
− | | |
− | Icons getIcons();
| |
− | | |
− | Link getLink();
| |
− | | |
− | /** the map this node belongs to. */
| |
− | Map getMap();
| |
− | | |
− | /** @deprecated since 1.2 - use Node.getId() instead. */
| |
− | String getNodeID();
| |
− | | |
− | /** @since 1.2 */
| |
− | String getId();
| |
− | | |
− | /** if countHidden is false then only nodes that are matched by the
| |
− | * current filter are counted. */
| |
− | int getNodeLevel(boolean countHidden);
| |
− | | |
− | /**
| |
− | * Returns a Convertible object for the plain not text. Convertibles behave like Strings in most respects.
| |
− | * Additionally String methods are overridden to handle Convertible arguments as if the argument were the
| |
− | * result of Convertible.getText().
| |
− | * @return Convertible getString(), getText() and toString() will return plain text instead of the HTML.
| |
− | * Use getNoteText() to get the HTML text.
| |
− | * @since 1.2
| |
− | */
| |
− | Convertible getNote();
| |
− |
| |
− | /** Returns the HTML text of the node. (Notes always contain HTML text.) */
| |
− | String getNoteText();
| |
− | | |
− | /** @since 1.2 */
| |
− | Node getParent();
| |
− | | |
− | /** @deprecated since 1.2 - use getParent() instead. */
| |
− | Node getParentNode();
| |
− | | |
− | NodeStyle getStyle();
| |
− | | |
− | /** use this method to remove all tags from an HTML node. @since 1.2 */
| |
− | String getPlainText();
| |
− | | |
− | /** use this method to remove all tags from an HTML node.
| |
− | * @deprecated since 1.2 - use getPlainText() or getTo().getPlain() instead. */
| |
− | String getPlainTextContent();
| |
− | | |
− | String getText();
| |
− | | |
− | /**
| |
− | * returns an object that performs conversions (method name is choosen to give descriptive code):
| |
− | * <dl>
| |
− | * <dt>node.to.num <dd>Long or Double, see {@link Convertible#getDate()}.
| |
− | * <dt>node.to.date <dd>Date, see {@link Convertible#getDate()}.
| |
− | * <dt>node.to.string <dd>Text, see {@link Convertible#getString()}.
| |
− | * <dt>node.to.text <dd>an alias for getString(), see {@link Convertible#getText()}.
| |
− | * <dt>node.to.object <dd>returns what fits best, see {@link Convertible#getObject()}.
| |
− | * </dl>
| |
− | * Note that parse errors result in {@link ConversionException}s.
| |
− | * @return ConvertibleObject
| |
− | * @since 1.2
| |
− | */
| |
− | Convertible getTo();
| |
− | | |
− | /** an alias for getTo(). @since 1.2 */
| |
− | Convertible getValue();
| |
− | | |
− | /** returns true if p is a parent, or grandparent, ... of this node, or if it <em>is equal<em>
| |
− | * to this node; returns false otherwise. */
| |
− | boolean isDescendantOf(Node p);
| |
− | | |
− | boolean isFolded();
| |
− | | |
− | boolean isLeaf();
| |
− | | |
− | boolean isLeft();
| |
− | | |
− | boolean isRoot();
| |
− | | |
− | boolean isVisible();
| |
− | | |
− | /** Starting from this node, recursively searches for nodes for which
| |
− | * <code>condition.checkNode(node)</code> returns true.
| |
− | * @deprecated since 1.2 use find(Closure) instead. */
| |
− | List<Node> find(ICondition condition);
| |
− | | |
− | /** Starting from this node, recursively searches for nodes for which <code>closure.call(node)</code>
| |
− | * returns true. See {@link Controller#find(Closure)} for details. */
| |
− | List<Node> find(Closure closure);
| |
− | | |
− | Date getLastModifiedAt();
| |
− | | |
− | Date getCreatedAt();
| |
− | }
| |
− | | |
− | interface Node extends NodeRO {
| |
− | Connector addConnectorTo(Node target);
| |
− | | |
− | /** adds a new Connector object to List<Node> connectors and returns
| |
− | * reference for optional further editing (style); also enlists the
| |
− | * Connector on the target Node object. */
| |
− | Connector addConnectorTo(String targetNodeId);
| |
− | | |
− | /** inserts *new* node as child, takes care of all construction work and
| |
− | * internal stuff inserts as last child. */
| |
− | Node createChild();
| |
− | | |
− | /** inserts *new* node as child, takes care of all construction work and
| |
− | * internal stuff */
| |
− | Node createChild(int position);
| |
− | | |
− | void delete();
| |
− | | |
− | /** removes connector from List<Node> connectors; does the corresponding
| |
− | * on the target Node object referenced by connectorToBeRemoved */
| |
− | void moveTo(Node parentNode);
| |
− | | |
− | void moveTo(Node parentNode, int position);
| |
− | | |
− | /** as above, using String nodeId instead of Node object to establish the connector*/
| |
− | void removeConnector(Connector connectorToBeRemoved);
| |
− | | |
− | void setFolded(boolean folded);
| |
− | | |
− | /**
| |
− | * Set the note text:
| |
− | * <ul>
| |
− | * <li>This methods provides automatic conversion to String in a way that node.getNote().getXyz()
| |
− | * methods will be able to convert the string properly to the wanted type.
| |
− | * <li>Special conversion is provided for dates and calendars: They will be converted in a way that
| |
− | * node.note.date and node.note.calendar will work. All other types are converted via value.toString().
| |
− | * <li>If the conversion result is not valid HTML it will be automatically converted to HTML.
| |
− | * </ul>
| |
− | * <p>
| |
− | * <pre>
| |
− | * // converts numbers and other stuff with toString()
| |
− | * node.note = 1.2
| |
− | * assert node.note.text == "<html><body><p>1.2"
| |
− | * assert node.note.plain == "1.2"
| |
− | * assert node.note.num == 1.2d
| |
− | * // == dates
| |
− | * // a date in some non-UTC time zone
| |
− | * def date = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ").
| |
− | * parse("1970-01-01 00:00:00.000-0200")
| |
− | * // converts to "1970-01-01T02:00:00.000+0000" (GMT)
| |
− | * // - note the shift due to the different time zone
| |
− | * // - the missing end tags don't matter for rendering
| |
− | * node.note = date
| |
− | * assert node.note == "<html><body><p>1970-01-01T02:00:00.000+0000"
| |
− | * assert node.note.plain == "1970-01-01T02:00:00.000+0000"
| |
− | * assert node.note.date == date
| |
− | * // == remove note
| |
− | * node.note = null
| |
− | * assert node.note.text == null
| |
− | * </pre>
| |
− | * @param value An object for conversion to String. Works well for all types that {@link Convertible}
| |
− | * handles, particularly {@link Convertible}s itself.
| |
− | * @since 1.2 (note that the old setNoteText() did not support non-String arguments.
| |
− | */
| |
− | void setNote(Object value);
| |
− | | |
− | /** @deprecated since 1.2 - use setNote() instead. */
| |
− | void setNoteText(String text);
| |
− | | |
− | /**
| |
− | * A node's text is String valued. This methods provides automatic conversion to String in a way that
| |
− | * node.to.getXyz() methods will be able to convert the string properly to the wanted type.
| |
− | * Special conversion is provided for dates and calendars: They will be converted in a way that
| |
− | * node.to.date and node.to.calendar will work. All other types are converted via value.toString():
| |
− | * <pre>
| |
− | * // converts non-Dates with toString()
| |
− | * node.text = 1.2
| |
− | * assert node.to.text == "1.2"
| |
− | * assert node.to.num == 1.2d
| |
− | * // == dates
| |
− | * // a date in some non-GMT time zone
| |
− | * def date = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ").
| |
− | * parse("1970-01-01 00:00:00.000-0200")
| |
− | * // converts to "1970-01-01T02:00:00.000+0000" (GMT)
| |
− | * // - note the shift due to the different time zone
| |
− | * node.text = date
| |
− | * assert node.to.text == "1970-01-01T02:00:00.000+0000"
| |
− | * assert node.to.date == date
| |
− | * </pre>
| |
− | * @param value A not-null object for conversion to String. Works well for all types that {@link Convertible}
| |
− | * handles, particularly {@link Convertible}s itself.
| |
− | */
| |
− | void setText(Object value);
| |
− | | |
− | void setLastModifiedAt(Date date);
| |
− | | |
− | void setCreatedAt(Date date);
| |
− | | |
− | // Attributes
| |
− | /**
| |
− | * Allows to set and to change attribute like array elements.
| |
− | * <p>
| |
− | * Note that attributes are String valued. This methods provides automatic conversion to String in a way that
| |
− | * node["a name"].getXyz() methods will be able to convert the string properly to the wanted type.
| |
− | * Special conversion is provided for dates and calendars: They will be converted in a way that
| |
− | * node["a name"].date and node["a name"].calendar will work. All other types are converted via
| |
− | * value.toString():
| |
− | * <pre>
| |
− | * // == text
| |
− | * node["attribute name"] = "a value"
| |
− | * assert node["attribute name"] == "a value"
| |
− | * assert node.attributes.getFirst("attribute name") == "a value" // the same
| |
− | * // == numbers and others
| |
− | * // converts numbers and other stuff with toString()
| |
− | * node["a number"] = 1.2
| |
− | * assert node["a number"].text == "1.2"
| |
− | * assert node["a number"].num == 1.2d
| |
− | * // == dates
| |
− | * // a date in some non-GMT time zone
| |
− | * def date = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ").
| |
− | * parse("1970-01-01 00:00:00.000-0200")
| |
− | * // converts to "1970-01-01T02:00:00.000+0000" (GMT)
| |
− | * // - note the shift due to the different time zone
| |
− | * node["a date"] = date
| |
− | * assert node["a date"].text == "1970-01-01T02:00:00.000+0000"
| |
− | * assert node["a date"].date == date
| |
− | * // == remove an attribute
| |
− | * node["removed attribute"] = "to be removed"
| |
− | * assert node["removed attribute"] == "to be removed"
| |
− | * node["removed attribute"] = null
| |
− | * assert node.attributes.find("removed attribute") == -1
| |
− | * </pre>
| |
− | * @param value An object for conversion to String. Works well for all types that {@link Convertible}
| |
− | * handles, particularly {@link Convertible}s itself. Use null to unset an attribute.
| |
− | * @return the new value
| |
− | */
| |
− | String putAt(String attributeName, Object value);
| |
− | | |
− | /** allows to set all attributes at once:
| |
− | * <pre>
| |
− | * node.attributes = [:] // clear the attributes
| |
− | * assert node.attributes.size() == 0
| |
− | * node.attributes = ["1st" : "a value", "2nd" : "another value"] // create 2 attributes
| |
− | * assert node.attributes.size() == 2
| |
− | * node.attributes = ["one attrib" : new Double(1.22)] // replace all attributes
| |
− | * assert node.attributes.size() == 1
| |
− | * assert node.attributes.getFirst("one attrib") == "1.22" // note the type conversion
| |
− | * assert node["one attrib"] == "1.22" // here we compare Convertible with String
| |
− | * </pre>
| |
− | */
| |
− | void setAttributes(java.util.Map<String, Object> attributes);
| |
− | }
| |
− | | |
− | interface NodeStyleRO {
| |
− | IStyle getStyle();
| |
− | | |
− | Node getStyleNode();
| |
− | | |
− | Color getBackgroundColor();
| |
− | | |
− | Edge getEdge();
| |
− | | |
− | Font getFont();
| |
− | | |
− | Color getNodeTextColor();
| |
− | }
| |
− | | |
− | interface NodeStyle extends NodeStyleRO {
| |
− | void setStyle(IStyle key);
| |
− | | |
− | void setBackgroundColor(Color color);
| |
− | | |
− | void setNodeTextColor(Color color);
| |
− | }
| |
− | }
| |
− | </groovy>
| |
− | | |
− | == Convertible ==
| |
− | | |
− | <groovy>
| |
− | package org.freeplane.plugin.script.proxy;
| |
− | | |
− | import groovy.lang.GroovyObjectSupport;
| |
− | import groovy.lang.MissingMethodException;
| |
− | | |
− | import java.text.ParsePosition;
| |
− | import java.text.SimpleDateFormat;
| |
− | import java.util.Calendar;
| |
− | import java.util.Date;
| |
− | import java.util.GregorianCalendar;
| |
− | import java.util.regex.Matcher;
| |
− | import java.util.regex.Pattern;
| |
− | | |
− | import org.apache.commons.lang.NotImplementedException;
| |
− | import org.apache.commons.lang.math.NumberUtils;
| |
− | import org.apache.commons.lang.time.DateFormatUtils;
| |
− | import org.codehaus.groovy.runtime.InvokerHelper;
| |
− | import org.freeplane.core.util.HtmlUtils;
| |
− | | |
− | /** Utility class that is used to convert node texts to different types.
| |
− | * <p>
| |
− | * <em>Warning:</em> The nodeModel is used for script invocation ({@link #getValue()}), not
| |
− | * for access its properties. Therefore text and nodeModel are not synchronized */
| |
− | // Unfortunately it seems impossible to implement Comparable<Object> since in this case
| |
− | // TypeTransformation.compareToWithEqualityCheck() is called and will return false for
| |
− | // assert new Comparable(2) == "2"
| |
− | // instead of just calling equals, which is correctly defined
| |
− | public class Convertible extends GroovyObjectSupport /*implements Comparable<Object>*/ {
| |
− | private static final Pattern DATE_REGEXP_PATTERN = Pattern.compile("\\d{4}(-?)\\d{2}(-?)\\d{2}" //
| |
− | + "(([ T])?\\d{2}(:?)\\d{2}(:?)(\\d{2})?(\\.\\d{3})?([-+]\\d{4})?)?");
| |
− | private final String text;
| |
− | | |
− | /** doesn't evaluate formulas since this would require a calculation rule or NodeModel. */
| |
− | public Convertible(String text) {
| |
− | this.text = text;
| |
− | }
| |
− | | |
− | /** same as toString(text), i.e. conversion is done properly. */
| |
− | public Convertible(Object text) {
| |
− | this.text = toString(text);
| |
− | }
| |
− | | |
− | /**
| |
− | * returns a Long or a Double, whatever fits best. All Java number literals are allowed as described
| |
− | * by {@link Long#decode(String)}
| |
− | *
| |
− | * @throws ConversionException if text is not a number.
| |
− | */
| |
− | public Number getNum() throws ConversionException {
| |
− | try {
| |
− | try {
| |
− | return text == null ? null : text.length() == 0 ? 0 : Long.decode(text);
| |
− | }
| |
− | catch (NumberFormatException e) {
| |
− | return Double.valueOf(text);
| |
− | }
| |
− | }
| |
− | catch (NumberFormatException e) {
| |
− | throw new ConversionException("not a number: '" + text + "'", e);
| |
− | }
| |
− | }
| |
− | | |
− | | |
− | /**
| |
− | * "Safe" variant of getNum(): returns a Long or a Double, and (long) 0 on conversion errors.
| |
− | *
| |
− | * @throws nothing - on error (long) 0 is returned.
| |
− | */
| |
− | public Number getNum0() {
| |
− | try {
| |
− | return getNum();
| |
− | }
| |
− | catch (ConversionException e) {
| |
− | return 0L;
| |
− | }
| |
− | }
| |
− | | |
− | public String getString() {
| |
− | return text;
| |
− | }
| |
− | | |
− | public String getText() {
| |
− | return text;
| |
− | }
| |
− | | |
− | public String getPlain() {
| |
− | return text == null ? null : HtmlUtils.htmlToPlain(text);
| |
− | }
| |
− | | |
− | /**
| |
− | * returns a Date for the parsed text.
| |
− | * The valid date patterns are "yyyy-MM-dd HH:dd:ss.SSSZ" with optional '-', ':'. ' ' may be replaced by 'T'.
| |
− | * @throws ConversionException if the text is not convertible to a date.
| |
− | */
| |
− | public Date getDate() throws ConversionException {
| |
− | return text == null ? null : parseDate(text);
| |
− | }
| |
− | | |
− | private static Date parseDate(String text) throws ConversionException {
| |
− | // 1 2 34 5 6 7 8 9
| |
− | // \\d{4}(-?)\\d{2}(-?)\\d{2}(([ T])?\\d{2}(:?)\\d{2}(:?)(\\d{2})?(\\.\\d{3})?([-+]\\d{4})?)?
| |
− | final Matcher matcher = DATE_REGEXP_PATTERN.matcher(text);
| |
− | if (matcher.matches()) {
| |
− | StringBuilder builder = new StringBuilder("yyyy");
| |
− | builder.append(matcher.group(1));
| |
− | builder.append("MM");
| |
− | builder.append(matcher.group(2));
| |
− | builder.append("dd");
| |
− | if (matcher.group(3) != null) {
| |
− | if (matcher.group(4) != null) {
| |
− | builder.append('\'');
| |
− | builder.append(matcher.group(4));
| |
− | builder.append('\'');
| |
− | }
| |
− | builder.append("HH");
| |
− | builder.append(matcher.group(5));
| |
− | builder.append("mm");
| |
− | if (matcher.group(7) != null) {
| |
− | builder.append(matcher.group(6));
| |
− | builder.append("ss");
| |
− | }
| |
− | if (matcher.group(8) != null) {
| |
− | builder.append(".SSS");
| |
− | }
| |
− | if (matcher.group(9) != null) {
| |
− | builder.append("Z");
| |
− | }
| |
− | }
| |
− | SimpleDateFormat parser = new SimpleDateFormat(builder.toString());
| |
− | ParsePosition pos = new ParsePosition(0);
| |
− | Date date = parser.parse(text, pos);
| |
− | if (date != null && pos.getIndex() == text.length()) {
| |
− | return date;
| |
− | }
| |
− | }
| |
− | throw new ConversionException("not a date: " + text);
| |
− | }
| |
− | | |
− | /**
| |
− | * returns a Calendar for the parsed text.
| |
− | * @throws ConversionException if the text is not convertible to a date.
| |
− | */
| |
− | public Calendar getCalendar() throws ConversionException {
| |
− | if (text == null)
| |
− | return null;
| |
− | final Date date = parseDate(text);
| |
− | final GregorianCalendar result = new GregorianCalendar(0, 0, 0);
| |
− | result.setTime(date);
| |
− | return result;
| |
− | }
| |
− | | |
− | /**
| |
− | * Uses the following priority ranking to determine the type of the text:
| |
− | * <ol>
| |
− | * <li>null
| |
− | * <li>Long
| |
− | * <li>Double
| |
− | * <li>Date
| |
− | * <li>String
| |
− | * </ol>
| |
− | * @return Object - the type that fits best.
| |
− | */
| |
− | public Object getObject() {
| |
− | if (text == null)
| |
− | return null;
| |
− | try {
| |
− | return getNum();
| |
− | }
| |
− | catch (ConversionException e1) {
| |
− | try {
| |
− | return getDate();
| |
− | }
| |
− | catch (ConversionException e2) {
| |
− | return text;
| |
− | }
| |
− | }
| |
− | }
| |
− | | |
− | /** Allow statements like this: <code>node['attr_name'].to.num</code>. */
| |
− | public Convertible getTo() {
| |
− | return this;
| |
− | }
| |
− | | |
− | /** returns true if the text is convertible to number. */
| |
− | public boolean isNum() {
| |
− | // handles null -> false
| |
− | return NumberUtils.isNumber(text);
| |
− | }
| |
− | | |
− | /** returns true if the text is convertible to date. */
| |
− | public boolean isDate() {
| |
− | if (text == null)
| |
− | return false;
| |
− | final Matcher matcher = DATE_REGEXP_PATTERN.matcher(text);
| |
− | return matcher.matches();
| |
− | }
| |
− | | |
− | /** pretend we are a String if we don't provide a property for ourselves. */
| |
− | public Object getProperty(String property) {
| |
− | // called methods should handle null values
| |
− | try {
| |
− | // disambiguate isNum()/getNum() in favor of getNum()
| |
− | if (property.equals("num"))
| |
− | return getNum();
| |
− | // same for isDate()/getDate()
| |
− | if (property.equals("date"))
| |
− | return getDate();
| |
− | return super.getProperty(property);
| |
− | }
| |
− | catch (ConversionException e) {
| |
− | throw new RuntimeException(e);
| |
− | }
| |
− | catch (Exception e) {
| |
− | return InvokerHelper.getMetaClass(String.class).getProperty(text, property);
| |
− | }
| |
− | }
| |
− | | |
− | /** pretend we are a String if we don't provide a method for ourselves. */
| |
− | public Object invokeMethod(String name, Object args) {
| |
− | try {
| |
− | // called methods should handle null values
| |
− | return super.invokeMethod(name, args);
| |
− | }
| |
− | catch (MissingMethodException mme) {
| |
− | return InvokerHelper.getMetaClass(String.class).invokeMethod(text, name, args);
| |
− | }
| |
− | }
| |
− | | |
− | /** has special conversions for
| |
− | * <ul>
| |
− | * <li>Date and Calendar are converted by
| |
− | * org.apache.commons.lang.time.DateFormatUtils.formatUTC(date, "yyyy-MM-dd'T'HH:mm:ss.SSSZ"), i.e. to
| |
− | * GMT timestamps, e.g.: "2010-08-16 22:31:55.123+0000".
| |
− | * <li>null is "converted" to null
| |
− | * </ul>
| |
− | * All other types are converted via value.toString().
| |
− | */
| |
− | public static String toString(Object value) {
| |
− | if (value == null)
| |
− | return null;
| |
− | else if (value.getClass().equals(String.class))
| |
− | return (String) value;
| |
− | else if (value instanceof Date)
| |
− | return Convertible.dateToString(((Date) value));
| |
− | else if (value instanceof Calendar)
| |
− | return Convertible.dateToString(((Calendar) value).getTime());
| |
− | else
| |
− | return value.toString();
| |
− | }
| |
− | | |
− | private static String dateToString(Date date) {
| |
− | return DateFormatUtils.formatUTC(date, "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
| |
− | }
| |
− |
| |
− | // Unfortunately it seems impossible to implement Comparable<Object> since in this case
| |
− | // TypeTransformation.compareToWithEqualityCheck() is called and will return false for
| |
− | // assert new Comparable(2) == "2"
| |
− | // instead of just calling equals, which is correctly defined
| |
− | public int compareTo(Object string) {
| |
− | if (string.getClass() == String.class)
| |
− | return text.compareTo((String) string);
| |
− | else
| |
− | return 1;
| |
− | }
| |
− |
| |
− | public int compareTo(Convertible convertible) {
| |
− | return text.compareTo(convertible.getText());
| |
− | }
| |
− | | |
− | /** since equals handles Strings special we have to stick to that here too since
| |
− | * equal objects have to have the same hasCode. */
| |
− | @Override
| |
− | public int hashCode() {
| |
− | return text == null ? 0 : text.hashCode();
| |
− | }
| |
− | | |
− | /** note: if obj is a String the result is true if String.equals(text). */
| |
− | @Override
| |
− | public boolean equals(Object obj) {
| |
− | if (this == obj)
| |
− | return true;
| |
− | if (obj == null)
| |
− | return false;
| |
− | if (obj.getClass() == String.class)
| |
− | return text.equals(obj);
| |
− | if (getClass() != obj.getClass())
| |
− | return false;
| |
− | Convertible other = (Convertible) obj;
| |
− | if (text == null) {
| |
− | if (other.text != null)
| |
− | return false;
| |
− | }
| |
− | else if (!text.equals(other.text))
| |
− | return false;
| |
− | return true;
| |
− | }
| |
− | | |
− | @Override
| |
− | public String toString() {
| |
− | return text;
| |
− | }
| |
− | | |
− | @Override
| |
− | public void setProperty(String property, Object newValue) {
| |
− | throw new NotImplementedException("Convertibles are immutable");
| |
− | }
| |
− | }
| |
− | </groovy>
| |
− | | |
− | [[Category:Scripting]][[Category:Advanced_Users]]
| |