/*******************************************************************************
* Copyright © 2011, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package org.eclipse.edt.ide.ui.internal.record.conversion.xml;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import javax.xml.namespace.QName;
import org.w3c.dom.Attr;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.eclipse.edt.ide.ui.internal.record.conversion.IMessageHandler;
import org.eclipse.edt.ide.ui.internal.record.conversion.PartsUtil;
import org.eclipse.edt.ide.ui.templates.parts.Annotation;
import org.eclipse.edt.ide.ui.templates.parts.ArrayType;
import org.eclipse.edt.ide.ui.templates.parts.Field;
import org.eclipse.edt.ide.ui.templates.parts.Part;
import org.eclipse.edt.ide.ui.templates.parts.Record;
import org.eclipse.edt.ide.ui.templates.parts.RecordType;
import org.eclipse.edt.ide.ui.templates.parts.SimpleType;
import org.eclipse.edt.ide.ui.templates.parts.Type;
public class PartsFromXMLUtil extends PartsUtil {
static java.util.Map<String,String> reservedXSINames = new java.util.HashMap<String,String>();
static {
reservedXSINames.put("type", "type");//$NON-NLS-1$
reservedXSINames.put("nil", "nil");//$NON-NLS-1$
reservedXSINames.put("schemaLocation", "schemaLocation");//$NON-NLS-1$
reservedXSINames.put("noNamespaceSchemaLocation", "noNamespaceSchemaLocation");//$NON-NLS-1$
}
public PartsFromXMLUtil(IMessageHandler msgHandler) {
super(msgHandler);
}
public Part[] process(Object node, Record wrapRec) {
XMLNode xmlNode = new XMLNode((Node) node);
String xmlName = getTypeName(xmlNode.getQName().getLocalPart());
wrapRec.setName(xmlName);
if (!xmlName.equals(xmlNode.getQName().getLocalPart())
|| (xmlNode.getQName().getNamespaceURI() != null && xmlNode.getQName().getNamespaceURI().length() > 0) || xmlNode.isNillable()) {
Annotation annotation = new Annotation();
annotation.setName("XMLRootElement");//$NON-NLS-1$
annotation.addField("name", "\"" + xmlNode.getQName().getLocalPart() + "\"");//$NON-NLS-1$
if (xmlNode.getQName().getNamespaceURI() != null && xmlNode.getQName().getNamespaceURI().length() > 0)
annotation.addField("namespace", "\"" + xmlNode.getQName().getNamespaceURI() + "\"");//$NON-NLS-1$
if (xmlNode.isNillable())
annotation.addField("nillable", "true");//$NON-NLS-1$
wrapRec.addAnnotation(annotation);
}
LinkedHashMap<String,Record> map = new LinkedHashMap<String,Record>();
map.put(wrapRec.getName().toUpperCase().toLowerCase(), wrapRec);
Part[] parts = process(xmlNode, wrapRec, map);
if(parts != null && parts.length == 1) {
boolean hasChildElement = false;
Field[] fields = wrapRec.getFields();
for(Field field : fields) {
String annoName = null;
annoName = field.getAnnotationString();
if(annoName == null) {
hasChildElement = true;
} else if(!annoName.contains("XMLAttribute")) {
hasChildElement = true;
}
}
if(!hasChildElement) {
String fieldName = "simValue";
Field field = new Field();
field.setName(fieldName);
SimpleType type = new SimpleType();
type.setName("string?");//$NON-NLS-1$
field.setType(type);
wrapRec.addField(field);
}
}
return parts;
}
public Part[] process(XMLNode node, Record wrapRec, LinkedHashMap<String,Record> recs) {
java.util.HashMap<String,String> fieldNames = getFieldNames(node);
processChildAttributes(node, wrapRec, recs, fieldNames);
processChildElements(node, wrapRec, recs, fieldNames);
return (Part[]) recs.values().toArray(new Part[recs.values().size()]);
}
private java.util.HashMap<String,String> getFieldNames( XMLNode node ) {
java.util.HashMap<String,String> fieldNames = new java.util.HashMap<String,String>();
Iterator<ArrayList<XMLNode>> attrs = node.getChildAttributes().iterator();
while (attrs.hasNext()) {
ArrayList<XMLNode> child = attrs.next();
XMLNode xmlNode = child.get(0);
String originalName = xmlNode.getQName().getLocalPart();
fieldNames.put(originalName.toLowerCase(), originalName);
}
Iterator<ArrayList<XMLNode>> elems = node.getChildElements().iterator();
while (elems.hasNext()) {
ArrayList<XMLNode> child = elems.next();
XMLNode xmlNode = child.get(0);
String originalName = xmlNode.getQName().getLocalPart();
fieldNames.put(originalName.toLowerCase(), originalName);
}
return fieldNames;
}
public void processChildAttributes(XMLNode node, Record wrapRec, LinkedHashMap<String,Record> recs, java.util.HashMap<String,String> fieldNames) {
Iterator<ArrayList<XMLNode>> i = node.getChildAttributes().iterator();
while (i.hasNext()) {
ArrayList<XMLNode> child = i.next();
XMLNode xmlNode = child.get(0);
String originalName = xmlNode.getQName().getLocalPart();
String fieldName = getFieldName(originalName, wrapRec.getName(), fieldNames);
String xmlName = (originalName.equals(fieldName)) ? null : originalName;
Field field = new Field();
field.setName(fieldName);
field.setType(getType(child, recs, fieldName));
Annotation annotation = new Annotation();
annotation.setName("XMLAttribute");//$NON-NLS-1$
if (xmlName != null)
annotation.addField("name", "\"" + xmlName + "\"");//$NON-NLS-1$
if (xmlNode.getQName().getNamespaceURI() != null && xmlNode.getQName().getNamespaceURI().length() > 0)
annotation.addField("namespace", "\"" + xmlNode.getQName().getNamespaceURI() + "\"");//$NON-NLS-1$
field.addAnnotation(annotation);
wrapRec.addField(field);
}
}
public void processChildElements(XMLNode node, Record wrapRec, LinkedHashMap<String,Record> recs, java.util.HashMap<String,String> fieldNames) {
Iterator<ArrayList<XMLNode>> i = node.getChildElements().iterator();
while (i.hasNext()) {
ArrayList<XMLNode> child = i.next();
XMLNode xmlNode = child.get(0);
String originalName = xmlNode.getQName().getLocalPart();
String fieldName = getFieldName(originalName, wrapRec.getName(), fieldNames);
String xmlName = (originalName.equals(fieldName)) ? null : originalName;
Field field = new Field();
field.setName(fieldName);
field.setType(getType(child, recs, (originalName.equals(originalName.toUpperCase()) ? fieldName.toUpperCase() : fieldName)));
if (xmlName != null || (xmlNode.getQName().getNamespaceURI() != null && xmlNode.getQName().getNamespaceURI().length() > 0) || xmlNode.isNillable()) {
Annotation annotation = new Annotation();
annotation.setName("XMLElement");//$NON-NLS-1$
if (xmlName != null)
annotation.addField("name", "\"" + xmlName + "\"");//$NON-NLS-1$
if (xmlNode.getQName().getNamespaceURI() != null && xmlNode.getQName().getNamespaceURI().length() > 0)
annotation.addField("namespace", "\"" + xmlNode.getQName().getNamespaceURI() + "\"");//$NON-NLS-1$
if (xmlNode.isNillable())
annotation.addField("nillable", "true");//$NON-NLS-1$
field.addAnnotation(annotation);
}
wrapRec.addField(field);
}
}
private Type getType(ArrayList<XMLNode> nodelist, final LinkedHashMap<String,Record> recs, final String fieldName) {
final Type[] type = new Type[1];
if (nodelist.size() > 1) { // process array
type[0] = processArrayType(nodelist, recs, fieldName);
} else if (nodelist.get(0).getChildElements().size() > 0) { // process
// record
type[0] = processRecordType(nodelist.get(0), recs, fieldName);
} else if (nodelist.get(0).getChildAttributes().size() > 0) {
type[0] = processSimpleRecordType(nodelist.get(0), recs, fieldName);
} else {
SimpleType t = new SimpleType();
t.setName("string");//$NON-NLS-1$
type[0] = t;
}
return type[0];
}
private Type processArrayType(ArrayList<XMLNode> nodelist, LinkedHashMap<String,Record> recs, String fieldName) {
ArrayType type = new ArrayType();
Iterator<XMLNode> i = nodelist.iterator();
while (i.hasNext()) {
ArrayList<XMLNode> list = new ArrayList<XMLNode>();
list.add(i.next());
Type elementType = getType(list, recs, fieldName);
// if no type is set, then use the current type
if (type.getElementType() == null) {
type.setElementType(elementType);
}
// if the new type is a record and the old type is simple,
// do a merge to set the record fields as nullable and use the
// record as the type.
// can happen if:
// <Details>
// <WeatherData />
// <WeatherData>
// <Day>Saturday, February 13, 2010</Day>
// <WeatherImage>http://forecast.weather.gov/images/wtf/sct.jpg</WeatherImage>
// <MaxTemperatureF>41</MaxTemperatureF>
// <MinTemperatureF>23</MinTemperatureF>
// <MaxTemperatureC>5</MaxTemperatureC>
// <MinTemperatureC>-5</MinTemperatureC>
// </WeatherData>
// </Details>
else if (elementType instanceof RecordType && type.getElementType() instanceof SimpleType) {
String typeName = elementType.getName();
String fn = typeName.toUpperCase().toLowerCase();
Record rec = (Record) recs.get(fn);
mergeRecords(new Record(), rec);
type.setElementType(elementType);
}
// If the new type is simple and the old type is a record,
// use the record but call mergerecords to set the record fields
// as nullable.
// can happen if:
// <Details>
// <WeatherData>
// <Day>Saturday, February 13, 2010</Day>
// <WeatherImage>http://forecast.weather.gov/images/wtf/sct.jpg</WeatherImage>
// <MaxTemperatureF>41</MaxTemperatureF>
// <MinTemperatureF>23</MinTemperatureF>
// <MaxTemperatureC>5</MaxTemperatureC>
// <MinTemperatureC>-5</MinTemperatureC>
// </WeatherData>
// <WeatherData />
// </Details>
else if (elementType instanceof SimpleType && type.getElementType() instanceof RecordType) {
String typeName = type.getElementType().getName();
String fn = typeName.toUpperCase().toLowerCase();
Record rec = (Record) recs.get(fn);
mergeRecords(new Record(), rec);
}
}
return type;
}
private Type processRecordType(XMLNode node, LinkedHashMap<String,Record> recs, String fieldName) {
String typeName = getTypeName(fieldName);
RecordType type = new RecordType();
type.setName(typeName);
String fn = typeName.toUpperCase().toLowerCase();
Record oldrec = (Record) recs.get(fn);
Record rec = new Record();
rec.setName(typeName);
recs.put(fn, rec);
new PartsFromXMLUtil(_msgHandler).process(node, rec, recs);
if (oldrec != null) {
mergeRecords(oldrec, rec);
}
return type;
}
private Type processSimpleRecordType(XMLNode node, LinkedHashMap<String,Record> recs, String fieldName) {
String typeName = getTypeName(fieldName);
RecordType type = new RecordType();
type.setName(typeName);
String fn = typeName.toUpperCase().toLowerCase();
Record oldrec = (Record) recs.get(fn);
Record rec = new Record();
rec.setName(typeName);
recs.put(fn, rec);
// add simpleContent annotation
Annotation annotation = new Annotation();
annotation.setName("XMLValue");//$NON-NLS-1$
annotation.addField("kind", "XMLStructureKind.simpleContent");//$NON-NLS-1$
//annotation.setValue("XMLStructureKind.simpleContent");//$NON-NLS-1$
rec.addAnnotation(annotation);
new PartsFromXMLUtil(_msgHandler).process(node, rec, recs);
// add value field
Field field = new Field();
field.setName("egl_value");//$NON-NLS-1$
SimpleType t = new SimpleType();
t.setName("string");//$NON-NLS-1$
t.setNullable(true);
field.setType(t);
rec.addField(field);
if (oldrec != null) {
mergeRecords(oldrec, rec);
}
return type;
}
class XMLAttr extends XMLNode {
public XMLAttr(Node node) {
super(node);
}
public QName getQName() {
if (qname == null) {
String localpart = node.getNodeName();
String prefix = "";//$NON-NLS-1$
String namespace = null;
int ndx = localpart.indexOf(':');
// only get attribute namespace if it's qualified
if (ndx != -1) {
prefix = localpart.substring(0, ndx);
localpart = localpart.substring(ndx + 1);
if (prefix.length() > 0)
namespace = lookupNamespace(node, prefix);
}
qname = new QName(namespace, localpart, prefix);
}
return qname;
}
}
class XMLNode {
protected Node node = null;
protected QName qname = null;
protected java.util.List<ArrayList<XMLNode>> children = null;
protected java.util.List<ArrayList<XMLNode>> attributes = null;
protected Boolean isNillable = null;
public XMLNode(Node node) {
this.node = node;
}
public java.util.List<ArrayList<XMLNode>> getChildElements() {
if (this.children == null) {
java.util.Map<String,ArrayList<XMLNode>> map = new java.util.LinkedHashMap<String,ArrayList<XMLNode>>();
NodeList children = this.node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
// test to see if child is an element node
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
XMLNode xmlNode = new XMLNode(child);
if (map.containsKey(xmlNode.getQName().toString())) {
ArrayList<XMLNode> list = map.get(xmlNode.getQName().toString());
list.add(xmlNode);
} else {
ArrayList<XMLNode> list = new ArrayList<XMLNode>();
list.add(xmlNode);
map.put(xmlNode.getQName().toString(), list);
}
}
}
this.children = new ArrayList<ArrayList<XMLNode>>(map.values());
}
return this.children;
}
public java.util.List<ArrayList<XMLNode>> getChildAttributes() {
if (this.attributes == null) {
java.util.Map<String,ArrayList<XMLNode>> map = new java.util.LinkedHashMap<String,ArrayList<XMLNode>>();
NamedNodeMap children = this.node.getAttributes();
if (children != null) {
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
XMLAttr xmlAttr = new XMLAttr(child);
// if the attribute is a special xml name or is the
// start of a
// namespace definition, then ignore
if (isSpecialAttribute(xmlAttr))
continue;
ArrayList<XMLNode> list = new ArrayList<XMLNode>();
list.add(xmlAttr);
map.put(xmlAttr.getQName().toString(), list);
}
}
this.attributes = new ArrayList<ArrayList<XMLNode>>(map.values());
}
return this.attributes;
}
private boolean isSpecialAttribute(XMLAttr xmlAttr) {
if (xmlAttr.getQName().getPrefix().equals("xmlns") || xmlAttr.getQName().getLocalPart().equals("xmlns"))//$NON-NLS-1$
return true;
if (reservedXSINames.containsKey(xmlAttr.getQName().getLocalPart())
&& (xmlAttr.getQName().getNamespaceURI().equalsIgnoreCase("http://www.w3.org/2001/XMLSchema-instance") || xmlAttr.getQName().getPrefix()
.equals("xsi")))//$NON-NLS-1$
return true;
return false;
}
public boolean isNillable() {
if (isNillable == null) {
isNillable = new Boolean(false);
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
Node attribute = attributes.item(i);
XMLAttr xmlAttr = new XMLAttr(attribute);
if (xmlAttr.getQName().getLocalPart().equals("nil")//$NON-NLS-1$
&& (xmlAttr.getQName().getNamespaceURI().equalsIgnoreCase("http://www.w3.org/2001/XMLSchema-instance") || xmlAttr.getQName()
.getPrefix().equals("xsi"))) {//$NON-NLS-1$
String value = getElementText(attribute);
isNillable = new Boolean((value != null && value.equals("true")));//$NON-NLS-1$
}
}
}
}
return isNillable.booleanValue();
}
private String getElementText(Node node) {
NodeList children = node.getChildNodes();
StringBuffer value = new StringBuffer();
for (int j = 0; j < children.getLength(); j++) {
Node childnode = children.item(j);
if (childnode.getNodeType() == 3)
value.append(childnode.getNodeValue());
}
return (value.toString().trim().length() == 0) ? null : value.toString().trim();
}
public QName getQName() {
if (qname == null) {
String localpart = node.getNodeName();
String prefix = "";
String namespace = null;
int ndx = localpart.indexOf(':');
if (ndx != -1) {
prefix = localpart.substring(0, ndx);
localpart = localpart.substring(ndx + 1);
}
namespace = lookupNamespace(node, prefix);
qname = new QName(namespace, localpart, prefix);
}
return qname;
}
protected String lookupNamespace(Node node, String prefix) {
String namespace = null;
int type = node.getNodeType();
switch (type) {
case Node.ELEMENT_NODE:
NamedNodeMap map = node.getAttributes();
boolean bFound = false;
if (map != null) {
for (int i = 0; i < map.getLength(); i++) {
Attr attribute = (Attr) map.item(i);
// if a namespace attribute
if (attribute.getNodeName().startsWith("xmlns")) {//$NON-NLS-1$
String nsPrefix = "";
int ndx = attribute.getNodeName().indexOf(':');
if (ndx != -1) {
nsPrefix = attribute.getNodeName().substring(ndx + 1);
}
if (prefix.equals(nsPrefix)) {
namespace = attribute.getNodeValue();
bFound = true;
break;
}
}
}
}
if (!bFound) {
namespace = lookupNamespace(node.getParentNode(), prefix);
}
break;
case Node.DOCUMENT_NODE:
case Node.ENTITY_NODE:
case Node.NOTATION_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
case Node.DOCUMENT_TYPE_NODE:
break;
case Node.ATTRIBUTE_NODE:
namespace = lookupNamespace(((Attr) node).getOwnerElement(), prefix);
break;
default: {
System.out.println("We are here!!, type=" + node.getNodeType());
}
}
return namespace;
}
}
}