| /* |
| * Copyright (c) 2010 JBoss, Inc. and others |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| */ |
| package org.eclipse.bpel.model.util; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.OutputStreamWriter; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Stack; |
| |
| import javax.xml.namespace.QName; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| |
| import org.eclipse.emf.common.notify.Notifier; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMDataType; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMGroup; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMNode; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMNodeList; |
| import org.eclipse.wst.xml.core.internal.contentmodel.ContentModelManager; |
| import org.eclipse.wst.xml.core.internal.contentmodel.basic.CMElementDeclarationImpl; |
| import org.eclipse.wst.xml.core.internal.contentmodel.basic.CMNamedNodeMapImpl; |
| import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilder; |
| import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilderImpl; |
| import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMNamespaceInfoManager; |
| import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMWriter; |
| import org.eclipse.wst.xml.core.internal.contentmodel.util.NamespaceInfo; |
| import org.eclipse.wst.xml.ui.internal.wizards.NewXMLGenerator; |
| import org.eclipse.wst.xsd.contentmodel.internal.XSDImpl.XSDAttributeUseAdapter; |
| import org.eclipse.wst.xsd.contentmodel.internal.XSDImpl.XSDElementDeclarationAdapter; |
| import org.eclipse.xsd.XSDAttributeDeclaration; |
| import org.eclipse.xsd.XSDAttributeUse; |
| import org.eclipse.xsd.XSDConcreteComponent; |
| import org.eclipse.xsd.XSDElementDeclaration; |
| import org.eclipse.xsd.XSDEnumerationFacet; |
| import org.eclipse.xsd.XSDNamedComponent; |
| import org.eclipse.xsd.XSDSchema; |
| import org.eclipse.xsd.XSDSimpleTypeDefinition; |
| import org.eclipse.xsd.XSDTypeDefinition; |
| import org.eclipse.xsd.impl.XSDElementDeclarationImpl; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| /** |
| * This extends the internal NewXMLGenerator and fixes some issues with <choice> elements |
| * |
| * @see https://jira.jboss.org/browse/JBIDE-6748, https://jira.jboss.org/browse/JBIDE-7351 |
| * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=330813 |
| * @author Bob Brodt |
| * @date Oct 29, 2010 |
| */ |
| @SuppressWarnings("restriction") |
| public class XSD2XMLGenerator extends NewXMLGenerator { |
| |
| // this mess is an attempt to insulate ourselves from changes in the DOMCOntentBuilder API |
| public static final int BUILD_OPTIONAL_ATTRIBUTES = 1; |
| public static final int BUILD_OPTIONAL_ELEMENTS = 1 << 1; |
| public static final int BUILD_FIRST_CHOICE = 1 << 2; |
| public static final int BUILD_TEXT_NODES = 1 << 3; |
| public static final int BUILD_FIRST_SUBSTITUTION = 1 << 4; |
| public static final int BUILD_STRUCTURE_ONLY = 1 << 5; |
| |
| public static final int BUILD_ONLY_REQUIRED_CONTENT = |
| BUILD_FIRST_CHOICE |
| | BUILD_TEXT_NODES; |
| public static final int BUILD_ALL_CONTENT = |
| BUILD_OPTIONAL_ATTRIBUTES |
| | BUILD_OPTIONAL_ELEMENTS |
| | BUILD_FIRST_CHOICE |
| | BUILD_TEXT_NODES; |
| |
| MyDOMContentBuilderImpl contentBuilder = null; |
| protected String queryPath[] = new String[0]; |
| protected int myBuildPolicy = BUILD_ONLY_REQUIRED_CONTENT; |
| protected String xsdURI = null; |
| |
| /** |
| * @param xsdURI |
| * @param rootElementName |
| */ |
| public XSD2XMLGenerator(String xsdURI, String rootName) { |
| setRootElementName(rootName); |
| this.xsdURI = xsdURI; |
| } |
| |
| |
| /** |
| * Default constructor requires a call to setRoot() which will determine |
| * the root element name and XSD uri |
| */ |
| public XSD2XMLGenerator() { |
| super(); |
| } |
| |
| /** |
| * @param path |
| */ |
| public void setQueryPath(String path) |
| { |
| if (path!=null && path.length()>0) { |
| this.queryPath = path.split("/"); |
| if (this.queryPath!=null && this.queryPath.length>0) { |
| for (int i=0; i<this.queryPath.length; ++i) { |
| this.queryPath[i] = this.queryPath[i].replaceFirst(".*:", ""); |
| } |
| } |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.xml.ui.internal.wizards.NewXMLGenerator#setBuildPolicy(int) |
| */ |
| @Override |
| public void setBuildPolicy(int buildPolicy) { |
| this.myBuildPolicy = buildPolicy; |
| } |
| |
| /** |
| * @return |
| * @throws Exception |
| */ |
| public String createXML() throws Exception { |
| |
| if (xsdURI==null) |
| throw new IllegalArgumentException("XML Generator: XSD location is unknown"); |
| |
| CMDocument cmDocument = ContentModelManager.getInstance().createCMDocument(xsdURI, "xsd"); |
| setCMDocument(cmDocument); |
| createNamespaceInfoList(); |
| |
| // create the xml model |
| CMNamedNodeMap map = cmDocument.getElements(); |
| CMElementDeclaration cmRootElement = null; |
| |
| cmRootElement = (CMElementDeclaration) map.getNamedItem(getRootElementName()); |
| if (cmRootElement==null) |
| throw new IllegalArgumentException("XML Generator: Root element \'"+getRootElementName()+"\' is not defined in this XSD"); |
| |
| Document xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); |
| |
| createContentBuilder(xmlDocument); |
| |
| // do the dirty deed! |
| contentBuilder.createDefaultRootContent(cmDocument, cmRootElement, namespaceInfoList); |
| |
| // create an output stream so we can generate a string |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); |
| |
| DOMWriter domWriter = new DOMWriter(outputStreamWriter); |
| |
| // TODO... instead of relying on file extensions, we need to keep |
| // track of the grammar type... |
| // better yet we should create an SSE document so that we can format |
| // it nicely before saving then we won't need the DOMWriter at all |
| domWriter.print(xmlDocument); |
| outputStream.flush(); |
| outputStream.close(); |
| |
| return outputStream.toString(); |
| } |
| |
| /** |
| * @param xmlDocument |
| */ |
| private void createContentBuilder(Document xmlDocument) { |
| contentBuilder = new MyDOMContentBuilderImpl(xmlDocument); |
| |
| // convert our build policy to theirs |
| int bp = 0; |
| if ((myBuildPolicy|BUILD_OPTIONAL_ATTRIBUTES) != 0) bp |= DOMContentBuilder.BUILD_OPTIONAL_ATTRIBUTES; |
| if ((myBuildPolicy|BUILD_OPTIONAL_ELEMENTS) != 0) bp |= DOMContentBuilder.BUILD_OPTIONAL_ELEMENTS; |
| if ((myBuildPolicy|BUILD_FIRST_CHOICE) != 0) bp |= DOMContentBuilder.BUILD_FIRST_CHOICE; |
| if ((myBuildPolicy|BUILD_TEXT_NODES) != 0) bp |= DOMContentBuilder.BUILD_TEXT_NODES; |
| if ((myBuildPolicy|BUILD_FIRST_SUBSTITUTION) != 0) bp |= DOMContentBuilder.BUILD_FIRST_SUBSTITUTION; |
| contentBuilder.setBuildPolicy(bp); |
| } |
| |
| |
| /** |
| * Given a CMNode object, determine the XSD type definition for the Node |
| * |
| * @param cmNode - a CMNode object encapsulating either an Element or an Attribute |
| * @return XSD type definition, or null |
| */ |
| public static XSDTypeDefinition getXSDType(CMNode cmNode) { |
| // this code snippet was obtained here: https://bugs.eclipse.org/bugs/show_bug.cgi?id=265274 |
| XSDTypeDefinition xsdType = null; |
| |
| if(cmNode instanceof CMElementDeclaration) { |
| CMElementDeclaration cmElementDeclaration = (CMElementDeclaration)cmNode; |
| if(cmElementDeclaration instanceof XSDElementDeclarationAdapter) { |
| XSDElementDeclarationAdapter xsdElementDeclarationAdapter = (XSDElementDeclarationAdapter)cmElementDeclaration; |
| Notifier target = xsdElementDeclarationAdapter.getTarget(); |
| if(target instanceof XSDElementDeclaration) { |
| XSDElementDeclaration xsdElementDeclaration = (XSDElementDeclaration)target; |
| xsdType = xsdElementDeclaration.getResolvedElementDeclaration().getTypeDefinition(); |
| } |
| } |
| } |
| else if(cmNode instanceof CMAttributeDeclaration) { |
| CMAttributeDeclaration cmAttributeDeclaration = (CMAttributeDeclaration)cmNode; |
| if(cmAttributeDeclaration instanceof XSDAttributeUseAdapter) { |
| XSDAttributeUseAdapter xsdAttributeUseAdapter = (XSDAttributeUseAdapter)cmAttributeDeclaration; |
| Notifier target = xsdAttributeUseAdapter.getTarget(); |
| if(target instanceof XSDAttributeUse) { |
| XSDAttributeUse xsdAttributeUse = (XSDAttributeUse)target; |
| XSDAttributeDeclaration xsdAttributeDeclaration = xsdAttributeUse.getAttributeDeclaration(); |
| if(xsdAttributeDeclaration != null) { |
| xsdType = xsdAttributeDeclaration.getResolvedAttributeDeclaration().getTypeDefinition(); |
| } |
| } |
| } |
| } |
| |
| return xsdType; |
| } |
| |
| /** |
| * Internal class that extends the actual XML generator. |
| * There are a couple of, let's call them "misbehaviors", of the DOMContentBuilder |
| * implementation that we need to correct: |
| * 1. if the caller specified an XPath and if any of the elements in that path are |
| * "choices" then generate the XML for those particular choices, instead of picking |
| * the first choice in the sequence. |
| * 2. if the generated "choice" contains an "enumeration" element, generate the correct |
| * text value for that element, instead of picking the first enumeration value derived |
| * from the base type. |
| */ |
| public class MyDOMContentBuilderImpl extends DOMContentBuilderImpl { |
| |
| // need to keep track of the current parent CMNode object so we can determine the |
| // XSD type definition when trying to build text content for a node (see createTextNode()) |
| protected Stack<CMNode> cmNodeStack = new Stack<CMNode>(); |
| |
| public MyDOMContentBuilderImpl(Document document) { |
| super(document); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilderImpl#createDefaultRootContent(org.eclipse.wst.xml.core.internal.contentmodel.CMDocument, org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration) |
| */ |
| @Override |
| public void createDefaultRootContent(CMDocument cmDocument, |
| CMElementDeclaration rootCMElementDeclaration) throws Exception { |
| if (namespaceInfoList != null) { |
| DOMNamespaceInfoManager manager = new DOMNamespaceInfoManager(); |
| String name = rootCMElementDeclaration.getNodeName(); |
| if (namespaceInfoList.size() > 0) { |
| NamespaceInfo info = (NamespaceInfo) namespaceInfoList.get(0); |
| if (info.prefix != null && info.prefix.length() > 0) { |
| name = info.prefix + ":" + name; //$NON-NLS-1$ |
| } |
| } |
| rootElement = createElement(rootCMElementDeclaration, name, document); |
| if ((myBuildPolicy & BUILD_STRUCTURE_ONLY)==0) |
| manager.addNamespaceInfo(rootElement, namespaceInfoList, true); |
| } |
| createDefaultContent(document, rootCMElementDeclaration); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilderImpl#visitCMGroup(org.eclipse.wst.xml.core.internal.contentmodel.CMGroup) |
| */ |
| @Override |
| public void visitCMGroup(CMGroup e) { |
| // this was copied directly from DOMContentBuilderImpl because there is apparently |
| // no way of having CMNode return the "contentHint" property (see original implementation) |
| // short of subclassing CMNode...an example provided by the wtp team would have |
| // helped here. |
| cmGroupStack.push(e); |
| |
| int forcedMin = (buildOptionalElements(myBuildPolicy) || alwaysVisit) ? 1 : 0; |
| int min = Math.max(e.getMinOccur(), forcedMin); |
| |
| int max = 0; |
| if (e.getMaxOccur() == -1) // unbounded |
| max = getNumOfRepeatableElements(); |
| else |
| max = Math.min(e.getMaxOccur(), getNumOfRepeatableElements()); |
| |
| if (max < min) |
| max = min; |
| |
| alwaysVisit = false; |
| |
| for (int i = 1; i <= max; i++) { |
| if (e.getOperator() == CMGroup.CHOICE && buildFirstChoice(myBuildPolicy)) { |
| CMNode cmNode = null; |
| |
| if (domLevel>0 && domLevel<=queryPath.length) |
| { |
| for (int n=0; n<e.getChildNodes().getLength(); ++n ) { |
| CMNode cn = e.getChildNodes().item(n); |
| if (cn.getNodeName().equals(queryPath[domLevel-1])) { |
| cmNode = cn; |
| break; |
| } |
| } |
| } |
| |
| // if no cmNode has been determined from the path, just use the first choice |
| if (cmNode == null) { |
| CMNodeList nodeList = e.getChildNodes(); |
| if (nodeList.getLength() > 0) { |
| cmNode = nodeList.item(0); |
| } |
| } |
| |
| if (cmNode != null) { |
| visitCMNode(cmNode); |
| } |
| } |
| else if (e.getOperator() == CMGroup.ALL // ALL |
| || e.getOperator() == CMGroup.SEQUENCE) // SEQUENCE |
| { |
| // visit all of the content |
| super.visitCMGroup(e); |
| } |
| } |
| |
| cmGroupStack.pop(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilderImpl#handlePushParent(org.w3c.dom.Element, org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration) |
| */ |
| @Override |
| protected void handlePushParent(Element parent, CMElementDeclaration ed) { |
| super.handlePushParent(parent, ed); |
| cmNodeStack.push(ed); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilderImpl#handlePopParent(org.w3c.dom.Element, org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration) |
| */ |
| @Override |
| protected void handlePopParent(Element element, CMElementDeclaration ed) { |
| super.handlePopParent(element, ed); |
| cmNodeStack.pop(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilderImpl#createElement(org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration, java.lang.String, org.w3c.dom.Node) |
| */ |
| @Override |
| protected org.w3c.dom.Element createElement(CMElementDeclaration ed, String name, Node parent) { |
| // if building XML that represents only structure, use a generic element name |
| // (either "simpleElem" or "complexElem") instead of the elements actual tag name |
| if ((myBuildPolicy & BUILD_STRUCTURE_ONLY) != 0) { |
| CMDataType dt = ed.getDataType(); |
| if (dt!=null) |
| return document.createElement("simpleElem"); |
| return document.createElement("complexElem"); |
| } |
| return document.createElement(name); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilderImpl#createAttribute(org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration, java.lang.String, org.w3c.dom.Node) |
| */ |
| @Override |
| protected org.w3c.dom.Attr createAttribute(CMAttributeDeclaration ad, String name, Node parent) { |
| // TODO: I think attribute names MUST match even if we're only building XML structure |
| // for the purposes of comparing two XSD fragments... |
| return document.createAttribute(name); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.xml.core.internal.contentmodel.util.DOMContentBuilderImpl#createTextNode(org.eclipse.wst.xml.core.internal.contentmodel.CMDataType, java.lang.String, org.w3c.dom.Node) |
| */ |
| @Override |
| protected org.w3c.dom.Text createTextNode(CMDataType dataType, String value, Node parent) { |
| |
| // get XSD type definition from parent object currently at the top of our stack |
| // so that we can get the (reduced) enumeration value types for that element |
| // instead of for the base type (which is what XSDImpl#getEnumeratedValuesForType() does!) |
| if (cmNodeStack.size()>0) { |
| XSDTypeDefinition xsdType = getXSDType(cmNodeStack.peek()); |
| if (xsdType!=null) { |
| List result = new ArrayList(); |
| getEnumeratedValuesForSimpleType(xsdType, result); |
| if (!result.isEmpty()) { |
| value = (String)result.get(0); |
| return document.createTextNode(value); |
| } |
| } |
| } |
| |
| // if building XML that represents only structure, use a generic text value |
| // (the data type) instead of the value determined by the generator (see |
| // XSDTypeUtil#getInstanceValue() for example...) |
| if ((myBuildPolicy & BUILD_STRUCTURE_ONLY) != 0) |
| value = dataType.getDataTypeName(); |
| |
| return document.createTextNode(value); |
| } |
| |
| /** |
| * This was stolen directly from XSDImpl. It builds a list of the enumeration |
| * values for the given XSD type definition. |
| * |
| * @param type - an XSD type definition (presumably determined from a CMNode) |
| * @param result - List to which we will add our results |
| */ |
| public void getEnumeratedValuesForSimpleType(XSDTypeDefinition type, List result) { |
| List enumerationFacets = ((XSDSimpleTypeDefinition) type) |
| .getEnumerationFacets(); |
| for (Iterator i = enumerationFacets.iterator(); i.hasNext();) { |
| XSDEnumerationFacet enumFacet = (XSDEnumerationFacet) i.next(); |
| List values = enumFacet.getValue(); |
| for (Iterator j = values.iterator(); j.hasNext();) { |
| Object o = j.next(); |
| if (o != null) { |
| if (!result.contains(o)) { |
| result.add(o.toString()); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |