blob: a6c9a22437b115f4b530113ab2a67ed4b52ed1eb [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2018 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* E.D.Willink - Initial API and implementation
*
* </copyright>
*/
package org.eclipse.qvtd.xml.utilities;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import org.eclipse.emf.common.util.SegmentSequence;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.impl.BasicEObjectImpl;
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.util.ExtendedMetaData;
import org.eclipse.emf.ecore.xmi.XMIException;
import org.eclipse.emf.ecore.xmi.XMLHelper;
import org.eclipse.emf.ecore.xmi.XMLLoad;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.XMLSave;
import org.eclipse.emf.ecore.xmi.impl.XMILoadImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
import org.eclipse.emf.ecore.xmi.impl.XMISaveImpl;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.qvtd.xml.Attribute;
import org.eclipse.qvtd.xml.CDATA;
import org.eclipse.qvtd.xml.Characters;
import org.eclipse.qvtd.xml.ClassAttribute;
import org.eclipse.qvtd.xml.ClassElement;
import org.eclipse.qvtd.xml.Comment;
import org.eclipse.qvtd.xml.DTD;
import org.eclipse.qvtd.xml.DataTypeAttribute;
import org.eclipse.qvtd.xml.DataTypeElement;
import org.eclipse.qvtd.xml.Document;
import org.eclipse.qvtd.xml.Element;
import org.eclipse.qvtd.xml.Entity;
import org.eclipse.qvtd.xml.Node;
import org.eclipse.qvtd.xml.PrefixMapping;
import org.eclipse.qvtd.xml.ProcessingInstruction;
import org.eclipse.qvtd.xml.XMLmodelFactory;
import org.eclipse.qvtd.xml.XMLmodelPackage;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.DefaultHandler;
/**
* XMLmodelResourceImpl loads an XML Resource creating a model that conforms to http://www.eclipse.org/qvt/2015/XML.
* It therefore comprioses a hierrachy of Nodes such s Element, Attribute, Comment corresponding to SAX parser call-backs.
*
* This allows the XML technology space to be manipulated using Model techology space tooling.
*/
public class XMLmodelResourceImpl extends XMIResourceImpl
{
/**
* By default xmlns references to user URIs are resolved to EPackages enabling accurate resolution of
* xsi:Type child elements and reference resolution.
*/
public static final @NonNull String IGNORE_ECORE = "ignore-ecore";
protected static class XMLmodelLoadImpl extends XMILoadImpl
{
private SAXParser makeParser = null;
public XMLmodelLoadImpl(XMLHelper helper) {
super(helper);
}
@Override
protected DefaultHandler makeDefaultHandler() {
XMLmodelHandler handler = new XMLmodelHandler(resource, helper, options);
try {
makeParser.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
} catch (SAXException e) {
// Never happens, but we can survive without a lexical-handler
}
return handler;
}
@Override
protected SAXParser makeParser() throws ParserConfigurationException, SAXException {
makeParser = super.makeParser();
return makeParser;
}
}
protected static class XMLmodelHandler extends DefaultHandler implements LexicalHandler
{
private ResourceSet resourceSet = new ResourceSetImpl();
private XMLResource xmlResource;
private Document document = null;
private @NonNull Stack<@NonNull Node> nodeStack = new Stack<>();
private boolean useEcore;
/**
* Map from xmlns prefix to its corresponding URI. The default xmlns namespace is the blank string.
*/
protected final @NonNull Map<@NonNull String, @NonNull String> xmlns2uri = new HashMap<>();
/**
* Map from xmlns prefix to its corresponding EPackage. The default xmlns namespace is the blank string.
*/
protected final @NonNull Map<@NonNull String, @NonNull EPackage> xmlns2ePackage = new HashMap<>();
protected final @NonNull Map<@NonNull String, @NonNull String> uri2schemaLocation = new HashMap<>();
protected final @NonNull Map<@NonNull String, @NonNull Element> xmiid2element = new HashMap<>();
public XMLmodelHandler(XMLResource xmlResource, XMLHelper helper, Map<?, ?> options) {
this.xmlResource = xmlResource;
this.useEcore = (options == null) || (options.get(IGNORE_ECORE) != Boolean.TRUE);
}
protected void addError(@NonNull String string) {
xmlResource.getErrors().add(new XMIException(string));
}
protected void addNode(Node node) {
if (nodeStack.isEmpty()) {
document.getChildren().add(node);
}
else {
nodeStack.peek().getChildren().add(node);
}
}
protected void addWarning(@NonNull String string) {
xmlResource.getWarnings().add(new XMIException(string));
}
protected @Nullable Element basicGetChildElement(@NonNull Element parentElement, @NonNull String pathElement) {
if (pathElement.length() <= 0) {
return basicGetChildElementByIndex(parentElement, 0);
}
char firstChar = pathElement.charAt(0);
if (firstChar == '@') {
String childName;
int childIndex = -1;
int index = pathElement.indexOf('.');
if (index >= 0) {
childName = pathElement.substring(1, index);
childIndex = Integer.valueOf(pathElement.substring(index+1));
}
else {
childName = pathElement.substring(1);
childIndex = 0;
}
return basicGetChildElementByName(parentElement, childName, childIndex);
}
else if (Character.isDigit(firstChar)) {
int childIndex = Integer.valueOf(pathElement);
return basicGetChildElementByIndex(parentElement, childIndex);
}
else {
return basicGetChildElementByEClassName(parentElement, pathElement); // Ecore EClassifier.name match
}
}
protected @Nullable Element basicGetChildElementByEClassName(@NonNull Element parentElement, @NonNull String childName) {
for (@NonNull Node childNode : parentElement.getChildren()) {
if (childNode instanceof Element) {
Element childElement = (Element)childNode;
for (@NonNull Node grandchildNode : childElement.getChildren()) {
if (grandchildNode instanceof DataTypeAttribute) {
DataTypeAttribute grandchildElement = (DataTypeAttribute)grandchildNode;
if (grandchildElement.getEcoreAttribute() == EcorePackage.Literals.ENAMED_ELEMENT__NAME) {
if (childName.equals(grandchildElement.getValue())) {
return childElement;
}
}
}
}
}
}
return null;
}
protected @Nullable Element basicGetChildElementByIndex(@NonNull Element parentElement, int childElementIndex) {
int matchIndex = 0;
for (@NonNull Node childNode : parentElement.getChildren()) {
if (childNode instanceof Element) {
Element childElement = (Element)childNode;
if (matchIndex == childElementIndex) {
return childElement;
}
matchIndex++;
}
}
return null;
}
protected @Nullable Element basicGetChildElementByName(@NonNull Element parentElement, @NonNull String childName, int childIndex) {
int matchIndex = 0;
for (@NonNull Node childNode : parentElement.getChildren()) {
if (childNode instanceof Element) {
Element childElement = (Element)childNode;
if (childName.equals(childElement.getQName())) {
if (matchIndex == childIndex) {
return childElement;
}
matchIndex++;
}
}
}
return null;
}
/**
* Returns the object based on the fragment path as a list of Strings.
*/
protected @Nullable Element basicGetElementForPath(List<String> uriFragmentPath) {
int size = uriFragmentPath.size();
Element element = getRootElement(size == 0 ? "" : uriFragmentPath.get(0));
for (int i = 1; i < size && element != null; ++i)
{
String uriFragmentSegment = uriFragmentPath.get(i);
element = basicGetChildElement(element, uriFragmentSegment);
}
return element;
}
private @Nullable EPackage basicGetEPackage(@NonNull String prefix) {
return xmlns2ePackage.get(prefix);
}
@Override
public void characters(char[] ch, int start, int length) {
boolean isWhite = true;
for (int i = 0; i < length; i++) {
char c = ch[start+i];
if (!Character.isWhitespace(c)) {
isWhite = false;
break;
}
}
if (!isWhite) {
String characterText = new String(ch, start, length);
Characters characters = XMLmodelFactory.eINSTANCE.createCharacters();
// if (!characterText.trim().isEmpty()) {
characters.setData(characterText);
// }
addNode(characters);
}
}
@Override
public void comment(char[] ch, int start, int length) {
Comment comment = XMLmodelFactory.eINSTANCE.createComment();
comment.setData(new String(ch, start, length));
addNode(comment);
}
@Override
public void endCDATA() {
nodeStack.pop();
}
@Override
public void endDTD() {
nodeStack.pop();
}
@Override
public void endDocument() {
xmlResource.getContents().add(document);
resolveReferences(document);
document = null;
}
@Override
public void endElement(String uri, String localName, String name) {
Node node = nodeStack.pop();
if (node instanceof ClassElement) {
ClassElement element = (ClassElement)node;
EClass ecoreClass = element.getEcoreClass();
if (ecoreClass != null) {
EAttribute idAttribute = ecoreClass.getEIDAttribute();
if (idAttribute != null) {
Object id = element.eGet(idAttribute);
if (id != null) {
Element oldElement = xmiid2element.put(String.valueOf(id), element);
if (oldElement != null) {
addError("Ambiguous xmi:id '" + id + "'");
}
}
}
}
}
}
@Override
public void endEntity(String name) {
nodeStack.pop();
}
@Override
public void endPrefixMapping(String prefix) {
nodeStack.pop();
}
protected @Nullable EClassifier getEClassifier(@NonNull String qName) {
String prefix = "";
int colonIndex = qName.indexOf(":");
String suffix = qName;
if (colonIndex >= 0) {
prefix = qName.substring(0, colonIndex);
suffix = qName.substring(colonIndex+1);
}
EPackage ePackage = getEPackage(prefix);
if (ePackage != null) {
EClassifier eClassifier = ePackage.getEClassifier(suffix);
if (eClassifier != null) {
return eClassifier;
}
}
addError("Failed to locate an EClassifier for '" + qName + "'");
return null;
}
protected @Nullable EPackage getEPackage(@NonNull String prefix) {
EPackage ePackage = xmlns2ePackage.get(prefix);
if (ePackage == null) {
String uri = getURIofXMLNS(prefix); // FIXME xsi:schemaLocation
if ((uri != null) && !ExtendedMetaData.XMI_URI.equals(uri)) {
ePackage = resourceSet.getPackageRegistry().getEPackage(uri);
if (ePackage instanceof EPackage.Descriptor) {
ePackage = ((EPackage.Descriptor)ePackage).getEPackage();
}
if (ePackage != null) {
xmlns2ePackage.put(prefix, ePackage);
}
else if (!xmlns2ePackage.containsKey(prefix)) {
xmlns2ePackage.put(prefix, null);
if (useEcore) {
addError("Failed to locate EPackage for '" + uri + "'");
}
else {
// addWarning("Failed to locate EPackage for '" + uri + "'");
}
}
}
}
return ePackage;
}
protected @Nullable Element getElementAtChildIndex(@NonNull Node node, int index) {
int elementIndex = 0;
for (@NonNull Node child : node.getChildren()) {
if (child instanceof Element) {
if (elementIndex == index) {
return (Element)child;
}
elementIndex++;
}
}
return null;
}
protected @Nullable Element getElement(@NonNull String uriFragment) {
// based on ResourceImpl.getEObject etc
int length = uriFragment.length();
if (length > 0) {
if (uriFragment.charAt(0) == '/') {
List<String> path = SegmentSequence.create("/", uriFragment).subSegmentsList(1);
Element element = basicGetElementForPath(path);
if (element == null) {
addError("Failed to resolve '" + uriFragment + "'");
}
return element;
}
else if (uriFragment.charAt(length - 1) == '?') {
int index = uriFragment.lastIndexOf('?', length - 2);
if (index > 0) {
uriFragment = uriFragment.substring(0, index);
}
}
}
Element element = xmiid2element.get(uriFragment);
if (element == null) {
addError("Failed to resolve xmi:id '" + uriFragment + "'");
}
return element;
}
/**
* Returns the object associated with the URI fragment root segment.
* This default implementation uses the position of the object;
* an empty string is the same as <code>"0"</code>.
* @return the object associated with the URI fragment root segment.
*/
protected @Nullable Element getRootElement(String uriFragmentRootSegment) {
int position = 0;
if (uriFragmentRootSegment.length() > 0) {
if (uriFragmentRootSegment.charAt(0) == '?') {
return xmiid2element.get(uriFragmentRootSegment.substring(1));
}
try {
position = Integer.parseInt(uriFragmentRootSegment);
}
catch (NumberFormatException exception) {
throw new WrappedException(exception);
}
}
Node rootElement = document;
List<Node> rootChildren = rootElement.getChildren();
if (rootChildren.size() == 1) {
Node onlyChild = rootChildren.get(0);
if (onlyChild.eClass() == XMLmodelPackage.Literals.ELEMENT) { // Not a ClassElement
rootElement = onlyChild;
}
}
return getElementAtChildIndex(rootElement, position);
}
protected @Nullable String getURIofXMLNS(@NonNull String xmlnsPrefix) {
String value = xmlns2uri.get(xmlnsPrefix);
if (value == null) {
addError("Unresolved XMLNS prefix '" + xmlnsPrefix + "'");
};
return value;
}
protected @Nullable EClassifier getXsiType(@NonNull Attributes attributes) {
int length = attributes.getLength();
for (int index = 0; index < length; index++) {
String qName = attributes.getQName(index);
if (qNameEquals(qName, ExtendedMetaData.XMI_URI, "type") || qNameEquals(qName, ExtendedMetaData.XSI_URI, "type")) {
String value = attributes.getValue(index);
return getEClassifier(value);
}
}
return null;
}
protected void processAttribute(@NonNull Element element, @NonNull String attributeQName, @NonNull String value) {
Attribute attribute = null;
Element childElement = null;
int colonIndex = attributeQName.indexOf(':');
if (colonIndex >= 0) {
attribute = XMLmodelFactory.eINSTANCE.createAttribute();
if (qNameEquals(attributeQName, ExtendedMetaData.XMI_URI, "id")) {
Element oldElement = xmiid2element.put(value, element);
if (oldElement != null) {
addError("Ambiguous xmi:id '" + value + "'");
}
}
}
else if (ExtendedMetaData.XMLNS_PREFIX.equals(attributeQName)) {
attribute = XMLmodelFactory.eINSTANCE.createAttribute();
}
else {
EClassifier ecoreClassifier = element.getEcoreClassifier();
if (ecoreClassifier == null) {
attribute = XMLmodelFactory.eINSTANCE.createAttribute();
}
else if (ecoreClassifier instanceof EClass) {
EStructuralFeature eFeature = ((EClass)ecoreClassifier).getEStructuralFeature(attributeQName);
if (eFeature instanceof EAttribute) {
DataTypeAttribute dataTypeAttribute = XMLmodelFactory.eINSTANCE.createDataTypeAttribute();
dataTypeAttribute.setEcoreAttribute((EAttribute) eFeature);
attribute = dataTypeAttribute;
}
else {
ClassAttribute classAttribute = XMLmodelFactory.eINSTANCE.createClassAttribute();
classAttribute.setEcoreReference((EReference) eFeature);
attribute = classAttribute;
}
}
else if (ecoreClassifier instanceof EDataType) {
DataTypeElement dataTypeElement = XMLmodelFactory.eINSTANCE.createDataTypeElement();
dataTypeElement.setEcoreDataType((EDataType) ecoreClassifier);
childElement = dataTypeElement;
}
else {
childElement = XMLmodelFactory.eINSTANCE.createElement();
// EStructuralFeature eFeature = ((EClass)ecoreClassifier).getEStructuralFeature(attributeQName);
// if (eFeature instanceof EAttribute) {
// DataTypeAttribute dataTypeAttribute = XMLmodelFactory.eINSTANCE.createDataTypeAttribute();
// dataTypeAttribute.setEcoreAttribute((EAttribute) eFeature);
// attribute = dataTypeAttribute;
// }
// else {
// ClassAttribute classAttribute = XMLmodelFactory.eINSTANCE.createClassAttribute();
// classAttribute.setEcoreReference((EReference) eFeature);
// attribute = classAttribute;
// }
}
}
if (attribute != null) {
attribute.setName(attributeQName);
attribute.setValue(value);
element.getChildren().add(attribute);
}
else if (childElement != null) {
element.getChildren().add(childElement);
}
}
protected void processRootAttributes(@NonNull Attributes attributes) {
int length = attributes.getLength();
for (int index = 0; index < length; index++) {
String xmlns = null;
String qName = attributes.getQName(index);
int colonIndex = qName.indexOf(':');
if (colonIndex >= 0) {
String prefix = qName.substring(0, colonIndex);
if (ExtendedMetaData.XMLNS_PREFIX.equals(prefix)) {
xmlns = qName.substring(colonIndex+1);
}
}
else if (ExtendedMetaData.XMLNS_PREFIX.equals(qName)) {
xmlns = "";
}
if (xmlns != null) {
String newValue = attributes.getValue(index);
String oldValue = xmlns2uri.put(xmlns, newValue);
if (oldValue != null) {
addError("Conflicting '" + xmlns + "' XMLNS values '" + oldValue + "' and '" + newValue + "'");
}
}
}
for (int index = 0; index < length; index++) {
String qName = attributes.getQName(index);
int colonIndex = qName.indexOf(':');
if (colonIndex >= 0) {
String prefix = qName.substring(0, colonIndex);
if (!ExtendedMetaData.XMLNS_PREFIX.equals(prefix)) {
String suffix = qName.substring(colonIndex+1);
String uri = getURIofXMLNS(prefix);
if (ExtendedMetaData.XSI_URI.equals(uri) && "schemaLocation".equals(suffix)) {
String value = attributes.getValue(index);
int spaceIndex = value.indexOf(' ');
String schemaLocation = value;
String schemaURI = "";
if (spaceIndex >= 0) {
schemaLocation = value.substring(spaceIndex+1).trim();
schemaURI = value.substring(spaceIndex);
}
String oldValue = uri2schemaLocation.put(schemaURI, schemaLocation);
if (oldValue != null) {
addError("Conflicting '" + schemaURI + "' schemaLocation values '" + oldValue + "' and '" + schemaLocation + "'");
}
}
}
}
}
}
@Override
public void processingInstruction(String target, String data) {
ProcessingInstruction processingInstruction = XMLmodelFactory.eINSTANCE.createProcessingInstruction();
processingInstruction.setTarget(target);
processingInstruction.setData(data);
addNode(processingInstruction);
}
/**
* Return true if a qName such as 'xmi:id' corresponds to uri:name where uri is registered XMLNS URI for the
* qName prefix of defauly XMLNS if qName has no : to separate a prefix.
*/
protected boolean qNameEquals(@NonNull String qName, @NonNull String uri, @NonNull String name) {
int colonIndex = qName.indexOf(':');
if (colonIndex < 0) {
if (!uri.equals(xmlns2uri.get(""))) {
return false;
}
return name.equals(qName);
}
else {
String prefix = qName.substring(0, colonIndex);
if (!uri.equals(xmlns2uri.get(prefix))) {
return false;
}
String suffix = qName.substring(colonIndex+1);
return name.equals(suffix);
}
}
protected void resolveReferences(@NonNull Node node) {
if (node instanceof ClassAttribute) {
ClassAttribute classAttribute = (ClassAttribute)node;
String[] values = classAttribute.getValue().split(" ");
for (int i = 0; i < values.length; i++) {
EClassifier eClassifier = classAttribute.getEcoreReference().getEType();
String value = values[i].trim();
int hashIndex = value.indexOf('#');
if ((hashIndex < 0) && ((i+1) < values.length)) {
String nextValue = values[i+1].trim();
int nextHashIndex = nextValue.indexOf('#');
if (nextHashIndex > 0) {
eClassifier = getEClassifier(value);
i++; // Skip type prefix to external reference
value = nextValue;
hashIndex = nextHashIndex;
}
}
if (value.length() > 0) {
EObject resolvedElement = null;
if (hashIndex <= 0) { // absent or at start
resolvedElement = getElement(hashIndex < 0 ? value : value.substring(1));
}
if (resolvedElement == null) {
resolvedElement = eClassifier.getEPackage().getEFactoryInstance().create((EClass) eClassifier);
((BasicEObjectImpl)resolvedElement).eSetProxyURI(URI.createURI(value));
}
classAttribute.getEObjects().add(resolvedElement);
// }
}
}
}
for (@NonNull Node child : node.getChildren()) {
resolveReferences(child);
}
}
@Override
public void startCDATA() {
CDATA cdata = XMLmodelFactory.eINSTANCE.createCDATA();
addNode(cdata);
nodeStack.push(cdata);
}
@Override
public void startDTD(String name, String publicId, String systemId) {
DTD dtd = XMLmodelFactory.eINSTANCE.createDTD();
dtd.setName(name);
dtd.setPublicId(publicId);
dtd.setSystemId(systemId);
addNode(dtd);
nodeStack.push(dtd);
}
@Override
public void startDocument() {
document = XMLmodelFactory.eINSTANCE.createDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (nodeStack.isEmpty()) {
processRootAttributes(attributes);
}
EPackage ecorePackage = null;
EClassifier ecoreClassifier = null;
String name = qName;
int colonIndex = qName.indexOf(':');
if (colonIndex >= 0) { // Qualified element name
String prefix = qName.substring(0, colonIndex);
name = qName.substring(colonIndex+1);
ecorePackage = getEPackage(prefix);
if (ecorePackage != null) {
ecoreClassifier = ecorePackage.getEClassifier(name);
if (ecoreClassifier == null) {
addError("Failed to locate EClassifier '" + name + "' in '" + ecorePackage.getNsURI() + "'");
}
}
}
else {
ecoreClassifier = getXsiType(attributes);
if (ecoreClassifier == null) {
ecorePackage = basicGetEPackage("");
if (ecorePackage != null) {
ecoreClassifier = ecorePackage.getEClassifier(name);
if (ecoreClassifier == null) {
addError("Failed to locate EClassifier '" + name + "' in '" + ecorePackage.getNsURI() + "'");
}
}
else {
Element container = (Element)nodeStack.peek();
EClassifier ecoreContainerClassifier = container.getEcoreClassifier();
if (ecoreContainerClassifier instanceof EClass) {
EStructuralFeature eStructuralFeature = ((EClass)ecoreContainerClassifier).getEStructuralFeature(name);
if (eStructuralFeature != null) {
ecoreClassifier = eStructuralFeature.getEType();
}
else {
addError("Failed to locate EStructuralFeature '" + name + "' in '" + ecoreContainerClassifier.getName() + "'");
}
}
else if (ecoreContainerClassifier instanceof EDataType) {
// EReference ecoreContainerReference = (EAttribute) ((EDataType)ecoreContainerClassifier).getEStructuralFeature(name);
// if (ecoreContainerReference != null) {
// ecoreClass = ecoreContainerReference.getEReferenceType();
// }
// else {
// addError("Failed to locate EReference '" + name + "' in '" + ecoreContainerClassifier.getName() + "'");
// }
}
}
}
}
Element element;
if (ecoreClassifier instanceof EClass) {
ClassElement classElement = XMLmodelFactory.eINSTANCE.createClassElement();
classElement.setEcoreClass((EClass) ecoreClassifier);
element = classElement;
}
else if (ecoreClassifier instanceof EDataType) {
DataTypeElement dataTypeElement = XMLmodelFactory.eINSTANCE.createDataTypeElement();
dataTypeElement.setEcoreDataType((EDataType) ecoreClassifier);
element = dataTypeElement;
}
else {
element = XMLmodelFactory.eINSTANCE.createElement();
}
element.setUri(uri);
element.setLocalName(localName);
element.setQName(qName);
int length = attributes.getLength();
for (int index = 0; index < length; index++) {
String attributeQName = attributes.getQName(index);
String value = attributes.getValue(index);
processAttribute(element, attributeQName, value);
}
addNode(element);
nodeStack.push(element);
}
@Override
public void startEntity(String name) {
Entity entity = XMLmodelFactory.eINSTANCE.createEntity();
entity.setName(name);
addNode(entity);
nodeStack.push(entity);
}
@Override
public void startPrefixMapping(String prefix, String uri) {
PrefixMapping prefixMapping = XMLmodelFactory.eINSTANCE.createPrefixMapping();
prefixMapping.setPrefix(prefix);
prefixMapping.setUri(uri);
addNode(prefixMapping);
nodeStack.push(prefixMapping);
}
}
/**
* The XMLString inherited by XMLSaveImpl is very close to what is required, but its handling of mixed content is
* hard to understand. Some of the whitespacing around characters is not quite right. XMLStringBuilder is significantly
* simpler, mostly with justofiication, but sometimes through naivety.
*/
protected static class XMLStringBuilder
{
protected enum XMLstate {
OUTSIDE, // e.g at "@<xx/>" or <xx/>@"
HAS_ELEMENT_OPEN, // e.g at "<xx ... @>
HAS_ELEMENT_CLOSE // e.g at "<xx>...@<xx/>
};
private StringBuilder s = new StringBuilder();
private int currentColumn = 0;
// private int indentColumn = 0;
private Stack<@NonNull String> elementNameStack = new Stack<>();
// private boolean hasAttributes = false;
private StringBuilder attributeBuilder = new StringBuilder(256);
private @NonNull String lineSeparator;
private int lineWidth;
// private boolean hasElements = false;
private XMLstate xmlState = XMLstate.OUTSIDE;
public XMLStringBuilder(@NonNull String lineSeparator, int lineWidth) {
this.lineSeparator = lineSeparator;
this.lineWidth = lineWidth;
}
public void add(@NonNull String string) {
append(string);
}
public void addAttributeContent(@NonNull String content) {
assert attributeBuilder.length() > 0;
attributeBuilder.append(content);
}
public void addCDATA(@NonNull String characters) {
appendOuterElementClose();
s.append("<![CDATA[");
s.append(characters);
s.append("]]>");
addLine();
}
public void addCharacters(@NonNull String characters) {
appendOuterElementClose();
append(characters);
}
public void addComment(@NonNull String string) {
appendOuterElementClose();
addLine();
append("<!--" + string + "-->");
addLine();
}
public void addLine() {
if (currentColumn > 0) {
s.append(lineSeparator);
currentColumn = 0;
}
}
public void addProcessingInstruction(@Nullable String target, @Nullable String data) {
appendOuterElementClose();
addLine();
s.append("<?");
s.append(target == null ? "_" : target);
if (data != null) {
s.append(" ");
s.append(data); // FIXME line wrap at new-lines
}
s.append("?>");
addLine();
}
protected void append(@NonNull String string) {
int length = string.length();
if (length > 0) {
int indentColumn = (elementNameStack.size() + (attributeBuilder.length() > 0 ? 1 : 0)) * 2;
for ( ; currentColumn < indentColumn; currentColumn++) {
s.append(' ');
}
s.append(string);
currentColumn += length;
}
}
protected void appendOuterElementClose() {
assert attributeBuilder.length() <= 0;
assert xmlState != XMLstate.OUTSIDE;
if (xmlState == XMLstate.HAS_ELEMENT_OPEN) {
append(">");
xmlState = XMLstate.HAS_ELEMENT_CLOSE;
}
}
public void endAttribute() {
assert attributeBuilder.length() > 0;
attributeBuilder.append("\"");
int length = attributeBuilder.length();
if (currentColumn + 1 + length > lineWidth) {
addLine();
}
else {
append(" ");
}
append(attributeBuilder.toString());
attributeBuilder.replace(0, length, "");
}
public void endContentElement(@NonNull String contentElement) {
append(">");
addLine();
}
public void endElement() {
assert xmlState != XMLstate.OUTSIDE;
assert attributeBuilder.length() <= 0;
String qName = elementNameStack.pop();
append(xmlState == XMLstate.HAS_ELEMENT_CLOSE ? "</" + qName + ">" : "/>");
addLine();
xmlState = elementNameStack.isEmpty() ? XMLstate.OUTSIDE : XMLstate.HAS_ELEMENT_CLOSE;
}
public void startAttribute(@NonNull String name) {
assert xmlState == XMLstate.HAS_ELEMENT_OPEN;
assert attributeBuilder.length() <= 0;
attributeBuilder.append(name);
attributeBuilder.append("=\"");
}
public void startElement(@NonNull String qName) {
if (xmlState != XMLstate.OUTSIDE) {
appendOuterElementClose();
}
addLine();
append("<" + qName);
elementNameStack.push(qName);
xmlState = XMLstate.HAS_ELEMENT_OPEN;
}
public void write(@NonNull Writer os, int flushThreshold) throws IOException {
String s2 = s.toString();
assert xmlState == XMLstate.OUTSIDE;
os.write(s2);
}
public void writeAscii(@NonNull OutputStream os, int flushThreshold) throws IOException {
String s2 = s.toString();
assert xmlState == XMLstate.OUTSIDE;
os.write(s2.getBytes()); // No encoding necessary
}
}
protected static class XMLmodelSave extends XMISaveImpl
{
// The inherited doc is never intentionally used.
private XMLStringBuilder doc2;
public XMLmodelSave(XMLHelper helper) {
super(helper);
}
private @NonNull String getCharacters(@NonNull Node node) {
StringBuilder s = new StringBuilder();
for (@NonNull Node child : node.getChildren()) {
if (child instanceof Characters) {
s.append(((Characters)child).getData());
}
}
return s.toString();
}
@Override
protected void init(XMLResource resource, Map<?, ?> options) {
Integer lineWidth = (Integer)options.get(XMLResource.OPTION_LINE_WIDTH);
if (lineWidth == null) {
lineWidth = Integer.MAX_VALUE;
}
String lineSeparator = (String)options.get(Resource.OPTION_LINE_DELIMITER);
if (lineSeparator == null || lineSeparator.equals(Resource.OPTION_LINE_DELIMITER_UNSPECIFIED)) {
lineSeparator = System.getProperty("line.separator");
}
super.init(resource, options);
this.doc2 = new XMLStringBuilder(lineSeparator, lineWidth);
}
@Override
public void traverse(List<? extends EObject> contents) {
doc2.add("<?xml version=\"" + xmlVersion + "\" encoding=\"" + encoding + "\"?>");
traverseElements(contents);
}
protected void traverseElements(List<? extends EObject> contents) {
for (EObject eObject : contents) {
if (eObject instanceof Document) {
traverseElements(((Document)eObject).getChildren());
}
else if (eObject instanceof Element) {
Element element = (Element)eObject;
doc2.startElement(element.getQName());
traverseElements(element.getChildren());
doc2.endElement();
}
else if (eObject instanceof Attribute) {
Attribute attribute = (Attribute)eObject;
doc2.startAttribute(attribute.getName());
doc2.addAttributeContent(escape.convert(attribute.getValue()));
doc2.endAttribute();
}
else if (eObject instanceof Comment) {
Comment comment = (Comment)eObject;
doc2.addComment(comment.getData());
}
else if (eObject instanceof Characters) {
Characters characters = (Characters)eObject;
String data = characters.getData();
doc2.addCharacters(data);
}
else if (eObject instanceof CDATA) {
CDATA cdata = (CDATA)eObject;
doc2.addCDATA(getCharacters(cdata));
}
else if (eObject instanceof DTD) {
@SuppressWarnings("unused")
DTD dtd = (DTD)eObject;
// doc2.addDTD(dtd.getName(), dtd.getPublicId(), dtd.getSystemId());
}
else if (eObject instanceof Entity) {
@SuppressWarnings("unused")
Entity entity = (Entity)eObject;
// doc2.addEntity(entity.getName());
}
else if (eObject instanceof ProcessingInstruction) {
ProcessingInstruction processingInstruction = (ProcessingInstruction)eObject;
doc2.addProcessingInstruction(processingInstruction.getTarget(), processingInstruction.getData());
}
}
}
@Override
public void write(Writer os) throws IOException {
doc2.write(os, flushThreshold);
os.flush();
}
@Override
public void writeAscii(OutputStream os) throws IOException {
doc2.writeAscii(os, flushThreshold);
os.flush();
}
}
public XMLmodelResourceImpl(URI uri) {
super(uri);
}
@Override
protected XMLLoad createXMLLoad() {
return new XMLmodelLoadImpl(createXMLHelper());
}
@Override
protected XMLLoad createXMLLoad(Map<?, ?> options) {
return createXMLLoad();
}
@Override
protected XMLSave createXMLSave() {
return new XMLmodelSave(createXMLHelper());
}
@Override
protected XMLSave createXMLSave(Map<?, ?> options) {
return createXMLSave();
}
}