PageRenderTime 100ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/bundles/plugins-trunk/XML/xml/parser/SchemaToCompletion.java

#
Java | 1068 lines | 774 code | 151 blank | 143 comment | 153 complexity | 34113c15bb143bdb4565a97e35afe063 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * SchemaToCompletion.java
  3. * :folding=explicit:collapseFolds=1:
  4. *
  5. * Copyright (C) 2009 Eric Le Lay
  6. *
  7. * The XML plugin is licensed under the GNU General Public License, with
  8. * the following exception:
  9. *
  10. * "Permission is granted to link this code with software released under
  11. * the Apache license version 1.1, for example used by the Xerces XML
  12. * parser package."
  13. */
  14. package xml.parser;
  15. import java.util.*;
  16. import java.io.IOException;
  17. import org.xml.sax.ErrorHandler;
  18. import org.xml.sax.InputSource;
  19. import com.thaiopensource.relaxng.edit.*;
  20. import com.thaiopensource.xml.util.Name;
  21. import org.gjt.sp.util.Log;
  22. import org.gjt.sp.jedit.Buffer;
  23. import xml.cache.Cache;
  24. import xml.cache.CacheEntry;
  25. import xml.completion.CompletionInfo;
  26. import xml.completion.ElementDecl;
  27. import static xml.completion.ElementDecl.AttributeDecl;
  28. import static xml.Debug.*;
  29. /**
  30. * converts the RNG object model to a list of CompletionInfo
  31. */
  32. @SuppressWarnings({"rawtypes", "unchecked"}) // whole class is littered with lists of AttributeDecl,ElementDecl which do not share common super type
  33. public class SchemaToCompletion
  34. {
  35. private static final String RNG_DTD_COMPATIBILITY_NS =
  36. "http://relaxng.org/ns/compatibility/annotations/1.0";
  37. /** the empty namespace */
  38. private static final String INHERIT = "#inherit";
  39. /** any local name... */
  40. private static final String ANY = "__WHAT_YOU_WANT_IN_NS__";
  41. /**
  42. * @param current systemId of the mapping document, to resolve the schema URL
  43. * @param schemaFileNameOrURL identifier of the schema to load
  44. * @param handler channel to report errors
  45. * @param requestingBuffer Buffer requesting the CompletionInfo (for caching)
  46. */
  47. public static Map<String,CompletionInfo> rngSchemaToCompletionInfo(String current, String schemaFileNameOrURL, ErrorHandler handler, Buffer requestingBuffer){
  48. Map<String,CompletionInfo> infos = new HashMap<String,CompletionInfo>();
  49. String realLocation = null;
  50. try{
  51. realLocation = xml.Resolver.instance().resolveEntityToPath(
  52. /*name*/null,
  53. /*publicId*/null,
  54. current,
  55. schemaFileNameOrURL);
  56. }catch(IOException ioe){
  57. Log.log(Log.ERROR,SchemaToCompletion.class,"error resolving schema location",ioe);
  58. return infos;
  59. }
  60. CacheEntry en = Cache.instance().get(realLocation,"CompletionInfo");
  61. if(en == null){
  62. if(DEBUG_CACHE)Log.log(Log.DEBUG, SchemaToCompletion.class,"CompletionInfo not found in cache for "+schemaFileNameOrURL);
  63. com.thaiopensource.relaxng.input.InputFormat pif;
  64. if(schemaFileNameOrURL.endsWith(".rnc")){
  65. pif = new xml.translate.BufferCompactParseInputFormat();
  66. }else{
  67. pif = new xml.translate.BufferSAXParseInputFormat();
  68. }
  69. try{
  70. InputSource is = xml.Resolver.instance().resolveEntity(
  71. /*name*/null,
  72. /*publicId*/null,
  73. current,
  74. schemaFileNameOrURL);
  75. SchemaCollection schemas = pif.load(
  76. is.getSystemId(),
  77. new String[]{},
  78. "unused",
  79. handler,
  80. new xml.translate.EntityResolverWrapper(xml.Resolver.instance(),false));
  81. SchemaDocument mainSchema = schemas.getSchemaDocumentMap().get(schemas.getMainUri());
  82. Pattern p = mainSchema.getPattern();
  83. MyPatternVisitor v = new MyPatternVisitor(infos,schemas);
  84. List roots = p.accept(v);
  85. v.addRootsToCompletionInfos(roots);
  86. v.resolveRefs();
  87. // use a more intuitive '' key for the completion info
  88. // of the no-namespace elements
  89. if(infos.containsKey(INHERIT)){
  90. CompletionInfo nons = infos.get(INHERIT);
  91. nons.namespace = "";
  92. infos.put("",nons);
  93. infos.remove(INHERIT);
  94. }
  95. //{{{ put everything in cache
  96. List<CacheEntry> related = new ArrayList<CacheEntry>(schemas.getSchemaDocumentMap().size());
  97. for(String url : schemas.getSchemaDocumentMap().keySet()){
  98. if(DEBUG_XSD_SCHEMA)Log.log(Log.DEBUG,SchemaToCompletion.class,"grammar is composed of "+url);
  99. try{
  100. String realLoc = xml.Resolver.instance().resolveEntityToPath(null, null, current, url);
  101. CacheEntry ce = Cache.instance().put(realLoc,"GrammarComponent","Dummy");
  102. related.add(ce);
  103. }catch(IOException ioe){
  104. Log.log(Log.ERROR, SchemaToCompletion.class, "error resolving path for "+url, ioe);
  105. }
  106. }
  107. related.add(Cache.instance().put(realLocation,"CompletionInfo",infos));
  108. // mark all components related and requested by the buffer
  109. for(CacheEntry ce : related){
  110. ce.getRelated().addAll(related);
  111. ce.getRelated().remove(ce);
  112. ce.getRequestingBuffers().add(requestingBuffer);
  113. }
  114. //}}}
  115. }catch(Exception e){
  116. // FIXME: handle exceptions
  117. Log.log(Log.ERROR, SchemaToCompletion.class, e);
  118. }
  119. }else{
  120. if(DEBUG_CACHE)Log.log(Log.DEBUG, SchemaToCompletion.class,"found CompletionInfo in cache for "+schemaFileNameOrURL);
  121. infos = (Map<String,CompletionInfo>)en.getCachedItem();
  122. }
  123. return infos;
  124. }
  125. private static class GrabDefinesVisitor
  126. extends AbstractPatternVisitor< Map<String,List<DefineComponent>> >
  127. implements ComponentVisitor < Map<String,List<DefineComponent>> >
  128. {
  129. private Map<String,List<DefineComponent>> comps = new HashMap<String,List<DefineComponent>>();
  130. public Map<String,List<DefineComponent>> visitGrammar(GrammarPattern p){
  131. p.componentsAccept(this);
  132. return comps;
  133. }
  134. public Map<String,List<DefineComponent>> visitDefine(DefineComponent c){
  135. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,GrabDefinesVisitor.class,"visitDefine("+c.getName()+")");
  136. if(!comps.containsKey(c.getName())){
  137. comps.put(c.getName(),new ArrayList<DefineComponent>());
  138. }
  139. comps.get(c.getName()).add(c);
  140. return comps;
  141. }
  142. public Map<String,List<DefineComponent>> visitDiv(DivComponent c){
  143. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,GrabDefinesVisitor.class,"visitDiv()");
  144. c.componentsAccept(this);
  145. return comps;
  146. }
  147. public Map<String,List<DefineComponent>> visitInclude(IncludeComponent c){
  148. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,GrabDefinesVisitor.class,"visitInclude("+c.getHref()+")");
  149. // do not grab definitions inside the include element
  150. return comps;
  151. }
  152. public Map<String,List<DefineComponent>> visitPattern(Pattern p){
  153. throw new UnsupportedOperationException("visitPattern("+p+")");
  154. }
  155. }
  156. /** add-hock collection to gather definitions in nested grammars.
  157. */
  158. private static class StackableMap<K,V>
  159. {
  160. private List<Map<K,V>> contents;
  161. StackableMap(int initialSize)
  162. {
  163. contents = new ArrayList<Map<K,V>>(initialSize);
  164. }
  165. public void push(Map<K,V> step)
  166. {
  167. contents.add(0,step);
  168. }
  169. public Map<K,V> pop()
  170. {
  171. return contents.remove(0);
  172. }
  173. public V get(K key)
  174. {
  175. if(contents.isEmpty())return null;
  176. return contents.get(0).get(key);
  177. }
  178. public V getFromParent(K key)
  179. {
  180. if(contents.size()<2)return null;
  181. return contents.get(1).get(key);
  182. }
  183. public boolean containsKey(K key)
  184. {
  185. if(contents.isEmpty())return false;
  186. return contents.get(0).containsKey(key);
  187. }
  188. public boolean parentContainsKey(K key)
  189. {
  190. if(contents.size()<2)return false;
  191. return contents.get(1).containsKey(key);
  192. }
  193. public String toString(){
  194. return "StackableMap{"+contents+"}";
  195. }
  196. }
  197. /**
  198. * only visitAttribute returns a non empty list of AttributeDecl
  199. */
  200. private static class MyAttributeVisitor extends AbstractPatternVisitor< List<AttributeDecl> >{
  201. private Map<String,String> values;
  202. private List<String> data;
  203. private StackableMap<String,List<DefineComponent>> defined;
  204. private boolean required;
  205. MyAttributeVisitor(StackableMap<String,List<DefineComponent>> defined,boolean required){
  206. this.defined=defined;
  207. this.required = required;
  208. }
  209. public List<AttributeDecl> visitAttribute(AttributePattern p){
  210. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitAttribute(req="+required+")");
  211. if(values!=null)throw new IllegalArgumentException("attribute//attribute isn't allowed");
  212. values = new HashMap<String,String>();
  213. data = new ArrayList<String>();
  214. List<Name> names = p.getNameClass().accept(new MyNameClassVisitor());
  215. p.getChild().accept(this);
  216. List<AttributeDecl> attrs = new ArrayList<AttributeDecl>(names.size());
  217. String value;
  218. String type;
  219. if(values.isEmpty())
  220. {
  221. if(data.isEmpty()) type = "";
  222. else type = data.get(0);
  223. value = "";
  224. }
  225. else
  226. {
  227. value = values.keySet().iterator().next();
  228. type = values.get(value);
  229. }
  230. if(p.getAttributeAnnotation(RNG_DTD_COMPATIBILITY_NS,"defaultValue")!=null){
  231. value = p.getAttributeAnnotation(RNG_DTD_COMPATIBILITY_NS,"defaultValue");
  232. }
  233. // as soon as there are more than one name required, can't mark each attributeDecl as required !
  234. required &= names.size() < 2;
  235. for(Name name:names){
  236. attrs.add(new AttributeDecl(name.getLocalName(),
  237. name.getNamespaceUri(),
  238. value,
  239. new ArrayList<String>(values.keySet()),
  240. type,
  241. required));
  242. }
  243. return attrs;
  244. }
  245. public List<AttributeDecl> visitChoice(ChoicePattern p){
  246. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitChoice()");
  247. // here is only the choice of values, so it doesn't change the requiredness of the attribute
  248. for(Pattern c: p.getChildren())
  249. {
  250. c.accept(this);
  251. }
  252. return Collections.emptyList();
  253. }
  254. public List<AttributeDecl> visitElement(ElementPattern p){
  255. throw new IllegalArgumentException("attribute//element isn't allowed");
  256. }
  257. public List<AttributeDecl> visitEmpty(EmptyPattern p){
  258. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitEmpty()");
  259. return Collections.emptyList();
  260. }
  261. public List<AttributeDecl> visitExternalRef(ExternalRefPattern p){
  262. throw new IllegalArgumentException("attribute//externalRef isn't allowed");
  263. }
  264. public List<AttributeDecl> visitGrammar(GrammarPattern p){
  265. throw new IllegalArgumentException("attribute//grammar isn't allowed");
  266. }
  267. public List<AttributeDecl> visitGroup(GroupPattern p){
  268. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitGroup()");
  269. for(Pattern c: p.getChildren())
  270. {
  271. c.accept(this);
  272. }
  273. return Collections.emptyList();
  274. }
  275. public List<AttributeDecl> visitInterleave(InterleavePattern p){
  276. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitInterleave()");
  277. for(Pattern c: p.getChildren())
  278. {
  279. c.accept(this);
  280. }
  281. return Collections.emptyList();
  282. }
  283. public List<AttributeDecl> visitList(ListPattern p){
  284. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitList()");
  285. return p.getChild().accept(this);
  286. }
  287. public List<AttributeDecl> visitMixed(MixedPattern p){
  288. throw new IllegalArgumentException("attribute//mixed doesn't make sense");
  289. }
  290. public List<AttributeDecl> visitNotAllowed(NotAllowedPattern p){
  291. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitNotAllowed()");
  292. return Collections.emptyList();
  293. }
  294. public List<AttributeDecl> visitOneOrMore(OneOrMorePattern p){
  295. throw new IllegalArgumentException("attribute//oneOrMore doesn't make sense to me");
  296. }
  297. public List<AttributeDecl> visitOptional(OptionalPattern p){
  298. throw new IllegalArgumentException("attribute//optional doesn't make sense to me");
  299. }
  300. public List<AttributeDecl> visitParentRef(ParentRefPattern p){
  301. throw new IllegalArgumentException("attribute//parentRef isn't allowed");
  302. }
  303. /**
  304. * no endless loop here, since definitions in attributes can't be recursive
  305. */
  306. public List<AttributeDecl> visitRef(RefPattern p){
  307. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitRef()");
  308. if(defined.containsKey(p.getName()))
  309. {
  310. for(DefineComponent dc: defined.get(p.getName())){
  311. dc.getBody().accept(this);
  312. }
  313. return Collections.emptyList();
  314. }
  315. else throw new IllegalArgumentException("unknown define : "+p.getName());
  316. }
  317. public List<AttributeDecl> visitText(TextPattern p){
  318. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitText()");
  319. return Collections.emptyList();
  320. }
  321. public List<AttributeDecl> visitValue(ValuePattern p){
  322. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitValue("+p.getPrefixMap()+")");
  323. values.put(p.getValue(),p.getType());
  324. return Collections.emptyList();
  325. }
  326. public List<AttributeDecl> visitData(DataPattern p){
  327. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyAttributeVisitor.class,"visitData()");
  328. data.add(p.getType());
  329. return Collections.emptyList();
  330. }
  331. public List<AttributeDecl> visitZeroOrMore(ZeroOrMorePattern p){
  332. throw new IllegalArgumentException("attribute//zeroOrMore doesn't make sense to me");
  333. }
  334. public List<AttributeDecl> visitPattern(Pattern p){
  335. throw new IllegalArgumentException("which pattern did I forget ? "+p);
  336. }
  337. }
  338. private static class MyNameClassVisitor implements NameClassVisitor<List<Name> >
  339. {
  340. private boolean any = false;
  341. private List<Name> names = new ArrayList<Name>();
  342. public List<Name> visitName(NameNameClass nc)
  343. {
  344. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,SchemaToCompletion.class,"visitName("+nc.getNamespaceUri()+","+nc.getPrefix()+":"+nc.getLocalName()+")");
  345. names.add(new Name(nc.getNamespaceUri(),nc.getLocalName()));
  346. return names;
  347. }
  348. public List<Name> visitNsName(NsNameNameClass nc)
  349. {
  350. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyNameClassVisitor.class,"visitNsName("+nc.getNs()+")");
  351. names.add(new Name(nc.getNs(),ANY));
  352. Log.log(Log.WARNING,MyNameClassVisitor.class,
  353. "doesn't handle \"any element\" in namespace in RNG schema (namespace is "+nc.getNs()+")");
  354. if(nc.getExcept()!=null)
  355. {
  356. Log.log(Log.WARNING,MyNameClassVisitor.class,
  357. "doesn't handle except clause in RNG schema");
  358. }
  359. return names;
  360. }
  361. public List<Name> visitAnyName(AnyNameNameClass nc)
  362. {
  363. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyNameClassVisitor.class,"visitAnyName()");
  364. any = true;
  365. return names;
  366. }
  367. public List<Name> visitChoice(ChoiceNameClass nc)
  368. {
  369. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyNameClassVisitor.class,"visitChoiceNameClass()");
  370. nc.childrenAccept(this);
  371. return names;
  372. }
  373. }
  374. /**
  375. * main visitor of the RNG grammar.
  376. * Most of the complexity comes from handling defines/ref patterns
  377. * the list returned is used to cache the translation of the content of definitions.
  378. * @see MyPatternVisitor#handleRef(List)
  379. * */
  380. private static class MyPatternVisitor extends AbstractPatternVisitor<List>
  381. implements ComponentVisitor<List>
  382. {
  383. /** all the CompletionInfo discovered so far,
  384. * one by target namespace.
  385. * */
  386. private Map<String,CompletionInfo> info;
  387. /**
  388. * for grammars defined in multiple files
  389. * */
  390. private SchemaCollection schemas;
  391. /**
  392. * all the defines pattern curently visible (StackeableMap because of parentRef)
  393. **/
  394. private StackableMap<String,List<DefineComponent>> defines;
  395. /**
  396. * the content of all defines patterns so far.
  397. * for each DefineComponent, list of ElementDecl and AttributeDecl (ElementDecl can be ElementRefDecl)
  398. * */
  399. private Map<DefineComponent,List> definesContents;
  400. /**
  401. * current element must be empty
  402. * */
  403. private boolean empty = false;
  404. /**
  405. * current attribute is required
  406. * */
  407. private boolean required = true;
  408. /**
  409. * current parent
  410. * */
  411. private ElementDecl parent;
  412. MyPatternVisitor(Map<String,CompletionInfo> info,SchemaCollection schemas){
  413. this.info = info;
  414. this.schemas = schemas;
  415. parent = null;
  416. defines = new StackableMap<String,List<DefineComponent>>(2);
  417. definesContents = new HashMap<DefineComponent,List>();
  418. }
  419. public List visitAttribute(AttributeAnnotation a)
  420. {
  421. throw new UnsupportedOperationException("visitAttribute(Annotation)");
  422. }
  423. public List visitAttribute(AttributePattern p)
  424. {
  425. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitAttribute()");
  426. MyAttributeVisitor visitor = new MyAttributeVisitor(defines,required);
  427. List<AttributeDecl> attrs = p.accept(visitor);
  428. if(parent!=null)
  429. {
  430. for(AttributeDecl d:attrs)
  431. {
  432. parent.addAttribute(d);
  433. }
  434. }
  435. return attrs;
  436. }
  437. public List visitChoice(ChoicePattern p)
  438. {
  439. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitChoice()");
  440. boolean savedRequired = required;
  441. required = false;
  442. List res = new ArrayList<Object>();
  443. for(Pattern c: p.getChildren())
  444. {
  445. res.addAll(c.accept(this));
  446. }
  447. required = savedRequired;
  448. return res;
  449. }
  450. public List visitComment(Comment c)
  451. {
  452. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitComment()");
  453. return Collections.emptyList();
  454. }
  455. public List visitComponent(Component c)
  456. {
  457. throw new UnsupportedOperationException(" visitComponent() should not be called");
  458. }
  459. public List visitData(DataPattern p)
  460. {
  461. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitData()");
  462. // TODO: display the data type of an element
  463. return Collections.emptyList();
  464. }
  465. /**
  466. * define components are ignored by this visitor; GrabDefinesVisitor has collected them already
  467. * @see GrabDefinesVisitor
  468. * @see MyPatternVisitor#visitGrammar(GrammarPattern)
  469. */
  470. public List visitDefine(DefineComponent c)
  471. {
  472. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitDefine("+c.getName()+","+c.getCombine()+")");
  473. return Collections.emptyList();
  474. }
  475. public List visitDiv(DivComponent p)
  476. {
  477. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitDiv()");
  478. // may be used to add documentation to a group of elements.
  479. List res = new ArrayList<Object>();
  480. for(Component c: p.getComponents())
  481. {
  482. res.addAll(c.accept(this));
  483. }
  484. return res;
  485. }
  486. public List visitElement(ElementAnnotation ea)
  487. {
  488. throw new UnsupportedOperationException("visitElement(Annotation)");
  489. }
  490. @SuppressWarnings("unchecked")
  491. public List visitElement(ElementPattern p)
  492. {
  493. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitElement()");
  494. ElementDecl myParent = parent;
  495. empty = false;
  496. MyNameClassVisitor nameVisitor = new MyNameClassVisitor();
  497. List<Name> myNames = p.getNameClass().accept(nameVisitor);
  498. boolean isEmpty = empty;
  499. boolean isRequired = required;
  500. required = true;// by default, attributes are required
  501. if(parent != null && parent.content == null)
  502. parent.content = new HashSet<String>();
  503. if(parent != null && parent.elementHash == null)
  504. parent.elementHash = new HashMap<String,ElementDecl>();
  505. // grab all variants of this element and return them
  506. List res = new ArrayList();
  507. for(Name name:myNames)
  508. {
  509. CompletionInfo myInfo = info.get(name.getNamespaceUri());
  510. if(myInfo==null)
  511. {
  512. myInfo = new CompletionInfo();
  513. myInfo.namespace = name.getNamespaceUri();
  514. info.put(name.getNamespaceUri(),myInfo);
  515. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"setting completionInfo for "+myInfo.namespace);
  516. }
  517. ElementDecl me = myInfo.elementHash.get(name.getLocalName());
  518. if(me == null){
  519. me = new ElementDecl(myInfo,name.getLocalName(),"");
  520. // only start elements are added to the elementHash of the info,
  521. // so that only start elements show up before root node
  522. if(parent == null)
  523. {
  524. myInfo.addElement(me);
  525. }
  526. else
  527. {
  528. myInfo.elements.add(me);
  529. }
  530. parent = me;
  531. p.getChild().accept(this);
  532. parent = myParent;
  533. }
  534. res.add(me);
  535. if(myParent!=null)
  536. {
  537. myParent.content.add(name.getLocalName());
  538. myParent.elementHash.put(name.getLocalName(),me);
  539. }
  540. }
  541. if(myParent!=null){
  542. myParent.any = nameVisitor.any;
  543. myParent.empty = isEmpty;
  544. }
  545. required=isRequired;
  546. return res;
  547. }
  548. public List visitEmpty(EmptyPattern p)
  549. {
  550. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitEmpty()");
  551. empty=true;
  552. return Collections.emptyList();
  553. }
  554. public List visitExternalRef(ExternalRefPattern p)
  555. {
  556. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitExternalRef("+p.getUri()+")");
  557. // "The externalRef matches if the pattern contained in the specified URL matches" [RNG tutorial]
  558. SchemaDocument sc = schemas.getSchemaDocumentMap().get(p.getUri());
  559. // no risk of endless loop since externalRefs are not allowed to be recursive
  560. return sc.getPattern().accept(this);
  561. }
  562. public List visitGrammar(GrammarPattern p)
  563. {
  564. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitGrammar()");
  565. defines.push(p.accept(new GrabDefinesVisitor()));
  566. //for the include
  567. p.componentsAccept(this);
  568. List res = new ArrayList();
  569. // explore the tree from <start>
  570. if(defines.containsKey(DefineComponent.START)){
  571. for(DefineComponent dc: defines.get(DefineComponent.START)){
  572. res.addAll(dc.getBody().accept(this));
  573. }
  574. }else{
  575. throw new IllegalArgumentException("Grammar without a start element !");
  576. }
  577. defines.pop();
  578. return res;
  579. }
  580. public List visitGroup(GroupPattern p)
  581. {
  582. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitGroup()");
  583. List res = new ArrayList<Object>();
  584. for(Pattern c: p.getChildren())
  585. {
  586. res.addAll(c.accept(this));
  587. }
  588. return res;
  589. }
  590. public List visitInclude(IncludeComponent c)
  591. {
  592. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitInclude("+c.getUri()+")");
  593. SchemaDocument sc = schemas.getSchemaDocumentMap().get(c.getUri());
  594. // the included element MUST be a grammar per the spec
  595. GrammarPattern g = (GrammarPattern)sc.getPattern();
  596. // a grammar contains only start, define, div, include elements
  597. Map<String,List<DefineComponent>> grammarDefinitions = g.accept(new GrabDefinesVisitor());
  598. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"grammar definitions: "+grammarDefinitions.keySet());
  599. // an include contains only start, define, div, elements
  600. GrabDefinesVisitor v = new GrabDefinesVisitor();
  601. c.componentsAccept(v);
  602. Map<String,List<DefineComponent>> includeDefinitions = v.comps;
  603. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"include definitions: "+includeDefinitions.keySet());
  604. // override each define and the start in the grammar with the include's contents
  605. grammarDefinitions.putAll(includeDefinitions);
  606. // the included grammar is then like a div : definitions should be merged
  607. Map<String,List<DefineComponent>> parentDefinitions = defines.pop();
  608. for(Map.Entry<String,List<DefineComponent>> e:grammarDefinitions.entrySet()){
  609. if(!parentDefinitions.containsKey(e.getKey())){
  610. parentDefinitions.put(e.getKey(),new ArrayList<DefineComponent>());
  611. }
  612. parentDefinitions.get(e.getKey()).addAll(e.getValue());
  613. }
  614. defines.push(parentDefinitions);
  615. // proceed with the grammar's content
  616. if(defines.containsKey(DefineComponent.START)){
  617. List res = new ArrayList();
  618. for(DefineComponent dc: defines.get(DefineComponent.START)){
  619. res.addAll(dc.getBody().accept(this));
  620. }
  621. return res;
  622. }else{
  623. throw new UnsupportedOperationException("included grammar without a start element !");
  624. }
  625. }
  626. public List visitInterleave(InterleavePattern p)
  627. {
  628. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitInterleave()");
  629. List res = new ArrayList<Object>();
  630. for(Pattern c: p.getChildren())
  631. {
  632. res.addAll(c.accept(this));
  633. }
  634. return res;
  635. }
  636. public List visitZeroOrMore(ZeroOrMorePattern p)
  637. {
  638. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitZeroOrMore()");
  639. boolean savedRequired = required;
  640. required = false;
  641. List res = p.getChild().accept(this);
  642. savedRequired = required;
  643. return res;
  644. }
  645. public List visitList(ListPattern p)
  646. {
  647. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitList()");
  648. //TODO: text completion inside this element
  649. return Collections.emptyList();
  650. }
  651. public List visitMixed(MixedPattern p)
  652. {
  653. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitMixed()");
  654. //indicates that the element may contain text
  655. return Collections.emptyList();
  656. }
  657. public List visitNameClass(NameClass nc)
  658. {
  659. throw new UnsupportedOperationException("visitNameClass() shouldn't be called");
  660. }
  661. public List visitNotAllowed(NotAllowedPattern p)
  662. {
  663. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitNotAllowed()");
  664. // not interesting
  665. return Collections.emptyList();
  666. }
  667. public List visitOneOrMore(OneOrMorePattern p)
  668. {
  669. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitOneOrMore()");
  670. return p.getChild().accept(this);
  671. }
  672. public List visitOptional(OptionalPattern p)
  673. {
  674. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitOptional()");
  675. boolean savedRequired = required;
  676. required = false;
  677. List res = p.getChild().accept(this);
  678. required = savedRequired;
  679. return res;
  680. }
  681. public List visitParentRef(ParentRefPattern p)
  682. {
  683. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitParentRef("+p.getName()+")");
  684. if(!defines.parentContainsKey(p.getName()))
  685. throw new IllegalArgumentException("Undefined reference :"+p.getName());
  686. List<DefineComponent> parentRef = defines.getFromParent(p.getName());
  687. // must pop the definition to be in the context of the parent grammar
  688. // if the referenced contents also reference sthing
  689. Map<String,List<DefineComponent>> myDefinitions = defines.pop();
  690. List res = handleRef(parentRef);
  691. // restore the current grammar"s context
  692. defines.push(myDefinitions);
  693. return res;
  694. }
  695. /** handle ref and parentRef all the same.
  696. * The first time that a reference to a define is visited,
  697. * visit its body (so the current parent gets its content),
  698. * save the content (return of the visitXXX methods) in definesContents;
  699. * Otherwise, don't do it.
  700. * Always return an ElementRefDecl anyway.
  701. * */
  702. private List handleRef(List<DefineComponent> refs){
  703. List res = new ArrayList();
  704. for(DefineComponent dc : refs){
  705. // will return it anyway !
  706. ElementDecl e = new ElementRefDecl(dc,required);
  707. res.add(e);
  708. if(!definesContents.containsKey(dc)){
  709. //first time we see a reference to this define
  710. // we will not add the ElementRefDecl to the parent, since it will get the real content
  711. // via dc.getBody.accept(this) (see bellow)
  712. // let the attributes get their required flag from the body
  713. // of the definition only
  714. boolean savedRequired = required;
  715. required = true;
  716. //don't recurse indefinitely :
  717. // - mark that this reference is explored
  718. definesContents.put(dc,null);
  719. // - explore it
  720. List r = dc.getBody().accept(this);
  721. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,dc.getName()+" defined as "+r);
  722. // - set the actual value
  723. definesContents.put(dc,r);
  724. // correct the requiredness of attributes
  725. if(parent!=null && !savedRequired){
  726. for(Object o: r){
  727. if(o instanceof AttributeDecl){
  728. AttributeDecl newO = ((AttributeDecl)o).copy();
  729. newO.required = false;
  730. parent.attributes.remove((AttributeDecl)o);
  731. parent.addAttribute(newO);
  732. }
  733. }
  734. }
  735. required = savedRequired;
  736. }else{
  737. if(parent != null){
  738. // add the reference to the element as a normal child element
  739. if(parent.content == null)
  740. parent.content = new HashSet<String>();
  741. if(parent.elementHash == null)
  742. parent.elementHash = new HashMap<String,ElementDecl>();
  743. parent.elementHash.put(e.name,e);
  744. parent.content.add(e.name);
  745. }
  746. }
  747. }
  748. return res;
  749. }
  750. public List visitPattern(Pattern p)
  751. {
  752. // see http://code.google.com/p/jing-trang/issues/detail?id=102
  753. if(p instanceof ZeroOrMorePattern)
  754. {
  755. return visitZeroOrMore((ZeroOrMorePattern)p);
  756. }
  757. else
  758. {
  759. // we visit everything, so it shouldn't be called
  760. throw new UnsupportedOperationException("visitPattern("+p+")");
  761. }
  762. }
  763. public List visitRef(RefPattern p)
  764. {
  765. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitRef("+p.getName()+")");
  766. if(!defines.containsKey(p.getName())){
  767. throw new IllegalArgumentException("Undefined reference :"+p.getName());
  768. }
  769. return handleRef(defines.get(p.getName()));
  770. }
  771. public List visitText(TextAnnotation ta)
  772. {
  773. throw new UnsupportedOperationException("visitText(Annotation)");
  774. }
  775. public List visitText(TextPattern p)
  776. {
  777. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitText("+p+")");
  778. //throwing away : there is no 'mixed' information in the ElementDecl
  779. return Collections.emptyList();
  780. }
  781. public List visitValue(ValuePattern p)
  782. {
  783. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"visitValue()");
  784. //TODO : text completion inside an element
  785. return Collections.emptyList();
  786. }
  787. /** replace all ElementRefDecls by their value,
  788. * going through all elements of all CompletionInfo */
  789. void resolveRefs(){
  790. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"resolving references...");
  791. for(CompletionInfo i : info.values()){
  792. for(ElementDecl e:i.elements){
  793. resolveRefs(e);
  794. }
  795. }
  796. for(CompletionInfo i : info.values()){
  797. for(ElementDecl e:i.elements){
  798. if(e.elementHash!=null){
  799. for(ElementDecl d:e.elementHash.values()){
  800. if(d instanceof ElementRefDecl){
  801. throw new IllegalStateException("still some undereferenced ElementRefDecl: "+d.name);
  802. }
  803. }
  804. }
  805. }
  806. }
  807. }
  808. /**
  809. * if start elements are define components (like article, book in Docbook 5 grammar),
  810. * their content must be added to CompletionInfo.elementHash because they are really top level elements.
  811. * and were not visited as such before.
  812. * Beware: unbounded recursion here, so defines may not be mutually recursive !
  813. * @param roots list returned by visitGrammar
  814. * */
  815. void addRootsToCompletionInfos(List roots){
  816. for(Object o: roots){
  817. if(o instanceof ElementRefDecl){
  818. ElementRefDecl e = (ElementRefDecl)o;
  819. List res = definesContents.get(e.ref);
  820. if(res == null)throw new IllegalArgumentException("can't find definitions for "+e.ref.getName()+"="+e.ref);
  821. else {
  822. for(Object r:res){
  823. if(r instanceof ElementDecl){
  824. if(r instanceof ElementRefDecl){
  825. addRootsToCompletionInfos(Collections.singletonList(r));
  826. }else{
  827. ElementDecl oo = (ElementDecl)r;
  828. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"adding root "+oo);
  829. if(oo.name != null){
  830. oo.completionInfo.elementHash.put(oo.name, oo);
  831. }
  832. }
  833. }
  834. }
  835. }
  836. }
  837. }
  838. }
  839. /** replace all ElementRefDecls children in this ElementDecl by their content,
  840. * recursively dereferencing if necessary
  841. * @see MyPatternVisitor#resolveRefs(List, boolean)
  842. */
  843. private void resolveRefs(ElementDecl e){
  844. if(e.elementHash != null){
  845. List res = resolveRefs(new ArrayList(e.elementHash.values()), true);
  846. e.elementHash.clear();
  847. e.content.clear();
  848. for(Object o:res){
  849. if(o instanceof ElementDecl){
  850. ElementDecl oo = (ElementDecl)o;
  851. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"adding "+oo);
  852. if(oo.name != null){
  853. e.elementHash.put(oo.name,oo);
  854. e.content.add(oo.name);
  855. }
  856. }else if(o instanceof AttributeDecl){
  857. AttributeDecl oo = (AttributeDecl)o;
  858. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"adding "+oo);
  859. //requiredness has already been take care of in resolveRefs(List,boolean)
  860. e.addAttribute(oo);
  861. }else{
  862. throw new IllegalArgumentException("what's this : "+o);
  863. }
  864. }
  865. }
  866. }
  867. /**
  868. * recursively resolve references in res
  869. * @param required are referenced attributes required.
  870. * If only 1 reference in the chain of references marks attributes as non required (field ElementRefDecl.required),
  871. * resulting attributes will be non required
  872. * @return list of ElementDecl or AttributeDecl
  873. */
  874. private List resolveRefs(List res, boolean required){
  875. List l = new ArrayList(res.size());
  876. for(Object o:res){
  877. if(o instanceof ElementRefDecl){
  878. ElementRefDecl oref = (ElementRefDecl)o;
  879. DefineComponent odc = oref.ref;
  880. List ores = definesContents.get(odc);
  881. if(ores == null)throw new IllegalArgumentException("can't find definitions for "+odc.getName()+"="+odc);
  882. // it only takes 1 ref in <optional> to make attributes non required
  883. l.addAll(resolveRefs(ores,required && oref.required));
  884. } else if(o instanceof ElementDecl){
  885. l.add(o);
  886. }else if(o instanceof AttributeDecl){
  887. AttributeDecl oo = (AttributeDecl)o;
  888. if(DEBUG_RNG_SCHEMA)Log.log(Log.DEBUG,MyPatternVisitor.class,"adding "+oo);
  889. // must make a copy with correct requiredness because attribute could be referenced somewhere else
  890. // where it is required
  891. if(!required){
  892. oo = oo.copy();
  893. oo.required = false;
  894. }
  895. l.add(oo);
  896. }else{
  897. throw new IllegalArgumentException("what's this : "+o);
  898. }
  899. }
  900. return l;
  901. }
  902. /** intermediate placeholder for a reference */
  903. private static class ElementRefDecl extends ElementDecl{
  904. DefineComponent ref;
  905. static int i = 0;
  906. boolean required;
  907. ElementRefDecl(DefineComponent ref, boolean required){
  908. super(null,"_:"+ref.getName()+"_"+(i++),null);
  909. this.ref = ref;
  910. this.required = required;
  911. }
  912. public String toString(){
  913. return "ref to "+ref.getName()+"="+ref.toString();
  914. }
  915. }
  916. }
  917. }