/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.ajax4jsf.io.parser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.Map;
import org.ajax4jsf.context.SAXEndElementEvent;
import org.ajax4jsf.context.SAXStartElementEvent;
import org.ajax4jsf.context.SAXTextEvent;
import org.ajax4jsf.io.FastBufferReader;
import org.ajax4jsf.webapp.HtmlParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Simplified and fast HTML parsed - for find insertion point of <html><head>
* there can be inserted <script><style> and <meta> tags.
*
* @author shura
*
*/
public class FastHtmlParser implements HtmlParser {
private static final Log log = LogFactory.getLog(FastHtmlParser.class);
private String encoding;
private String doctype;
private String viewState;
private String mimeType;
private Object[] headEvents;
public void parse(Reader in, Writer out) throws IOException {
boolean haveHtml = false;
boolean haveHead = false;
boolean closingElement = false;
//see if any characters has been written http://jira.jboss.com/jira/browse/RF-3685
boolean parsed = false;
// Always parse content, see http://jira.jboss.com/jira/browse/RF-3577
// if (null != scripts || null != styles || null != userStyles) {
ParsingStateManager stateManager = ParsingStateManager
.getInstance();
ParserState state = stateManager.getInitialState();
ParsingContext context = new ParsingContext(out);
int nextChar;
while ((nextChar = in.read()) > 0) {
parsed = true;
char c = (char)nextChar;
state = state.getNextState(c, context);
if (log.isDebugEnabled()) {
// Find state name.
Class<? extends ParsingStateManager> stateMgrClass = stateManager.getClass();
Field[] fields = stateMgrClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
try {
if (field.get(stateManager) == state) {
log
.debug("Parser reached state is StateManager."
+ field.getName());
break;
}
} catch (Exception e) {
// ignore ...
}
}
}
state.send(c, context);
if (state == stateManager.LT) {
closingElement = false;
} else if (state == stateManager.CLOSINGELEMENT) {
closingElement = true;
} else if (state == stateManager.ENDELEMENT) {
if (context.getLastMatched() == stateManager.HTML) {
haveHtml = true;
//if xmlns is not set on HTML element, append xmlns attribute
if (!context.contains("xmlns=")) {
context.insert(" xmlns=\"http://www.w3.org/1999/xhtml\"");
}
context.send();
if (log.isDebugEnabled()) {
log.debug("Found <html> element");
}
} else if (context.getLastMatched() == stateManager.HEAD) {
haveHead = true;
if (log.isDebugEnabled()) {
log.debug("Found <head> element");
}
if (closingElement) {
writeToHead(out, haveHtml, haveHead);
break;
} else {
context.send();
}
} else if (context.getLastMatched() == stateManager.TITLE) {
context.send();
haveHead = true;
haveHtml = true;
if (log.isDebugEnabled()) {
log.debug("Found <title> element");
}
} else if (context.getLastMatched() == stateManager.BASE) {
context.send();
haveHead = true;
haveHtml = true;
if (log.isDebugEnabled()) {
log.debug("Found <base> element");
}
} else {
if (log.isDebugEnabled()) {
log
.debug("non <html> or <head><title>|<base> element");
}
writeToHead(out, haveHtml, haveHead);
break;
}
} else if (state == stateManager.ELEMENT) {
writeToHead(out, haveHtml, haveHead);
break;
}
}
context.send();
// } else {
// haveHtml = true;
// }
// Send rest of input.
if (in instanceof FastBufferReader) {
FastBufferReader fastIn = (FastBufferReader) in;
fastIn.writeTo(out);
} else {
char[] buffer = new char[1024];
int buffersCount = -1;
int length;
for (length = in.read(buffer); length > 0; length = in.read(buffer)) {
out.write(buffer, 0, length);
buffersCount++;
}
}
if (!haveHtml && parsed) {
out.write("</html>");
}
}
private void writeToHead(Writer out, boolean haveHtml, boolean haveHead)
throws IOException {
if (!haveHead && !haveHtml) {
out.write("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
}
if (!haveHead) {
out.write("<head>");
}
if (headEvents != null && headEvents.length > 0) {
LinkedList<Boolean> closedStack = new LinkedList<Boolean>();
for (Object event : headEvents) {
if (event instanceof SAXStartElementEvent) {
SAXStartElementEvent startElementEvent = (SAXStartElementEvent) event;
if (!closedStack.isEmpty() && Boolean.FALSE.equals(closedStack.peek())) {
out.write('>');
closedStack.set(0, Boolean.TRUE);
}
out.write('<');
out.write(startElementEvent.getName());
for (Map.Entry<String, String> entry : startElementEvent.getAttributes().entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
out.write(' ');
out.write(name);
out.write("='");
out.write(value);
out.write('\'');
}
closedStack.addFirst(Boolean.FALSE);
} else if (event instanceof SAXTextEvent) {
SAXTextEvent textEvent = (SAXTextEvent) event;
if (!closedStack.isEmpty() && Boolean.FALSE.equals(closedStack.peek())) {
out.write('>');
closedStack.set(0, Boolean.TRUE);
}
out.write(textEvent.getText());
} else if (event instanceof SAXEndElementEvent) {
SAXEndElementEvent endElementEvent = (SAXEndElementEvent) event;
String elementName = endElementEvent.getName();
if (Boolean.FALSE.equals(closedStack.removeFirst())) {
if ("script".equals(elementName)) {
out.write('>');
out.write("</");
out.write(elementName);
out.write('>');
} else {
out.write(" />");
}
} else {
out.write("</");
out.write(elementName);
out.write('>');
}
} else {
throw new IllegalArgumentException();
}
}
}
if (!haveHead) {
out.write("</head>");
}
}
public void parseHtml(InputStream input, Writer output) throws IOException {
parse(new InputStreamReader(input, encoding), output);
}
public void parseHtml(Reader input, Writer output) throws IOException {
parse(input, output);
}
public void setInputEncoding(String encoding) {
this.encoding = encoding;
}
public void setOutputEncoding(String encoding) {
// TODO Auto-generated method stub
}
public void setMoveElements(boolean move) {
}
public void setDoctype(String doctype) {
this.doctype = doctype;
}
public void setViewState(String viewState) {
this.viewState = viewState;
}
public boolean setMime(String mimeType) {
this.mimeType=mimeType;
return true;
}
public void setHeadEvents(Object[] events) {
this.headEvents = events;
}
}