/*
* LingPipe v. 3.7
* Copyright (C) 2003-2008 Alias-i
*
* This program is licensed under the Alias-i Royalty Free License
* Version 1 WITHOUT ANY WARRANTY, without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Alias-i
* Royalty Free License Version 1 for more details.
*
* You should have received a copy of the Alias-i Royalty Free License
* Version 1 along with this program; if not, visit
* http://alias-i.com/lingpipe/licenses/lingpipe-license-1.txt or contact
* Alias-i, Inc. at 181 North 11th Street, Suite 401, Brooklyn, NY 11211,
* +1 (718) 290-9170.
*/
package com.aliasi.demo.framework;
import com.aliasi.util.Streams;
import com.aliasi.xml.SAXWriter;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* The DemoServlet class provides a servlet interface
* to streaming demos. The demo servlet presents demos in three
* ways: web form, file upload and web service, each with its own
* method of transmitting property information.
*
*
See the Web demo instructions for more information on using * this class.
* * @author Bob Carpenter * @version 2.3.0 * @since LingPipe2.3 */ public class DemoServlet extends HttpServlet { private StreamDemo mDemo; /** * Construct a demo servlet. */ public DemoServlet() { } /** * Initialize the demo servlet. This is the initialization * called by the servlet container. It constructs the * demo using reflection given the parameters. */ public void init() throws ServletException { String demoClass = getInitParameter("demoConstructor"); String demoConstructorArgs = getInitParameter("demoConstructorArgs"); try { mDemo = Constants .constructDemo(demoClass, demoConstructorArgs); } catch (IllegalArgumentException e) { String msg = "Error constructing demo=" + e; throw new ServletException(e); } catch (ClassCastException e) { String msg = "Could not convert class=" + demoClass + " to instance of StreamDemo."; throw new ServletException(e); } } /** * Destroys this demo by null-ing out the resources used. */ public void destroy() { mDemo = null; } /** * HandlesGET requests by deferring them
* to POST requests.
*
* @param request Servlet equest.
* @param response Servlet response.
* @throws IOException If there is an underlying I/O error.
* @throws ServletException If there is an underlying servlet
* error.
*/
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
doPost(request,response);
}
/**
* Handles POST requests. First it determines what
* type of demo is involved by looking at the path information.
* If the extension is /textInput.html, it generates
* the input form for the text input demo. If the extension is
* /fileInput.html, it generates the input form for
* file upload. If neither extension is present, it generates
* the output for the demo, based on the properties determined
* from the input.
*
* @param request Servlet equest.
* @param response Servlet response.
* @throws IOException If there is an underlying I/O error.
* @throws ServletException If there is an underlying servlet
* error.
*/
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
String pathInfo = request.getPathInfo();
if (pathInfo.equals("/response.xml")) {
generateOutput(request,response);
} else if (pathInfo.equals("/textInput.html")) {
generateInputForm(request,response,true);
} else if (pathInfo.equals("/fileInput.html")) {
generateInputForm(request,response,false);
}
}
void generateOutput(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
InputStream in = null;
OutputStream out = null;
try {
response.setContentType(mDemo.responseType());
out = response.getOutputStream();
Properties properties
= mapToProperties(request.getParameterMap());
String reqContentType = request.getContentType();
if (reqContentType == null
|| reqContentType.startsWith("text/plain")) {
properties.setProperty("inputType","text/plain");
String reqCharset = request.getCharacterEncoding();
if (reqCharset != null)
properties.setProperty("inputCharset",reqCharset);
in = request.getInputStream();
} else if (reqContentType
.startsWith("application/x-www-form-urlencoded")) {
String codedText = request.getParameter("inputText");
byte[] bytes = codedText.getBytes("ISO-8859-1");
in = new ByteArrayInputStream(bytes);
} else if (ServletFileUpload
.isMultipartContent
(new ServletRequestContext(request))) {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload uploader = new ServletFileUpload(factory);
List items = uploader.parseRequest(request);
Iterator it = items.iterator();
while (it.hasNext()) {
log("found item");
FileItem item = (FileItem) it.next();
if (item.isFormField()) {
String key = item.getFieldName();
String val = item.getString();
properties.setProperty(key,val);
} else {
byte[] bytes = item.get();
in = new ByteArrayInputStream(bytes);
}
}
} else {
System.out.println("unexpected content type");
String msg = "Unexpected request content"
+ reqContentType;
throw new ServletException(msg);
}
mDemo.process(in,out,properties);
} catch (FileUploadException e) {
throw new ServletException(e);
} finally {
Streams.closeInputStream(in);
Streams.closeOutputStream(out);
}
}
private void generateInputForm(HttpServletRequest request,
HttpServletResponse response,
boolean isTextField)
throws IOException, ServletException {
OutputStream out = null;
try {
response.setContentType("text/html");
out = response.getOutputStream();
String charset
= mDemo.defaultProperties()
.getProperty("Input Character Encoding");
if (charset == null) {
charset = "UTF-8";
}
SAXWriter saxWriter = new SAXWriter(out,charset,XHTML_MODE);
saxWriter.setDTDString(XHTML_DTD_STRING);
saxWriter.startDocument();
Attributes htmlAtts
= SAXWriter
.createAttributes("xmlns","http://www.w3.org/1999/xhtml",
"xml:lang","en");
saxWriter.characters("\n");
saxWriter.startSimpleElement("html",htmlAtts);
saxWriter.characters("\n");
saxWriter.startSimpleElement("head");
saxWriter.characters("\n");
saxWriter.startSimpleElement("title");
saxWriter.characters(mDemo.title());
saxWriter.endSimpleElement("title");
saxWriter.characters("\n");
Attributes httpAtts
= SAXWriter
.createAttributes("http-equiv",
"Content-type",
"content",
"application/xhtml+xml; charset=" + charset);
saxWriter.startEndSimpleElement("meta",httpAtts);
saxWriter.characters("\n");
saxWriter.startSimpleElement("style","type","text/css");
saxWriter.characters("\n");
saxWriter.characters(CSS_STYLE);
saxWriter.characters("\n");
saxWriter.endSimpleElement("style");
saxWriter.characters("\n");
saxWriter.endSimpleElement("head");
saxWriter.characters("\n");
saxWriter.startSimpleElement("body");
saxWriter.characters("\n");
saxWriter.startSimpleElement("div","id","header");
Attributes logoAtts
= SAXWriter
.createAttributes("href","http://alias-i.com",
"id","logo");
saxWriter.characters("\n");
saxWriter.startSimpleElement("a",logoAtts);
Attributes imgAtts
= SAXWriter
.createAttributes("src","http://alias-i.com/lingpipe/web/img/logo-small.gif",
"alt","Alias-i Logo");
saxWriter.startSimpleElement("img",imgAtts);
saxWriter.endSimpleElement("img");
saxWriter.endSimpleElement("a");
saxWriter.characters("\n");
saxWriter.startSimpleElement("h1","id","pageTitle");
saxWriter.characters(mDemo.title());
saxWriter.endSimpleElement("h1");
saxWriter.characters("\n");
saxWriter.endSimpleElement("div"); // id=header
saxWriter.characters("\n");
saxWriter.startSimpleElement("h2","id","poweredBy");
saxWriter.characters("Powered by ");
saxWriter.startSimpleElement("a","href","http://alias-i.com/lingpipe");
saxWriter.characters("LingPipe");
saxWriter.endSimpleElement("a");
saxWriter.endSimpleElement("h2");
String[] lines = mDemo.description().split("\n");
if (lines.length > 0) {
saxWriter.characters("\n");
saxWriter.startSimpleElement("div","id","description");
for (int i = 0; i < lines.length; ++i) {
saxWriter.characters("\n");
saxWriter.startSimpleElement("p");
if (i == 0) {
saxWriter.startSimpleElement("b");
saxWriter.characters("This Demo:");
saxWriter.endSimpleElement("b");
}
saxWriter.characters(" ");
saxWriter.characters(lines[i]);
saxWriter.endSimpleElement("p");
}
saxWriter.characters("\n");
saxWriter.endSimpleElement("div");
}
String actionPath =
request.getContextPath()
+ request.getServletPath()
+ "/response.xml";
Attributes formAtts
= isTextField
? SAXWriter.createAttributes("method","post",
"action",actionPath)
: SAXWriter.createAttributes("method","post",
"action",actionPath,
"enctype","multipart/form-data");
saxWriter.characters("\n");
saxWriter.startSimpleElement("form",formAtts);
saxWriter.characters("\n");
saxWriter.startSimpleElement("div","id","license");
saxWriter.characters("\n");
saxWriter.startSimpleElement("p");
saxWriter.characters("\n");
saxWriter.startSimpleElement("b");
saxWriter.characters("Terms of Use:");
saxWriter.endSimpleElement("b");
saxWriter.characters(" This demo is for evaluation purposes only.");
saxWriter.characters(" Please do not overload our demo server. ");
saxWriter.startSimpleElement("a","href",
"http://alias-i.com/lingpipe/web/contact.html");
saxWriter.characters("Contact Alias-i");
saxWriter.endSimpleElement("a");
saxWriter.characters(" about other uses.");
saxWriter.endSimpleElement("p");
saxWriter.endSimpleElement("div"); // license
saxWriter.characters("\n");
saxWriter.startSimpleElement("div","id","input");
saxWriter.characters("\n");
saxWriter.startSimpleElement("h4");
saxWriter.characters((isTextField ? "Text" : "File") + " Input");
saxWriter.endSimpleElement("h4");
if (isTextField) {
Attributes textAreaAtts
= SAXWriter
.createAttributes("name","inputText",
"rows","12",
"cols","60");
saxWriter.characters("\n");
saxWriter.startEndSimpleElement("textarea",textAreaAtts);
} else {
Attributes fileUploadAtts
= SAXWriter
.createAttributes("type","file",
"name","inputFile",
"size","50%",
"id","fileUpload");
saxWriter.characters("\n");
saxWriter.startEndSimpleElement("input",fileUploadAtts);
}
saxWriter.characters("\n");
saxWriter.startEndSimpleElement("br");
Attributes inputAtts
= isTextField
? SAXWriter
.createAttributes("type","submit",
"value","Submit Text",
"id","submitButton",
"title","Click to analyze input text.")
: SAXWriter
.createAttributes("type","submit",
"value","Submit File",
"id","submitButton",
"title",
"Click to upload and analyze selected file.");
saxWriter.characters("\n");
saxWriter.startEndSimpleElement("input",inputAtts);
saxWriter.characters("\n");
saxWriter.startSimpleElement("span","id","switch");
saxWriter.characters("(or switch to:");
if (isTextField) {
saxWriter.startSimpleElement("a","href","fileInput.html");
saxWriter.characters("File Input Form");
} else {
saxWriter.startSimpleElement("a","href","textInput.html");
saxWriter.characters("Text Input Form");
}
saxWriter.endSimpleElement("a");
saxWriter.characters(")");
saxWriter.endSimpleElement("span");
if (!isTextField) {
saxWriter.startEndSimpleElement("br");
saxWriter.startEndSimpleElement("br");
saxWriter.startEndSimpleElement("br");
saxWriter.startEndSimpleElement("br");
saxWriter.startEndSimpleElement("br");
saxWriter.startEndSimpleElement("br");
saxWriter.startEndSimpleElement("br");
saxWriter.startEndSimpleElement("br");
saxWriter.startEndSimpleElement("br");
}
saxWriter.characters("\n");
saxWriter.endSimpleElement("div");
saxWriter.characters("\n");
saxWriter.startSimpleElement("div","id","params");
Map propertyDeclarations = mDemo.propertyDeclarations();
Iterator entryIt = propertyDeclarations.entrySet().iterator();
while (entryIt.hasNext()) {
Map.Entry entry = (Map.Entry) entryIt.next();
String key = entry.getKey().toString();
String[] vals = (String[]) entry.getValue();
String toolTip = (String) mDemo.propertyToolTips().get(key);
saxWriter.characters("\n");
saxWriter.startSimpleElement("h4");
saxWriter.characters(key);
saxWriter.endSimpleElement("h4");
if (vals.length > 1) {
Attributes selectAtts
= (toolTip != null)
? SAXWriter.createAttributes("name",key,
"title",toolTip)
: SAXWriter.createAttributes("name",key);
saxWriter.characters("\n");
saxWriter.startSimpleElement("select",selectAtts);
for (int i = 0; i < vals.length; ++i) {
saxWriter.characters("\n");
saxWriter.startSimpleElement("option","value",vals[i]);
saxWriter.characters(vals[i]);
saxWriter.endSimpleElement("option");
}
saxWriter.characters("\n");
saxWriter.endSimpleElement("select");
} else {
Attributes paramInputAtts
= (vals.length == 1)
? SAXWriter.createAttributes("type","text",
"name",key,
"value",vals[0])
: SAXWriter.createAttributes("type","text",
"name",key);
saxWriter.characters("\n");
saxWriter.startEndSimpleElement("input",paramInputAtts);
}
}
saxWriter.characters("\n");
saxWriter.endSimpleElement("div");
saxWriter.characters("\n");
saxWriter.endSimpleElement("form");
saxWriter.characters("\n");
saxWriter.startSimpleElement("div","id","footInstructions");
saxWriter.characters("\n");
saxWriter.startSimpleElement("div","id","instructions");
saxWriter.characters("\n");
saxWriter.startSimpleElement("h3");
saxWriter.characters("General Web Demo Instructions");
saxWriter.endSimpleElement("h3");
saxWriter.characters("\n");
saxWriter.startSimpleElement("ul");
writeItems(GENERAL_WEB_INSTRUCTIONS,saxWriter);
if (isTextField)
writeItems(TEXT_WEB_INSTRUCTIONS,saxWriter);
else
writeItems(FILE_WEB_INSTRUCTIONS,saxWriter);
saxWriter.characters("\n");
saxWriter.endSimpleElement("ul");
saxWriter.startSimpleElement("h3");
saxWriter.characters("For more info: ");
saxWriter.startSimpleElement("a","href","http://alias-i.com/lingpipe/demos.html");
saxWriter.characters("Complete LingPipe Demo Instructions");
saxWriter.endSimpleElement("a");
saxWriter.endSimpleElement("h3");
saxWriter.characters("\n");
saxWriter.endSimpleElement("div"); // instructions
Map modelToResource = mDemo.modelToResource();
Map tutorialToUrl = mDemo.tutorialToUrl();
if ((modelToResource.size() > 0)
|| (tutorialToUrl.size() > 0)) {
saxWriter.characters("\n");
saxWriter.startSimpleElement("div","id","resources");
saxWriter.characters("\n");
saxWriter.startSimpleElement("h3");
saxWriter.characters("Related LingPipe Resources");
saxWriter.endSimpleElement("h3");
if (tutorialToUrl.size() > 0) {
saxWriter.characters("\n");
saxWriter.startSimpleElement("h4");
saxWriter.characters("Tutorials");
saxWriter.endSimpleElement("h4");
saxWriter.characters("\n");
saxWriter.startSimpleElement("ul");
Iterator it = tutorialToUrl.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String tutorialName = entry.getKey().toString();
String tutorialUrl = entry.getValue().toString();
saxWriter.characters("\n");
saxWriter.startSimpleElement("li");
saxWriter.startSimpleElement("a","href",tutorialUrl);
saxWriter.characters(tutorialName);
saxWriter.endSimpleElement("a");
saxWriter.endSimpleElement("li");
}
saxWriter.characters("\n");
saxWriter.endSimpleElement("ul");
}
if (modelToResource.size() > 0) {
saxWriter.characters("\n");
saxWriter.startSimpleElement("h4");
saxWriter.characters("Models");
saxWriter.endSimpleElement("h4");
saxWriter.characters("\n");
saxWriter.startSimpleElement("ul");
Iterator it = modelToResource.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String modelName = entry.getKey().toString();
String modelResourcePath = entry.getValue().toString();
saxWriter.characters("\n");
saxWriter.startSimpleElement("li");
saxWriter.startSimpleElement("a","href","../resource" + modelResourcePath);
saxWriter.characters(modelName);
saxWriter.endSimpleElement("a");
saxWriter.endSimpleElement("li");
}
saxWriter.characters("\n");
saxWriter.endSimpleElement("ul");
}
saxWriter.characters("\n");
saxWriter.endSimpleElement("div"); // resources
}
saxWriter.characters("\n");
saxWriter.endSimpleElement("div"); // footInstructions
saxWriter.characters("\n");
saxWriter.endSimpleElement("body");
saxWriter.characters("\n");
saxWriter.endSimpleElement("html");
saxWriter.endDocument();
} catch (SAXException e) {
throw new ServletException(e);
} finally {
Streams.closeOutputStream(out);
}
}
static void writeItems(String text, SAXWriter saxWriter)
throws SAXException {
String[] paragraphs = text.split("\n");
for (int i = 0; i < paragraphs.length; ++i) {
saxWriter.characters("\n");
saxWriter.startSimpleElement("li");
saxWriter.characters("\n");
saxWriter.characters(paragraphs[i]);
saxWriter.characters("\n");
saxWriter.endSimpleElement("li");
}
}
private Properties mapToProperties(Map map) {
Properties reqProperties
= new Properties(mDemo.defaultProperties());
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String key = entry.getKey().toString();
String[] vals = (String[]) entry.getValue();
if (vals == null || vals.length == 0) continue;
reqProperties.setProperty(key,vals[0]);
}
return reqProperties;
}
static final boolean XHTML_MODE = true;
private static final String XHTML_DTD_STRING
= "";
private static final String GENERAL_WEB_INSTRUCTIONS
= "Set the web browser's character encoding based on the encoding of text to be submitted (Use browser menu=View, submenu=Encoding)."
+ "\n"
+ "Set the input character encoding to match the actual encoding of the input bytes."
+ "\n"
+ "Set the input type selection to match the type of the input, either plain text, HTML or XML."
+ "\n"
+ "Set the output character encoding to any value; it need not match the input character set or browser.";
private static final String TEXT_WEB_INSTRUCTIONS
= "Cut-and-paste or enter text in the specified character encoding."
+ "\n"
+ "To analyze a file, first switch to the file input form, the link for which is next to the submit button.";
private static final String FILE_WEB_INSTRUCTIONS
= "Files may be analyzed by entering an absolute path to them in the file input field."
+ " They may also be selected by clicking on the browse button and opening the desired file."
+ " This will write its absolute path into the input field."
+ " Use the submit button to upload the file for analysis."
+ "\n"
+ "To analyze direct text input, first switch to the text input form, the link for which is next to the submit button.";
private static final String CSS_STYLE
= "* { margin: 0; padding: 0; font-family: verdana, arial, sans-serif; background-color: #DDD; border: none; font-size: 1em; }"
+ "\n"
+ "#header {background-color: #272651; padding: 0; color: #272651; margin: 0 0 1px 0; border: 15px solid #DDD; border-top: 1em solid #DDD; border-left: 1.15em solid #DDD; border-bottom: 1px solid #DDD; }"
+ "\n"
+ "h1#pageTitle { font-size: 1.5em; color: #EEE; background-color: #272651; padding: 0.75em 0 .75em .333em; }"
+ "\n"
+ "a#logo { float: right; display: inline; padding: 1em 1em 0 0; background-color: #272651; }"
+ "\n"
+ "h2 { font-size: 1em; }"
+ "\n"
+ "h2#poweredBy { float: right; font-size: 0.8em; font-weight: normal; background-color: #272651; color: #EEE; margin: 0; padding: 1em 1.5em; border: 15px solid #DDD; border-top: none }"
+ "\n"
+ "h2#poweredBy a { font-size: 1.5em; font-weight: normal; background-color: #272651; color: #F7EB00; }"
+ "\n"
+ "h3 { font-size: 1em; margin: 1.0em; }"
+ "\n"
+ "h4 { font-size: 0.8em; margin: .25em 0; }"
+ "\n"
+ "#description p { width: 100%; margin-bottom: 0.25em; padding: 0.5em; background-color: #EEE; border: 1px solid #888; font-size: 0.75em; overflow:hidden }"
+ "\n"
+ "#description p b { background-color: #EEE; }"
+ "\n"
+ "#description { width: 30em; padding: 1.15em 0.75em 1.15em 1.15em; line-height: 140%; }"
+ "\n"
+ "#license { clear: both; width: 30em; padding: 0em 0.75em 1.15em 1.15em; line-height: 140%; }"
+ "\n"
+ "#license p { width: 100%; margin-bottom: 0.25em; padding: 0.75em 0.5em; background-color: #EEE; border: 1px solid #888; font-size: 0.75em; overflow:hidden }"
+ "\n"
+ "#license p b { background-color: #EEE; }"
+ "\n"
+ "#license p a { background-color: #EEE; }"
+ "\n"
+ "#input { float: left; width: 30em; padding: 1.15em .75em 1.15em 1.15em; }"
+ "\n"
+ "#input textarea { width: 100%; margin-bottom: 0.25em; padding: 0.5em; background-color: #FFF; border: 1px solid #888; font-size: 0.8em; overflow:hidden }"
+ "\n"
+ "#input #fileUpload { margin-bottom: 0.25em; padding: 0.5em; background-color:#FFF; border: 1px solid black; font-size: 0.8em; }"
+ "\n"
+ "#input #submitButton { float: left; font-size: 0.8em; font-weight: bold; padding: 0.5em; margin-bottom: 1.5em; background-color: #FFF; border: 1px solid #888; }"
+ "\n"
+ "#switch { font-weight: normal; font-size: .8em; float: right; text-align: right; }"
+ "\n"
+ "#params { padding-top: 1.75em; padding-left: 3em; margin-left: 30em; }"
+ "\n"
+ "#params h4 { margin: 1em 0 .1em 0; padding: 0; }"
+ "\n"
+ "#params select { font-size: 0.9em; background-color: #EEE; border: 1px solid #333; padding: 0; }"
+ "\n"
+ "#params input { font-size: 0.9em; background-color: #EEE; border: 1px solid #888; }"
+ "\n"
+ "#footInstructions { clear: both; float: left; width: 30em; padding: 1.15em 0.75em 1.15em 1.15em; }"
+ "\n"
+ "#instructions { width: 100%; margin-bottom: 0.25em; padding: 0.5em; background-color: #EEE; border: 1px solid #888; font-size: 0.8em; overflow:hidden }"
+ "\n"
+ "#instructions h3 { font-weight: bold; margin: 0em 1em 0 0.5em; background: #EEE; padding-top: 0.75em; }"
+ "\n"
+ "#instructions ul { margin: 0 1.5em 0.5em 2.0em; background: #EEE; }"
+ "\n"
+ "#instructions ul li { margin: 0.75em 0 0 0; background: #EEE; }"
+ "\n"
+ "#resources { width: 100%; margin-bottom: 0.25em; margin-top: 1em; padding: 0.5em; background-color: #EEE; border: 1px solid #888; font-size: 0.8em; overflow:hidden }"
+ "\n"
+ "#resources h3 { font-size: 1em; background: #EEE; margin: 0; padding: 0.5em 0.5em; }"
+ "\n"
+ "#instructions a { background: #EEE; }"
+ "\n"
+ "#resources h4 { font-size: 0.9em; margin: 0; padding: 0.5em 0.5em 0 0.5em; background-color: #EEE; }"
+ "\n"
+ "#resources ul { margin: 0 1.5em 0.5em 2.0em; background: #EEE; }"
+ "\n"
+ "#resources ul li { margin: 0.5em 0 0 0; background: #EEE; }"
+ "\n"
+ "#resources ul li a { background: #EEE; font-size: 0.9em; }"
+ "\n"
;
}