| /* |
| * Copyright 2000-2010 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * XSD/DTD Model generator tool |
| * |
| * By Gregory Shrago |
| * 2002 - 2006 |
| */ |
| package org.jetbrains.idea.devkit.dom.generator; |
| |
| import com.wutka.dtd.*; |
| import org.apache.xerces.xni.parser.XMLEntityResolver; |
| |
| import java.io.File; |
| import java.util.*; |
| |
| /** |
| * @author Gregory.Shrago |
| * @author Konstantin Bulenkov |
| */ |
| public class DTDModelLoader implements ModelLoader { |
| private ModelDesc model; |
| |
| public void loadModel(ModelDesc model, Collection<File> schemas, XMLEntityResolver resolver) throws Exception { |
| this.model = model; |
| for (File dtdFile : schemas) { |
| String fileName = dtdFile.getPath(); |
| if (dtdFile.isDirectory() || !fileName.endsWith(".dtd") || fileName.endsWith("datatypes.dtd")) { |
| Util.log("skipping " + fileName); |
| continue; |
| } |
| Util.log("loading " + fileName+".."); |
| String ns = fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1); |
| //loadDTDByXerces(ns, dtdFile, resolver); |
| |
| loadDTDByWutka(ns, dtdFile); |
| |
| } |
| } |
| |
| private NamespaceDesc ensureNamespaceExists(String ns) { |
| if (!model.nsdMap.containsKey(ns)) { |
| Util.log("Adding default ns desc for: " + ns); |
| NamespaceDesc nsd = new NamespaceDesc(ns, model.nsdMap.get("")); |
| model.nsdMap.put(ns, nsd); |
| } |
| return model.nsdMap.get(ns); |
| } |
| |
| |
| private void loadDTDByWutka(String ns, File dtdFile) throws Exception { |
| DTDParser parser = new DTDParser(dtdFile, false); |
| // Parse the DTD and ask the parser to guess the root element |
| DTD dtd = parser.parse(true); |
| checkDTDRootElement(dtd); |
| processDTD(ns, dtd, model.jtMap, model.nsdMap); |
| } |
| |
| |
| private void processDTD(String namespace, DTD dtd, Map<String, TypeDesc> jtMap, Map<String, NamespaceDesc> nsdMap) { |
| final NamespaceDesc nsd = ensureNamespaceExists(namespace); |
| if (nsd.skip) return; |
| final ArrayList<String> resultQNames = new ArrayList<String>(); |
| final DTDElement[] elements = new DTDElement[dtd.elements.size()]; |
| int ptr = 1; |
| |
| final HashSet<DTDElement> visitedElements = new HashSet<DTDElement>(); |
| elements[0] = dtd.rootElement; |
| |
| while (--ptr > -1) { |
| final DTDElement el = elements[ptr]; |
| visitedElements.add(el); |
| final String typeName = model.toJavaTypeName(el.name, namespace); |
| final String typeQName = model.toJavaQualifiedTypeName(namespace, typeName, false); |
| if (resultQNames.contains(typeQName)) { |
| continue; |
| } else { |
| resultQNames.add(typeQName); |
| } |
| final TypeDesc td = new TypeDesc(el.name, namespace, typeName, TypeDesc.TypeEnum.CLASS); |
| boolean duplicates = false; |
| if ((el.content instanceof DTDAny) || (el.content instanceof DTDMixed)) { |
| FieldDesc fd = new FieldDesc(FieldDesc.SIMPLE, "value", "String", null, "null", false); |
| fd.realIndex = td.fdMap.size(); |
| td.fdMap.put(fd.name, fd); |
| } |
| for (Object o : el.attributes.keySet()) { |
| String attrName = (String) o; |
| DTDAttribute attr = (DTDAttribute) el.attributes.get(attrName); |
| if (attr.decl == DTDDecl.FIXED || "ID".equals(attr.type)) { |
| continue; |
| } |
| boolean required = attr.decl == DTDDecl.REQUIRED; |
| FieldDesc fd1 = new FieldDesc(FieldDesc.ATTR, Util.toJavaFieldName(attrName), "String", null, "\"\"", required); |
| fd1.tagName = attrName; |
| fd1.documentation = "Attribute " + attrName + ""; |
| fd1.realIndex = td.fdMap.size(); |
| duplicates = Util.addToNameMap(td.fdMap, fd1, false) || duplicates; |
| } |
| final ArrayList<List<DTDItem>> choiceList = new ArrayList<List<DTDItem>>(); |
| final LinkedList<Entry> plist = new LinkedList<Entry>(); |
| if (el.content instanceof DTDContainer) { |
| //if ((el.content instanceof DTDChoice) || (el.content instanceof DTDSequence)) { |
| plist.add(new Entry(el.content, false, true)); |
| } |
| while (!plist.isEmpty()) { |
| final Entry pentry = plist.removeFirst(); |
| |
| final DTDItem p = pentry.p; |
| |
| if (p instanceof DTDName) { |
| final DTDName n = (DTDName) p; |
| final DTDElement nel = (DTDElement) dtd.elements.get(n.value); |
| final String pName = n.value; |
| final FieldDesc fd1 = new FieldDesc(FieldDesc.STR, Util.toJavaFieldName(pName), pName, null, "null", pentry.required && (n.cardinal == DTDCardinal.ONEMANY || n.cardinal == DTDCardinal.NONE)); |
| fd1.tagName = pName; |
| if (nel != null) { |
| fd1.documentation = parseDTDItemDocumentation(dtd, nel, "Type " + nel.name + " documentation"); |
| } |
| if (nel == null) { |
| fd1.type = model.toJavaTypeName(fd1.tagName, namespace); |
| } else if (nel.content instanceof DTDEmpty || nel.content instanceof DTDAny) { |
| boolean hasAttrFields = false; |
| boolean hasTextContents = nel.content instanceof DTDAny; |
| for (Object o : nel.attributes.values()) { |
| DTDAttribute attr = (DTDAttribute) o; |
| if (attr.decl != DTDDecl.FIXED && !"ID".equals(attr.type)) { |
| hasAttrFields = true; |
| break; |
| } |
| } |
| if (hasAttrFields || hasTextContents) { |
| fd1.clType = FieldDesc.OBJ; |
| fd1.type = model.toJavaTypeName(fd1.tagName, namespace); |
| fd1.contentQualifiedName = model.toJavaQualifiedTypeName(namespace, fd1.name, false); |
| fd1.def = "null"; |
| // next type |
| if (!visitedElements.contains(nel)) { |
| elements[ptr++] = nel; |
| } |
| } else if (hasTextContents) { |
| fd1.clType = FieldDesc.STR; |
| fd1.type = "String"; |
| fd1.def = "null"; |
| } else { |
| fd1.clType = FieldDesc.BOOL; |
| fd1.type = "boolean"; |
| fd1.def = "false"; |
| } |
| } else if (nel.content instanceof DTDContainer) { |
| boolean hasAttrFields = false; |
| boolean hasTextField = false; |
| if ((nel.content instanceof DTDMixed) && ((DTDMixed) nel.content).getItemsVec().size() == 1) { |
| hasTextField = true; |
| for (Object o : nel.attributes.values()) { |
| final DTDAttribute attr = (DTDAttribute) o; |
| if (attr.decl != DTDDecl.FIXED && !"ID".equals(attr.type)) { |
| hasAttrFields = true; |
| break; |
| } |
| } |
| } |
| if (hasTextField && !hasAttrFields) { |
| fd1.clType = FieldDesc.STR; |
| fd1.type = "String"; |
| fd1.def = "null"; |
| } else { |
| fd1.clType = FieldDesc.OBJ; |
| fd1.type = model.toJavaTypeName(fd1.tagName, namespace); |
| fd1.contentQualifiedName = model.toJavaQualifiedTypeName(namespace, fd1.tagName, false); |
| // next type |
| if (!visitedElements.contains(nel)) { |
| elements[ptr++] = nel; |
| } |
| } |
| } else { |
| fd1.type = "ERROR:Name"; |
| } |
| if ((pentry.many || n.cardinal.type >= 2) && fd1.clType != FieldDesc.BOOL) { |
| fd1.elementType = fd1.type; |
| fd1.elementName = fd1.name; |
| fd1.type = "List<" + fd1.elementType + ">"; |
| fd1.name = Util.pluralize(fd1.name); |
| fd1.def = "new ArrayList(0)"; |
| fd1.clType = -fd1.clType; |
| fd1.comment = "array of " + fd1.elementType; |
| } |
| fd1.realIndex = td.fdMap.size(); |
| duplicates = Util.addToNameMap(td.fdMap, fd1, false) || duplicates; |
| } else if (p instanceof DTDContainer) { |
| final DTDContainer cont = (DTDContainer) p; |
| final boolean isChoice = cont instanceof DTDChoice; |
| // 0 - NONE, 1 - OPT, 2 - ZEROMANY, 3 - ONEMANY |
| final boolean required = !isChoice && pentry.required && p.cardinal != DTDCardinal.ZEROMANY && p.cardinal != DTDCardinal.OPTIONAL; |
| final boolean many = p.cardinal == DTDCardinal.ONEMANY || p.cardinal == DTDCardinal.ZEROMANY; |
| List<DTDItem> l = cont.getItemsVec(); |
| if (!many && isChoice) { |
| choiceList.add(l); |
| } |
| for (DTDItem aL : l) { |
| plist.add(new Entry(aL, many, required)); |
| } |
| } else { |
| Util.logerr("unknown item " + p); |
| } |
| } |
| td.duplicates = duplicates; |
| td.documentation = parseDTDItemDocumentation(dtd, el, "Type " + el.name + " documentation"); |
| jtMap.put(model.toJavaQualifiedTypeName(namespace, td.name, false), td); |
| int i = 0; |
| for (FieldDesc fd : td.fdMap.values()) { |
| fd.idx = i++; |
| } |
| for (List<DTDItem> l : choiceList) { |
| ArrayList<DTDItem> clist = new ArrayList<DTDItem>(); |
| LinkedList<DTDItem> elist = new LinkedList<DTDItem>(); |
| for (i = 0; i < l.size(); i++) { |
| elist.add(l.get(i)); |
| } |
| while (!elist.isEmpty()) { |
| DTDItem p = elist.removeFirst(); |
| if (p instanceof DTDContainer) { |
| List<DTDItem> l2 = ((DTDContainer) p).getItemsVec(); |
| for (DTDItem aL2 : l2) { |
| elist.addFirst(aL2); |
| } |
| } else if (p instanceof DTDName) { |
| clist.add(p); |
| } |
| } |
| boolean choiceOpt = true; |
| FieldDesc[] choice = new FieldDesc[clist.size()]; |
| for (i = 0; i < choice.length; i++) { |
| DTDName p = (DTDName) clist.get(i); |
| String s = Util.toJavaFieldName(p.value); |
| FieldDesc fd = td.fdMap.get(s); |
| if (fd == null) { |
| fd = td.fdMap.get(Util.pluralize(s)); |
| if (fd == null) { |
| Util.logerr("uknown choice element: " + s); |
| continue; |
| } |
| } |
| choice[i] = fd; |
| choice[i].choice = choice; |
| if (fd.required) choiceOpt = false; |
| } |
| for (i = 0; i < choice.length; i++) { |
| choice[i].choiceOpt = choiceOpt; |
| } |
| } |
| } |
| List<DTDEntity> entList = dtd.getItemsByType(DTDEntity.class); |
| for (DTDEntity entity : entList) { |
| String value = entity.value; |
| if (!value.startsWith("(") || !value.endsWith(")")) { |
| continue; |
| } |
| String typeName = model.toJavaTypeName(entity.name, namespace); |
| TypeDesc td = new TypeDesc(entity.name, namespace, typeName, TypeDesc.TypeEnum.ENUM); |
| StringTokenizer st = new StringTokenizer(value, "(|)"); |
| while (st.hasMoreTokens()) { |
| final String s = st.nextToken(); |
| td.fdMap.put(s, new FieldDesc(Util.computeEnumConstantName(s, td.name), s)); |
| } |
| td.documentation = parseDTDItemDocumentation(dtd, entity, "Type " + entity.name + " documentation"); |
| jtMap.put(model.toJavaQualifiedTypeName(namespace, td.name, true), td); |
| } |
| } |
| |
| private static String parseDTDItemDocumentation(DTD dtd, DTDOutput obj, String title) { |
| int elementIndex = dtd.items.indexOf(obj); |
| if (elementIndex < 1) return null; |
| Object prev = dtd.items.get(elementIndex - 1); |
| if (!(prev instanceof DTDComment)) return null; |
| DTDComment comment = (DTDComment) prev; |
| return title + "\n" + "<pre>\n" + comment.getText() + "\n</pre>"; |
| } |
| |
| static class Entry { |
| boolean required; |
| boolean many; |
| DTDItem p; |
| |
| Entry(DTDItem p, boolean many, boolean required) { |
| this.required = required; |
| this.many = many; |
| this.p = p; |
| } |
| |
| Entry parent; |
| DTDItem it; |
| Vector choice; |
| int num; |
| |
| Entry(Entry parent, DTDItem it, Vector choice, int num) { |
| this.parent = parent; |
| this.it = it; |
| this.choice = choice; |
| this.num = num; |
| } |
| } |
| |
| |
| private static void checkDTDRootElement(DTD dtd) throws Exception { |
| if (dtd.rootElement == null) { |
| StringBuffer sb = new StringBuffer("Empty root: possible elements: "); |
| HashMap map = new HashMap(dtd.elements); |
| for (Object o : dtd.elements.values()) { |
| DTDElement el = (DTDElement) o; |
| if (el.content instanceof DTDContainer) { |
| for (Object obj : ((DTDContainer) el.content).getItemsVec()) { |
| if (obj instanceof DTDName) { |
| map.remove(((DTDName) obj).value); |
| } |
| } |
| } |
| } |
| if (dtd.rootElement != null) return; |
| sb.append(map.size()).append(map.keySet()); |
| throw new Exception(sb.toString()); |
| } |
| } |
| |
| } |