In addition to the global properties described in
the section called “GraphML Compatibility Modes”, class
GraphMLIOHandler
also allows
to customize partial aspects of the serialization process.
The following sections describe:
Interface IXmlWriter
is used throughout the yFiles FLEX GraphML framework.
The GraphML framework classes create a suitable instance for this interface,
depending on the global properties that can be set on the GraphMLIOHandler that
can be used everywhere where structured XML output is required.
API Excerpt 3.8, “IXmlWriter overview” lists the more common methods on
interface IXmlWriter.
API Excerpt 3.8. IXmlWriter overview
// Beginning a new XML element with given parameters. writeStartElement(localName:String, ns:Namespace = null):IXmlWriter // Writing an XML attribute node. Calls to this method must be nested within // writeStartElement and writeEndElement calls. writeAttribute(localName:String, value:*, ns:Namespace = null):IXmlWriter // Closing an XML element previously opened with writeStartElement. writeEndElement():IXmlWriter // Writing a text node with given content. writeText(s:String):IXmlWriter
Reading data stored in GraphML data attributes and writing data to GraphML attributes can be done by means of the methods from class GraphMLIOHandler as listed in API Excerpt 3.9, “GraphMLIOHandler support for the GraphML default extension mechanism”.
API Excerpt 3.9. GraphMLIOHandler support for the GraphML default extension mechanism
// Register a node scope IMapper for use as an input data target and output // data source, respectively. addNodeAttribute(mapper:IMapper, attrName:String, type:String = "string"):void // Register an edge scope IMapper for use as an input data target and output // data source, respectively. addEdgeAttribute(mapper:IMapper, attrName:String, type:String = "string"):void
These methods are used to register an
IMapper
instance that binds the
data stored in the attributes to the corresponding graph elements.
The attribute name specified must correspond to the attr.name
attribute of the GraphML attribute definition key.
Note that RoundtripHandler
offers
a convenience method for easily adding graph item attributes while reading and
writing the graph for server communication.
Server communication is described in depth in Chapter 4, Communicating with the Server.
The following fragment shows how to register an IMapper as input target/output source for a node attribute that stores long values.
Example 3.6. Adding a GraphML input/output attribute to GraphMLIOHandler
// Create a mapper. var nodeMapper:IMapper = new DictionaryMapper(); var ioh:GraphMLIOHandler = new GraphMLIOHandler(); // Register the mapper with the GraphML I/O handler. The deserializer will be // looking for data that has attr.name "nodeAttr". ioh.addNodeAttribute(nodeMapper, "nodeAttr", GraphMLConstants.TYPE_LONG);
Example 3.7, “Outline of a GraphML file with GraphML attribute of simple type” is an excerpt of a GraphML file that shows both the resulting GraphML attribute declaration and also the definition of its values with each of the nodes.
Example 3.7. Outline of a GraphML file with GraphML attribute of simple type
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="...">
...
<!-- Definition of a GraphML attribute to store additional data for a -->
<!-- graph's nodes. -->
<key id="d0" for="node" attr.name="color" attr.type="string"/>
<graph id="G" edgedefault="directed">
...
<!-- A node that has a <data> element referring to the GraphML attribute -->
<!-- "d0." The node's value (of type string) is "green." -->
<node id="n0">
<data key="d0">green</data>
</node>
<node id="n1">
<data key="d0">black</data>
</node>
...
</graph>
</graphml>There is a Knowlegde Base article that explains in detail the necessary steps for transferring custom simple data between a yFiles FLEX client and the server.
To store structured element content, i.e., more than just simple data types
with a GraphML attribute, interfaces
IInputHandler
and
IOutputHandler
can be
implemented.
The methods in the IInputHandler and IOutputHandler interfaces use so-called
"contexts" to access the current state of the parse and write process.
For parsing, the framework provides the class
GraphMLParseContext
.
For writing, class
GraphMLWriteContext
is used.
Both classes extend GraphMLContext
.
Besides other convenience methods, both classes provide access to both the
current nesting of graph elements and the current graph element that is read or
written.
API Excerpt 3.10. Stack access methods in GraphMLContext
// Returns the current nesting of graphs and graph elements as an ordered // read-only list. get containers():List // Returns the most current graph element within the container hierarchy. get lastContainer():Object // Returns the last container of the given type. getContainer(clazz:Class):Object
For (de)serialization of complex data, yFiles FLEX provides the
IInputHandler
and
IOutputHandler
together with
their abstract base class implementations
AbstractInputHandler
and
AbstractOutputHandler
.
Note that it is usually much easier to extend these abstract base classes
instead of implementing the interfaces.
Class GraphMLIOHandler provides the methods necessary to register custom input or output handlers.
API Excerpt 3.11. GraphMLIOHandler handler registration
// Adds a custom input handler to this GraphMLIOHandler. addInputHandler(handler:IInputHandler):void // Adds a custom output handler for the given element scope to this // GraphMLIOHandler. addOutputHandler(outputHandler:IOutputHandler, scope:String):void
To create a custom input handler, it is usually sufficient to inherit from
AbstractInputHandler
and implement
acceptKey
,
applyItemDefault
,
and
parseItemData
methods, as shown in API Excerpt 3.12, “Custom input handler creation and usage” that
creates an input handler for node scope.
API Excerpt 3.12. Custom input handler creation and usage
public class MyInputHandler extends AbstractInputHandler {
// Accept if the keyElement has node scope, name "myattr", and complex
// attribute type.
override public function acceptKey(keyElement:XML,
scopeType:String):Boolean {
var attrName:String = keyElement.attribute(GraphMLConstants.ATTR_NAME);
var attrType:String = keyElement.attribute(GraphMLConstants.ATTR_TYPE);
return scopeType == GraphMLConstants.SCOPE_NODE &&
attrName == "myattr" &&
attrType == GraphMLConstants.ATTR_TYPE_COMPLEX;
}
// Called when the key definition is parsed.
override protected function applyItemDefault(context:GraphMLParseContext,
graph:IGraph, item:Object):void {
// do nothing
}
// Parse the actual data for the current node
override protected function parseItemData(
context:GraphMLParseContext, graph:IGraph, item:Object,
defaultMode:Boolean, element:XML):void {
var node:INode = item as INode;
if (null == node || null == graph || null == elment ||
element.children().length() == 0) {
return;
}
// Do something...
}
}
// Register handler.
var ioh:GraphMLIOHandler = new GraphMLIOHandler();
ioh.addInputHandler(new MyInputHandler());
Creating a custom output handler is done by inheriting from
AbstractOutputHandler
and implementing the
printKeyOutput
,
printKeyAttributes
,
and
printItemDataOutput
methods.
API Excerpt 3.13, “Custom output handler creation and usage” shows an how to create an
output handler for node scope.
API Excerpt 3.13. Custom output handler creation and usage
public class MyOutputHandler extends AbstractOutputHandler {
// Declare the custom attribute.
override public function printKeyAttributes(context:GraphMLWriteContext,
writer:IXmlWriter):void {
writer.writeAttribute(GraphMLConstants.ATTR_NAME, "myattr");
writer.writeAttribute(GraphMLConstants.ATTR_TYPE,
GraphMLConstants.ATTR_TYPE_COMPLEX);
}
//
override public function printKeyOutput(context:GraphMLWriteContext,
writer:IXmlWriter):void {
// Do nothing.
}
override public function printItemDataOutput(
context:GraphMLWriteContext, graph:IGraph, item:Object,
writer:IXmlWriter):void {
var node:INode = item as INode;
if (null != node) {
// Do something.
}
}
}
// Register handler.
var ioh:GraphMLIOHandler = new GraphMLIOHandler();
ioh.addOutputHandler(new MyOutputHandler(), GraphMLConstants.SCOPE_NODE);
yFiles FLEX already provides several predefined input and output handlers for most common use cases, such as reading and writing an element's geometry or its style. These handlers read and write the data for the predefined data keys listed in Table 3.3, “Predefined combinations for XML attributes for and attr.name”.
The following tables list the predefined handlers for reading and writing element geometry, style information, and label information, respectively.
Table 3.4. Predefined input/output handlers (element geometry)
| Input Handler | Output Handler | Combination of Values | ||
|---|---|---|---|---|
| ReadNodeLayoutHandler, ReadEdgeLayoutHandler, ReadPortLayoutHandler | WriteNodeLayoutHandler, WriteEdgeLayoutHandler, WritePortLayoutHandler | for= "node" | "edge" | "port" | attr.name= "geometry" | attr.type= "complex" |
Table 3.5. Predefined input/output handlers (style information)
| Input Handler | Output Handler | Combination of Values | ||
|---|---|---|---|---|
| NodeStyleInputHandler, EdgeStyleInputHandler | NodeStyleOutputHandler, EdgeStyleOutputHandler | for= "node" | "edge" | attr.name= "style" | attr.type= "complex" |
Table 3.6. Predefined input/output handlers (label information)
| Input Handler | Output Handler | Combination of Values | ||
|---|---|---|---|---|
| LabelInputHandler | LabelOutputHandler | for= "node" | "edge" | attr.name= "labels" | attr.type= "complex" |
Note that these handlers should not be directly instantiated. Instead, they all have a public instance property that allows access to the static instance of these handlers.
The previous sections focused on methods to read or write the whole content of a graphml attribute at once. In order to (de)serialize only fragments of an attribute, such as a specific property of an object, yFiles FLEX offers a general serializer and deserializer framework. This concept is used extensively throughout all predefined handler classes, thus allowing to
This section introduces the general aspects of the (de)serializer mechanism, while the next section shows how to use this mechanism to read and write custom item styles.
Interface ISerializer
is the main interface for object serialization.
Instead of implementing the interface, however, it is strongly advised to
extend abstract base class
AbstractSerializer
in
order to create custom serializers.
Creating a custom serializer for a given class using AbstractSerializer as the base type is done as follows:
In order to actually use a custom serializer, it is important to understand the serializer look-up mechanism in yFiles FLEX. The GraphML framework uses a three-level look-up mechanism:
Interface
IDeserializer
is the
main interface for object deserialization.
Creating a custom deserializer for a given class is done as follows:
In order to actually use a custom deserializer, it needs to be registered with
the GraphMLIOHandler instance using the
registerDeserializer
method.
Deserializers are checked in their order of registration and the first (if any)
deserializer whose canHandle method returns true for
a given XML node is used.
This section gives an overview of how to use the serializer and deserializer mechanism for a custom node style (edge and label style work similarly).
Creating and using a serializer for a custom node style using AbstractSerializer as the base type is done as follows:
private var myNamespace:Namespace =
new Namespace("myPrefix", "http://mydomain.com");
override protected function serializeContent(
context:GraphMLWriteContext, subject:Object, writer:IXmlWriter):void {
var style:MyNodeStyle = subject as MyNodeStyle;
if (null == style) {
return;
}
writer.writeAttribue("someStyleProperty", style.someStyleProperty);
//...
}
override public function get elementName():String {
return "MyNodeStyle";
}
override public function get xmlNamespace():Namespace {
return myNamespace;
}
// Serialize only subjects of type MyNodeStyle.
override public function canHandle(context:GraphMLWriteContext, subject:Object):Boolean {
return super.canHandle(subject) && subject is MyNodeStyle;
}public class MyStyleRenderer extends AbstractNodeStyleRenderer {
//...
override public function lookup(type:Class):Object {
if (type == ISerializer) {
return new MyNodeStyleSerializer();
}
return super.lookup(type);
}
}
public class MyNodeStyle implements INodeStyle {
private var renderer:MyStyleRenderer;
public function MyNodeStyle() {
this.renderer = new MyStyleRenderer();
}
}
Creating and using a custom deserializer for a given class is done as follows:
public class MyNodeStyleDeserializer implements IDeserializer {
private var myNamespace:Namespace =
new Namespace("myPrefix", "http://mydomain.com");
public function deserialize(context:GraphMLParseContext, element:XML):Object {
var style:MyNodeStyle = new MyNodeStyle();
var attribute:XMLList = element.attribute("someDoubleValue");
if (attribute != null) {
style.someDoubleValue = Number(attribute);
}
return style;
}
public function canHandle(context:GraphMLParseContext, element:XML):Boolean {
if (null == element) {
return false;
}
return element.localName() == elementName &&
element.namespace() == xmlNamespace;
}
public function get elementName():String { return "MyNodeStyle"; }
public function get xmlNamespace():Namespace { return myNamespace; }
}var ioh:GraphMLIOHandler = new GraphMLIOHandler(); // Register the deserializer for the custom style. ioh.registerDeserializer(new MyNodeStyleDeserializer());
|
Copyright ©2007-2008, yWorks GmbH. All rights reserved. |