/*************************************************************************** copyright : (C) 2003 Brian Thomas brian.thomas@gsfc.nasa.gov (C) 2004-2006 Umbrello UML Modeller Authors ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "xmlschemawriter.h" #include #include #include #include #include #include #include "../umldoc.h" #include "../classifier.h" #include "../operation.h" #include "../umlnamespace.h" // Constructor XMLSchemaWriter::XMLSchemaWriter() { packageNamespaceTag = "tns"; packageNamespaceURI = "http://foo.example.com/"; schemaNamespaceTag = "xs"; schemaNamespaceURI = "http://www.w3.org/2001/XMLSchema"; } // form of..."the Destructor"!! XMLSchemaWriter::~XMLSchemaWriter() { } /** * returns "XMLSchema" */ Uml::Programming_Language XMLSchemaWriter::getLanguage() { return Uml::pl_XMLSchema; } // main method for invoking.. void XMLSchemaWriter::writeClass(UMLClassifier *c) { if (!c) { kDebug()<<"Cannot write class of NULL classifier!\n"; return; } // find an appropriate name for our file TQString fileName = findFileName(c,".xsd"); if (fileName.isEmpty()) { emit codeGenerated(c, false); return; } // check that we may open that file for writing TQFile file; if ( !openFile(file, fileName) ) { emit codeGenerated(c, false); return; } TQTextStream XMLschema(&file); // set package namespace tag appropriately if(!c->getPackage().isEmpty()) packageNamespaceTag = c->getPackage(); // START WRITING // 0. FIRST THING: open the xml processing instruction. This MUST be // the first thing in the file XMLschema<<""<"<getPackage() != c->getPackage()) XMLschema<<"import "<getPackage()<<"."<getName())<<";"<"<"<getDoc().isEmpty()) writeComment(c->getDoc(),XMLschema); if(c->getAbstract() || c->isInterface() ) writeAbstractClassifier(c,XMLschema); // if its an interface or abstract class else writeConcreteClassifier(c,XMLschema); } UMLAttributeList XMLSchemaWriter::findAttributes (UMLClassifier *c) { // sort attributes by Scope UMLAttributeList attribs; attribs.setAutoDelete(false); if (!c->isInterface()) { UMLAttributeList atl = c->getAttributeList(); for(UMLAttribute *at=atl.first(); at ; at=atl.next()) { switch(at->getVisibility()) { case Uml::Visibility::Public: case Uml::Visibility::Protected: attribs.append(at); break; case Uml::Visibility::Private: // DO NOTHING! no way to print in the schema break; default: break; } } } return attribs; } // We have to do 2 things with abstract classifiers (e.g. abstract classes and interfaces) // which is to: // 1) declare it as a complexType so it may be inherited (I can see an option here: to NOT write // this complexType declaration IF the classifier itself isnt inherited by or is inheriting // from anything because no other element will use this complexType). // 2) Create a group so that elements, which obey the abstract class /interface may be placed in // aggregation with other elements (again, and option here to NOT write the group if no other // element use the interface in element aggregation) // void XMLSchemaWriter::writeAbstractClassifier (UMLClassifier *c, TQTextStream &XMLschema) { // preparations UMLClassifierList subclasses = c->findSubClassConcepts(); // list of what inherits from us UMLClassifierList superclasses = c->findSuperClassConcepts(); // list of what we inherit from // write the main declaration writeConcreteClassifier (c, XMLschema); writeGroupClassifierDecl (c, subclasses, XMLschema); markAsWritten(c); // now go back and make sure all sub-classing nodes are declared if(subclasses.count() > 0) { TQString elementName = getElementName(c); UMLAttributeList attribs = findAttributes(c); TQStringList attribGroups = findAttributeGroups(c); writeAttributeGroupDecl(elementName, attribs, XMLschema); // now write out inheriting classes, as needed for(UMLClassifier * classifier = subclasses.first(); classifier; classifier = subclasses.next()) writeClassifier(classifier, XMLschema); } // write out any superclasses as needed for(UMLClassifier *classifier = superclasses.first(); classifier; classifier = superclasses.next()) writeClassifier(classifier, XMLschema); } void XMLSchemaWriter::writeGroupClassifierDecl (UMLClassifier *c, UMLClassifierList subclasses, TQTextStream &XMLschema) { // name of class, subclassing classifiers TQString elementTypeName = getElementGroupTypeName(c); // start Writing node but only if it has subclasses? Nah..right now put in empty group XMLschema<"<"<"<"< 0; bool hasAttributes = attribs.count() > 0 || attribGroups.count() > 0; // START WRITING // start body of element TQString elementTypeName = getElementTypeName(c); XMLschema<"<"<"<"<getID(), XMLschema); didFirstOne = writeAssociationDecls(aggregations, false, didFirstOne, c->getID(), XMLschema); didFirstOne = writeAssociationDecls(compositions, false, didFirstOne, c->getID(), XMLschema); if (didFirstOne) { m_indentLevel--; XMLschema<"<"<"<"<"<"<findSuperClassConcepts(); // list of what inherits from us UMLClassifierList subclasses = c->findSubClassConcepts(); // list of what we inherit from UMLAssociationList aggregations = c->getAggregations(); UMLAssociationList compositions = c->getCompositions(); // BAD! only way to get "general" associations. UMLAssociationList associations = c->getSpecificAssocs(Uml::at_Association); // write the main declaration writeComplexTypeClassifierDecl(c, associations, aggregations, compositions, superclasses, XMLschema); markAsWritten(c); // Now write out any child def's writeChildObjsInAssociation(c, associations, XMLschema); writeChildObjsInAssociation(c, aggregations, XMLschema); writeChildObjsInAssociation(c, compositions, XMLschema); // write out any superclasses as needed for(UMLClassifier *classifier = superclasses.first(); classifier; classifier = superclasses.next()) writeClassifier(classifier, XMLschema); // write out any subclasses as needed for(UMLClassifier *classifier = subclasses.first(); classifier; classifier = subclasses.next()) writeClassifier(classifier, XMLschema); } // these exist for abstract classes only (which become xs:group nodes) TQStringList XMLSchemaWriter::findAttributeGroups (UMLClassifier *c) { // we need to look for any class we inherit from. IF these // have attributes, then we need to notice TQStringList list; UMLClassifierList superclasses = c->findSuperClassConcepts(); // list of what inherits from us for(UMLClassifier *classifier = superclasses.first(); classifier; classifier = superclasses.next()) { if(classifier->getAbstract()) { // only classes have attributes.. if (!classifier->isInterface()) { UMLAttributeList attribs = c->getAttributeList(); if (attribs.count() > 0) list.append(getElementName(classifier)+"AttribGroupType"); } } } return list; } bool XMLSchemaWriter::determineIfHasChildNodes( UMLClassifier *c) { UMLObjectList aggList = findChildObjsInAssociations (c, c->getAggregations()); UMLObjectList compList = findChildObjsInAssociations (c, c->getCompositions()); UMLAssociationList associations = c->getSpecificAssocs(Uml::at_Association); // BAD! only way to get "general" associations. UMLObjectList assocList = findChildObjsInAssociations (c, associations); return aggList.count() > 0 || compList.count() > 0 || assocList.count() > 0; } void XMLSchemaWriter::writeChildObjsInAssociation (UMLClassifier *c, UMLAssociationList assoc, TQTextStream &XMLschema) { UMLObjectList list = findChildObjsInAssociations (c, assoc); for(UMLObject * obj = list.first(); obj; obj = list.next()) { UMLClassifier * thisClassifier = dynamic_cast(obj); if(thisClassifier) writeClassifier(thisClassifier, XMLschema); } } bool XMLSchemaWriter::hasBeenWritten(UMLClassifier *c) { if (writtenClassifiers.contains(c)) return true; else return false; } void XMLSchemaWriter::markAsWritten(UMLClassifier *c) { writtenClassifiers.append(c); } void XMLSchemaWriter::writeAttributeDecls(UMLAttributeList &attribs, TQTextStream &XMLschema ) { UMLAttribute *at; for(at=attribs.first(); at; at=attribs.next()) { writeAttributeDecl(at,XMLschema); } } void XMLSchemaWriter::writeAttributeDecl(UMLAttribute *attrib, TQTextStream &XMLschema ) { TQString documentation = attrib->getDoc(); TQString typeName = fixTypeName(attrib->getTypeName()); bool isStatic = attrib->getStatic(); TQString initialValue = fixInitialStringDeclValue(attrib->getInitialValue(), typeName); if(!documentation.isEmpty()) writeComment(documentation, XMLschema); XMLschema<getName())<<"\"" <<" type=\""<"< 0) { // write a little documentation writeComment("attributes for element "+elementName,XMLschema); // open attribute group XMLschema<"<"<"<"<getObjectId(Uml::A) == id && a->getVisibility(Uml::B) != Uml::Visibility::Private) printRoleB = true; if (a->getObjectId(Uml::B) == id && a->getVisibility(Uml::A) != Uml::Visibility::Private) printRoleA = true; // First: we insert documentaion for association IF it has either role // AND some documentation (!) if ((printRoleA || printRoleB) && !(a->getDoc().isEmpty())) writeComment(a->getDoc(), XMLschema); // opening for sequence if(!didFirstOne && (printRoleA || printRoleB)) { didFirstOne = true; XMLschema<"<(a->getObjectB()); if (classifierB) { // ONLY write out IF there is a rolename given // otherwise its not meant to be declared if (!a->getRoleNameB().isEmpty() || noRoleNameOK) writeAssociationRoleDecl(classifierB, a->getMultiB(), XMLschema); } } */ // print RoleA decl if (printRoleA) { UMLClassifier *classifierA = dynamic_cast(a->getObject(Uml::A)); if (classifierA) { // ONLY write out IF there is a rolename given // otherwise its not meant to be declared if (!a->getRoleName(Uml::A).isEmpty() || noRoleNameOK ) writeAssociationRoleDecl(classifierA, a->getMulti(Uml::A), XMLschema); } } } } return didFirstOne; } UMLObjectList XMLSchemaWriter::findChildObjsInAssociations (UMLClassifier *c, UMLAssociationList associations) { Uml::IDType id = c->getID(); UMLObjectList list; for(UMLAssociation *a = associations.first(); a; a = associations.next()) { if (a->getObjectId(Uml::A) == id && a->getVisibility(Uml::B) != Uml::Visibility::Private && !a->getRoleName(Uml::B).isEmpty() ) list.append(a->getObject(Uml::B)); if (a->getObjectId(Uml::B) == id && a->getVisibility(Uml::A) != Uml::Visibility::Private && !a->getRoleName(Uml::A).isEmpty() ) list.append(a->getObject(Uml::A)); } return list; } void XMLSchemaWriter::writeAssociationRoleDecl( UMLClassifier *c, const TQString &multi, TQTextStream &XMLschema) { bool isAbstract = c->getAbstract(); bool isInterface = c->isInterface(); TQString elementName = getElementName(c); TQString doc = c->getDoc(); if (!doc.isEmpty()) writeComment(doc, XMLschema); // Min/Max Occurs is based on whether it is this a single element // or a List (maxoccurs>1). One day this will be done correctly with special // multiplicity object that we don't have to figure out what it means via regex. TQString minOccurs = "0"; TQString maxOccurs = "unbounded"; if (multi.isEmpty()) { // in this case, association will only specify ONE element can exist // as a child minOccurs = "1"; maxOccurs = "1"; } else { TQStringList values = TQStringList::split( TQRegExp("[^\\d{1,}|\\*]"), multi); // could use some improvement here.. for sequences like "0..1,3..5,10" we // don't capture the whole "richness" of the multi. Instead we translate it // now to minOccurs="0" maxOccurs="10" if (values.count() > 0) { // populate both with the actual value as long as our value isnt an asterix // In that case, use special value (from above) if(values[0].contains(TQRegExp("\\d{1,}"))) minOccurs = values[0]; // use first number in sequence if(values[values.count()-1].contains(TQRegExp("\\d{1,}"))) maxOccurs = values[values.count()-1]; // use only last number in sequence } } // Now declare the class in the association as an element or group. // // In a semi-arbitrary way, we have decided to make abstract classes into // "groups" and concrete classes into "complexTypes". // // This is because about the only thing you can do with an abstract // class (err. node) is inherit from it with a "concrete" element. Therefore, // in this manner, we need a group node for abstract classes to lay out the child // element choices so that the child, concrete class may be plugged into whatever spot // it parent could go. The tradeoff is that "group" nodes may not be extended, so the // choices that you lay out here are it (e.g. no more nodes may inherit" from this group) // // The flipside for concrete classes is that we want to use them as elements in our document. // Unfortunately, about all you can do with complexTypes in terms of inheritance, is to // use these as the basis for a new node type. This is NOT full inheritence because the new // class (err. element node) wont be able to go into the document where it parent went without // you heavily editing the schema. // // Therefore, IF a group is abstract, but has no inheriting sub-classes, there are no choices, and its nigh // on pointless to declare it as a group, in this special case, abstract classes get declared // as complexTypes. // // Of course, in OO methodology, you should be able to inherit from // any class, but schema just don't allow use to have full inheritence using either groups // or complexTypes. Thus we have taken a middle rode. If someone wants to key me into a // better way to represent this, I'd be happy to listen. =b.t. // // UPDATE: partial solution to the above: as of 13-Mar-2003 we now write BOTH a complexType // AND a group declaration for interfaces AND classes which are inherited from. // if ((isAbstract || isInterface ) && c->findSubClassConcepts().count() > 0) XMLschema<"<getName()); } TQString XMLSchemaWriter::getElementTypeName(UMLClassifier *c) { TQString elementName = getElementName(c); return elementName + "ComplexType"; } TQString XMLSchemaWriter::getElementGroupTypeName(UMLClassifier *c) { TQString elementName = getElementName(c); return elementName + "GroupType"; } TQString XMLSchemaWriter::makePackageTag (TQString tagName) { tagName.prepend( packageNamespaceTag + ':'); return tagName; } TQString XMLSchemaWriter::makeSchemaTag (TQString tagName) { tagName.prepend( schemaNamespaceTag + ':'); return tagName; } const TQStringList XMLSchemaWriter::reservedKeywords() const { static TQStringList keywords; if (keywords.isEmpty()) { keywords << "ATTLIST" << "CDATA" << "DOCTYPE" << "ELEMENT" << "ENTITIES" << "ENTITY" << "ID" << "IDREF" << "IDREFS" << "NMTOKEN" << "NMTOKENS" << "NOTATION" << "PUBLIC" << "SHORTREF" << "SYSTEM" << "USEMAP"; } return keywords; } #include "xmlschemawriter.moc"