PageRenderTime 181ms CodeModel.GetById 115ms app.highlight 49ms RepoModel.GetById 1ms app.codeStats 1ms

/bundles/plugins-trunk/XML/xml/translate/BufferDtdConverter.java

#
Java | 1164 lines | 955 code | 125 blank | 84 comment | 206 complexity | d9030a86396cbd4e7c3725ae8c628f0b MD5 | raw file
   1/*
   2Copyright (c) 2001-2003 Thai Open Source Software Center Ltd
   3All rights reserved.
   4
   5Redistribution and use in source and binary forms, with or without
   6modification, are permitted provided that the following conditions are
   7met:
   8
   9    Redistributions of source code must retain the above copyright
  10    notice, this list of conditions and the following disclaimer.
  11
  12    Redistributions in binary form must reproduce the above copyright
  13    notice, this list of conditions and the following disclaimer in
  14    the documentation and/or other materials provided with the
  15    distribution.
  16
  17    Neither the name of the Thai Open Source Software Center Ltd nor
  18    the names of its contributors may be used to endorse or promote
  19    products derived from this software without specific prior written
  20    permission.
  21
  22THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  25A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
  26CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  27EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  28PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  29PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  30LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  31NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  32SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33*/
  34package xml.translate;
  35
  36import com.thaiopensource.relaxng.edit.Annotated;
  37import com.thaiopensource.relaxng.edit.AnyNameNameClass;
  38import com.thaiopensource.relaxng.edit.AttributeAnnotation;
  39import com.thaiopensource.relaxng.edit.AttributePattern;
  40import com.thaiopensource.relaxng.edit.ChoicePattern;
  41import com.thaiopensource.relaxng.edit.Combine;
  42import com.thaiopensource.relaxng.edit.Comment;
  43import com.thaiopensource.relaxng.edit.Component;
  44import com.thaiopensource.relaxng.edit.CompositePattern;
  45import com.thaiopensource.relaxng.edit.DataPattern;
  46import com.thaiopensource.relaxng.edit.DefineComponent;
  47import com.thaiopensource.relaxng.edit.ElementPattern;
  48import com.thaiopensource.relaxng.edit.EmptyPattern;
  49import com.thaiopensource.relaxng.edit.GrammarPattern;
  50import com.thaiopensource.relaxng.edit.GroupPattern;
  51import com.thaiopensource.relaxng.edit.IncludeComponent;
  52import com.thaiopensource.relaxng.edit.NameClass;
  53import com.thaiopensource.relaxng.edit.NameNameClass;
  54import com.thaiopensource.relaxng.edit.NotAllowedPattern;
  55import com.thaiopensource.relaxng.edit.OneOrMorePattern;
  56import com.thaiopensource.relaxng.edit.OptionalPattern;
  57import com.thaiopensource.relaxng.edit.Pattern;
  58import com.thaiopensource.relaxng.edit.RefPattern;
  59import com.thaiopensource.relaxng.edit.SchemaCollection;
  60import com.thaiopensource.relaxng.edit.SchemaDocument;
  61import com.thaiopensource.relaxng.edit.TextPattern;
  62import com.thaiopensource.relaxng.edit.ValuePattern;
  63import com.thaiopensource.relaxng.edit.ZeroOrMorePattern;
  64import com.thaiopensource.relaxng.input.CommentTrimmer;
  65import com.thaiopensource.relaxng.output.common.ErrorReporter;
  66import com.thaiopensource.xml.dtd.om.AttributeDefault;
  67import com.thaiopensource.xml.dtd.om.AttributeGroup;
  68import com.thaiopensource.xml.dtd.om.AttributeGroupMember;
  69import com.thaiopensource.xml.dtd.om.AttributeGroupVisitor;
  70import com.thaiopensource.xml.dtd.om.Datatype;
  71import com.thaiopensource.xml.dtd.om.DatatypeVisitor;
  72import com.thaiopensource.xml.dtd.om.Def;
  73import com.thaiopensource.xml.dtd.om.Dtd;
  74import com.thaiopensource.xml.dtd.om.EnumGroup;
  75import com.thaiopensource.xml.dtd.om.EnumGroupVisitor;
  76import com.thaiopensource.xml.dtd.om.Flag;
  77import com.thaiopensource.xml.dtd.om.ModelGroup;
  78import com.thaiopensource.xml.dtd.om.ModelGroupVisitor;
  79import com.thaiopensource.xml.dtd.om.NameSpec;
  80import com.thaiopensource.xml.dtd.om.TokenizedDatatype;
  81import com.thaiopensource.xml.dtd.om.TopLevel;
  82import com.thaiopensource.xml.dtd.om.TopLevelVisitor;
  83import com.thaiopensource.xml.em.ExternalId;
  84import com.thaiopensource.xml.util.WellKnownNamespaces;
  85
  86import java.util.Collections;
  87import java.util.HashMap;
  88import java.util.HashSet;
  89import java.util.List;
  90import java.util.Map;
  91import java.util.Set;
  92import java.util.Vector;
  93
  94/* copy of http://jing-trang.googlecode.com/svn/tags/V20091111/mod/convert-from-dtd/src/main/com/thaiopensource/relaxng/input/dtd/Converter.java
  95 * it is necessary since the Options class is package protected and I need to create one in BufferDtdInputFormat
  96 */
  97public class BufferDtdConverter {
  98  static class Options {
  99    boolean inlineAttlistDecls;
 100    boolean generateStart = true;
 101    boolean strictAny;
 102    String elementDeclPattern;
 103    String attlistDeclPattern;
 104    String colonReplacement;
 105    String anyName;
 106    String annotationPrefix;
 107    String defaultNamespace;
 108    final Map<String, String> prefixMap = new HashMap<String, String>();
 109  }
 110
 111  private final Dtd dtd;
 112  private final ErrorReporter er;
 113  private final SchemaCollection sc = new SchemaCollection();
 114  private final Options options;
 115  /**
 116   * true if any uses of ANY have been encountered in the DTD
 117   */
 118  private boolean hadAny = false;
 119  /**
 120   * true if any default values have been encountered in the DTD
 121   */
 122  private boolean hadDefaultValue = false;
 123  /**
 124   * Maps each element name to an Integer containing a set of flags.
 125   */
 126  private final Map<String, Integer> elementNameTable = new HashMap<String, Integer>();
 127  /**
 128   * Maps each element name to a List of attribute groups of each attlist declaration.
 129   */
 130  private final Map<String, List<AttributeGroup>> attlistDeclTable = new HashMap<String, List<AttributeGroup>>();
 131  /**
 132   * Set of strings representing names for which there are definitions in the DTD.
 133   */
 134  private final Set<String> definedNames = new HashSet<String>();
 135  /**
 136   * Maps prefixes to namespace URIs.
 137   */
 138  private final Map<String, String> prefixTable = new HashMap<String, String>();
 139
 140  /**
 141   * Maps a string representing an element name to the set of names of attributes
 142   * that have been declated for that element.
 143   */
 144  private final Map<String, Set<String>> attributeNamesTable = new HashMap<String, Set<String>>();
 145  /**
 146   * Contains the set of attribute names that have already been output in the current scope.
 147   */
 148  private Set<String> attributeNames = null;
 149  private String defaultNamespace = null;
 150  private String annotationPrefix = null;
 151
 152  // These variables control the names use for definitions.
 153  private String colonReplacement = null;
 154  private String elementDeclPattern = null;
 155  private String attlistDeclPattern = null;
 156  private String anyName = null;
 157
 158  /**
 159   * Flags for element names used in elementDeclTable.
 160   */
 161  private static final int ELEMENT_DECL = 01;
 162  private static final int ATTLIST_DECL = 02;
 163  private static final int ELEMENT_REF = 04;
 164
 165  /**
 166   * Characters that will be considered for use as a replacement for colon in
 167   * a QName.  Also used as separators in constructing names of definitions
 168   * corresponding to element declarations and attlist declarations,
 169   */
 170  private static final String SEPARATORS = ".-_";
 171
 172  // # is the category; % is the name in the category
 173
 174  private static final String DEFAULT_PATTERN = "#.%";
 175
 176  private final String[] ELEMENT_KEYWORDS = {
 177    "element", "elem", "e"
 178  };
 179
 180  private final String[] ATTLIST_KEYWORDS = {
 181    "attlist", "attributes", "attribs", "atts", "a"
 182  };
 183
 184  private final String[] ANY_KEYWORDS = {
 185    "any", "ANY", "anyElement"
 186  };
 187
 188  private static abstract class VisitorBase implements TopLevelVisitor {
 189    public void processingInstruction(String target, String value) throws Exception { }
 190    public void comment(String value) throws Exception { }
 191    public void flagDef(String name, Flag flag) throws Exception { }
 192    public void includedSection(Flag flag, TopLevel[] contents)
 193      throws Exception {
 194      for (int i = 0; i < contents.length; i++)
 195	contents[i].accept(this);
 196    }
 197
 198    public void ignoredSection(Flag flag, String contents) throws Exception { }
 199    public void internalEntityDecl(String name, String value) throws Exception { }
 200    public void externalEntityDecl(String name, ExternalId externalId) throws Exception { }
 201    public void notationDecl(String name, ExternalId externalId) throws Exception { }
 202    public void nameSpecDef(String name, NameSpec nameSpec) throws Exception { }
 203    public void overriddenDef(Def def, boolean isDuplicate) throws Exception { }
 204    public void externalIdDef(String name, ExternalId externalId) throws Exception { }
 205    public void externalIdRef(String name, ExternalId externalId,
 206			      String uri, String encoding, TopLevel[] contents)
 207      throws Exception {
 208      for (int i = 0; i < contents.length; i++)
 209	contents[i].accept(this);
 210    }
 211    public void paramDef(String name, String value) throws Exception { }
 212    public void attributeDefaultDef(String name, AttributeDefault ad) throws Exception { }
 213  }
 214
 215
 216  private class Analyzer extends VisitorBase implements ModelGroupVisitor,
 217							AttributeGroupVisitor {
 218    public void elementDecl(NameSpec nameSpec, ModelGroup modelGroup)
 219      throws Exception {
 220      noteElementName(nameSpec.getValue(), ELEMENT_DECL);
 221      modelGroup.accept(this);
 222    }
 223
 224    public void attlistDecl(NameSpec nameSpec, AttributeGroup attributeGroup)
 225      throws Exception {
 226      noteElementName(nameSpec.getValue(), ATTLIST_DECL);
 227      noteAttlist(nameSpec.getValue(), attributeGroup);
 228      attributeGroup.accept(this);
 229    }
 230
 231    public void modelGroupDef(String name, ModelGroup modelGroup)
 232      throws Exception {
 233      noteDef(name);
 234      modelGroup.accept(this);
 235    }
 236
 237    public void attributeGroupDef(String name, AttributeGroup attributeGroup)
 238      throws Exception {
 239      noteDef(name);
 240      attributeGroup.accept(this);
 241    }
 242
 243    public void enumGroupDef(String name, EnumGroup enumGroup) {
 244      noteDef(name);
 245    }
 246
 247    public void datatypeDef(String name, Datatype datatype) {
 248      noteDef(name);
 249    }
 250
 251    public void choice(ModelGroup[] members) throws Exception {
 252      for (int i = 0; i < members.length; i++)
 253	members[i].accept(this);
 254    }
 255
 256    public void sequence(ModelGroup[] members) throws Exception {
 257      for (int i = 0; i < members.length; i++)
 258	members[i].accept(this);
 259    }
 260
 261    public void oneOrMore(ModelGroup member) throws Exception {
 262      member.accept(this);
 263    }
 264
 265    public void zeroOrMore(ModelGroup member) throws Exception {
 266      member.accept(this);
 267    }
 268
 269    public void optional(ModelGroup member) throws Exception {
 270      member.accept(this);
 271    }
 272
 273    public void modelGroupRef(String name, ModelGroup modelGroup) {
 274    }
 275
 276    public void elementRef(NameSpec name) {
 277      noteElementName(name.getValue(), ELEMENT_REF);
 278    }
 279
 280    public void pcdata() {
 281    }
 282
 283    public void any() {
 284      hadAny = true;
 285    }
 286
 287    public void attribute(NameSpec nameSpec,
 288			  Datatype datatype,
 289			  AttributeDefault attributeDefault) {
 290      noteAttribute(nameSpec.getValue(), attributeDefault.getDefaultValue());
 291    }
 292
 293    public void attributeGroupRef(String name, AttributeGroup attributeGroup) {
 294    }
 295
 296  }
 297
 298
 299  private class ComponentOutput extends VisitorBase {
 300    private final List<Component> components;
 301    private final Annotated grammar;
 302    private List<Comment> comments = null;
 303
 304    ComponentOutput(GrammarPattern grammar) {
 305      components = grammar.getComponents();
 306      this.grammar = grammar;
 307    }
 308
 309    void finish() {
 310      if (comments != null)
 311        grammar.getFollowingElementAnnotations().addAll(comments);
 312    }
 313
 314    private void addComponent(Component c) {
 315      if (comments != null) {
 316        if (components.isEmpty())
 317          grammar.getLeadingComments().addAll(comments);
 318        else
 319          c.getLeadingComments().addAll(comments);
 320        comments = null;
 321      }
 322      components.add(c);
 323    }
 324
 325    public void elementDecl(NameSpec nameSpec, ModelGroup modelGroup) throws Exception {
 326      GroupPattern gp = new GroupPattern();
 327      if (options.inlineAttlistDecls) {
 328        List<AttributeGroup> groups = attlistDeclTable.get(nameSpec.getValue());
 329        if (groups != null) {
 330          attributeNames = new HashSet<String>();
 331          AttributeGroupVisitor agv = new AttributeGroupOutput(gp);
 332          for (AttributeGroup group : groups)
 333            group.accept(agv);
 334        }
 335      }
 336      else
 337        gp.getChildren().add(ref(attlistDeclName(nameSpec.getValue())));
 338      Pattern pattern = convert(modelGroup);
 339      if (gp.getChildren().size() > 0) {
 340        if (pattern instanceof GroupPattern)
 341          gp.getChildren().addAll(((GroupPattern)pattern).getChildren());
 342        else
 343          gp.getChildren().add(pattern);
 344        pattern = gp;
 345      }
 346      addComponent(new DefineComponent(elementDeclName(nameSpec.getValue()),
 347                                       new ElementPattern(convertQName(nameSpec.getValue(), true),
 348                                                          pattern)));
 349      if (!options.inlineAttlistDecls && (nameFlags(nameSpec.getValue()) & ATTLIST_DECL) == 0) {
 350        DefineComponent dc = new DefineComponent(attlistDeclName(nameSpec.getValue()), new EmptyPattern());
 351        dc.setCombine(Combine.INTERLEAVE);
 352        addComponent(dc);
 353      }
 354      if (anyName != null && options.strictAny) {
 355        DefineComponent dc = new DefineComponent(anyName, ref(elementDeclName(nameSpec.getValue())));
 356        dc.setCombine(Combine.CHOICE);
 357        addComponent(dc);
 358      }
 359    }
 360
 361    public void attlistDecl(NameSpec nameSpec, AttributeGroup attributeGroup) throws Exception {
 362      if (options.inlineAttlistDecls)
 363        return;
 364      String name = nameSpec.getValue();
 365      attributeNames
 366	= attributeNamesTable.get(name);
 367      if (attributeNames == null) {
 368	attributeNames = new HashSet<String>();
 369	attributeNamesTable.put(name, attributeNames);
 370      }
 371      Pattern pattern = convert(attributeGroup);
 372      if (pattern instanceof EmptyPattern) {
 373        // Only keep an empty definition if this is the first attlist for this element,
 374        // and all attlists are also empty.  In this case, if we didn't keep the
 375        // definition, we would have no definition for the attlist.
 376        List<AttributeGroup> decls = attlistDeclTable.get(name);
 377        if (decls.get(0) != attributeGroup)
 378          return;
 379        attributeNames = new HashSet<String>();
 380        for (int i = 1, len = decls.size(); i < len; i++)
 381          if (!(convert(decls.get(i)) instanceof EmptyPattern))
 382            return;
 383      }
 384      DefineComponent dc = new DefineComponent(attlistDeclName(name), pattern);
 385      dc.setCombine(Combine.INTERLEAVE);
 386      addComponent(dc);
 387    }
 388
 389    public void modelGroupDef(String name, ModelGroup modelGroup)
 390      throws Exception {
 391      addComponent(new DefineComponent(name, convert(modelGroup)));
 392    }
 393
 394    public void attributeGroupDef(String name, AttributeGroup attributeGroup)
 395            throws Exception {
 396      // This takes care of duplicates within the group
 397      attributeNames = new HashSet<String>();
 398      Pattern pattern;
 399      AttributeGroupMember[] members = attributeGroup.getMembers();
 400      GroupPattern group = new GroupPattern();
 401      AttributeGroupVisitor agv = new AttributeGroupOutput(group);
 402      for (int i = 0; i < members.length; i++)
 403        members[i].accept(agv);
 404      switch (group.getChildren().size()) {
 405      case 0:
 406        pattern = new EmptyPattern();
 407        break;
 408      case 1:
 409        pattern = group.getChildren().get(0);
 410        break;
 411      default:
 412        pattern = group;
 413        break;
 414      }
 415      addComponent(new DefineComponent(name, pattern));
 416    }
 417
 418    public void enumGroupDef(String name, EnumGroup enumGroup) throws Exception {
 419      ChoicePattern choice = new ChoicePattern();
 420      enumGroup.accept(new EnumGroupOutput(choice));
 421      Pattern pattern;
 422      switch (choice.getChildren().size()) {
 423      case 0:
 424        pattern = new NotAllowedPattern();
 425        break;
 426      case 1:
 427        pattern = choice.getChildren().get(0);
 428        break;
 429      default:
 430        pattern = choice;
 431        break;
 432      }
 433      addComponent(new DefineComponent(name, pattern));
 434    }
 435
 436    public void datatypeDef(String name, Datatype datatype) throws Exception {
 437      addComponent(new DefineComponent(name, convert(datatype)));
 438    }
 439
 440    public void comment(String value) {
 441      if (comments == null)
 442        comments = new Vector<Comment>();
 443      comments.add(new Comment(CommentTrimmer.trimComment(value)));
 444    }
 445
 446    public void externalIdRef(String name, ExternalId externalId,
 447			      String uri, String encoding, TopLevel[] contents)
 448      throws Exception {
 449      if (uri == null) {
 450        // I don't think this can happen
 451	super.externalIdRef(name, externalId, uri, encoding, contents);
 452	return;
 453      }
 454      SignificanceDetector sd = new SignificanceDetector();
 455      try {
 456	sd.externalIdRef(name, externalId, uri, encoding, contents);
 457	if (!sd.significant)
 458	  return;
 459      }
 460      catch (Exception e) {
 461	throw (RuntimeException)e;
 462      }
 463      if (sc.getSchemaDocumentMap().get(uri) != null) {
 464        // I don't think this can happen because the second and subsequent inclusions
 465        // will never pass the SignificanceDetector, but just in case
 466        super.externalIdRef(name, externalId, uri, encoding, contents);
 467        return;
 468      }
 469      IncludeComponent ic = new IncludeComponent(uri);
 470      ic.setNs(defaultNamespace);
 471      addComponent(ic);
 472      GrammarPattern included = new GrammarPattern();
 473      ComponentOutput co = new ComponentOutput(included);
 474      for (int i = 0; i < contents.length; i++)
 475        contents[i].accept(co);
 476      co.finish();
 477      sc.getSchemaDocumentMap().put(uri, new SchemaDocument(included, encoding));
 478    }
 479
 480  }
 481
 482  private class AttributeGroupOutput implements AttributeGroupVisitor {
 483    final List<Pattern> group;
 484
 485    AttributeGroupOutput(GroupPattern gp) {
 486      group = gp.getChildren();
 487    }
 488
 489    public void attribute(NameSpec nameSpec,
 490                          Datatype datatype,
 491                          AttributeDefault attributeDefault) throws Exception {
 492      String name = nameSpec.getValue();
 493      if (attributeNames.contains(name))
 494        return;
 495      attributeNames.add(name);
 496      if (name.equals("xmlns") || name.startsWith("xmlns:"))
 497        return;
 498      String dv = attributeDefault.getDefaultValue();
 499      String fv = attributeDefault.getFixedValue();
 500      Pattern dt;
 501      if (fv != null) {
 502        String[] typeName = valueType(datatype);
 503        dt = new ValuePattern(typeName[0], typeName[1], fv);
 504      }
 505      else if (datatype.getType() != Datatype.CDATA)
 506        dt = convert(datatype);
 507      else
 508        dt = new TextPattern();
 509      AttributePattern pattern = new AttributePattern(convertQName(name, false), dt);
 510      if (dv != null) {
 511        AttributeAnnotation anno = new AttributeAnnotation(WellKnownNamespaces.RELAX_NG_COMPATIBILITY_ANNOTATIONS, "defaultValue", dv);
 512        anno.setPrefix(annotationPrefix);
 513        pattern.getAttributeAnnotations().add(anno);
 514      }
 515      if (!attributeDefault.isRequired())
 516        group.add(new OptionalPattern(pattern));
 517      else
 518        group.add(pattern);
 519    }
 520
 521    public void attributeGroupRef(String name, AttributeGroup attributeGroup)
 522            throws Exception {
 523      DuplicateAttributeDetector detector = new DuplicateAttributeDetector();
 524      attributeGroup.accept(detector);
 525      if (detector.containsDuplicate)
 526        attributeGroup.accept(this);
 527      else {
 528        group.add(ref(name));
 529        attributeNames.addAll(detector.names);
 530      }
 531    }
 532
 533
 534   }
 535
 536  private class DatatypeOutput implements DatatypeVisitor {
 537    Pattern pattern;
 538
 539    public void cdataDatatype() {
 540      pattern = new DataPattern("", "string");
 541    }
 542
 543    public void tokenizedDatatype(String typeName) {
 544      pattern = new DataPattern(WellKnownNamespaces.XML_SCHEMA_DATATYPES, typeName);
 545    }
 546
 547    public void enumDatatype(EnumGroup enumGroup) throws Exception {
 548      if (enumGroup.getMembers().length == 0)
 549        pattern = new NotAllowedPattern();
 550      else {
 551        ChoicePattern tem = new ChoicePattern();
 552        pattern = tem;
 553        enumGroup.accept(new EnumGroupOutput(tem));
 554      }
 555    }
 556
 557    public void notationDatatype(EnumGroup enumGroup) throws Exception {
 558      enumDatatype(enumGroup);
 559    }
 560
 561    public void datatypeRef(String name, Datatype datatype) {
 562      pattern = ref(name);
 563    }
 564  }
 565
 566  private class EnumGroupOutput implements EnumGroupVisitor {
 567    final private List<Pattern> list;
 568
 569    EnumGroupOutput(ChoicePattern choice) {
 570      list = choice.getChildren();
 571    }
 572
 573    public void enumValue(String value) {
 574      list.add(new ValuePattern("", "token", value));
 575     }
 576
 577     public void enumGroupRef(String name, EnumGroup enumGroup) {
 578       list.add(ref(name));
 579     }
 580  }
 581
 582  private class ModelGroupOutput implements ModelGroupVisitor {
 583    private Pattern pattern;
 584
 585    public void choice(ModelGroup[] members) throws Exception {
 586      if (members.length == 0)
 587        pattern = new NotAllowedPattern();
 588      else if (members.length == 1)
 589	members[0].accept(this);
 590      else {
 591        ChoicePattern tem = new ChoicePattern();
 592        pattern = tem;
 593        List<Pattern> children = tem.getChildren();
 594	for (int i = 0; i < members.length; i++)
 595          children.add(convert(members[i]));
 596      }
 597    }
 598
 599    public void sequence(ModelGroup[] members) throws Exception {
 600      if (members.length == 0)
 601        pattern = new EmptyPattern();
 602      else if (members.length == 1)
 603	members[0].accept(this);
 604      else {
 605        GroupPattern tem = new GroupPattern();
 606        pattern = tem;
 607        List<Pattern> children = tem.getChildren();
 608	for (int i = 0; i < members.length; i++)
 609	  children.add(convert(members[i]));
 610      }
 611    }
 612
 613    public void oneOrMore(ModelGroup member) throws Exception {
 614      pattern = new OneOrMorePattern(convert(member));
 615    }
 616
 617    public void zeroOrMore(ModelGroup member) throws Exception {
 618      pattern = new ZeroOrMorePattern(convert(member));
 619    }
 620
 621    public void optional(ModelGroup member) throws Exception {
 622      pattern = new OptionalPattern(convert(member));
 623    }
 624
 625    public void modelGroupRef(String name, ModelGroup modelGroup) {
 626      pattern = ref(name);
 627    }
 628
 629    public void elementRef(NameSpec name) {
 630      pattern = ref(elementDeclName(name.getValue()));
 631    }
 632
 633    public void pcdata() {
 634      pattern = new TextPattern();
 635    }
 636
 637    public void any() {
 638      pattern = ref(anyName);
 639      if (options.strictAny)
 640        pattern = new ZeroOrMorePattern(pattern);
 641    }
 642
 643  }
 644
 645
 646  private class DuplicateAttributeDetector implements AttributeGroupVisitor {
 647    private boolean containsDuplicate = false;
 648    private final List<String> names = new Vector<String>();
 649
 650    public void attribute(NameSpec nameSpec,
 651			  Datatype datatype,
 652			  AttributeDefault attributeDefault) {
 653      String name = nameSpec.getValue();
 654      if (attributeNames.contains(name))
 655	containsDuplicate = true;
 656      names.add(name);
 657    }
 658
 659    public void attributeGroupRef(String name, AttributeGroup attributeGroup) throws Exception {
 660      attributeGroup.accept(this);
 661    }
 662
 663  }
 664
 665  private class SignificanceDetector extends VisitorBase {
 666    boolean significant = false;
 667    public void elementDecl(NameSpec nameSpec, ModelGroup modelGroup)
 668      throws Exception {
 669      significant = true;
 670    }
 671
 672    public void attlistDecl(NameSpec nameSpec, AttributeGroup attributeGroup)
 673      throws Exception {
 674      significant = true;
 675    }
 676
 677    public void modelGroupDef(String name, ModelGroup modelGroup)
 678      throws Exception {
 679      significant = true;
 680    }
 681
 682    public void attributeGroupDef(String name, AttributeGroup attributeGroup)
 683      throws Exception {
 684      significant = true;
 685    }
 686
 687    public void enumGroupDef(String name, EnumGroup enumGroup) {
 688      significant = true;
 689    }
 690
 691    public void datatypeDef(String name, Datatype datatype) {
 692      significant = true;
 693    }
 694  }
 695
 696  public BufferDtdConverter(Dtd dtd, ErrorReporter er, Options options) {
 697    this.dtd = dtd;
 698    this.er = er;
 699    this.options = options;
 700  }
 701
 702  public SchemaCollection convert() {
 703    try {
 704      dtd.accept(new Analyzer());
 705      chooseNames();
 706      GrammarPattern grammar = new GrammarPattern();
 707      sc.setMainUri(dtd.getUri());
 708      sc.getSchemaDocumentMap().put(dtd.getUri(),
 709                                    new SchemaDocument(grammar, dtd.getEncoding()));
 710      ComponentOutput co = new ComponentOutput(grammar);
 711      dtd.accept(co);
 712      outputUndefinedElements(grammar.getComponents());
 713      if (options.generateStart)
 714        outputStart(grammar.getComponents());
 715      outputAny(grammar.getComponents());
 716      co.finish();
 717      return sc;
 718    }
 719    catch (Exception e) {
 720      throw (RuntimeException)e;
 721    }
 722  }
 723
 724  private void chooseNames() {
 725    chooseAny();
 726    chooseColonReplacement();
 727    chooseDeclPatterns();
 728    choosePrefixes();
 729    chooseAnnotationPrefix();
 730  }
 731
 732  private void chooseAny() {
 733    if (!hadAny)
 734      return;
 735    if (options.anyName != null) {
 736      if (!definedNames.contains(options.anyName)) {
 737        anyName = options.anyName;
 738        definedNames.add(anyName);
 739        return;
 740      }
 741      warning("cannot_use_any_name");
 742    }
 743    for (int n = 0;; n++) {
 744      for (int i = 0; i < ANY_KEYWORDS.length; i++) {
 745	anyName = repeatChar('_', n) + ANY_KEYWORDS[i];
 746	if (!definedNames.contains(anyName)) {
 747	  definedNames.add(anyName);
 748	  return;
 749	}
 750      }
 751    }
 752  }
 753
 754  private void choosePrefixes() {
 755    if (options.defaultNamespace != null) {
 756      if (defaultNamespace != null && !defaultNamespace.equals(options.defaultNamespace))
 757        warning("default_namespace_conflict");
 758      defaultNamespace = options.defaultNamespace;
 759    }
 760    else if (defaultNamespace == null)
 761      defaultNamespace = NameClass.INHERIT_NS;
 762    for (Map.Entry<String, String> entry : options.prefixMap.entrySet()) {
 763      String prefix = entry.getKey();
 764      String ns = entry.getValue();
 765      String s = prefixTable.get(prefix);
 766      if (s == null)
 767        warning("irrelevant_prefix", prefix);
 768      else {
 769        if (!s.equals("") && !s.equals(ns))
 770          warning("prefix_conflict", prefix);
 771        prefixTable.put(prefix, ns);
 772      }
 773    }
 774  }
 775
 776  private void chooseAnnotationPrefix() {
 777    if (!hadDefaultValue)
 778      return;
 779    if (options.annotationPrefix != null) {
 780      if (prefixTable.get(options.annotationPrefix) == null) {
 781        annotationPrefix = options.annotationPrefix;
 782        return;
 783      }
 784      warning("cannot_use_annotation_prefix");
 785    }
 786    for (int n = 0;; n++) {
 787      annotationPrefix = repeatChar('_', n) + "a";
 788      if (prefixTable.get(annotationPrefix) == null)
 789	return;
 790    }
 791  }
 792
 793  private void chooseColonReplacement() {
 794    if (options.colonReplacement != null) {
 795      colonReplacement = options.colonReplacement;
 796      if (colonReplacementOk())
 797        return;
 798      warning("cannot_use_colon_replacement");
 799      colonReplacement = null;
 800    }
 801    if (colonReplacementOk())
 802      return;
 803    for (int n = 1;; n++) {
 804      for (int i = 0; i < SEPARATORS.length(); i++) {
 805	colonReplacement = repeatChar(SEPARATORS.charAt(i), n);
 806	if (colonReplacementOk())
 807	  return;
 808      }
 809    }
 810  }
 811
 812  private boolean colonReplacementOk() {
 813    Set<String> names = new HashSet<String>();
 814    for (String s : elementNameTable.keySet()) {
 815      String name = mungeQName(s);
 816      if (names.contains(name))
 817        return false;
 818      names.add(name);
 819    }
 820    return true;
 821  }
 822
 823  private void chooseDeclPatterns() {
 824    if (options.elementDeclPattern != null) {
 825      if (patternOk(options.elementDeclPattern, null))
 826        elementDeclPattern = options.elementDeclPattern;
 827      else
 828        warning("cannot_use_element_decl_pattern");
 829    }
 830    if (options.attlistDeclPattern != null) {
 831      if (patternOk(options.attlistDeclPattern, elementDeclPattern))
 832        attlistDeclPattern = options.attlistDeclPattern;
 833      else
 834        warning("cannot_use_attlist_decl_pattern");
 835    }
 836    if (elementDeclPattern != null && attlistDeclPattern != null)
 837      return;
 838    // XXX Try to match length and case of best prefix
 839    String pattern = namingPattern();
 840    if (elementDeclPattern == null) {
 841      if (patternOk("%", attlistDeclPattern))
 842        elementDeclPattern = "%";
 843      else
 844        elementDeclPattern = choosePattern(pattern, ELEMENT_KEYWORDS, attlistDeclPattern);
 845    }
 846    if (attlistDeclPattern == null)
 847      attlistDeclPattern = choosePattern(pattern, ATTLIST_KEYWORDS, elementDeclPattern);
 848  }
 849
 850  private String choosePattern(String metaPattern, String[] keywords, String otherPattern) {
 851    for (;;) {
 852      for (int i = 0; i < keywords.length; i++) {
 853	String pattern = substitute(metaPattern, '#', keywords[i]);
 854	if (patternOk(pattern, otherPattern))
 855	  return pattern;
 856      }
 857      // add another separator
 858      metaPattern = (metaPattern.substring(0, 1)
 859		     + metaPattern.substring(1, 2)
 860		     + metaPattern.substring(1, 2)
 861		     + metaPattern.substring(2));
 862    }
 863  }
 864
 865  private String namingPattern() {
 866    Map<String, Integer> patternTable = new HashMap<String, Integer>();
 867    for (String name : definedNames) {
 868      for (int i = 0; i < SEPARATORS.length(); i++) {
 869        char sep = SEPARATORS.charAt(i);
 870        int k = name.indexOf(sep);
 871        if (k > 0)
 872          inc(patternTable, name.substring(0, k + 1) + "%");
 873        k = name.lastIndexOf(sep);
 874        if (k >= 0 && k < name.length() - 1)
 875          inc(patternTable, "%" + name.substring(k));
 876      }
 877    }
 878    String bestPattern = null;
 879    int bestCount = 0;
 880    for (Map.Entry<String, Integer> entry : patternTable.entrySet()) {
 881      int count = entry.getValue();
 882      if (bestPattern == null || count > bestCount) {
 883        bestCount = count;
 884        bestPattern = entry.getKey();
 885      }
 886    }
 887    if (bestPattern == null)
 888      return DEFAULT_PATTERN;
 889    if (bestPattern.charAt(0) == '%')
 890      return bestPattern.substring(0, 2) + "#";
 891    else
 892      return "#" + bestPattern.substring(bestPattern.length() - 2);
 893  }
 894
 895  private static void inc(Map<String, Integer> table, String str) {
 896    Integer n = table.get(str);
 897    if (n == null)
 898      table.put(str, 1);
 899    else
 900      table.put(str, n + 1);
 901  }
 902
 903  private boolean patternOk(String pattern, String otherPattern) {
 904    Set<String> usedNames = new HashSet<String>();
 905    for (String s : elementNameTable.keySet()) {
 906      String name = mungeQName(s);
 907      String declName = substitute(pattern, '%', name);
 908      if (definedNames.contains(declName))
 909        return false;
 910      if (otherPattern != null) {
 911        String otherDeclName = substitute(otherPattern, '%', name);
 912        if (usedNames.contains(declName)
 913            || usedNames.contains(otherDeclName)
 914            || declName.equals(otherDeclName))
 915          return false;
 916        usedNames.add(declName);
 917        usedNames.add(otherDeclName);
 918      }
 919    }
 920    return true;
 921  }
 922
 923  private void noteDef(String name) {
 924    definedNames.add(name);
 925  }
 926
 927  private void noteElementName(String name, int flags) {
 928    Integer n = elementNameTable.get(name);
 929    if (n != null) {
 930      flags |= n;
 931      if (n == flags)
 932	return;
 933    }
 934    else
 935      noteNamePrefix(name);
 936    elementNameTable.put(name, flags);
 937  }
 938
 939  private void noteAttlist(String name, AttributeGroup group) {
 940    List<AttributeGroup> groups = attlistDeclTable.get(name);
 941    if (groups == null) {
 942      groups = new Vector<AttributeGroup>();
 943      attlistDeclTable.put(name, groups);
 944    }
 945    groups.add(group);
 946  }
 947
 948  private void noteAttribute(String name, String defaultValue) {
 949    if (name.equals("xmlns")) {
 950      if (defaultValue != null) {
 951	if (defaultNamespace != null
 952	    && !defaultNamespace.equals(defaultValue))
 953	  error("INCONSISTENT_DEFAULT_NAMESPACE");
 954	else
 955	  defaultNamespace = defaultValue;
 956      }
 957    }
 958    else if (name.startsWith("xmlns:")) {
 959      if (defaultValue != null) {
 960	String prefix = name.substring(6);
 961	String ns = prefixTable.get(prefix);
 962	if (ns != null
 963	    && !ns.equals("")
 964	    && !ns.equals(defaultValue))
 965	  error("INCONSISTENT_PREFIX", prefix);
 966	else if (!prefix.equals("xml"))
 967	  prefixTable.put(prefix, defaultValue);
 968      }
 969    }
 970    else {
 971      if (defaultValue != null)
 972	hadDefaultValue = true;
 973      noteNamePrefix(name);
 974    }
 975  }
 976
 977  private void noteNamePrefix(String name) {
 978    int i = name.indexOf(':');
 979    if (i < 0)
 980      return;
 981    String prefix = name.substring(0, i);
 982    if (prefixTable.get(prefix) == null && !prefix.equals("xml"))
 983      prefixTable.put(prefix, "");
 984  }
 985
 986  private int nameFlags(String name) {
 987    Integer n = elementNameTable.get(name);
 988    if (n == null)
 989      return 0;
 990    return n;
 991  }
 992
 993  private String elementDeclName(String name) {
 994    return substitute(elementDeclPattern, '%', mungeQName(name));
 995  }
 996
 997  private String attlistDeclName(String name) {
 998    return substitute(attlistDeclPattern, '%', mungeQName(name));
 999  }
1000
1001  private String mungeQName(String name) {
1002    if (colonReplacement == null) {
1003      int i = name.indexOf(':');
1004      if (i < 0)
1005	return name;
1006      return name.substring(i + 1);
1007    }
1008    return substitute(name, ':', colonReplacement);
1009  }
1010
1011  private static String repeatChar(char c, int n) {
1012    char[] buf = new char[n];
1013    for (int i = 0; i < n; i++)
1014      buf[i] = c;
1015    return new String(buf);
1016  }
1017
1018  /* Replace the first occurrence of ch in pattern by value. */
1019
1020  private static String substitute(String pattern, char ch, String value) {
1021    int i = pattern.indexOf(ch);
1022    if (i < 0)
1023      return pattern;
1024    StringBuffer buf = new StringBuffer();
1025    buf.append(pattern.substring(0, i));
1026    buf.append(value);
1027    buf.append(pattern.substring(i + 1));
1028    return buf.toString();
1029  }
1030
1031  private void outputStart(List<Component> components) {
1032    ChoicePattern choice = new ChoicePattern();
1033    // Use the defined but unreferenced elements.
1034    // If there aren't any, use all defined elements.
1035    int mask = ELEMENT_REF|ELEMENT_DECL;
1036    for (;;) {
1037      boolean gotOne = false;
1038      for (Map.Entry<String, Integer> entry : elementNameTable.entrySet()) {
1039        if ((entry.getValue() & mask) == ELEMENT_DECL) {
1040          gotOne = true;
1041          choice.getChildren().add(ref(elementDeclName(entry.getKey())));
1042        }
1043      }
1044      if (gotOne)
1045	break;
1046      if (mask == ELEMENT_DECL)
1047        return;
1048      mask = ELEMENT_DECL;
1049    }
1050    components.add(new DefineComponent(DefineComponent.START, choice));
1051  }
1052
1053  private void outputAny(List<Component> components) {
1054    if (!hadAny)
1055      return;
1056    if (options.strictAny) {
1057      DefineComponent dc = new DefineComponent(anyName, new TextPattern());
1058      dc.setCombine(Combine.CHOICE);
1059      components.add(dc);
1060    }
1061    else {
1062      // any = (element * { attribute * { text }*, any } | text)*
1063      CompositePattern group = new GroupPattern();
1064      group.getChildren().add(new ZeroOrMorePattern(new AttributePattern(new AnyNameNameClass(),
1065                                                                         new TextPattern())));
1066      group.getChildren().add(ref(anyName));
1067      CompositePattern choice = new ChoicePattern();
1068      choice.getChildren().add(new ElementPattern(new AnyNameNameClass(), group));
1069      choice.getChildren().add(new TextPattern());
1070      components.add(new DefineComponent(anyName, new ZeroOrMorePattern(choice)));
1071    }
1072  }
1073
1074  private void outputUndefinedElements(List<Component> components) {
1075    List<String> elementNames = new Vector<String>();
1076    elementNames.addAll(elementNameTable.keySet());
1077    Collections.sort(elementNames);
1078    for (String elementName : elementNames) {
1079      if ((elementNameTable.get(elementName) & ELEMENT_DECL) == 0) {
1080        DefineComponent dc = new DefineComponent(elementDeclName(elementName), new NotAllowedPattern());
1081        dc.setCombine(Combine.CHOICE);
1082        components.add(dc);
1083      }
1084    }
1085  }
1086
1087  static private Pattern ref(String name) {
1088    return new RefPattern(name);
1089  }
1090
1091  private void error(String key) {
1092    er.error(key, null);
1093  }
1094
1095  private void error(String key, String arg) {
1096    er.error(key, arg, null);
1097  }
1098
1099  private void warning(String key) {
1100    er.warning(key, null);
1101  }
1102
1103  private void warning(String key, String arg) {
1104    er.warning(key, arg, null);
1105  }
1106
1107  private static String[] valueType(Datatype datatype) {
1108    datatype = datatype.deref();
1109    switch (datatype.getType()) {
1110    case Datatype.CDATA:
1111      return new String[] { "", "string" };
1112    case Datatype.TOKENIZED:
1113      return new String[] { WellKnownNamespaces.XML_SCHEMA_DATATYPES, ((TokenizedDatatype)datatype).getTypeName() };
1114    }
1115    return new String[] { "", "token" };
1116  }
1117
1118  private Pattern convert(ModelGroup mg) throws Exception {
1119    ModelGroupOutput mgo = new ModelGroupOutput();
1120    mg.accept(mgo);
1121    return mgo.pattern;
1122  }
1123
1124  private Pattern convert(Datatype dt) throws Exception {
1125    DatatypeOutput dto = new DatatypeOutput();
1126    dt.accept(dto);
1127    return dto.pattern;
1128  }
1129
1130  private Pattern convert(AttributeGroup ag) throws Exception {
1131    GroupPattern group = new GroupPattern();
1132    ag.accept(new AttributeGroupOutput(group));
1133    switch (group.getChildren().size()) {
1134    case 0:
1135      return new EmptyPattern();
1136    case 1:
1137      return group.getChildren().get(0);
1138    }
1139    return group;
1140  }
1141
1142  private NameClass convertQName(String name, boolean useDefault) {
1143    int i = name.indexOf(':');
1144    if (i < 0)
1145      return new NameNameClass(useDefault ? defaultNamespace : "", name);
1146    String prefix = name.substring(0, i);
1147    String localName = name.substring(i + 1);
1148    String ns;
1149    if (prefix.equals("xml"))
1150      ns = WellKnownNamespaces.XML;
1151    else {
1152      ns = prefixTable.get(prefix);
1153      if (ns.equals("")) {
1154        error("UNDECLARED_PREFIX", prefix);
1155        ns = "##" + prefix;
1156        prefixTable.put(prefix, ns);
1157      }
1158    }
1159    NameNameClass nnc = new NameNameClass(ns, localName);
1160    nnc.setPrefix(prefix);
1161    return nnc;
1162  }
1163}
1164