home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1995 November / PCWK1195.iso / inne / win95 / sieciowe / hotja32.lzh / hotjava / classsrc / net / www / html / parser.java < prev    next >
Text File  |  1995-08-11  |  16KB  |  631 lines

  1. /*
  2.  * @(#)Parser.java    1.53 95/05/10 Jonathan Payne
  3.  *
  4.  * Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
  5.  *
  6.  * Permission to use, copy, modify, and distribute this software
  7.  * and its documentation for NON-COMMERCIAL purposes and without
  8.  * fee is hereby granted provided that this copyright notice
  9.  * appears in all copies. Please refer to the file "copyright.html"
  10.  * for further important copyright and licensing information.
  11.  *
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  13.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  14.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  15.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  16.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  17.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  18.  */
  19.  
  20. package net.www.html;
  21.  
  22. import java.util.*;
  23. import java.io.*;
  24.  
  25. /** Net.www.html.Parser takes an input stream and parses it for
  26.     html tags.  It produces an instance of net.www.html.Document
  27.     as a result. */
  28.  
  29. public class Parser {
  30.     final boolean debug = false;
  31.     static Hashtable    ampChars = new Hashtable();
  32.     static {
  33.     ampChars.put("lt", new Character('<'));
  34.     ampChars.put("gt", new Character('>'));
  35.     ampChars.put("amp", new Character('&'));
  36.     ampChars.put("quot", new Character('"'));
  37.     ampChars.put("nbsp", new Character(' '));   /* remind - incorrect */
  38.     ampChars.put("shy", new Character('-'));    /* remind - incorrect */
  39.  
  40.     /* NOTE: These are case SENSITIVE, e.g., AElig and aelig. */
  41.     ampChars.put("AElig", new Character(198));
  42.     ampChars.put("Aacute", new Character(193));
  43.     ampChars.put("Acirc", new Character(194));
  44.     ampChars.put("Agrave", new Character(192));
  45.     ampChars.put("Aring", new Character(197));
  46.     ampChars.put("Atilde", new Character(195));
  47.     ampChars.put("Auml", new Character(196));
  48.     ampChars.put("Ccedil", new Character(199));
  49.     ampChars.put("ETH", new Character(208));
  50.     ampChars.put("Eacute", new Character(201));
  51.     ampChars.put("Ecirc", new Character(202));
  52.     ampChars.put("Egrave", new Character(200));
  53.     ampChars.put("Euml", new Character(203));
  54.     ampChars.put("Iacute", new Character(205));
  55.     ampChars.put("Icirc", new Character(206));
  56.     ampChars.put("Igrave", new Character(204));
  57.     ampChars.put("Iuml", new Character(207));
  58.     ampChars.put("Ntilde", new Character(209));
  59.     ampChars.put("Oacute", new Character(211));
  60.     ampChars.put("Ocirc", new Character(212));
  61.     ampChars.put("Ograve", new Character(210));
  62.     ampChars.put("Oslash", new Character(216));
  63.     ampChars.put("Otilde", new Character(213));
  64.     ampChars.put("Ouml", new Character(214));
  65.     ampChars.put("THORN", new Character(222));
  66.     ampChars.put("Uacute", new Character(218));
  67.     ampChars.put("Ucirc", new Character(219));
  68.     ampChars.put("Ugrave", new Character(217));
  69.     ampChars.put("Uuml", new Character(220));
  70.     ampChars.put("Yacute", new Character(221));
  71.     ampChars.put("aacute", new Character(225));
  72.     ampChars.put("acirc", new Character(226));
  73.     ampChars.put("aelig", new Character(230));
  74.     ampChars.put("agrave", new Character(224));
  75.     ampChars.put("aring", new Character(229));
  76.     ampChars.put("atilde", new Character(227));
  77.     ampChars.put("auml", new Character(228));
  78.     ampChars.put("ccedil", new Character(231));
  79.     ampChars.put("eacute", new Character(233));
  80.     ampChars.put("ecirc", new Character(234));
  81.     ampChars.put("egrave", new Character(232));
  82.     ampChars.put("eth", new Character(240));
  83.     ampChars.put("euml", new Character(235));
  84.     ampChars.put("iacute", new Character(237));
  85.     ampChars.put("icirc", new Character(238));
  86.     ampChars.put("igrave", new Character(236));
  87.     ampChars.put("iuml", new Character(239));
  88.     ampChars.put("ntilde", new Character(241));
  89.     ampChars.put("oacute", new Character(243));
  90.     ampChars.put("ocirc", new Character(244));
  91.     ampChars.put("ograve", new Character(242));
  92.     ampChars.put("oslash", new Character(248));
  93.     ampChars.put("otilde", new Character(245));
  94.     ampChars.put("ouml", new Character(246));
  95.     ampChars.put("szlig", new Character(223));
  96.     ampChars.put("thorn", new Character(254));
  97.     ampChars.put("uacute", new Character(250));
  98.     ampChars.put("ucirc", new Character(251));
  99.     ampChars.put("ugrave", new Character(249));
  100.     ampChars.put("uuml", new Character(252));
  101.     ampChars.put("yacute", new Character(253));
  102.     ampChars.put("yuml", new Character(255));
  103.  
  104.     ampChars.put("copy", new Character(169));
  105.     ampChars.put("reg", new Character(174));
  106.     }
  107.  
  108.     /** usually the same as input except for ISINDEX (see below) */
  109.     byte    output[];
  110.  
  111.     /** input string we're parsing */
  112.     byte    input[];
  113.  
  114.     /** input length */
  115.     int        inputLength;
  116.  
  117.     /** current position in input */
  118.     int        inputSeek;        
  119.  
  120.     /** current line number */
  121.     int        lineCount = 1;  
  122.  
  123.     /** Document we're building. */
  124.     Document    html;
  125.  
  126.     /** Read a stream of bytes into a String object as quickly
  127.     as possible. */
  128.     private void readInput(InputStream in) {
  129.     input = new byte[1024*8];
  130.         output = input;
  131.  
  132.     inputLength = 0;
  133.  
  134.     int n;
  135.     while ((n = in.read(input, inputLength, input.length - inputLength)) >= 0) {
  136.         inputLength += n;
  137.         if (inputLength == input.length) {
  138.         byte newinput[] = new byte[inputLength * 2];
  139.         System.arraycopy(input, 0, newinput, 0, inputLength);
  140.         input = newinput;
  141.         }
  142.     }
  143.     }
  144.  
  145.     final int nextChar() {
  146.     if (inputSeek >= inputLength) {
  147.         return -1;
  148.     }
  149.  
  150.     int c = input[inputSeek++];
  151.     switch (c) {
  152.       case '\r':
  153.         if (inputSeek >= inputLength) {
  154.         lineCount++;
  155.         return '\n';
  156.         }
  157.         c = input[inputSeek++];
  158.         if (c != '\n') {
  159.         inputSeek--;
  160.         c = '\n';
  161.         }
  162.         if (c == '\n') {
  163.         lineCount++;
  164.         }
  165.         break;
  166.         
  167.       case '\n':
  168.         lineCount++;
  169.         break;
  170.     }
  171.     return c;
  172.     }
  173.  
  174.     final int peekChar() {
  175.     return (inputSeek >= inputLength) ? -1 : input[inputSeek];
  176.     }
  177.  
  178.     final void pushBack() {
  179.     if (input[--inputSeek] == '\n') {
  180.         lineCount--;
  181.     }
  182.     }
  183.  
  184.     final void skipWhiteSpace() {
  185.     int c;
  186.  
  187.     while ((c = nextChar()) == ' ' || c == '\n' || c == '\t')
  188.         ;
  189.     pushBack();
  190.     }
  191.  
  192.     final boolean isWhiteSpace(int c) {
  193.     return (c == ' ' || c == '\t' || c == '\n');
  194.     }
  195.  
  196.     void skipUntil(int what) {
  197.     int c;
  198.  
  199.     while ((c = nextChar()) != what) {
  200.         if (c == -1)
  201.         break;
  202.     }
  203.     pushBack();
  204.     }
  205.  
  206.     final boolean isLetter(int c) {
  207.     return (c >= 'A' && c <= 'Z' ||
  208.         c >= 'a' && c <= 'z');
  209.     }
  210.  
  211.     final boolean isDigit(int c) {
  212.     return (c >= '0' && c <= '9');
  213.     }
  214.  
  215.     final boolean isTagChar(int c) {
  216.     return (isLetter(c) || isDigit(c) || c == '.' || c == '-');
  217.     }
  218.  
  219.     int parseCharacter() {
  220.     int val = 0;
  221.     int c;
  222.  
  223.     insistThat(nextChar() == '#');
  224.     while (isDigit(c = nextChar())) {
  225.         val = val * 10 + c - '0';
  226.     }
  227.     if (c != ';') {
  228.         pushBack();
  229.     }
  230.     return val;
  231.     }
  232.  
  233.     int parseEntity() {
  234.     Character   ch;
  235.     String        name;
  236.     int        start = inputSeek;
  237.     int        c;
  238.  
  239.     if (!isLetter(peekChar())) {
  240.         return '&';
  241.     }
  242.     while ((c = nextChar()) != -1 && isLetter(c))
  243.         ;
  244.  
  245.     int lastc = c;
  246.     name = new String(input, 0, start, inputSeek - start - 1);
  247.     if ((ch = (Character) ampChars.get(name)) != null) {
  248.         c = ch.charValue();
  249.     } else {
  250.         name = name.toLowerCase();
  251.         if ((ch = (Character) ampChars.get(name)) != null) {
  252.         c = ch.charValue();
  253.         } else {
  254.         warning("Warning: failed to find: &" + name); 
  255.         c = -1;
  256.         }
  257.     }
  258.     if (lastc != ';') {
  259.         pushBack();
  260.     }
  261.     return c;
  262.     }
  263.  
  264.     String makeLowerCaseString(byte str[], int start, int len) {
  265.     return new String(str, 0, start, len).toLowerCase();
  266.     }
  267.  
  268.     String parseTagName() {
  269.     int c;
  270.     int start = inputSeek;
  271.  
  272.     if ((c = nextChar()) == '!') {
  273.         /* This is a comment, as far as mosaic is concerned,
  274.            so we just eat up all the characters until the '>',
  275.            and return 0 (which means ignore). */
  276.         skipUntil('>');
  277.         return "<comment>";
  278.     } else if (!isTagChar(c)) {
  279.         pushBack();        /* c */
  280.         return null;
  281.     }
  282.         
  283.     /* Read tag name until end.  Don't complain about illegal
  284.        tag names, because mosaic doesn't. */
  285.     while ((c = nextChar()) != -1 && isTagChar(c))
  286.         ;
  287.     pushBack();        /* push back the delimitor */
  288.     if (inputSeek - start == 0) {
  289.         return null;
  290.     }
  291.     return makeLowerCaseString(input, start, inputSeek - start);
  292.     }    
  293.  
  294.     final void warning(String msg) {
  295.     if (debug) {
  296.         System.out.print("Warning (line " + lineCount + "): ");
  297.         System.out.println(msg);
  298.     }
  299.     }
  300.  
  301.     private String  delim = "> =";
  302.     void parseAttributes(TagRef ref) {
  303.     int    start;
  304.     int    c;
  305.     String    name;
  306.     String    value;
  307.  
  308.     while (true) {
  309.         skipWhiteSpace();
  310.         start = inputSeek;
  311.         while ((c = nextChar()) != -1 && c != '>' && c != ' '
  312.            && c != '=' && c != '\n' && c != '\t')
  313.         ;
  314.         pushBack();
  315.         if (start == inputSeek) {
  316.         name = null;
  317.         } else {
  318.         name = makeLowerCaseString(input, start, inputSeek - start);
  319.         }
  320.         skipWhiteSpace();
  321.         if (name == null) {
  322.         return;
  323.         }
  324.         if (peekChar() == '=') {
  325.         nextChar();
  326.         skipWhiteSpace();
  327.         c = nextChar();
  328.  
  329.         int    match, cnt;
  330.  
  331.         if (c != '\'' && c != '"') {
  332.             pushBack();
  333.             start = inputSeek;
  334.             cnt = 0;
  335.             while ((c = nextChar()) != -1 && c != ' ' && c != '\t' && c != '\n' && c != '>')
  336.             ;
  337.         } else {
  338.             match = c;
  339.             start = inputSeek;
  340.             cnt = 0;
  341.             while ((c = nextChar()) != -1 && c != match && c != '>')
  342.             ;
  343.         }
  344.         if (c == -1) {
  345.             warning("unexpected EOF");
  346.             return;
  347.         }
  348.         value = new String(input, 0, start, inputSeek - start - 1);
  349.         if (c == '>') {
  350.             pushBack();
  351.         }
  352.         } else {
  353.         value = new String("true");
  354.         }
  355. //        System.out.println(name + ":" + value + ", line = " + lineCount);
  356.         if (ref != null) {
  357.         ref.addAttribute(name, value);
  358.         }
  359.     }
  360.     }
  361.  
  362.     void insistThat(boolean expr) {
  363.     if (!expr) {
  364.         throw new Exception("assertion failed: " + lineCount);
  365.     }
  366.     }
  367.  
  368.     Stack   tagStack = new Stack();
  369.  
  370.     boolean handleTag(Tag tag, boolean isEnd, int offset) {
  371.     if (isEnd) {
  372.         Tag    tos;
  373.         try {
  374.         tos = (Tag) tagStack.peek();
  375.  
  376.         if (tos != tag) {
  377.             if (tagStack.search(tag) == -1) {
  378.             warning("Ignoring tag: </" + tag.name + ">");
  379.             return false;        /* ignore this tag completely */
  380.             } else {
  381.             while (true) {
  382.                 Tag t = (Tag) tagStack.pop();
  383.  
  384.                 if (t != tag) {
  385.                 warning("Missing </" + t.name + "> just noticed by </" + tag.name + ">");
  386.                 html.endTag(t, offset);
  387.                 } else {
  388.                 break;
  389.                 }
  390.             }
  391.             }
  392.         } else {
  393.             if (tag.id == Tag.PRE) {
  394.             preFormatted = false;
  395.             }
  396.             tagStack.pop();
  397.         }
  398.         } catch (EmptyStackException e) {
  399.         warning("Ignoring tag: </" + tag.name + ">");
  400.         return false;
  401.         }
  402.     } else if (tag.hasEndTag) {
  403.         if (tag.id == Tag.PRE) {
  404.         preFormatted = true;
  405.         }
  406.         tagStack.push(tag);
  407.     }
  408.     return true;
  409.     }
  410.  
  411.     boolean    preFormatted = false;
  412.     static private Tag    FORMtag = Tag.lookup("form");
  413.     static private Tag    INPUTtag = Tag.lookup("input");
  414.     static private Tag    HRtag = Tag.lookup("hr");
  415.  
  416.     void parse() {
  417.     int    textIndex = 0;
  418.     int    c;
  419.     Tag    lastTag = null;
  420.     boolean    wasWhite = false;
  421.     boolean    textSinceLastBreak = false;
  422.  
  423. mainloop:
  424.     while ((c = nextChar()) != -1) {
  425.  
  426.         /* In this switch statement, a break means we've read
  427.            a character suitable for inserting into the document.
  428.            Continue means we've read some sort of markup. */
  429.  
  430.         switch (c) {
  431.         case '<': {
  432.         boolean        isEnd;
  433.         String        tagName;
  434.         TagRef        ref;
  435.         Tag        newTag;
  436.  
  437.         if ((c = peekChar()) == '/') {
  438.             nextChar();            /* read it */
  439.             isEnd = true;
  440.         } else {
  441.             isEnd = false;
  442.         }
  443.         tagName = parseTagName();
  444.         if (tagName == null) {        /* wasn't a real tag */
  445.             c = '<';
  446.             break;
  447.         }
  448.         newTag = Tag.lookup(tagName);
  449.         ref = null;
  450.         if (handleTag(newTag, isEnd, textIndex)) {
  451.             /* Html spec says newline right before and
  452.                just after a tag is markup and should
  453.                be deleted. */
  454.             if (isEnd && newTag.hasEndTag
  455.             && lastTag == null  /* so we only eat one newline */
  456.             /* && newTag.breaks */) {
  457.             if ((textIndex > 0) && (input[textIndex-1] == '\n')) {
  458.                 textIndex--;
  459.             }
  460.             }
  461.             if (!isEnd) {
  462.             ref = html.startTag(newTag, textIndex);
  463.             } else {
  464.             ref = html.endTag(newTag, textIndex);
  465.             }
  466.             lastTag = newTag;
  467.             if (newTag.breaks) {
  468.             textSinceLastBreak = false;
  469.             }
  470.         }
  471.  
  472.         if (!isEnd) {
  473.             parseAttributes(ref);
  474.         } else {
  475.             skipUntil('>');
  476.         }
  477.         if (nextChar() != '>') {
  478.             warning("Malformed tag: " + lastTag);
  479.         }
  480.         if ((ref != null) && (ref.tag.id == Tag.ISINDEX)) {
  481.             String prompt = ref.getAttribute("prompt");
  482.             int       oldTextIndex = textIndex;
  483.  
  484.             TagRef fref = html.startTag(FORMtag, textIndex);
  485.             String action = ref.getAttribute("action");
  486.             if (action != null) {
  487.             fref.addAttribute("action", action);
  488.             }
  489.             html.startTag(HRtag, textIndex);
  490.  
  491.             if (prompt == null) {
  492.             // At this point since the prompt wasn't in
  493.             // the input buffer we need to handle the case
  494.             // where we don't have enough space in the
  495.             // buffer so we need to make a larger buffer
  496.             // copy the first part of the input buffer,
  497.             // then the prompt and the rest of the input
  498.             // buffer into it.
  499.             prompt = "This is a searchable index.  Enter search keywords: ";
  500.             output = new byte[input.length + prompt.length() + 1];
  501.             inputLength += prompt.length() + 1;
  502.             System.arraycopy(input, 0,
  503.                      output, 0, textIndex);
  504.  
  505.             for (int i = 0 ; i < prompt.length() ; i++) {
  506.                 output[textIndex++] = (byte)prompt.charAt(i);
  507.             }
  508.  
  509.             System.arraycopy(input,
  510.                      oldTextIndex - 1,
  511.                      output, textIndex,
  512.                      input.length - oldTextIndex);
  513.             inputSeek += prompt.length() + 1;
  514.             input = output;
  515.             } else {
  516.             for (int i = 0 ; i < prompt.length() ; i++) {
  517.                 input[textIndex++] = (byte)prompt.charAt(i);
  518.             }
  519.             }
  520.  
  521.             fref = html.startTag(INPUTtag, textIndex);
  522.             fref.addAttribute("name", "isindex");
  523.             html.endTag(HRtag, textIndex);
  524.             html.endTag(FORMtag, textIndex);
  525.         }
  526.         if (debug) {
  527.             if (ref != null) {
  528.             System.out.println("Line " + lineCount + ": " + ref.toExternalForm());
  529.             }
  530.         }
  531.         /* Html spec says newline right before and
  532.            just after a tag is markup and should
  533.            be deleted. */
  534.         if (!isEnd && newTag.hasEndTag && newTag.breaks
  535.             && peekChar() == '\n') {
  536.             nextChar();
  537.         }
  538.  
  539.         if (lastTag != null && lastTag.id == Tag.PLAINTEXT) {
  540.             textIndex = inputLength - inputSeek;
  541.             System.arraycopy(input, inputSeek, input, 0, textIndex);
  542.             break mainloop;
  543.         }
  544.         wasWhite = false;
  545.         continue;
  546.         }
  547.  
  548.         case '&':
  549.         if (peekChar() == '#') {
  550.             c = parseCharacter();
  551.         } else {
  552.             if ((c = parseEntity()) == -1) {
  553.             continue;
  554.             }
  555.         }
  556.         wasWhite = false;
  557.         break;
  558.  
  559.         case '\n':
  560.         case '\t':
  561.         if (!preFormatted) {
  562.             c = ' ';
  563.         }
  564.         /* falls into ... */
  565.  
  566.         case ' ':
  567.         if (!preFormatted) {
  568.             if (!textSinceLastBreak
  569.             /* (lastTag != null && lastTag.breaks) */
  570.             || wasWhite) {
  571.             continue;
  572.             }
  573.         }
  574.         wasWhite = true;
  575.         break;
  576.  
  577.         default:
  578.         wasWhite = false;
  579.         break;
  580.         }
  581.         lastTag = null;
  582.         textSinceLastBreak = true;
  583.         //html.addCharacter(c);
  584.         input[textIndex++] = (byte)c;
  585.     }
  586.  
  587.     if (tagStack.size() != 0) {
  588.         String  error = "Missing ";
  589.         int        i = tagStack.size();
  590.         Tag tag;
  591.  
  592.         while (--i > 1) {
  593.         tag = (Tag) tagStack.pop();
  594.         html.endTag(tag, textIndex);
  595.         error = error + "</" + tag.name + ">, ";
  596.         }
  597.         tag = (Tag) tagStack.pop();
  598.         html.endTag(tag, textIndex);
  599.         error = error + "</" + tag.name + ">";
  600.  
  601.         warning(error + " at end of document.\n");
  602.     }
  603.     tagStack = null;
  604.  
  605.     if (input.length != textIndex) {
  606.         byte newinput[] = new byte[textIndex];
  607.         System.arraycopy(input, 0, newinput, 0, textIndex);
  608.         input = newinput;
  609.     }
  610.  
  611.     html.setText(input);
  612.     }
  613.  
  614.     public Parser(InputStream is, Document html) {
  615.     readInput(is);
  616.     this.html = html;
  617.  
  618.     try {
  619.         parse();
  620.     } catch (Exception e) {
  621.         warning("Caught exception while parsing\n");
  622.         e.printStackTrace();
  623.     }
  624.     }
  625.  
  626.     static public void main(String args[]) {
  627.     URL url = new URL(null, args[0]);
  628.     Parser p = new Parser(url.openStream(), new Document());
  629.     }
  630. }
  631.