Scripting XML documents that conform to an XSD schema using Epsilon¶
In this article we demonstrate how you can create, query and modify XML documents backed by an XSD schema in Epsilon.
Querying an XML document¶
We use the following library.xml
as a base for demonstrating the EOL syntax for querying XML documents.
<?xml version="1.0" encoding="UTF-8"?>
<library xsi:noNamespaceSchemaLocation="library.xsd">
<book title="EMF Eclipse Modeling Framework" pages="744">
<author>Dave Steinberg</author>
<author>Frank Budinsky</author>
<author>Marcelo Paternostro</author>
<author>Ed Merks</author>
<published>2009</published>
</book>
<book title="Eclipse Modeling Project: A Domain-Specific Language (DSL) Toolkit" pages="736">
<author>Richard Gronback</author>
<published>2009</published>
</book>
<book title="Official Eclipse 3.0 FAQs" pages="432">
<author>John Arthorne</author>
<author>Chris Laffra</author>
<published>2004</published>
</book>
</library>
The XSD schema library.xsd
that backs the library.xml
file is the following.
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="library">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="author" maxOccurs="unbounded" type="xs:string"></xs:element>
<xs:element name="published" type="xs:int"></xs:element>
</xs:sequence>
<xs:attribute name="title" type="xs:string"></xs:attribute>
<xs:attribute name="pages" type="xs:int"></xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Querying XML documents in EOL¶
The XML driver uses a predefined naming convention to allow developers to programmatically access complex types in XML documents.
How can I query by element type?¶
The word Type
should be appended at the end of the name of the tag that is used to represent a type. In addition, the first letter of the tag should be capitalised (no matter if it is in lowercase in the schema/xml file). For instance, BookType.all()
can be used to get all elements tagged as <book>
in the document. Also, if b
is an element with a <book>
tag, then b.isTypeOf(BookType)
shall return true.
// Get the first library element in the document
var library = LibraryType.all().first();
// Get all the books contained in this library
var allBooks = library.book;
// We can get all the books in the document by querying directly the book type
var allBooksAlternative = BookType.all();
// Iterate through the collection of books, navigate the pages attribute and
// return the title of the book if it has more than 700 pages
for (aBook in allBooks) {
if (aBook.pages > 700) {
aBook.isTypeOf(BookType).println();
("The " + aBook.title + " is a large book!").println();
}
}
How can I get/set the attributes of an element?¶
You can use the attribute name as a property of the element object. For example, if b
is the first book of library.xml
, b.title
will return EMF Eclipse Modeling Framework
. Attribute properties are read/write.
In this example, b.pages
will return 744
as an integer. Thus, the following program will return the total number of pages of all the books in the library.
// Get all the books contained in this library
var allBooks = BookType.all();
// Print the total number of pages of all books
var total = 0;
for (aBook in allBooks) {
total = total + aBook.pages;
}
("Total pages: " + total).println();
// ... the same using collect() and sum()
// instead of a for loop
BookType.all().collect(b|b.pages).sum().println();
How can I set the text of an element?¶
You can use the property name and the assignment symbol =
for this.
// Get the first book contained in the library
var emfBook = BookType.all().first();
// Set the title to a new one
emfBook.title = "EMF Book";
// Check that the title has changed
emfBook.title.println();
How do I create an element and add it to an existing element?¶
You can use the new
operator for this.
// Get all the books contained in this library
var library = LibraryType.all().first();
var allBooks = library.book;
// Print the current number of books
allBooks.size().println();
// Create a new book
var newBook: new BookType;
newBook.title = "MDE in Practice";
// Add the book to the library
library.book.add(newBook);
// Get all books and print the new size
BookType.all().size().println();
How does it work?¶
The driver uses EMF's XSDEcoreBuilder to transform the XSD into an Ecore metamodel, and then loads the XML document against the metamodel. The XSD to Ecore mapping process is described in this document. If you'd like to save and inspect the Ecore metamodel that gets produced from your XSD, you can use the following snippet.
package org.eclipse.epsilon.examples.xsdxml;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.xsd.ecore.XSDEcoreBuilder;
public class XSDToEcoreExample {
public static void main(String[] args) throws Exception {
// Convert the XSD into a collection of Ecore EPackages
XSDEcoreBuilder xsdEcoreBuilder = new XSDEcoreBuilder();
Collection<EObject> ePackages = xsdEcoreBuilder.generate(URI.createURI("http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"));
// Create a new XMI resource to save the EPackages
ResourceSet resourceSet = new ResourceSetImpl();
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("ecore", new XMIResourceFactoryImpl());
Resource resource = resourceSet.createResource(URI.createURI(new File("reqif.ecore").toURI().toString()));
// Add the XSD-generated EPackages to the resource and save
resource.getContents().addAll(ePackages);
resource.save(null);
}
}
Running this example from Java¶
You can use the driver's API as shown below to load the library XML document and XSD schema and run the EOL snippets above from Java. The complete Maven project is here.
package org.eclipse.epsilon.examples.xsdxml;
import java.io.File;
import org.eclipse.epsilon.common.util.StringProperties;
import org.eclipse.epsilon.emc.emf.xml.XmlModel;
import org.eclipse.epsilon.eol.EolModule;
public class LibraryExample {
public static void main(String[] args) throws Exception {
// Parse the EOL program
EolModule module = new EolModule();
module.parse(new File("library/query-by-type.eol"));
// Load the model
XmlModel model = new XmlModel();
StringProperties properties = new StringProperties();
properties.put(XmlModel.PROPERTY_NAME, "M");
// No need to specify XSD URI due to xsi:noNamespaceSchemaLocation="library.xsd" in library.xml
properties.put(XmlModel.PROPERTY_MODEL_URI, new File("library/library.xml").toURI());
properties.put(XmlModel.PROPERTY_READONLOAD, "true");
properties.put(XmlModel.PROPERTY_STOREONDISPOSAL, "false");
model.load(properties);
// Make the model available to the program
module.getContext().getModelRepository().addModel(model);
// Execute the EOL program
module.execute();
// Dispose of the model
module.getContext().getModelRepository().dispose();
}
}
Adding an XML document to your launch configuration¶
To add an XML document to your Epsilon launch configuration, you need to select "XML document backed by XSD (EMF)" from the list of available model types.
Then you can configure the details of your document (name, file etc.) in the screen that pops up. If you are making changes to the XML document, remember to tick the "Store on disposal" check box to save the changes in your document.