This is the README file for the Excelsior! project. Click here to download Excelsior! now.
These features are new in Excelsior! If you are new to Excelsior!, start with section I. OVERVIEW, and come back here when you have read the rest of the document.
One of the most requested features for Excelsior! was the ability to refer to a single mapping multiple times in a mapping file. Reusing mappings would make mapping files more elegant, less redundant, smaller, easier to understand, and easier to maintain. Reusable mappings are part of Excelsior! 2.
Actually, it is more accurate to speak of reusable mapping types than reusable mappings. This is because when a mapping is referenced multiple times, the referenced mapping does not contain all of the required information. For example, xmlPath and objectPath attributes are not included in referenced mappings. The information that can be multiply referenced we will refer to as a mapping type. A fully qualified mapping, including xml and object paths, we will continue to call a mapping. (This should become clear in the examples below.)
The mapping file poMapping.xml demonstrates how to reuse mappings. The first difference you will notice is that the root element of this document is a MappingList, not a Mapping. The scope of a mapping type is its enclosing MappingList.
Now, consider the first Mapping child of MappingList.
<Mapping type="date" format="%Y-%m-%d" name="poDate"/>
Notice the name attribute. This allows other mappings to refer to this as a mapping type. For example
<Mapping ref="poDate" xmlPath="@orderDate" objectPath="orderDate"/>
and
<Mapping ref="poDate" xmlPath="shipDate" objectPath="shipDate"/>
both reference this mapping type (ref="poDate"). This means that both of these mappings
will be of type date with format "%Y-%m-%d". But the poDate mapping type is only defined once.
Without mapping type references, these mappings would look like
<Mapping type="date" format="%Y-%m-%d" xmlPath="@orderDate" objectPath="orderDate"/> <Mapping type="date" format="%Y-%m-%d" xmlPath="shipDate" objectPath="shipDate"/>
Less elegant, more redundant, and harder to maintain. Object mapping types can also be reused. Consider
<Mapping type="object" class="JMRAddress" name="address">
<Mapping type="string" xmlPath="@country" objectPath="country"/>
<Mapping type="string" xmlPath="name" objectPath="name"/>
<Mapping type="string" xmlPath="street" objectPath="street"/>
<Mapping type="string" xmlPath="city" objectPath="city"/>
<Mapping type="string" xmlPath="state" objectPath="state"/>
<Mapping type="string" xmlPath="zip" objectPath="zip"/>
</Mapping>
This mapping type is referenced twice in the mapping list
<Mapping ref="address" xmlPath="shipTo" objectPath="shipTo"/>
...
<Mapping ref="address" xmlPath="billTo" objectPath="billTo"/>
Without mapping types, this would look like
<Mapping type="object" class="JMRAddress" xmlPath="shipTo" objectPath="shipTo">
<Mapping type="string" xmlPath="@country" objectPath="country"/>
<Mapping type="string" xmlPath="name" objectPath="name"/>
<Mapping type="string" xmlPath="street" objectPath="street"/>
<Mapping type="string" xmlPath="city" objectPath="city"/>
<Mapping type="string" xmlPath="state" objectPath="state"/>
<Mapping type="string" xmlPath="zip" objectPath="zip"/>
</Mapping>
<Mapping type="object" class="JMRAddress" xmlPath="billTo" objectPath="billTo">
<Mapping type="string" xmlPath="@country" objectPath="country"/>
<Mapping type="string" xmlPath="name" objectPath="name"/>
<Mapping type="string" xmlPath="street" objectPath="street"/>
<Mapping type="string" xmlPath="city" objectPath="city"/>
<Mapping type="string" xmlPath="state" objectPath="state"/>
<Mapping type="string" xmlPath="zip" objectPath="zip"/>
</Mapping>
Much less elegant, much more redundant and much harder to maintain.
Here is an example of how to create a JMRMappingList from a mapping list file and reference a JMRObjectMapping from it by name. The object mapping must be a direct child of the mapping list.
url = [NSURL fileURLWithPath:[projectDir stringByAppendingString:@"/poMapping.xml"]];
mappingList = [JMRMappingList mappingListWithDataFromURL:url];
poMapping = [mappingList mappingWithRootName:@"purchaseOrder"];
At this point, poMapping can be used
the same as any other JMRObjectMapping. This example is from the main.m test file.
In summary
ref="name");Mapping files created with previous versions of Excelsior should continue to work unchanged.
Here's another mapping type introduced in Excelsior! 2. I found this mapping type essential in a project that uses Excelsior!, and thought others might have a use for it as well. Scan the next few paragraphs. If switch mappings don't appear to solve any particular problem you're having right now, please feel free to ignore them.
A switch mapping is used when you want to map a bunch of child elements, with different tag names, into a single to-many relationship on the object side. For an example, look at the testSwitch test in TestMarshalling.m. It uses the mapping file switchMapping.xml, which contains the mapping
`
<Mapping type="object" rootName="a" class="A">
<Mapping type="switch" isList="YES" xmlPath="." objectPath="bcs">
<Mapping ref="b" xmlPath="b"/>
<Mapping ref="c" xmlPath="c"/>
<Mapping type="object" xmlPath="d" class="D">
<Mapping type="switch" xmlPath="." objectPath="bc">
<Mapping ref="b" xmlPath="b"/>
<Mapping ref="c" xmlPath="c"/>
</Mapping>
</Mapping>
</Mapping>
</Mapping>
Notice the first child of this mapping, with type "switch". It maps the immediate children of an element "a" to the "bcs" property of an object instance of class "A". isList="YES" indicates that "bcs" is a to-many property. Also Note that the children mappings of the switch mapping lack an objectPath mapping. This is because the objectPath mapping of the switch mapping applies to all of its children.
When Excelsior! is processing the children of an element with a switch mapping, it uses the child mapping whose xmlPath attribute matches the child element's tag name. So given
<a>
<b name="1"/>
<c name="2">value1</c>
<b name="3"/>
<b name="4"/>
<c name="5">value2</c>
<d><b name="6"/></d>
<d><c name="7">value3</c></d>
</a>
<b name="1"/>, <b name="3"/>, and <b name="4"/> will be processed with
mapping <Mapping ref="b" xmlPath="b"/>. <c name="2">value1</c> and
<c name="5">value2</c> will be processed with mapping <Mapping ref="c" xmlPath="c"/>.
XMLKeyValueElement instances can now be rendered as a neatly indented NSString. description still generates
XML with no extra whitespace, but the new method formattedString on JMRXMLTree (XMLKeyValueElement's superclass)
will produce the indented version. formattedString is now used in the main.m testing file to output XML.
formattedString relies on the class JMROutputStream, which wraps the NSOutputStream class. Because NSOutputStream
was introduced in Panther (OS X 10.3), formattedString only works on OS X 10.3 or higher.
Implementing reusable mapping types required a significant change to Excelsior!'s design. Now every JMRMapping object refers to a JMRMappingType object. The JMRMappingType encapsulates much of the behavior formerly specified in the JMRMapping subclasses.
JMRMappingType is somewhat of an abstract class, as it implements no functionality on its own. Its two subclasses are JMRObjectMapping and JMRScalarMappingType. (Why not JMRObjectMappingType? Because the JMRObjectMapping class already existed and played pretty much the same role it does now. It was made a subclass of JMRMappingType to conform to the new design.)
The various subclasses of JMRScalarMappingType (JMRBooleanMappingType, JMRDateMappingType, JMRNumberMappingType, JMRStringMappingType, JMRConstantMappingType) encapsulate the behavior formerly contained in the various JMRMapping subclasses with corresponding names. For example, now a JMRBooleanMapping simply always returns a JMRBooleanMappingType as its mapping type, which now encapsulates much of the logic formerly encapsulated in JMRBooleanMapping itself.
This made reusing mapping types through references relatively straightforward.
Excelsior! is an XML marshaller for Cocoa/Objective C. What this means is that Objective C object instances can be generated from XML data with just a few messages. Inversely, XML can be generated from object instances. An external file defines how the marshalling takes place.
Castor is an example of an XML marshaller for Java and WebObjects has similar functionality. But as far as I know, this is the only XML marshaller for Objective C.
Other features of Excelsior! are
Excelsior! builds on the Core Foundation XML functions and data structures in OS X. There are 3 levels of abstraction built on top of CFXML.
JMRTree wraps a CFTreeRef. JMRXMLTree extends JMRTree and wraps functions for handling XML. JMRXMLNode wraps a CFXMLNodeRef.
XMLKeyValueElement extends JMRXMLTree and facilitates getting and setting XML data using key paths, similar to the key-value coding defined in the NSKeyValueCoding protocol.
For example, given elt, an XMLKeyValueElement representing the following XML,
<root>
<tag1>
<tag2 attr="attrText">tag2 text</tag2>
<multiple>first multiple text</multiple>
<multiple>second multiple text</multiple>
</tag1>
</root>
[elt elementForKeyPath:@"tag1/tag2"]will return @"tag2 text" and
[elt elementForKeyPath:@"tag1/tag2@attr"]will return @"attrText". Also, multiple nodes with the same tag name can be distinguished by appending an index to the keypath. So
[elt elementForKeyPath:@"tag1/multiple[0]"]will return @"first multiple text" and
[elt elementForKeyPath:@"tag1/multiple[1]"]will return @"second multiple text". Note that "root" is NOT included in the paths. Paths are relative to the root. Also note that indexes start at 0.
In addition to creating XML data from scratch, Excelsior! can marshal object instances into an XML or XHTML template. This can be used to view your object's data in a web browser, for example.
Two features that facilitate template marshalling are
This project has two targets: ExcelsiorFramework and MarshallingTest. You must build ExcelsiorFramework before you can build and run MarshallingTest.
Prebinding a library or framework allows applications built with the framework to launch faster. To enable prebinding, ExcelsiorFramework is built with a preferred first segment load address of 0x1a000000. This should avoid conflict with the load address of any application using it (0x00000000). If this conflicts with the memory space of any other library used by your application, change this value to something that does not conflict before building the framework. For more information about prebinding see this note.
To understand how to marshal and unmarshal, look at the file main.m and 2. below. To understand how to create a mapping file, look at PersonMapping.xml and read 3. below. To understand how to create a template mapping file, look at PersonHTMLMapping.xml and Person.html and read 3. below.
There are two new kinds of mappings in Excelsior! 2, reusable mappings and switch mappings. They are described in the What's New section above.
The files PersonMapping.xml and PersonHTMLMapping.xml illustrate most of the kinds of mappings you can specify.
<Mapping type="object" class="JMRPerson" rootName="VCARD">means that a VCARD XML is unmarshalled into a JMRPerson object. The root element of a mapping file must be an object mapping.
<Mapping type="object" isList="YES" class="JMRPhoneNumber" xmlPath="TEL" objectPath="numbers">indicates that a VCARD's "TEL" children are unmarshalled into an NSArray of JMRPhoneNumber's and assigned to the JMRPerson's "numbers" field.
stringmap XML text directly to an NSString.
constantused only in marshalling; set the text for xmlPath to the Mapping's "default" attribute.
datemap XML text to an NSDate; the "format" attribute must follow the format used by NSDateFormatter's initWithDateFormat:allowNaturalLanguage: message.
numbermap XML text to an NSNumber; the "format" attribute must follow the format used by NSNumberFormatter's setFormat: message.
So
<Mapping type="string" xmlPath="NAME" objectPath="name"/>means the text in the element with tag "NAME" is mapped to the "name" field of the JMRPerson, which is an NSString. See the Application Kit API reference for more information on format strings.
<Mapping type="string" xmlPath="body/table/tr[3]/td" objectPath="url"/>references the first <td> of the fourth <tr> contained by the element specified by "body/table" (see II.2 above for more examples).
This work is licensed under the Creative Commons Attribution License. To view a copy of this license, visit http://creativecommons.org/licenses/by/1.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
A copy of this license is also included in the Documentation folder of this project, in the file AttributionLicense.html.
In summary, this license stipulates that anyone who distributes this work or a derivative of this work must credit the original author (Jim Rankin), in a reasonable fashion. One way to satisfy this requirement would be to include the following in a text or HTML file distributed with the derivative work.
"This program uses the Excelsior! class library created by Jim Rankin. For more information about Excelsior, email jimbokun@mac.com."
The following people have contributed bug fixes or other enhancements.
Mark Smith
Don Briggs
Thanks!