001package com.hfg.xml.xsd;
002
003import java.io.*;
004import java.util.*;
005
006import org.xml.sax.*;
007
008import com.hfg.util.StringBuilderPlus;
009import com.hfg.util.collection.CollectionUtil;
010import com.hfg.util.StringUtil;
011import com.hfg.xml.XMLException;
012import com.hfg.xml.XMLName;
013import com.hfg.xml.XMLNamespace;
014import com.hfg.xml.XMLTag;
015import com.hfg.xml.parser.SaxyParser;
016import com.hfg.xml.parser.XMLTagSAXBroadcaster;
017import com.hfg.xml.parser.XMLTagSAXListener;
018
019//------------------------------------------------------------------------------
020/**
021 XML Schema Definition (XSD) specification container.
022 <div>
023  @author J. Alex Taylor, hairyfatguy.com
024 </div>
025 */
026//------------------------------------------------------------------------------
027// com.hfg XML/HTML Coding Library
028//
029// This library is free software; you can redistribute it and/or
030// modify it under the terms of the GNU Lesser General Public
031// License as published by the Free Software Foundation; either
032// version 2.1 of the License, or (at your option) any later version.
033//
034// This library is distributed in the hope that it will be useful,
035// but WITHOUT ANY WARRANTY; without even the implied warranty of
036// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
037// Lesser General Public License for more details.
038//
039// You should have received a copy of the GNU Lesser General Public
040// License along with this library; if not, write to the Free Software
041// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
042//
043// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
044// jataylor@hairyfatguy.com
045//------------------------------------------------------------------------------
046
047public class Xsd
048{
049   private final Map<String, XsdType>    mTypeMap    = new HashMap<>(2500);
050   private final Map<String, XsdGroup>   mGroupMap   = new HashMap<>(100);
051
052   // We have seen some cases (OfficeOpenXML SpreadsheetML) where the same element name is used with different types.
053   // Hence, the map allows a Set of elements.
054   private Map<String, Set<XsdElement>> mElementMap = new HashMap<>(2500);
055
056   private SaxyParser mParser;
057
058   //###########################################################################
059   // PUBLIC METHODS
060   //###########################################################################
061
062   //---------------------------------------------------------------------------
063   public void parse(File inFile)
064         throws IOException, SAXException
065   {
066      BufferedReader reader = null;
067      try
068      {
069         reader = new BufferedReader(new FileReader(inFile));
070         parse(reader);
071      }
072      finally
073      {
074         if (reader != null) reader.close();
075      }
076   }
077
078   //---------------------------------------------------------------------------
079   public void parse(Reader inReader)
080      throws IOException, SAXException
081   {
082      mParser = new SaxyParser();
083
084      XMLTagSAXListenerImpl listener = new XMLTagSAXListenerImpl();
085
086      XMLTagSAXBroadcaster contentHandler = new XMLTagSAXBroadcaster()
087         .addListener(listener, XsdXML.SCHEMA.getLocalName())
088         .addListener(listener, XsdXML.COMPLEX_TYPE.getLocalName())
089         .addListener(listener, XsdXML.SIMPLE_TYPE.getLocalName())
090         .addListener(listener, XsdXML.GROUP.getLocalName())
091         .addListener(listener, XsdXML.ELEMENT.getLocalName());
092
093      mParser.setContentHandler(contentHandler);
094
095      mParser.parse(new InputSource(inReader));
096   }
097
098   //---------------------------------------------------------------------------
099   public Set<XsdElement> getElements(XMLName inElementName)
100   {
101      return getElements(inElementName.getLocalName());
102   }
103
104   //---------------------------------------------------------------------------
105   public Set<XsdElement> getElements(String inElementName)
106   {
107      Set<XsdElement> elementSet = mElementMap.get(inElementName);
108      if (null == elementSet
109          && inElementName.indexOf(":") > 0)
110      {
111         // Try to look it up w/o the namespace prefix
112         String baseElementName = inElementName.substring(inElementName.indexOf(":") + 1);
113
114         elementSet = mElementMap.get(baseElementName);
115      }
116
117      return elementSet;
118   }
119
120   //---------------------------------------------------------------------------
121   /**
122    For debugging/logging purposes.
123    * @return a summary of the cached XSD information.
124    */
125   public CharSequence getConfigSummary()
126   {
127      StringBuilderPlus buffer = new StringBuilderPlus();
128      buffer.appendln("XSD Type Map Size: " + mTypeMap.size());
129      buffer.appendln("XSD Group Map Size: " + mGroupMap.size());
130      buffer.appendln("XSD Element Map Size: " + mElementMap.size());
131
132      return buffer;
133   }
134
135   //###########################################################################
136   // PROTECTED METHODS
137   //###########################################################################
138
139   //---------------------------------------------------------------------------
140   protected void integrateTypesWithElements()
141   {
142      // Not all namespace prefixes may have been set at the time the element was inserted into the Map.
143      // Hence, we need to rebuild the map so we can lookup elements using their qualified names.
144      Map<String, Set<XsdElement>> readjustedElementMap = new HashMap<>(mElementMap.size());
145      for (Set<XsdElement> elements : mElementMap.values())
146      {
147         for (XsdElement element : elements)
148         {
149            Set<XsdElement> elementSet = readjustedElementMap.get(element.getQualifiedName());
150            if (null == elementSet)
151            {
152               elementSet = new HashSet<>(4);
153               readjustedElementMap.put(element.getQualifiedName(), elementSet);
154            }
155
156            elementSet.add(element);
157         }
158      }
159      mElementMap = readjustedElementMap;
160
161      // Flesh out the type object for ea. element.
162      for (Set<XsdElement> elements : mElementMap.values())
163      {
164         for (XsdElement element : elements)
165         {
166            element.setType(mTypeMap.get(element.getQualifiedTypeName()));
167         }
168      }
169
170      // Flesh out complex types
171      for (XsdType xsdType : mTypeMap.values())
172      {
173         if (xsdType instanceof XsdComplexType)
174         {
175            // Is there a base type that needs to be set?
176            String baseTypeName = ((XsdComplexType) xsdType).getNameOfBaseType();
177            if (StringUtil.isSet(baseTypeName))
178            {
179               ((XsdComplexType) xsdType).setBaseType((XsdComplexType) mTypeMap.get(baseTypeName));
180            }
181
182            List<XsdContent> contentList = ((XsdComplexType)xsdType).getContent();
183            if (CollectionUtil.hasValues(contentList))
184            {
185               for (XsdContent content : contentList)
186               {
187                  recursivelyFleshOutContent(content);
188               }
189            }
190         }
191      }
192   }
193
194   //###########################################################################
195   // PRIVATE METHODS
196   //###########################################################################
197
198   //---------------------------------------------------------------------------
199   private void recursivelyFleshOutContent(XsdContent inContent)
200   {
201      if (inContent instanceof XsdSequence)
202      {
203         List<XsdContent> sequenceContentList = ((XsdSequence)inContent).getContent();
204         if (CollectionUtil.hasValues(sequenceContentList))
205         {
206            for (XsdContent sequenceContent : sequenceContentList)
207            {
208               recursivelyFleshOutContent(sequenceContent);
209            }
210         }
211      }
212      else if (inContent instanceof XsdChoice)
213      {
214         Set<XsdContent> choiceContents = ((XsdChoice)inContent).getOptions();
215         if (CollectionUtil.hasValues(choiceContents))
216         {
217            for (XsdContent choiceContent : choiceContents)
218            {
219               recursivelyFleshOutContent(choiceContent);
220            }
221         }
222      }
223      else if (inContent instanceof XsdGroup)
224      {
225         XsdGroup xsdGroup = (XsdGroup) inContent;
226         if (null == xsdGroup.getContent())
227         {
228            XsdGroup groupDef = mGroupMap.get(xsdGroup.getName());
229            if (groupDef != null
230                  && CollectionUtil.hasValues(groupDef.getContent()))
231            {
232               for (XsdContent groupContent : groupDef.getContent())
233               {
234                  recursivelyFleshOutContent(groupContent);
235                  ((XsdGroup) inContent).addContent(groupContent);
236               }
237            }
238         }
239      }
240      else if (inContent instanceof XsdElement
241            && ((XsdElement) inContent).getRef() != null)
242      {
243         // Flesh out ref elements
244         XsdElement refElement = ((XsdElement) inContent);
245
246         Set<XsdElement> elementSet = mElementMap.get(refElement.getRef().getQualifiedName());
247         if (elementSet != null)
248         {
249            // TODO: Not sure what to do if we have multiple elements with the same name.
250            XsdElement element = elementSet.iterator().next();
251            refElement.setName(element.getLocalName());
252            refElement.setType(element.getType());
253         }
254         else
255         {
256            System.out.println("Dangling xsd ref: " + refElement.getRef().getQualifiedName());
257         }
258      }
259   }
260
261
262   //###########################################################################
263   // INNER CLASS
264   //###########################################################################
265
266   private class XMLTagSAXListenerImpl implements XMLTagSAXListener
267   {
268
269      //########################################################################
270      // CONSTRUCTORS
271      //########################################################################
272
273      //------------------------------------------------------------------------
274      public XMLTagSAXListenerImpl()
275      {
276      }
277
278      //########################################################################
279      // PUBLIC METHODS
280      //########################################################################
281
282      //------------------------------------------------------------------------
283      public void receive(XMLTag inXMLTag)
284      {
285         try
286         {
287            XMLNamespace namespace = mParser.getCurrentDefaultNamespace();
288
289            if (inXMLTag.getTagName().equals(XsdXML.SIMPLE_TYPE.getLocalName()))
290            {
291               XsdSimpleType simpleType = new XsdSimpleType(inXMLTag);
292               simpleType.setNamespace(namespace);
293
294               mTypeMap.put(simpleType.getQualifiedName(), simpleType);
295            }
296            else if (inXMLTag.getTagName().equals(XsdXML.ELEMENT.getLocalName()))
297            {
298               if (inXMLTag.hasAttribute(XsdXML.NAME_ATT.getLocalName())
299                   && ! mElementMap.containsKey(inXMLTag.getQualifiedTagName()))
300               {
301                  XsdElement element = new XsdElement(inXMLTag);
302                  element.setNamespace(namespace);
303
304                  if (StringUtil.isSet(element.getLocalName()))
305                  {
306                     Set<XsdElement> elementSet = mElementMap.get(element.getQualifiedName());
307                     if (null == elementSet)
308                     {
309                        elementSet = new HashSet<>(4);
310                        mElementMap.put(element.getQualifiedName(), elementSet);
311                     }
312
313                     elementSet.add(element);
314                  }
315               }
316            }
317            else if (inXMLTag.getTagName().equals(XsdXML.COMPLEX_TYPE.getLocalName()))
318            {
319               XsdComplexType complexType = new XsdComplexType(inXMLTag, namespace);
320
321               mTypeMap.put(complexType.getQualifiedName(), complexType);
322            }
323            else if (inXMLTag.getTagName().equals(XsdXML.GROUP.getLocalName())
324                     && inXMLTag.hasAttribute(XsdXML.NAME_ATT.getLocalName()))
325            {
326               XsdGroup group = new XsdGroup(inXMLTag, namespace);
327               mGroupMap.put(group.getName(), group);
328            }
329         }
330         catch (Exception e)
331         {
332            throw new XMLException("Problem parsing " + inXMLTag.toXML(), e);
333         }
334      }
335   }
336
337}