diff --git a/generator/schema2template/src/main/java/schema2template/grammar/MSVExpressionVisitorDescendantsAsHTMLString.java b/generator/schema2template/src/main/java/schema2template/grammar/MSVExpressionVisitorDescendantsAsHTMLString.java
new file mode 100644
index 0000000000..7c464711ed
--- /dev/null
+++ b/generator/schema2template/src/main/java/schema2template/grammar/MSVExpressionVisitorDescendantsAsHTMLString.java
@@ -0,0 +1,635 @@
+/**
+ * **********************************************************************
+ *
+ *
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
+ *
+ *
Copyright 2009, 2010 Oracle and/or its affiliates. All rights reserved.
+ *
+ *
Use is subject to license terms.
+ *
+ *
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. You can also obtain a copy of the License at
+ * http://odftoolkit.org/docs/license.txt
+ *
+ *
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.
+ *
+ *
**********************************************************************
+ */
+package schema2template.grammar;
+
+import com.sun.msv.datatype.SerializationContext;
+import com.sun.msv.datatype.xsd.*;
+import com.sun.msv.grammar.*;
+import com.sun.msv.grammar.util.ExpressionWalker;
+import java.util.*;
+import org.relaxng.datatype.Datatype;
+import org.relaxng.datatype.ValidationContext;
+
+/**
+ * This visitor visits an Expression and returns an HTML String with its children (or descendants)
+ * relationship as brief pseudo reg-ex for documentation purpose.
+ *
+ *
Usage example: (String) myExpression.visit(myMSVExpressionVisitorDescendantsAsHTMLString,
+ * XMLModel.getHeadsOfIslands(grammar), 1)
+ *
+ *
The HeadOfIslands are necessary to avoid endless loops, where islands are parts of the grammar
+ * that are being reused/referenced multiple times. The final parameter express how many child
+ * element level can be found before stopping. 1 will stop right after writing the name of the first
+ * encountered child element.
+ *
+ *
Please note that you do not use any method of this class directly!
+ */
+public class MSVExpressionVisitorDescendantsAsHTMLString extends ExpressionWalker {
+
+ // ExpressionWalker class traverses expressions in depth-first order.
+ // So this invocation traverses the all reachable expressions from
+ // the top level expression.
+ public MSVExpressionVisitorDescendantsAsHTMLString(
+ StringBuilder builder, Set headsOfIslands) {
+ this(builder, headsOfIslands, 1);
+ }
+
+ public MSVExpressionVisitorDescendantsAsHTMLString(
+ StringBuilder builder, Set headsOfIslands, int maxElementDepth) {
+ this.builder = builder;
+ this.maxElementDepth = maxElementDepth;
+ // an island is a sub(tree) of the grammar being reused and should be written only once to avoid
+ // endless-loops
+ this.headsOfIslands = headsOfIslands;
+ }
+
+ StringBuilder builder = null;
+ int maxElementDepth = 1;
+ int currentElementDepth = 0;
+ final Set headsOfIslands;
+ final Set islandHeadPassed = new HashSet();
+
+ boolean isWithinAttributeValue = false;
+
+ public void onElement(ElementExp exp) {
+ String elementName = evaluateNameClass(exp.getNameClass());
+ builder.append("<" + elementName + " ");
+ // prevent infinite loops by marking earlier multiple used/referenced expression (being
+ // elements/references) as "head of islands"
+ boolean isIslandHead = headsOfIslands.contains(exp);
+ if ((!isIslandHead || (isIslandHead && !islandHeadPassed.contains(exp)))
+ && (maxElementDepth > currentElementDepth)) {
+ currentElementDepth++;
+ if (isIslandHead) {
+ // making sure that an island is only entered once
+ islandHeadPassed.add(exp);
+ }
+ visitUnary(exp.contentModel);
+ } else {
+ builder.append(" ... ");
+ }
+ builder.append(">");
+ }
+
+ public void onEpsilon() {
+ builder.append("EMPTY");
+ }
+
+ public void onNullSet() {
+ builder.append("\nEXPR_notAllowed");
+ }
+
+ public void onAnyString() {
+ builder.append("TEXT");
+ }
+
+ public void onInterleave(InterleaveExp exp) {
+ visitBinExp("interleave", exp, InterleaveExp.class);
+ }
+
+ public void onConcur(ConcurExp exp) {
+ throw new IllegalArgumentException("the grammar includes concur, which is not supported");
+ }
+
+ public void onList(ListExp exp) {
+ builder.append("\nSTART_list");
+ visitUnary(exp.exp);
+ builder.append("\nEND_list");
+ }
+
+ protected void onOptional(Expression exp) {
+ if (exp instanceof OneOrMoreExp) {
+ // (X+)? == X*
+ onZeroOrMore((OneOrMoreExp) exp);
+ return;
+ }
+ if (isWithinAttributeValue) {
+ builder.append("(");
+ } else {
+ builder.append(" (");
+ }
+ visitUnary(exp);
+ // context/state driven layout
+ if (isWithinAttributeValue) {
+ builder.append(")?");
+ } else {
+ builder.append(")?\n\t");
+ }
+ }
+
+ public void onChoice(ChoiceExp exp) {
+ // use optional instead of p
+ if (exp.exp1 == Expression.epsilon) {
+ onOptional(exp.exp2);
+ return;
+ }
+ if (exp.exp2 == Expression.epsilon) {
+ onOptional(exp.exp1);
+ return;
+ }
+
+ Expression[] children = exp.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ children[i].visit(this);
+ if ((i + 1) < children.length) {
+ builder.append(" | ");
+ }
+ }
+ }
+
+ public void onSequence(SequenceExp exp) {
+ builder.append("\n\t(");
+ visitBinExp("group", exp, SequenceExp.class);
+ builder.append(")");
+ }
+
+ public void visitBinExp(String elementName, BinaryExp exp, Class> type) {
+ // since AGM is binarized,
+ // a b c is represented as
+ // a b c
+ // this method print them as a b c
+ // builder.append("\nSTART: " + elementName);
+ Expression[] children = exp.getChildren();
+ for (int i = 0; i < children.length; i++) children[i].visit(this);
+ // builder.append("\nEND: " + elementName);
+ }
+
+ public void onMixed(MixedExp exp) {
+ builder.append("");
+ visitUnary(exp.exp);
+ builder.append("");
+ }
+
+ public void onOneOrMore(OneOrMoreExp exp) {
+ builder.append("(");
+ visitUnary(exp.exp);
+ builder.append(")+ ");
+ }
+
+ protected void onZeroOrMore(OneOrMoreExp exp) {
+ // note that this method is not a member of TREXPatternVisitor.
+ builder.append("(");
+ visitUnary(exp.exp);
+ builder.append(")* ");
+ }
+
+ public void onAttribute(AttributeExp exp) {
+ builder.append(evaluateNameClass(exp.nameClass) + "=\"");
+ isWithinAttributeValue = true;
+ visitUnary(exp.exp);
+ builder.append("\" ");
+ isWithinAttributeValue = false;
+ }
+
+ /** print expression but suppress unnecessary sequence. */
+ public void visitUnary(Expression exp) {
+ // TREX treats p q
+ // as p q
+ // This method tries to exploit this property to
+ // simplify the result.
+ if (exp instanceof SequenceExp) {
+ SequenceExp seq = (SequenceExp) exp;
+ visitUnary(seq.exp1);
+ seq.exp2.visit(this);
+ } else {
+ exp.visit(this);
+ }
+ }
+
+ public void onValue(ValueExp exp) {
+ if (exp.dt instanceof XSDatatypeImpl) {
+ XSDatatypeImpl base = (XSDatatypeImpl) exp.dt;
+
+ final List ns = new ArrayList();
+
+ String lex =
+ base.convertToLexicalValue(
+ exp.value,
+ new SerializationContext() {
+ public String getNamespacePrefix(String namespaceURI) {
+ int cnt = ns.size() / 2;
+ ns.add("xmlns:ns" + cnt);
+ ns.add(namespaceURI);
+ return "ns" + cnt;
+ }
+ });
+
+ if (base != TokenType.theInstance) {
+ // if the type is token, we don't need @type.
+ // builder.append("\ntype: " + base.getName());
+ builder.append(base.getName());
+ }
+ builder.append(lex);
+ // builder.append("\nEND_value");
+ return;
+ }
+
+ throw new UnsupportedOperationException(exp.dt.getClass().getName());
+ }
+
+ public void onData(DataExp exp) {
+ Datatype dt = exp.dt;
+
+ if (dt instanceof XSDatatypeImpl) {
+ XSDatatypeImpl dti = (XSDatatypeImpl) dt;
+ if (dti.getName() != null
+ && (isPrimitiveDatatypeType(dti.getName()) || isDerivedDataType(dti.getName()))) {
+ builder.append(
+ "#"
+ + dti.getName()
+ + "");
+ } else if (exp.name.equals("anyIRI")) {
+ builder.append("#anyIRI");
+ } else if (isPredefinedType(dt)) {
+ // it's a MSV pre-defined type.
+ builder.append("");
+ } else {
+ serializeDataType(dti);
+ }
+ return;
+ }
+
+ // unknown datatype
+ builder.append("\nEXPR_data-unknown: " + "class " + dt.getClass().getName());
+ }
+
+ public void onOther(OtherExp exp) {
+ exp.exp.visit(this); // ignore otherexp
+ }
+
+ public void onRef(ReferenceExp exp) {
+ // prevent infinite loops by marking earlier multiple used/referenced expression (being
+ // elements/references) as "head of islands"
+ boolean isIslandHead = headsOfIslands.contains(exp);
+ if (!isIslandHead || (isIslandHead && !islandHeadPassed.contains(exp))) {
+ if (isIslandHead) {
+ // making sure that an island is only entered once
+ islandHeadPassed.add(exp);
+ }
+ // this expression will not be written as a named pattern.
+ if (isPrimitiveDatatypeType(exp.name) || isDerivedDataType(exp.name)) {
+ builder.append(
+ "#"
+ + exp.name
+ + "");
+ } else if (exp.name.equals("anyIRI")) {
+ builder.append("#anyIRI");
+ } else {
+ exp.exp.visit(this);
+ }
+ if (isIslandHead) {
+ // if the ref was left, we can re-enter it
+ islandHeadPassed.remove(exp);
+ }
+ } else {
+ if (isPrimitiveDatatypeType(exp.name) || isDerivedDataType(exp.name)) {
+ builder.append(
+ "#"
+ + exp.name
+ + "");
+ } else if (exp.name.equals("anyIRI")) {
+ builder.append("#anyIRI");
+ } else {
+ builder.append("<xsd:ref name=\"" + exp.name + "\"/>");
+ }
+ }
+ }
+
+ /** if XML Schema primitive datatypes https://www.w3.org/TR/xmlschema-2/#built-in-datatypes */
+ private static boolean isPrimitiveDatatypeType(String s) {
+ return (s.equals("string")
+ || s.equals("boolean")
+ || s.equals("decimal")
+ || s.equals("float")
+ || s.equals("double")
+ || s.equals("duration")
+ || s.equals("dateTime")
+ || s.equals("time")
+ || s.equals("date")
+ || s.equals("gYearMonth")
+ || s.equals("gYear")
+ || s.equals("gMonthDay")
+ || s.equals("gDay")
+ || s.equals("gMonth")
+ || s.equals("hexBinary")
+ || s.equals("base64Binary")
+ || s.equals("anyURI")
+ || s.equals("QName")
+ || s.equals("NOTATION"));
+ }
+
+ /** if XML Schema primitive datatypes https://www.w3.org/TR/xmlschema-2/#built-in-datatypes */
+ private static boolean isDerivedDataType(String s) {
+ return s.equals("normalizedString")
+ || s.equals("token")
+ || s.equals("language")
+ || s.equals("NMTOKEN")
+ || s.equals("NMTOKENS")
+ || s.equals("Name")
+ || s.equals("NCName")
+ || s.equals("ID")
+ || s.equals("IDREF")
+ || s.equals("IDREFS")
+ || s.equals("ENTITY")
+ || s.equals("ENTITIES")
+ || s.equals("integer")
+ || s.equals("nonPositiveInteger")
+ || s.equals("negativeInteger")
+ || s.equals("long")
+ || s.equals("int")
+ || s.equals("short")
+ || s.equals("byte")
+ || s.equals("nonNegativeInteger")
+ || s.equals("unsignedLong")
+ || s.equals("unsignedInt")
+ || s.equals("unsignedShort")
+ || s.equals("unsignedByte")
+ || s.equals("positiveInteger");
+ }
+
+ private static String evaluateNameClass(NameClass nc) {
+ String elementName = "";
+ if ((nc instanceof SimpleNameClass)
+ || (nc instanceof AnyNameClass)
+ || (nc instanceof ChoiceNameClass)) {
+ String elementPrefix = null;
+ if ((nc instanceof SimpleNameClass)) {
+ elementName = ((SimpleNameClass) nc).localName;
+ // the feature below works only with the namespace-prefix2 branch on MSV
+ elementPrefix = ((SimpleNameClass) nc).prefix;
+ if (elementPrefix != null && !elementPrefix.isEmpty()) {
+ elementName = elementPrefix.concat(":").concat(elementName);
+ }
+ } else if (nc instanceof AnyNameClass) {
+ elementName = "*:*";
+ } else if (nc instanceof ChoiceNameClass) {
+ elementName = "CHOICE_NAME_CLASS";
+ }
+ if (elementName.equals("ExtensionContent")) {
+ elementName = "ExtensionContent";
+ }
+ }
+ return elementName;
+ }
+
+ /**
+ * serializes the given datatype.
+ *
+ * The caller should generate events for <simpleType> element if necessary.
+ */
+ protected void serializeDataType(XSDatatype dt) {
+
+ if (dt instanceof UnionType) {
+ serializeUnionType((UnionType) dt);
+ return;
+ }
+
+ // store names of the applied facets into this set
+ Set appliedFacets = new HashSet();
+
+ // store effective facets (those which are not shadowed by another facet).
+ Vector effectiveFacets = new Vector();
+
+ XSDatatype x = dt;
+ while (x instanceof DataTypeWithFacet || x instanceof FinalComponent) {
+
+ if (x instanceof FinalComponent) {
+ // skip FinalComponent
+ x = x.getBaseType();
+ continue;
+ }
+
+ String facetName = ((DataTypeWithFacet) x).facetName;
+
+ if (facetName.equals(XSDatatypeImpl.FACET_ENUMERATION)) {
+ // if it contains enumeration, then we will serialize this
+ // by using s.
+ serializeEnumeration((XSDatatypeImpl) dt, (EnumerationFacet) x);
+ return;
+ }
+
+ if (facetName.equals(XSDatatypeImpl.FACET_WHITESPACE)) {
+ System.err.println("warning: unsupported whiteSpace facet is ignored");
+ x = x.getBaseType();
+ continue;
+ }
+
+ // find the same facet twice.
+ // pattern is allowed more than once.
+ if (!appliedFacets.contains(facetName)
+ || appliedFacets.equals(XSDatatypeImpl.FACET_PATTERN)) {
+
+ appliedFacets.add(facetName);
+ effectiveFacets.add(x);
+ }
+
+ x = ((DataTypeWithFacet) x).baseType;
+ }
+
+ if (x instanceof ListType) {
+ // the base type is list.
+ serializeListType((XSDatatypeImpl) dt);
+ return;
+ }
+
+ // it cannot be the union type. Union type cannot be derived by
+ // restriction.
+
+ // so this must be one of the pre-defined types.
+ if (!(x instanceof ConcreteType)) throw new Error(x.getClass().getName());
+
+ if (x instanceof com.sun.msv.grammar.relax.EmptyStringType) {
+ // empty string
+ builder.append("\"\"");
+ return;
+ }
+ if (x instanceof com.sun.msv.grammar.relax.NoneType) {
+ // "none" is equal to
+ builder.append("notAllowed");
+ return;
+ }
+
+ builder.append(x.getName());
+
+ // serialize effective facets
+ for (int i = effectiveFacets.size() - 1; i >= 0; i--) {
+ DataTypeWithFacet dtf = (DataTypeWithFacet) effectiveFacets.get(i);
+
+ if (dtf instanceof LengthFacet) {
+ param("length", Long.toString(((LengthFacet) dtf).length));
+ } else if (dtf instanceof MinLengthFacet) {
+ param("minLength", Long.toString(((MinLengthFacet) dtf).minLength));
+ } else if (dtf instanceof MaxLengthFacet) {
+ param("maxLength", Long.toString(((MaxLengthFacet) dtf).maxLength));
+ } else if (dtf instanceof PatternFacet) {
+ String pattern = "";
+ PatternFacet pf = (PatternFacet) dtf;
+ for (int j = 0; j < pf.getRegExps().length; j++) {
+ if (pattern.length() != 0) pattern += "|";
+ pattern += pf.patterns[j];
+ }
+ param("pattern", pattern);
+ } else if (dtf instanceof TotalDigitsFacet) {
+ param("totalDigits", Long.toString(((TotalDigitsFacet) dtf).precision));
+ } else if (dtf instanceof FractionDigitsFacet) {
+ param("fractionDigits", Long.toString(((FractionDigitsFacet) dtf).scale));
+ } else if (dtf instanceof RangeFacet) {
+ param(dtf.facetName, dtf.convertToLexicalValue(((RangeFacet) dtf).limitValue, null));
+ // we don't need to pass SerializationContext because it is only
+ // for QName.
+ } else if (dtf instanceof WhiteSpaceFacet) {; // do nothing.
+ } else
+ // undefined facet type
+ throw new Error();
+ }
+
+ builder.append("]");
+ }
+
+ protected void param(String name, String value) {
+ /*2DO
+ writer.start("param", new String[] { "name", name });
+ writer.characters(value);
+ writer.end("param");
+
+ */
+ }
+
+ /** returns true if the specified type is a pre-defined XSD type without any facet. */
+ protected boolean isPredefinedType(Datatype x) {
+ return !(x instanceof DataTypeWithFacet
+ || x instanceof UnionType
+ || x instanceof ListType
+ || x instanceof FinalComponent
+ || x instanceof com.sun.msv.grammar.relax.EmptyStringType
+ || x instanceof com.sun.msv.grammar.relax.NoneType);
+ }
+
+ /** serializes a union type. this method is called by serializeDataType method. */
+ protected void serializeUnionType(UnionType dt) {
+ // serialize member types.
+ for (int i = 0; i < dt.memberTypes.length; i++) serializeDataType(dt.memberTypes[i]);
+ }
+
+ /** serializes a list type. this method is called by serializeDataType method. */
+ protected void serializeListType(XSDatatypeImpl dt) {
+
+ ListType base = (ListType) dt.getConcreteType();
+
+ if (dt.getFacetObject(XSDatatype.FACET_LENGTH) != null) {
+ // with the length facet.
+ int len = ((LengthFacet) dt.getFacetObject(XSDatatype.FACET_LENGTH)).length;
+ builder.append("");
+ for (int i = 0; i < len; i++) serializeDataType(base.itemType);
+ builder.append("");
+
+ return;
+ }
+
+ if (dt.getFacetObject(XSDatatype.FACET_MAXLENGTH) != null)
+ throw new UnsupportedOperationException(
+ "warning: maxLength facet to list type is not properly converted.");
+
+ MinLengthFacet minLength = (MinLengthFacet) dt.getFacetObject(XSDatatype.FACET_MINLENGTH);
+
+ builder.append("");
+ if (minLength != null) {
+ // list n times
+ for (int i = 0; i < minLength.minLength; i++) serializeDataType(base.itemType);
+ }
+ builder.append("");
+ serializeDataType(base.itemType);
+ builder.append("");
+ builder.append("
");
+ }
+
+ /** serializes a type with enumeration. this method is called by serializeDataType method. */
+ protected void serializeEnumeration(XSDatatypeImpl dt, EnumerationFacet enums) {
+
+ Object[] values = enums.values.toArray();
+
+ if (values.length > 1) builder.append("rng:choice");
+
+ for (int i = 0; i < values.length; i++) {
+ final Vector ns = new Vector();
+
+ String lex =
+ dt.convertToLexicalValue(
+ values[i],
+ new SerializationContext() {
+ public String getNamespacePrefix(String namespaceURI) {
+ int cnt = ns.size() / 2;
+ ns.add("xmlns:ns" + cnt);
+ ns.add(namespaceURI);
+ return "ns" + cnt;
+ }
+ });
+
+ // make sure that the converted lexical value is allowed by this type.
+ // sometimes, facets that are added later rejects some of
+ // enumeration values.
+
+ boolean allowed =
+ dt.isValid(
+ lex,
+ new ValidationContext() {
+
+ public String resolveNamespacePrefix(String prefix) {
+ if (!prefix.startsWith("ns")) return null;
+ int i = Integer.parseInt(prefix.substring(2));
+ return (String) ns.get(i * 2 + 1);
+ }
+
+ public boolean isUnparsedEntity(String name) {
+ return true;
+ }
+
+ public boolean isNotation(String name) {
+ return true;
+ }
+
+ public String getBaseUri() {
+ return null;
+ }
+ });
+
+ ns.add("type");
+ ns.add(dt.getConcreteType().getName());
+
+ if (allowed) {
+ builder.append("");
+ builder.append(lex);
+ builder.append("");
+ }
+ }
+
+ if (values.length > 1) builder.append("");
+ }
+}
diff --git a/generator/schema2template/src/main/java/schema2template/grammar/TinkerPopGraph.java b/generator/schema2template/src/main/java/schema2template/grammar/TinkerPopGraph.java
index f88cfa8143..3bead667a4 100644
--- a/generator/schema2template/src/main/java/schema2template/grammar/TinkerPopGraph.java
+++ b/generator/schema2template/src/main/java/schema2template/grammar/TinkerPopGraph.java
@@ -108,8 +108,8 @@ private static Graph buildGraph(
v = createVertex(g, exp);
}
- // stop building the graph after first element and attribtue children
addGraphProperties(g, v, parentV, exp, parentExp);
+ // stop building the graph after first element and attribute children
if (!(exp instanceof NameClassAndExpression) || parentExp == null) {
List children = (List) exp.visit(CHILD_VISITOR);
Integer newChildNo = 0;
diff --git a/generator/schema2template/src/main/java/schema2template/grammar/XMLModel.java b/generator/schema2template/src/main/java/schema2template/grammar/XMLModel.java
index 346e76e8ce..25d572f014 100644
--- a/generator/schema2template/src/main/java/schema2template/grammar/XMLModel.java
+++ b/generator/schema2template/src/main/java/schema2template/grammar/XMLModel.java
@@ -23,19 +23,16 @@
*/
package schema2template.grammar;
+import com.sun.msv.grammar.ElementExp;
import com.sun.msv.grammar.Expression;
import com.sun.msv.grammar.Grammar;
+import com.sun.msv.grammar.ReferenceExp;
+import com.sun.msv.grammar.util.ExpressionWalker;
import com.sun.msv.reader.trex.ng.RELAXNGReader;
import com.sun.msv.reader.xmlschema.XMLSchemaReader;
import com.sun.msv.writer.relaxng.RELAXNGWriter;
import java.io.File;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.StringTokenizer;
-import java.util.TreeSet;
+import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.SAXParserFactory;
@@ -66,6 +63,8 @@ public class XMLModel {
private String mGrammarID;
private String mGrammarVersion;
+ private Set mIslandHeads;
+
/**
* Constructs new model by the grammar and a label
*
@@ -565,4 +564,85 @@ public static String extractLocalName(String name) {
public static String extractLocalName(PuzzleComponent def) {
return extractLocalName(def.getQName());
}
+
+ /**
+ * Allows to express the relationship of descendants as HTML with a pseudocode aligned to RegEx.
+ * Used for the OdfReference generated by this project.
+ *
+ * @param expr the expression which children should be viewed
+ * @param elementDepth the amount of elements that should be shown. 1 will show only the child
+ * elements not their content (the default and used by ODF Reference).
+ * @return the HTML as string. Usually to be inserted within documentation.
+ */
+ public String getDescendantsAsHTMLString(Expression expr, int elementDepth) {
+ StringBuilder builder = new StringBuilder();
+ if (this.mGrammar == null) {
+ System.err.println("xx");
+ }
+ if (expr == null) {
+ System.err.println("yy");
+ }
+ // ElementExps and ReferenceExps who are referenced more than once.
+ if (mIslandHeads == null) {
+ mIslandHeads = getHeadsOfIslands(this.mGrammar);
+ }
+
+ expr.visit(
+ new MSVExpressionVisitorDescendantsAsHTMLString(builder, mIslandHeads, elementDepth));
+ return builder.toString();
+ }
+
+ /**
+ * Allows to express the relationship of descendants as HTML with a pseudocode aligned to RegEx.
+ * Used for OdfReference (2DO: Add Reference)
+ *
+ * @param expr the expression which children should be viewed (must be of the grammar of this XML
+ * model)
+ * @return the HTML as string. Usually to be inserted within documentation.
+ */
+ public String getChildrenAsHTMLString(Expression expr) {
+ return getDescendantsAsHTMLString(expr, 1);
+ }
+
+ /**
+ * Identifies heads of islands of grammars, which are multiple used and must be identified to
+ * avoid infinite loops when traversing the grammar
+ */
+ public static Set getHeadsOfIslands(Grammar grammar) {
+ // collect all reachable ElementExps and ReferenceExps.
+ final Set nodes = new HashSet();
+ // ElementExps and ReferenceExps who are referenced more than once.
+ final Set heads = new HashSet();
+
+ grammar
+ .getTopLevel()
+ .visit(
+ new ExpressionWalker() {
+ // ExpressionWalker class traverses expressions in depth-first order.
+ // So this invokation traverses the all reachable expressions from
+ // the top level expression.
+
+ // Whenever visiting elements and RefExps, they are memorized
+ // to identify head of islands.
+ public void onElement(ElementExp exp) {
+ if (nodes.contains(exp)) {
+ heads.add(exp);
+ return; // prevent infinite recursion.
+ }
+ nodes.add(exp);
+ super.onElement(exp);
+ }
+
+ public void onRef(ReferenceExp exp) {
+ if (nodes.contains(exp)) {
+ heads.add(exp);
+ return; // prevent infinite recursion.
+ }
+ nodes.add(exp);
+ super.onRef(exp);
+ }
+ });
+
+ return heads;
+ }
}
diff --git a/generator/schema2template/src/test/java/schema2template/grammar/GenerationOdfReferenceTest.java b/generator/schema2template/src/test/java/schema2template/grammar/GenerationOdfReferenceTest.java
index 3babe4319d..65626d024a 100644
--- a/generator/schema2template/src/test/java/schema2template/grammar/GenerationOdfReferenceTest.java
+++ b/generator/schema2template/src/test/java/schema2template/grammar/GenerationOdfReferenceTest.java
@@ -44,7 +44,7 @@ public class GenerationOdfReferenceTest {
/** Test: It should be able to generate all examples without a failure. */
@Test
- public void testAllExampleGenerations() {
+ public void testAllExampleGenerations() throws Exception {
ArrayList generations = new ArrayList<>();
for (OdfSpecificationPart specPart : OdfSpecificationPart.values()) {
@@ -76,7 +76,7 @@ public void testAllExampleGenerations() {
SchemaToTemplate.run(generations);
} catch (Exception e) {
Assert.fail("Exception during test run: " + e.toString());
- throw new RuntimeException(e);
+ throw new Exception(e);
}
// Changing order of multiple puzzlepieces makes file comparison unuseable
diff --git a/generator/schema2template/src/test/java/schema2template/grammar/MSVDescendantsToHTMLTest.java b/generator/schema2template/src/test/java/schema2template/grammar/MSVDescendantsToHTMLTest.java
new file mode 100644
index 0000000000..41a21c1fd8
--- /dev/null
+++ b/generator/schema2template/src/test/java/schema2template/grammar/MSVDescendantsToHTMLTest.java
@@ -0,0 +1,132 @@
+/**
+ * **********************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
+ *
+ *
Copyright 2009, 2010 Oracle and/or its affiliates. All rights reserved.
+ *
+ *
Use is subject to license terms.
+ *
+ *
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. You can also obtain a copy of the License at
+ * http://odftoolkit.org/docs/license.txt
+ *
+ *
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.
+ *
+ *
*********************************************************************
+ */
+package schema2template.grammar;
+
+import com.sun.msv.grammar.*;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * MSVDescendantsToHTMLTest is able to dump for each XML element and XML attribute its content as a
+ * string expressing choice, sequence, etc. with regular expression syntax.
+ */
+public class MSVDescendantsToHTMLTest {
+
+ private static final Logger LOG = Logger.getLogger(MSVDescendantsToHTMLTest.class.getName());
+
+ private static final String CHILD_DESC_DIRECTORY = "childDesc";
+
+ @Before
+ public void intialize() {
+ new File(ConstantsBuildEnv.TARGET_BASE_DIR + CHILD_DESC_DIRECTORY).mkdirs();
+ }
+
+ /**
+ * This test iterates over all ODF grammars loads them into the MSV Validator and dumps the
+ * run-time model (ExpressionTree) into a file.
+ */
+ @Test
+ public void testMSVExpressionTree() {
+ try {
+ for (ConstantsOdf.OdfSpecificationPart specPart :
+ ConstantsOdf.OdfSpecificationPart.values()) {
+ if (specPart.grammarID.equals(ConstantsOdf.GrammarID.ODF_SCHEMA.ID)
+ && specPart.grammarVersion.equals(
+ ConstantsOdf.OdfSpecificationPart.ODF_1_3_SCHEMA.grammarVersion)) {
+ LOG.info(
+ "\n\nNew ODF grammar runtime serialization (MSV dump) for regression test:"
+ + "\n\tgrammarVersion "
+ + specPart.grammarVersion
+ + "\n\tgrammarID: "
+ + specPart.grammarID
+ + "\n\tgrammarPath: "
+ + specPart.grammarPath
+ + "\n\ttargetDirPath: "
+ + ConstantsBuildEnv.TARGET_BASE_DIR
+ + CHILD_DESC_DIRECTORY);
+ Grammar xmlGrammar = XMLModel.loadSchema(specPart.grammarPath);
+ String odfDump = dumpChildRelation(xmlGrammar);
+
+ String grammarLabel = specPart.grammarID + "-" + specPart.grammarVersion;
+
+ String targetChildDescFile =
+ ConstantsBuildEnv.TARGET_BASE_DIR
+ + CHILD_DESC_DIRECTORY
+ + File.separator
+ + grammarLabel
+ + "-msvRegEx.txt";
+ LOG.log(
+ Level.INFO,
+ "Writing MSV Child descriptions for " + grammarLabel + " + into file: {0}",
+ targetChildDescFile);
+ try (PrintWriter out = new PrintWriter(new FileWriter(targetChildDescFile))) {
+ out.print(odfDump);
+ }
+ }
+ /*DirectoryCompare.compareDirectories(
+ ConstantsBuildEnv.TARGET_BASE_DIR + CHILD_DESC_DIRECTORY,
+ ConstantsBuildEnv.REFERENCE_BASE_DIR + CHILD_DESC_DIRECTORY);*/
+ }
+ } catch (Exception ex) {
+ Logger.getLogger(MSVDescendantsToHTMLTest.class.getName()).log(Level.SEVERE, null, ex);
+ Assert.fail(ex.toString());
+ }
+ }
+ /**
+ * Iterates the MSVExpressionTree between parent and children and dumps their relation into a
+ * string
+ *
+ * @return the MSVExpressionTree serialized into a String
+ */
+ private static String dumpChildRelation(Grammar xmlGrammar) throws Exception {
+ MSVExpressionIterator iterator = new MSVExpressionIterator(xmlGrammar.getTopLevel());
+ StringBuilder builder = new StringBuilder();
+ while (iterator.hasNext()) {
+ Expression expr = iterator.next();
+ if (expr instanceof NameClassAndExpression) {
+ List names =
+ (List)
+ ((NameClassAndExpression) expr).getNameClass().visit(new MSVNameClassVisitorList());
+ // 2DO: ONGOING INITIAL DEBUG WITH ONE ELEMENT!!
+ // if (names.get(0).equals("form:property")) {
+
+ // see
+ // http://docs.oasis-open.org/office/OpenDocument/v1.3/os/schemas/OpenDocument-v1.3-schema-rng.html#table-table
+ if (names.get(0).equals("table:table")) {
+ expr.visit(
+ new MSVExpressionVisitorDescendantsAsHTMLString(
+ builder, XMLModel.getHeadsOfIslands(xmlGrammar)));
+ }
+ }
+ }
+ return builder.toString();
+ }
+}
diff --git a/generator/schema2template/src/test/java/schema2template/grammar/MSVRunTimeDumpTest.java b/generator/schema2template/src/test/java/schema2template/grammar/MSVRunTimeDumpTest.java
index 7725f07f88..3509689a11 100644
--- a/generator/schema2template/src/test/java/schema2template/grammar/MSVRunTimeDumpTest.java
+++ b/generator/schema2template/src/test/java/schema2template/grammar/MSVRunTimeDumpTest.java
@@ -103,7 +103,7 @@ public void testMSVExpressionTree() {
*
* @return the MSVExpressionTree serialized into a String
*/
- public static String dumpMSVExpressionTree(Expression rootExpression) throws Exception {
+ private static String dumpMSVExpressionTree(Expression rootExpression) throws Exception {
MSVExpressionIterator iterator = new MSVExpressionIterator(rootExpression);
StringBuilder builder = new StringBuilder();
while (iterator.hasNext()) {
diff --git a/generator/schema2template/src/test/resources/test-input/odf/generation/odf-reference/odf-reference-template.vm b/generator/schema2template/src/test/resources/test-input/odf/generation/odf-reference/odf-reference-template.vm
index 9c7391ed69..4cad23b22b 100644
--- a/generator/schema2template/src/test/resources/test-input/odf/generation/odf-reference/odf-reference-template.vm
+++ b/generator/schema2template/src/test/resources/test-input/odf/generation/odf-reference/odf-reference-template.vm
@@ -182,6 +182,13 @@ For example, manifest:key-de
#end
+#set ($elementExp = $element.getExpression())
+#if ($elementExp)
+
+ | Child Relations |
+ ${xmlModel.getChildrenAsHTMLString($elementExp)} |
+
+#end
#end
@@ -244,7 +251,7 @@ For example, manifest:key-de
Datatypes |
#foreach ($datatype in ${attribute.getDatatypes()})
- ${datatype}
+ ${datatype}
#end
|
@@ -252,10 +259,14 @@ For example, manifest:key-de
Values |
#foreach ($value in ${attribute.getValues()})
- "${value}"
+ "${value}"
#end
|
+
+ | RNG Relations |
+ ${xmlModel.getChildrenAsHTMLString($attribute.getExpression())} |
+
#end
diff --git a/generator/schema2template/src/test/resources/test-reference/odf/generation/odf-reference/odf-package-digital-signature-1.2/OdfReference.html b/generator/schema2template/src/test/resources/test-reference/odf/generation/odf-reference/odf-package-digital-signature-1.2/OdfReference.html
index 3a03ad56f6..ca5458fbcc 100644
--- a/generator/schema2template/src/test/resources/test-reference/odf/generation/odf-reference/odf-package-digital-signature-1.2/OdfReference.html
+++ b/generator/schema2template/src/test/resources/test-reference/odf/generation/odf-reference/odf-package-digital-signature-1.2/OdfReference.html
@@ -50,6 +50,10 @@
[any org.w3c.dom.Element]
+
+ | Child Relations |
+ <ds:Signature (*:*="TEXT" | TEXT | <*:* ... >)+ > |
+
@@ -72,6 +76,10 @@ dsig:document-signatures Elemen
<ds:Signature>*
+
+ | Child Relations |
+ <dsig:document-signatures dsig:version="1.2" (<ds:Signature ... >)+ > |
+
@@ -90,9 +98,13 @@
| Values |
- "1.2"
+ "1.2"
|
+
+ | RNG Relations |
+ dsig:version="1.2" |
+