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}