/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. * *************************************************************/ package org.apache.openoffice.ooxml.schema.generator; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.apache.openoffice.ooxml.schema.automaton.FiniteAutomaton; import org.apache.openoffice.ooxml.schema.automaton.FiniteAutomatonContainer; import org.apache.openoffice.ooxml.schema.automaton.SkipData; import org.apache.openoffice.ooxml.schema.automaton.State; import org.apache.openoffice.ooxml.schema.automaton.Transition; import org.apache.openoffice.ooxml.schema.model.attribute.Attribute; import org.apache.openoffice.ooxml.schema.model.attribute.AttributeBase.Use; import org.apache.openoffice.ooxml.schema.model.base.INode; import org.apache.openoffice.ooxml.schema.model.base.QualifiedName; import org.apache.openoffice.ooxml.schema.model.schema.NamespaceMap; import org.apache.openoffice.ooxml.schema.parser.FormDefault; import org.apache.openoffice.ooxml.schema.simple.BlobNode; import org.apache.openoffice.ooxml.schema.simple.DateTimeNode; import org.apache.openoffice.ooxml.schema.simple.ISimpleTypeNode; import org.apache.openoffice.ooxml.schema.simple.ISimpleTypeNodeVisitor; import org.apache.openoffice.ooxml.schema.simple.NumberNode; import org.apache.openoffice.ooxml.schema.simple.SimpleTypeContainer; import org.apache.openoffice.ooxml.schema.simple.SimpleTypeDescriptor; import org.apache.openoffice.ooxml.schema.simple.StringNode; import org.apache.openoffice.ooxml.schema.simple.UnionNode; public class ParserTablesGenerator { public ParserTablesGenerator ( final FiniteAutomatonContainer aAutomatons, final NamespaceMap aNamespaces, final SimpleTypeContainer aSimpleTypes, final Map aAttributeValueToIdMap) { maAutomatons = aAutomatons; maSimpleTypes = aSimpleTypes; maNamespaces = aNamespaces; maNameToIdMap = new TreeMap<>(); maPrefixToIdMap = new HashMap<>(); maTypeNameToIdMap = new TreeMap<>(); maAttributeValueToIdMap = aAttributeValueToIdMap; } public void Generate ( final File aParseTableFile) { final long nStartTime = System.currentTimeMillis(); SetupNameList(); AssignNameIds(); try { final PrintStream aOut = new PrintStream(new FileOutputStream(aParseTableFile)); WriteNamespaceList(aOut); WriteNameList(aOut); WriteGlobalStartEndStates(aOut); WriteAutomatonList(aOut); WriteSimpleTypes(aOut); WriteAttributeValues(aOut); aOut.close(); } catch (final FileNotFoundException aException) { aException.printStackTrace(); } final long nEndTime = System.currentTimeMillis(); System.out.printf("wrote parse tables to %s in %fs\n", aParseTableFile.toString(), (nEndTime-nStartTime)/1000.0); } private void SetupNameList () { final Set aNames = new TreeSet<>(); // Add the element names. for (final FiniteAutomaton aAutomaton : maAutomatons.GetAutomatons()) for (final Transition aTransition : aAutomaton.GetTransitions()) { if (aTransition.GetElementName() == null) throw new RuntimeException(); aNames.add(aTransition.GetElementName().GetLocalPart()); } // Add the attribute names. for (final FiniteAutomaton aAutomaton : maAutomatons.GetAutomatons()) for (final Attribute aAttribute : aAutomaton.GetAttributes()) aNames.add(aAttribute.GetName().GetLocalPart()); // Create unique ids for the names. int nIndex = 1; maNameToIdMap.clear(); for (final String sName : aNames) maNameToIdMap.put(sName, nIndex++); // Create unique ids for namespace prefixes. nIndex = 1; maPrefixToIdMap.clear(); for (final Entry aEntry : maNamespaces) { maPrefixToIdMap.put(aEntry.getValue(), nIndex++); } } /** During the largest part of the parsing process, states and elements are * identified not via their name but via a unique id. * That allows a fast lookup. */ private void AssignNameIds () { maTypeNameToIdMap.clear(); int nIndex = 0; // Process state names. final Set aSortedTypeNames = new TreeSet<>(); for (final State aState : maAutomatons.GetStates()) aSortedTypeNames.add(aState.GetQualifiedName()); for (final Entry aSimpleType : maSimpleTypes.GetSimpleTypes()) aSortedTypeNames.add(aSimpleType.getValue().GetName()); for (final QualifiedName aName : aSortedTypeNames) maTypeNameToIdMap.put(aName.GetStateName(), nIndex++); } private void WriteNamespaceList (final PrintStream aOut) { aOut.printf("# namespaces\n"); for (final Entry aEntry : maNamespaces) { aOut.printf("namespace %-8s %2d %s\n", aEntry.getValue(), maPrefixToIdMap.get(aEntry.getValue()), aEntry.getKey()); } } private void WriteGlobalStartEndStates (final PrintStream aOut) { aOut.printf("\n# start and end states\n"); final FiniteAutomaton aAutomaton = maAutomatons.GetTopLevelAutomaton(); final State aStartState = aAutomaton.GetStartState(); aOut.printf("start-state %4d %s\n", maTypeNameToIdMap.get(aStartState.GetFullname()), aStartState.GetFullname()); for (final State aAcceptingState : aAutomaton.GetAcceptingStates()) aOut.printf("end-state %4d %s\n", maTypeNameToIdMap.get(aAcceptingState.GetFullname()), aAcceptingState.GetFullname()); } private void WriteNameList (final PrintStream aOut) { aOut.printf("\n# %d names\n", maNameToIdMap.size()); for (final Entry aEntry : maNameToIdMap.entrySet()) { aOut.printf("name %4d %s\n", aEntry.getValue(), aEntry.getKey()); } aOut.printf("\n# %s states\n", maTypeNameToIdMap.size()); for (final Entry aEntry : maTypeNameToIdMap.entrySet()) { aOut.printf("state-name %4d %s\n", aEntry.getValue(), aEntry.getKey()); } } private void WriteAutomatonList (final PrintStream aOut) { for (final FiniteAutomaton aAutomaton : maAutomatons.GetAutomatons()) { aOut.printf("# %s at %s\n", aAutomaton.GetTypeName(), aAutomaton.GetLocation()); final State aStartState = aAutomaton.GetStartState(); final int nStartStateId = maTypeNameToIdMap.get(aStartState.GetFullname()); // Write start state. aOut.printf("start-state %d %s\n", nStartStateId, aStartState); // Write accepting states. for (final State aState : aAutomaton.GetAcceptingStates()) { aOut.printf("accepting-state %d %s\n", maTypeNameToIdMap.get(aState.GetFullname()), aState.GetFullname()); } // Write text type. final INode aTextType = aStartState.GetTextType(); if (aTextType != null) { switch(aTextType.GetNodeType()) { case BuiltIn: aOut.printf("text-type %d %d %s\n", nStartStateId, maTypeNameToIdMap.get(aTextType.GetName().GetStateName()), aTextType.GetName().GetStateName()); break; case SimpleType: aOut.printf("text-type %d %d %s\n", nStartStateId, maTypeNameToIdMap.get(aTextType.GetName().GetStateName()), aTextType.GetName().GetStateName()); break; default: throw new RuntimeException(); } } WriteAttributes( aOut, aStartState, aAutomaton.GetAttributes()); // Write transitions. for (final Transition aTransition : aAutomaton.GetTransitions()) { final Integer nId = maTypeNameToIdMap.get(aTransition.GetElementTypeName()); aOut.printf("transition %4d %4d %2d %4d %4d %s %s %s %s\n", maTypeNameToIdMap.get(aTransition.GetStartState().GetFullname()), maTypeNameToIdMap.get(aTransition.GetEndState().GetFullname()), maPrefixToIdMap.get(aTransition.GetElementName().GetNamespacePrefix()), maNameToIdMap.get(aTransition.GetElementName().GetLocalPart()), nId!=null ? nId : -1, aTransition.GetStartState().GetFullname(), aTransition.GetEndState().GetFullname(), aTransition.GetElementName().GetStateName(), aTransition.GetElementTypeName()); } // Write skip data. for (final State aState : aAutomaton.GetStates()) { for (@SuppressWarnings("unused") final SkipData aSkipData : aState.GetSkipData()) aOut.printf("skip %4d %s\n", maTypeNameToIdMap.get(aState.GetFullname()), aState.GetFullname()); } } } private void WriteAttributes ( final PrintStream aOut, final State aState, final Iterable aAttributes) { // Write attributes. for (final Attribute aAttribute : aAttributes) { aOut.printf("attribute %4d %2d %c %4d %4d %s %s %s %s %s\n", maTypeNameToIdMap.get(aState.GetFullname()), maPrefixToIdMap.get(aAttribute.GetName().GetNamespacePrefix()), aAttribute.GetFormDefault()==FormDefault.qualified ? 'q' : 'u', maNameToIdMap.get(aAttribute.GetName().GetLocalPart()), maTypeNameToIdMap.get(aAttribute.GetTypeName().GetStateName()), aAttribute.GetUse()==Use.Optional ? 'o' : 'u', aAttribute.GetDefault()==null ? "null" : '"'+aAttribute.GetDefault()+'"', aState.GetFullname(), aAttribute.GetName().GetStateName(), aAttribute.GetTypeName().GetStateName()); } } private void WriteSimpleTypes ( final PrintStream aOut) { if (maSimpleTypes == null) { aOut.printf("\n// There is no simple type information.\n"); } else { aOut.printf("\n// %d simple types.\n", maSimpleTypes.GetSimpleTypeCount()); for (final Entry aEntry : maSimpleTypes.GetSimpleTypesSorted()) { int nIndex = 0; for (final ISimpleTypeNode aSubType : aEntry.getValue().GetSubType()) { final int nCurrentIndex = nIndex++; final StringBuffer aLine = new StringBuffer(); aLine.append(String.format( "simple-type %5d %1d %c ", maTypeNameToIdMap.get(aEntry.getKey()), nCurrentIndex, aSubType.IsList() ? 'L' : 'T')); aSubType.AcceptVisitor(new ISimpleTypeNodeVisitor() { @Override public void Visit(UnionNode aType) { throw new RuntimeException("unexpected"); } @Override public void Visit(StringNode aType) { AppendStringDescription(aLine, aType); } @Override public void Visit(NumberNode aType) { AppendNumberDescription(aLine, aType); } @Override public void Visit(DateTimeNode aType) { AppendDateTimeDescription(aLine, aType); } @Override public void Visit(BlobNode aType) { AppendBlobDescription(aLine, aType); } }); aOut.printf("%s\n", aLine.toString()); } } } } private void WriteAttributeValues ( final PrintStream aOut) { final Map aSortedMap = new TreeMap<>(); aSortedMap.putAll(maAttributeValueToIdMap); aOut.printf("// %d attribute values from enumerations.\n", maAttributeValueToIdMap.size()); for (final Entry aEntry : aSortedMap.entrySet()) aOut.printf("attribute-value %5d %s\n", aEntry.getValue(), QuoteString(aEntry.getKey())); } private static void AppendStringDescription ( final StringBuffer aLine, final StringNode aType) { aLine.append("S "); switch(aType.GetRestrictionType()) { case Enumeration: aLine.append('E'); for (final int nValueId : aType.GetEnumerationRestriction()) { aLine.append(' '); aLine.append(nValueId); } break; case Pattern: aLine.append("P "); aLine.append(QuoteString(aType.GetPatternRestriction())); break; case Length: aLine.append("L "); final int[] aLengthRestriction = aType.GetLengthRestriction(); aLine.append(aLengthRestriction[0]); aLine.append(' '); aLine.append(aLengthRestriction[1]); break; case None: aLine.append('N'); break; default: throw new RuntimeException(); } } private static void AppendNumberDescription ( final StringBuffer aLine, final NumberNode aType) { aLine.append("N "); switch(aType.GetNumberType()) { case Boolean: aLine.append("u1"); break; case Byte: aLine.append("s8"); break; case UnsignedByte: aLine.append("u8"); break; case Short: aLine.append("s16"); break; case UnsignedShort: aLine.append("u16"); break; case Int: aLine.append("s32"); break; case UnsignedInt: aLine.append("u32"); break; case Long: aLine.append("s64"); break; case UnsignedLong: aLine.append("u64"); break; case Integer: aLine.append("s*"); break; case Float: aLine.append("f"); break; case Double: aLine.append("d"); break; default: throw new RuntimeException("unsupported numerical type "+aType.GetNumberType()); } aLine.append(' '); switch(aType.GetRestrictionType()) { case Enumeration: aLine.append("E "); for (final Object nValue : aType.GetEnumerationRestriction()) { aLine.append(" "); aLine.append(nValue); } break; case Size: aLine.append("S"); if (aType.GetMinimum() != null) { if (aType.IsMinimumInclusive()) aLine.append(" >= "); else aLine.append(" > "); aLine.append(aType.GetMinimum()); } if (aType.GetMaximum() != null) { if (aType.IsMaximumInclusive()) aLine.append(" <= "); else aLine.append(" < "); aLine.append(aType.GetMaximum()); } break; case None: aLine.append("N"); break; default: throw new RuntimeException("unsupported numerical restriction "+aType.GetRestrictionType()); } } private static void AppendDateTimeDescription ( final StringBuffer aLine, final DateTimeNode aType) { aLine.append("D"); } private static void AppendBlobDescription ( final StringBuffer aLine, final BlobNode aType) { aLine.append("B "); switch(aType.GetBlobType()) { case Base64Binary: aLine.append("B "); break; case HexBinary: aLine.append ("H "); break; default: throw new RuntimeException("unsupported blob type"); } switch(aType.GetRestrictionType()) { case Length: aLine.append("L "); aLine.append(aType.GetLengthRestriction()); break; case None: aLine.append("N"); break; default: throw new RuntimeException(); } } private static String QuoteString(final String sText) { return "\"" + sText.replace("\"", """).replace(" ", "%20") + "\""; } private final FiniteAutomatonContainer maAutomatons; private final SimpleTypeContainer maSimpleTypes; private final NamespaceMap maNamespaces; private final Map maNameToIdMap; private final Map maPrefixToIdMap; private final Map maTypeNameToIdMap; private final Map maAttributeValueToIdMap; }