home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / modules / edtplug / classes / netscape / plugin / composer / io / LexicalStream.java < prev    next >
Encoding:
Java Source  |  1998-04-08  |  10.6 KB  |  429 lines

  1. /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Netscape Public License
  4.  * Version 1.0 (the "NPL"); you may not use this file except in
  5.  * compliance with the NPL.  You may obtain a copy of the NPL at
  6.  * http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the NPL is distributed on an "AS IS" basis,
  9.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
  10.  * for the specific language governing rights and limitations under the
  11.  * NPL.
  12.  *
  13.  * The Initial Developer of this code under the NPL is Netscape
  14.  * Communications Corporation.  Portions created by Netscape are
  15.  * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
  16.  * Reserved.
  17.  */
  18.  
  19. package netscape.plugin.composer.io;
  20.  
  21. import java.io.*;
  22.  
  23. /** An HTML lexical stream. Takes a Reader and breaks it
  24.  * up into lexical tokens.
  25.  * @see Reader
  26.  * @see Comment
  27.  * @see JavaScriptEntity
  28.  * @see Entity
  29.  * @see Tag
  30.  * @see Text
  31.  * @see Token
  32.  */
  33.  
  34. public class LexicalStream {
  35.   private SlidingBuffer in;
  36.   private FooStringBuffer buffer;
  37.   private final static String NEWLINE = new String("\n");
  38.   private boolean bHaveClosedStream;
  39.  
  40.   /** Create a lexical stream from a unicode string.
  41.    * @param in the input string.
  42.    */
  43.  
  44.   public LexicalStream(String in) {
  45.     this(new CharArrayReader(in.toCharArray()));
  46.   }
  47.  
  48.   /** Create a lexical stream from a Reader. The
  49.    * stream's close() method will be called automaticly
  50.    * the first time next() returns null. (i.e. when the
  51.    * iterator finishes delivering tokens.)
  52.    * @param in the input stream.
  53.    */
  54.  
  55.   public LexicalStream(Reader in) {
  56.     this.in = new SlidingBuffer(in);
  57.   }
  58.  
  59.   private int read() throws IOException {
  60.     return in.read();
  61.   }
  62.  
  63.   private boolean lookAhead(char c) throws IOException {
  64.     return in.lookAhead(c);
  65.   }
  66.  
  67.   private boolean lookAhead(String s) throws IOException {
  68.     return in.lookAhead(s);
  69.   }
  70.  
  71.   private boolean lookAhead(String s, boolean ignoreCase) throws IOException {
  72.     return in.lookAhead(s, ignoreCase);
  73.   }
  74.  
  75.   private boolean eatNewline() throws IOException {
  76.     return in.eatNewline();
  77.   }
  78.  
  79.   private boolean eatWhiteSpace() throws IOException {
  80.     return in.eatWhiteSpace();
  81.   }
  82.  
  83.   /** Return the next token in an HTML input stream. \r\n's are
  84.    * considered their own token (though we get rid of the \r).
  85.    * Returns null if the input stream has run out of tokens.
  86.    * @return the next token in the stream, or null if the stream is
  87.    * out of tokens.
  88.    */
  89.   public Token next() throws IOException {
  90.     for (;;) {
  91.       int c = read();
  92.       if (c < 0) break;
  93.       if (c == '&') {
  94.         if (buffer != null) {
  95.           in.unread(1);
  96.           break;
  97.         }
  98.     FooStringBuffer buf = new FooStringBuffer();
  99.     /* Don't allow javascript entities outside of parameter values.
  100.      if (in.lookAhead('{')) {
  101.       parseJavaScriptEntity(buf);
  102.       return new JavaScriptEntity(buf);
  103.     }
  104.     */
  105.     parseEntity(buf);
  106.     return new Entity(buf);
  107.       } else if (c == '<') {
  108.     if (buffer != null) {
  109.       in.unread(1);
  110.       break;
  111.     }
  112.     if (in.lookAhead('/')) {
  113.       return parseTag(false);
  114.     } else if (in.lookAhead('!')) {
  115.       return parseComment();
  116.     }
  117.     return parseTag(true);
  118.       }
  119.       if (c == '\r') {
  120.     if (buffer != null) {
  121.       in.unread(1);
  122.       break;
  123.     }
  124.     in.lookAhead('\n');
  125.     return new Text(NEWLINE);
  126.       }
  127.       if (c == '\n') {
  128.     if (buffer != null) {
  129.       in.unread(1);
  130.       break;
  131.     }
  132.     return new Text(NEWLINE);
  133.       }
  134.       if (buffer == null) {
  135.     buffer = new FooStringBuffer();
  136.       }
  137.       buffer.append((char)c);
  138.     }
  139.     if (buffer != null) {
  140.       String rv = buffer.toString();
  141.       buffer = null;
  142.       return new Text(rv);
  143.     }
  144.     if ( ! bHaveClosedStream ) {
  145.         in.close();
  146.         bHaveClosedStream = true;
  147.     }
  148.     return null;
  149.   }
  150.  
  151.   private boolean isWhitespace(char c){
  152.     /* JDK 1.1 return Character.isWhitespace(c); */
  153.     return Character.isSpace(c);
  154.   }
  155.   private Token parseTag(boolean open) throws IOException {
  156.     // Capture tag name
  157.     FooStringBuffer name = new FooStringBuffer();
  158.     int c;
  159.     for (;;) {
  160.       c = read();
  161.       if (c < 0) break;
  162.       if ((c == '>') || isWhitespace((char)c)) break;
  163.       name.append((char) c);
  164.     }
  165.     if (name.length() == 0) {
  166.       name.append('<');
  167.       if (!open) name.append('/');
  168.       if (c >= 0) {
  169.     name.append((char) c);
  170.       }
  171.       return new Text(name.toString());
  172.     }
  173.     Tag tag = new Tag(name.toString(), open);
  174.     if (c == '>') return tag;
  175.  
  176.     // Now process tag attributes
  177.     for (;;) {
  178.       c = read();
  179.       if ((c < 0) || (c == '>')) break;
  180.       if (isWhitespace((char)c)) continue;
  181.       in.unread(1);
  182.       parseTagAttribute(tag);
  183.     }
  184.     return tag;
  185.   }
  186.  
  187.   private void parseTagAttribute(Tag tag) throws IOException {
  188.     // First get attribute name
  189.     FooStringBuffer name = new FooStringBuffer();
  190.     int c;
  191.     for (;;) {
  192.       c = read();
  193.       if (c < 0) break;
  194.       if ((c == '>') || (c == '=')) {
  195.     in.unread(1);
  196.     break;
  197.       }
  198.       if (isWhitespace((char)c)) {
  199.     break;
  200.       }
  201.       name.append((char) c);
  202.     }
  203.     if (name.length() == 0) {
  204.       return;
  205.     }
  206.  
  207.     // Allow for whitespace between the attribute name and value
  208.     eatWhiteSpace();
  209.     c = read();
  210.  
  211.     FooStringBuffer value = null;
  212.     if (c != '=') {
  213.       // No attribute value follows the attribute name
  214.       in.unread(1);
  215.     } else {
  216.       // Allow for whitespace between the '=' and the attribute value
  217.       eatWhiteSpace();
  218.  
  219.       // Possibly an attribute value follows the attribute name
  220.       c = read();
  221.       if (c < 0) {
  222.     // No attribute value follows the attribute name
  223.       } else if (c == '>') {
  224.     // No attribute value follows the attribute name. This
  225.     // is a syntax error within the tag
  226.     in.unread(1);
  227.       } else {
  228.     // Grab attribute value
  229.     if ((c == '\'') || (c == '"')) {
  230.       value = parseQuotedString(c);
  231.     } else {
  232.       value = new FooStringBuffer();
  233.       value.append((char) c);
  234.       for (;;) {
  235.         c = read();
  236.         if (c < 0) break;
  237.         if (c == '>') {
  238.           in.unread(1);
  239.           break;
  240.         }
  241.         if (isWhitespace((char)c)) break;
  242.         // XXX allow for concatenated quotes?
  243.         value.append((char) c);
  244.       }
  245.     }
  246.       }
  247.     }
  248.     tag.addAttribute(name.toString(), (value!=null) ? value.toString() : null);
  249.   }
  250.  
  251.   private FooStringBuffer parseQuotedString(int stop) throws IOException {
  252.     FooStringBuffer out = new FooStringBuffer();
  253.     for (;;) {
  254.       int c = read();
  255.       if (c < 0) {
  256.     break;
  257.       }
  258.       if (c == '&') {
  259.     // Entities can be embedded in html quoted strings; they will be
  260.     // reparsed later when the attribute value is evaluated
  261.     if (in.peek() == '{') {
  262.       read();
  263.       parseJavaScriptEntity(out);
  264.     } else {
  265.       parseEntity(out);
  266.     }
  267.       } else {
  268.     if (c == stop) {
  269.       break;
  270.     }
  271.     out.append((char) c);
  272.       }
  273.     }
  274.     return out;
  275.   }
  276.  
  277.   /* Process an HTML comment */
  278.   private Comment parseComment() throws IOException {
  279.     FooStringBuffer out = new FooStringBuffer();
  280.     boolean fancyTerminator = false;
  281.     if (in.lookAhead('-')) {
  282.       if (in.lookAhead('-')) {
  283.     // This comment started with "<!--"; therefore we will look for
  284.     // its terminator which is "-->"
  285.     fancyTerminator = true;
  286.       } else {
  287.     out.append('-');
  288.       }
  289.     }
  290.  
  291.     // Gobble up data that lives in the comment until we find the
  292.     // comment terminator (which is either ">" or "-->")
  293.     for (;;) {
  294.       int c = read();
  295.       if (c < 0) {
  296.         break;
  297.       }
  298.       if (fancyTerminator) {
  299.         if (c == '-') {
  300.           if (in.lookAhead('-')) {
  301.             if (in.lookAhead('>')) {
  302.               break;
  303.             } else {
  304.               out.append("--");
  305.             }
  306.           } else {
  307.             // the minus sign will be put out by the out.append((char) c); below.
  308.           }
  309.         }
  310.       } else if (c == '>') {
  311.         break;
  312.       }
  313.       out.append((char)c);
  314.     }
  315.     return fancyTerminator ? new Comment("--" + out + "--") : new Comment(out);
  316.   }
  317.  
  318.   /* Process an HTML entity */
  319.   private void parseEntity(FooStringBuffer out) throws IOException {
  320.     for (;;) {
  321.       int c = read();
  322.       if (c < 0) {
  323.     break;
  324.       }
  325.       if (c == ';') {
  326.     break;
  327.       }
  328.       // Ending an entity with a space is a Netscape-ism we support
  329.       if (isWhitespace((char)c)) {
  330.     in.unread(1);
  331.     break;
  332.       }
  333.       out.append((char)c);
  334.     }
  335.   }
  336.  
  337.   /* Process an HTML script entity */
  338.   private void parseJavaScriptEntity(FooStringBuffer out) throws IOException {
  339.     int count = 1;
  340.     for (;;) {
  341.       int c = read();
  342.       if (c < 0) break;
  343.       if ((c == '\'') || (c == '"')) {
  344.     parseJavaScriptQuotedString(out, c);
  345.       } else if (c == '{') {
  346.     out.append((char) c);
  347.     count++;
  348.       } else if (c == '}') {
  349.     if (--count == 0) {
  350.       in.lookAhead(';'); // eat trailing ";" that we don't care about
  351.       return;
  352.     }
  353.     out.append((char) c);
  354.       } else if (c == '/') {
  355.     c = read();
  356.     if (c < 0) break;
  357.     if (c == '*') {
  358.       parseCComment(out);
  359.     } else if (c == '/') {
  360.       parseEOLComment(out);
  361.     } else {
  362.       out.append('/');
  363.       out.append((char) c);
  364.     }
  365.       } else {
  366.     out.append((char) c);
  367.       }
  368.     }
  369.   }
  370.  
  371.   private void parseJavaScriptQuotedString(FooStringBuffer out, int stop) throws IOException {
  372.     out.append((char) stop);
  373.     for (;;) {
  374.       int c = read();
  375.       if (c < 0) {
  376.     break;
  377.       }
  378.       out.append((char) c);
  379.       if (c == '\\') {
  380.     c = read();
  381.     if (c < 0) {
  382.       break;
  383.     }
  384.     out.append((char) c);
  385.     continue;
  386.       }
  387.       if (c == stop) {
  388.     break;
  389.       }
  390.     }
  391.   }
  392.  
  393.   private void parseCComment(FooStringBuffer out) throws IOException {
  394.     out.append("/*");
  395.     for (;;) {
  396.       int c = read();
  397.       if (c < 0) {
  398.     break;
  399.       }
  400.       out.append((char) c);
  401.       if (c == '*') {
  402.     c = read();
  403.     if (c < 0) {
  404.       break;
  405.     }
  406.     out.append((char) c);
  407.     if (c == '/') {
  408.       break;
  409.     }
  410.       }
  411.     }
  412.   }
  413.  
  414.   private void parseEOLComment(FooStringBuffer out) throws IOException {
  415.     out.append("//");
  416.     for (;;) {
  417.       int c = read();
  418.       if (c < 0) {
  419.     break;
  420.       }
  421.       out.append((char) c);
  422.       if ((c == '\n') || (c == '\r')) {
  423.     out.append((char) c);
  424.     break;
  425.       }
  426.     }
  427.   }
  428. }
  429.