public static void main(String[] args) throws Exception {
    java.io.File inFile = null;
    java.io.File outDir = null;
    if (args.length != 2
        || !(inFile = new java.io.File(args[0])).isFile()
        || !(outDir = new java.io.File(args[1])).isDirectory()) {
      System.err.println(
          "usage: java com.xmlmind.ditac.preprocess.LinkGenerator" + " in_map_file out_dir");
      System.exit(1);
    }

    LoadedDocuments loadedDocs = new LoadedDocuments();
    LoadedDocument loadedMap = loadedDocs.load(inFile);
    switch (loadedMap.type) {
      case BOOKMAP:
      case MAP:
        break;
      default:
        System.err.println("'" + inFile + "' does not contain a map");
        System.exit(1);
    }

    (new LinkGenerator())
        .processMap(loadedMap.document.getDocumentElement(), loadedMap.url, loadedDocs);

    java.util.Iterator<LoadedDocument> iter = loadedDocs.iterator();
    while (iter.hasNext()) {
      LoadedDocument loadedDoc = iter.next();
      if (loadedDoc != loadedMap) {
        java.io.File outFile =
            new java.io.File(outDir, com.xmlmind.util.URLUtil.getBaseName(loadedDoc.url));

        com.xmlmind.ditac.util.SaveDocument.save(loadedDoc.document, outFile);
      }
    }
  }
  private Entry createEntry(Element topicref, String href, LoadedDocuments loadedDocs) {
    // scope ---

    String scope = DITAUtil.getNonEmptyAttribute(topicref, null, "scope");

    // format ---

    String format = DITAUtil.getFormat(topicref, href);
    if (format == null) {
      console.warning(topicref, Msg.msg("missingAttribute", "format"));
      return null;
    }

    // linking ---

    Linking linking = Linking.NORMAL;

    String value = DITAUtil.getNonEmptyAttribute(topicref, null, "linking");
    if (value != null) {
      if ("none".equals(value)) {
        linking = Linking.NONE;
      } else if ("sourceonly".equals(value)) {
        linking = Linking.SOURCE_ONLY;
      } else if ("targetonly".equals(value)) {
        linking = Linking.TARGET_ONLY;
      } else if ("normal".equals(value)) {
        linking = Linking.NORMAL;
      } else {
        console.warning(topicref, Msg.msg("invalidAttribute", value, "linking"));
        linking = Linking.NONE;
      }
    }

    // linktext, shortdesc ---

    Element linktext = null;
    Element shortdesc = null;

    Element topicmeta = DITAUtil.findChildByClass(topicref, "map/topicmeta");
    if (topicmeta != null) {
      // Notice "map/linktext" and not "topic/linktext".
      linktext = DITAUtil.findChildByClass(topicmeta, "map/linktext");

      // Notice "map/shortdesc" and not "topic/shortdesc".
      shortdesc = DITAUtil.findChildByClass(topicmeta, "map/shortdesc");
    }

    // topicId, elementId, topic ---

    String topicId = null;
    String elementId = null;
    Element topic = null;

    if ((scope == null || "local".equals(scope)) && "dita".equals(format)) {
      // Points to a local topic.

      URL url;
      try {
        url = URLUtil.createURL(href);
      } catch (MalformedURLException ignored) {
        console.warning(topicref, Msg.msg("invalidAttribute", href, "href"));
        return null;
      }

      String fragment = URLUtil.getFragment(url);
      url = URLUtil.setFragment(url, null);

      // LoadedDocuments.get() is sufficient in production.
      // We use load() here just to be able to run the test drive below.
      LoadedDocument loadedDoc = null;
      try {
        loadedDoc = loadedDocs.load(url);
      } catch (Exception shouldNotHappen) {
      }

      if (loadedDoc != null) {
        if (fragment != null) {
          int pos = fragment.indexOf('/');
          if (pos > 0 && pos + 1 < fragment.length()) {
            topicId = fragment.substring(0, pos);
            elementId = fragment.substring(pos + 1);
          } else {
            topicId = fragment;
          }
        }

        LoadedTopic loadedTopic = null;
        if (topicId != null) {
          loadedTopic = loadedDoc.findTopicById(topicId);
        } else {
          loadedTopic = loadedDoc.getFirstTopic();
        }

        if (loadedTopic != null) {
          if (loadedTopic.isExcluded()) {
            return null;
          } else {
            topicId = loadedTopic.topicId;
            topic = loadedTopic.element;

            // Normalize href ---

            StringBuilder buffer = new StringBuilder(url.toExternalForm());
            buffer.append('#');
            buffer.append(URIComponent.quoteFragment(topicId));
            if (elementId != null) {
              buffer.append('/');
              buffer.append(URIComponent.quoteFragment(elementId));
            }
            href = buffer.toString();
          }
        } else {
          console.warning(topicref, Msg.msg("pointsOutsidePreprocessedTopics", href));
          return null;
        }
      } else {
        console.warning(topicref, Msg.msg("pointsOutsidePreprocessedTopics", href));
        return null;
      }
    } else {
      // Points to an external resource.

      if (linking != Linking.NONE) {
        linking = Linking.TARGET_ONLY;
      }
    }

    return new Entry(href, scope, format, linking, linktext, shortdesc, topicId, elementId, topic);
  }