You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ctakes.apache.org by se...@apache.org on 2016/10/18 04:21:12 UTC
svn commit: r1765388 - in
/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html:
CssWriter.java HtmlTextWriter.java
Author: seanfinan
Date: Tue Oct 18 04:21:12 2016
New Revision: 1765388
URL: http://svn.apache.org/viewvc?rev=1765388&view=rev
Log:
Pretty Text Writer "cas consumer" that creates html.
Needs some comments and cleanup, but functional
Added:
ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java
ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java
Added: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java?rev=1765388&view=auto
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java (added)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/CssWriter.java Tue Oct 18 04:21:12 2016
@@ -0,0 +1,142 @@
+package org.apache.ctakes.core.cc.pretty.html;
+
+
+import org.apache.log4j.Logger;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+
+/**
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 10/15/2016
+ */
+final class CssWriter {
+
+ static private final Logger LOGGER = Logger.getLogger( "CssWriter" );
+
+
+ private CssWriter() {
+ }
+
+ static boolean writeCssFile( final String filePath ) {
+ final File outputFile = new File( filePath );
+ if ( outputFile.exists() ) {
+ return false;
+ }
+ try ( final BufferedWriter writer = new BufferedWriter( new FileWriter( outputFile ) ) ) {
+ writer.write( setUnderline( "affirmed", "green", "solid", "0.2" ) );
+ writer.write( setUnderline( "uncertain", "gold", "dotted", "0.2" ) );
+ writer.write( setUnderline( "negated", "red", "dashed", "0.2" ) );
+ writer.write( setUnderline( "uncertainnegated", "orange", "dashed", "0.2" ) );
+// writer.write( setColor( "Anatomy", "gray" ) );
+// writer.write( setColor( "Disorder", "black" ) );
+// writer.write( setColor( "Finding", "magenta" ) );
+// writer.write( setColor( "Drug", "red" ) );
+// writer.write( setColor( "Procedure", "blue" ) );
+ writer.write( getToolTipCss() );
+ } catch ( IOException ioE ) {
+ LOGGER.error( "Could not not write css file " + outputFile.getPath() );
+ LOGGER.error( ioE.getMessage() );
+ }
+ return true;
+ }
+
+
+ // dashType is solid or dashed or double or dotted, try wavy size is relative: 0.1 or 0.2 for 10%, 20%
+ static private String setUnderline( final String className, final String color, final String dashType,
+ final String size ) {
+ return "\n." + className + " {\n" +
+ " position: relative;\n" +
+ " display: inline-block " + color + ";\n" +
+ " border-bottom: " + size + "em " + dashType + " " + color + ";\n" +
+ "}\n";
+ }
+
+ static private String setColor( final String className, final String color ) {
+ return "\n." + className + " {\n" +
+ " color: " + color + ";\n" +
+ "}\n";
+ }
+
+
+ static private String setHighlight( final String idName, final String color ) {
+ // PowderBlue
+ return "#" + idName + "{\n background-color: " + color + ";\n}\n";
+ }
+
+ static private String getToolTipCss() {
+ return // position z
+ "\n[data-tooltip] {\n" +
+ " position: relative;\n" +
+ " z-index: 2;\n" +
+ " cursor: pointer;\n" +
+ "}\n" +
+ // invisible
+ "[data-tooltip]::before,\n" +
+ "[data-tooltip]::after {\n" +
+ " visibility: hidden;\n" +
+ " -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)\";\n" +
+ " filter: progid: DXImageTransform.Microsoft.Alpha(Opacity=0);\n" +
+ " opacity: 0;\n" +
+ " pointer-events: none;\n" +
+ "}\n" +
+ // position & sketch
+ "[data-tooltip]::before {\n" +
+ " position: absolute;\n" +
+ " bottom: 50%;\n" +
+ " left: 50%;\n" +
+ " margin-bottom: 5px;\n" +
+ " padding: 7px;\n" +
+ " -webkit-border-radius: 3px;\n" +
+ " -moz-border-radius: 3px;\n" +
+ " border-radius: 3px;\n" +
+ " background-color: #000;\n" +
+ " background-color: hsla(0, 0%, 20%, 0.9);\n" +
+ " color: #fff;\n" +
+ " content: attr(data-tooltip);\n" +
+ " text-align: center;\n" +
+ " font-size: 14px;\n" +
+ " line-height: 1.2;\n" +
+ "}\n" +
+ // hover show
+ "[data-tooltip]:hover::before,\n" +
+ "[data-tooltip]:hover::after {\n" +
+ " visibility: visible;\n" +
+ " -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)\";\n" +
+ " filter: progid: DXImageTransform.Microsoft.Alpha(Opacity=100);\n" +
+ " opacity: 1;\n" +
+ "}\n";
+ }
+
+
+ ///// TODO drawing code for semantic type asterisk
+// [data-tooltip]:after {
+// position: absolute;
+// bottom: 150%;
+// left: 50%;
+// margin-left: -5px;
+// width: 0;
+// border-top: 5px solid #000;
+// border-top: 5px solid hsla(0, 0%, 20%, 0.9);
+// border-right: 5px solid transparent;
+// border-left: 5px solid transparent;
+// content: " ";
+// font-size: 0;
+// line-height: 0;
+// }
+
+
+// static private String getAsterisk( final String className, final String color, final String xOffset, final String yOffset ) {
+// return "\n." + className + " {\n" +
+// " position: relative;\n" +
+// " display: inline-block " + color + ";\n" +
+// " border-bottom: " + size + "em " + dashType + " " + color + ";\n" +
+// "}\n";
+// }
+
+
+}
Added: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java?rev=1765388&view=auto
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java (added)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cc/pretty/html/HtmlTextWriter.java Tue Oct 18 04:21:12 2016
@@ -0,0 +1,446 @@
+package org.apache.ctakes.core.cc.pretty.html;
+
+
+import org.apache.ctakes.core.cc.pretty.SemanticGroup;
+import org.apache.ctakes.core.cc.pretty.textspan.DefaultTextSpan;
+import org.apache.ctakes.core.cc.pretty.textspan.TextSpan;
+import org.apache.ctakes.core.resource.FileLocator;
+import org.apache.ctakes.core.util.DocumentIDAnnotationUtil;
+import org.apache.ctakes.core.util.OntologyConceptUtil;
+import org.apache.ctakes.typesystem.type.refsem.UmlsConcept;
+import org.apache.ctakes.typesystem.type.syntax.BaseToken;
+import org.apache.ctakes.typesystem.type.syntax.NewlineToken;
+import org.apache.ctakes.typesystem.type.textsem.EventMention;
+import org.apache.ctakes.typesystem.type.textsem.IdentifiedAnnotation;
+import org.apache.ctakes.typesystem.type.textsem.TimeMention;
+import org.apache.ctakes.typesystem.type.textspan.Sentence;
+import org.apache.log4j.Logger;
+import org.apache.uima.analysis_engine.AnalysisEngineProcessException;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.CASException;
+import org.apache.uima.cas.text.AnnotationFS;
+import org.apache.uima.fit.component.CasConsumer_ImplBase;
+import org.apache.uima.fit.descriptor.ConfigurationParameter;
+import org.apache.uima.fit.util.JCasUtil;
+import org.apache.uima.jcas.JCas;
+
+import java.io.*;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.apache.ctakes.core.config.ConfigParameterConstants.DESC_OUTPUTDIR;
+import static org.apache.ctakes.core.config.ConfigParameterConstants.PARAM_OUTPUTDIR;
+
+/**
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 9/8/2016
+ */
+final public class HtmlTextWriter extends CasConsumer_ImplBase {
+
+ static private final Logger LOGGER = Logger.getLogger( "HtmlTextWriter" );
+
+
+ static private final String FILE_EXTENSION = ".pretty.html";
+
+ @ConfigurationParameter(
+ name = PARAM_OUTPUTDIR,
+ mandatory = false,
+ description = DESC_OUTPUTDIR,
+ defaultValue = ""
+ )
+ private String _outputDirPath;
+
+ /**
+ * @param outputDirectoryPath may be empty or null, in which case the current working directory is used
+ * @throws IllegalArgumentException if the provided path points to a File and not a Directory
+ * @throws SecurityException if the File System has issues
+ */
+ public void setOutputDirectory( final String outputDirectoryPath ) throws IllegalArgumentException,
+ SecurityException {
+ // If no outputDir is specified (null or empty) the current working directory will be used. Else check path.
+ if ( outputDirectoryPath == null || outputDirectoryPath.isEmpty() ) {
+ LOGGER.debug( "No Output Directory Path specified, using current working directory "
+ + System.getProperty( "user.dir" ) );
+ _outputDirPath = System.getProperty( "user.dir" );
+ return;
+ }
+ String fullDirPath;
+ try {
+ fullDirPath = FileLocator.getFullPath( outputDirectoryPath );
+ final File outputDir = new File( fullDirPath );
+ if ( !outputDir.exists() ) {
+ outputDir.mkdirs();
+ }
+ if ( !outputDir.isDirectory() ) {
+ throw new IllegalArgumentException( outputDirectoryPath + " is not a valid directory path" );
+ }
+ } catch ( FileNotFoundException fnfE ) {
+ throw new IllegalArgumentException( outputDirectoryPath + " is not a valid directory path" );
+ }
+ _outputDirPath = fullDirPath;
+ LOGGER.debug( "Output Directory Path set to " + _outputDirPath );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void process( final CAS aCAS ) throws AnalysisEngineProcessException {
+ try {
+ final JCas jcas = aCAS.getJCas();
+ process( jcas );
+ } catch ( CASException casE ) {
+ throw new AnalysisEngineProcessException( casE );
+ }
+ }
+
+ /**
+ * Process the jcas and write pretty sentences to file. Filename is based upon the document id stored in the cas
+ *
+ * @param jcas ye olde ...
+ */
+ public void process( final JCas jcas ) {
+ LOGGER.info( "Starting processing" );
+ final String docId = DocumentIDAnnotationUtil.getDocumentIdForFile( jcas );
+ final File outputFile = new File( _outputDirPath + "/" + docId + FILE_EXTENSION );
+ final String cssPath = _outputDirPath + "/ctakes.pretty.css";
+ try ( final BufferedWriter writer = new BufferedWriter( new FileWriter( outputFile ) ) ) {
+ writer.write( getHeader() );
+ writer.write( getCssLink( "ctakes.pretty.css" ) );
+
+ final Collection<Sentence> sentences = JCasUtil.select( jcas, Sentence.class );
+ for ( Sentence sentence : sentences ) {
+ writeSentence( jcas, sentence, writer );
+ }
+
+ writer.write( getFooter() );
+ } catch ( IOException ioE ) {
+ LOGGER.error( "Could not not write html file " + outputFile.getPath() );
+ LOGGER.error( ioE.getMessage() );
+ }
+ CssWriter.writeCssFile( cssPath );
+ LOGGER.info( "Finished processing" );
+ }
+
+
+ /**
+ * Write a paragraph from the document text
+ *
+ * @param jcas ye olde ...
+ * @param paragraph annotation containing the paragraph
+ * @param writer writer to which pretty html for the paragraph should be written
+ * @throws IOException if the writer has issues
+ */
+ static private void writeParagraph( final JCas jcas,
+ final AnnotationFS paragraph,
+ final BufferedWriter writer ) throws IOException {
+ final String sentenceText = paragraph.getCoveredText().trim();
+ if ( sentenceText.isEmpty() ) {
+ return;
+ }
+ final Map<TextSpan, String> baseTokenMap = createBaseTokenMap( jcas, paragraph );
+ if ( baseTokenMap.isEmpty() ) {
+ return;
+ }
+ final Map<TextSpan, Collection<IdentifiedAnnotation>> annotationMap = createAnnotationMap( jcas, paragraph );
+ final Map<Integer, String> tags = createTags( annotationMap );
+ final StringBuilder sb = new StringBuilder();
+ for ( Map.Entry<TextSpan, String> entry : baseTokenMap.entrySet() ) {
+ final String beginTag = tags.get( entry.getKey().getBegin() );
+ if ( beginTag != null ) {
+ sb.append( beginTag );
+ }
+ sb.append( entry.getKey() );
+ final String endTag = tags.get( entry.getKey().getEnd() );
+ if ( endTag != null ) {
+ sb.append( endTag );
+ }
+ sb.append( " " );
+ }
+ writer.write( "<p>\n" + sb.toString() + "\n</p>\n" );
+ }
+
+
+ /**
+ * Write a sentence from the document text
+ *
+ * @param jcas ye olde ...
+ * @param sentence annotation containing the sentence
+ * @param writer writer to which pretty text for the sentence should be written
+ * @throws IOException if the writer has issues
+ */
+ static private void writeSentence( final JCas jcas,
+ final AnnotationFS sentence,
+ final BufferedWriter writer ) throws IOException {
+ final String sentenceText = sentence.getCoveredText().trim();
+ if ( sentenceText.isEmpty() ) {
+ return;
+ }
+ // Map of TextSpans to their covered text
+ final Map<TextSpan, String> baseTokenMap = createBaseTokenMap( jcas, sentence );
+ if ( baseTokenMap.isEmpty() ) {
+ return;
+ }
+ // Map of TextSpans to their covered annotations
+ final Map<TextSpan, Collection<IdentifiedAnnotation>> annotationMap = createAnnotationMap( jcas, sentence );
+ final Map<Integer, String> tags = createTags( annotationMap );
+ final StringBuilder sb = new StringBuilder();
+ int previousEndIndex = -1;
+ for ( Map.Entry<TextSpan, String> entry : baseTokenMap.entrySet() ) {
+ final TextSpan textSpan = entry.getKey();
+ if ( textSpan.getBegin() != previousEndIndex ) {
+ // If the previous end index was this begin index then the tag was already written
+ final String beginTag = tags.get( textSpan.getBegin() );
+ if ( beginTag != null ) {
+ sb.append( beginTag );
+ }
+ }
+ sb.append( entry.getValue() );
+ final String endTag = tags.get( textSpan.getEnd() );
+ if ( endTag != null ) {
+ sb.append( endTag );
+ }
+ sb.append( " " );
+ previousEndIndex = textSpan.getEnd();
+ }
+ writer.write( "<div>\n" + sb.toString() + "\n<br></div>\n" );
+ }
+
+
+ static private Map<TextSpan, String> createBaseTokenMap( final JCas jcas, final AnnotationFS sentence ) {
+ final int sentenceBegin = sentence.getBegin();
+ final Collection<BaseToken> baseTokens = JCasUtil.selectCovered( jcas, BaseToken.class, sentence );
+ final Map<TextSpan, String> baseItemMap = new LinkedHashMap<>();
+ for ( BaseToken baseToken : baseTokens ) {
+ final TextSpan textSpan = new DefaultTextSpan( baseToken, sentenceBegin );
+ if ( textSpan.getWidth() == 0 ) {
+ continue;
+ }
+ if ( baseToken instanceof NewlineToken ) {
+ baseItemMap.put( textSpan, " " );
+ continue;
+ }
+ baseItemMap.put( textSpan, baseToken.getCoveredText() );
+ }
+ return baseItemMap;
+ }
+
+ static private Map<TextSpan, Collection<IdentifiedAnnotation>> createAnnotationMap( final JCas jcas,
+ final AnnotationFS sentence ) {
+ final Map<TextSpan, Collection<IdentifiedAnnotation>> annotationMap = new HashMap<>();
+ final int sentenceBegin = sentence.getBegin();
+ final Collection<IdentifiedAnnotation> identifiedAnnotations
+ = JCasUtil.selectCovered( jcas, IdentifiedAnnotation.class, sentence );
+ for ( IdentifiedAnnotation annotation : identifiedAnnotations ) {
+ final TextSpan textSpan = new DefaultTextSpan( annotation, sentenceBegin );
+ if ( textSpan.getWidth() == 0 ) {
+ continue;
+ }
+ final Collection<String> semanticNames = getSemanticNames( annotation );
+ if ( !semanticNames.isEmpty() || annotation instanceof TimeMention || annotation instanceof EventMention ) {
+ Collection<IdentifiedAnnotation> annotations = annotationMap.get( textSpan );
+ if ( annotations == null ) {
+ annotations = new ArrayList<>();
+ annotationMap.put( textSpan, annotations );
+ }
+ annotations.add( annotation );
+ }
+ }
+ return annotationMap;
+ }
+
+
+ static private Collection<String> getSemanticNames( final IdentifiedAnnotation identifiedAnnotation ) {
+ final Collection<UmlsConcept> umlsConcepts = OntologyConceptUtil.getUmlsConcepts( identifiedAnnotation );
+ if ( umlsConcepts == null || umlsConcepts.isEmpty() ) {
+ return Collections.emptyList();
+ }
+ final Collection<String> semanticNames = new HashSet<>();
+ for ( UmlsConcept umlsConcept : umlsConcepts ) {
+ final String tui = umlsConcept.getTui();
+ String semanticName = SemanticGroup.getSemanticName( tui );
+ if ( semanticName.equals( "Unknown" ) ) {
+ semanticName = identifiedAnnotation.getClass().getSimpleName();
+ }
+ semanticNames.add( semanticName );
+ }
+ final List<String> semanticList = new ArrayList<>( semanticNames );
+ Collections.sort( semanticList );
+ return semanticList;
+ }
+
+
+ static private Map<Integer, String> createTags(
+ final Map<TextSpan, Collection<IdentifiedAnnotation>> annotationMap ) {
+ if ( annotationMap.isEmpty() ) {
+ return Collections.emptyMap();
+ }
+ final Collection<Integer> indices = new HashSet<>();
+ final Map<Integer, Collection<String>> polarities = new HashMap<>();
+ final Map<Integer, Collection<String>> beginClasses = new HashMap<>();
+ final Map<Integer, Collection<String>> endClasses = new HashMap<>();
+ for ( Map.Entry<TextSpan, Collection<IdentifiedAnnotation>> entry : annotationMap.entrySet() ) {
+ final Collection<String> tagClasses = createClasses( entry.getValue() );
+ if ( tagClasses.isEmpty() ) {
+ continue;
+ }
+ final TextSpan textSpan = entry.getKey();
+ indices.add( textSpan.getBegin() );
+ indices.add( textSpan.getEnd() );
+ // add all class tags that begin at this textSpan
+ addAll( beginClasses, textSpan.getBegin(), tagClasses );
+ // add all class tags that end at this text span
+ addAll( endClasses, textSpan.getEnd(), tagClasses );
+ final Collection<String> polarity = createPolarity( entry.getValue() );
+ addAll( polarities, textSpan.getBegin(), polarity );
+ addAll( polarities, textSpan.getEnd(), polarity );
+ }
+ if ( indices.isEmpty() ) {
+ return Collections.emptyMap();
+ }
+ final List<Integer> indexList = new ArrayList<>( indices );
+ Collections.sort( indexList );
+ final Map<Integer, String> tagMap = new HashMap<>();
+ final Collection<String> currentClasses = new HashSet<>();
+ String currentTag = "";
+ for ( Integer index : indexList ) {
+ currentTag = currentClasses.isEmpty() ? "" : "</span>";
+ final Collection<String> enders = endClasses.get( index );
+ if ( enders != null ) {
+ // remove all of the classes that end here
+ currentClasses.removeAll( enders );
+ if ( currentClasses.isEmpty() ) {
+ // all annotations have ended, go to the next index
+ if ( !currentTag.isEmpty() ) {
+ tagMap.put( index, currentTag );
+ }
+ continue;
+ }
+ final Collection<String> currentPolarity = polarities.get( index );
+ final String polarClasses = String.join( " ", currentPolarity ) + " " + String.join( " ", currentClasses );
+ final String toolTip = currentClasses.isEmpty() ? "" : " data-tooltip=\"" + polarClasses + "\"";
+ // tag for the classes that continue into the next textspan
+ currentTag += "<span class=\"" + polarClasses + "\"" + toolTip + ">";
+ tagMap.put( index, currentTag );
+ continue;
+ }
+ final Collection<String> beginners = beginClasses.get( index );
+ if ( beginners != null ) {
+ int size = currentClasses.size();
+ currentClasses.addAll( beginners );
+ if ( currentClasses.size() != size ) {
+ final Collection<String> currentPolarity = polarities.get( index );
+ final String polarClasses = String.join( " ", currentPolarity ) + " " +
+ String.join( " ", currentClasses );
+ final String toolTip = currentClasses.isEmpty() ? "" : " data-tooltip=\"" + polarClasses + "\"";
+ // tag for the classes that continue and begin the next textspan
+ currentTag += "<span class=\"" + polarClasses + "\"" + toolTip + ">";
+ tagMap.put( index, currentTag );
+ }
+ }
+ }
+ if ( !currentTag.endsWith( "</span>" ) ) {
+ tagMap.put( indexList.get( indexList.size() - 1 ), currentTag + "</span>" );
+ }
+ return tagMap;
+ }
+
+
+ static private void addAll( final Map<Integer, Collection<String>> map, final Integer index,
+ final Collection<String> values ) {
+ // add all class tags that end at this text span
+ Collection<String> set = map.get( index );
+ if ( set == null ) {
+ set = new HashSet<>();
+ map.put( index, set );
+ }
+ set.addAll( values );
+ }
+
+
+ static private Collection<String> createPolarity( final Collection<IdentifiedAnnotation> annotations ) {
+ return annotations.stream()
+ .map( HtmlTextWriter::createPolarity )
+ .flatMap( Collection::stream )
+ .collect( Collectors.toSet() );
+ }
+
+ static private Collection<String> createPolarity( final IdentifiedAnnotation annotation ) {
+ final Collection<String> tags = new ArrayList<>();
+ if ( annotation.getPolarity() < 0 ) {
+ if ( annotation.getUncertainty() > 0 ) {
+ tags.add( "uncertainnegated" );
+ } else {
+ tags.add( "negated" );
+ }
+ } else if ( annotation.getUncertainty() > 0 ) {
+ tags.add( "uncertain" );
+ } else {
+ tags.add( "affirmed" );
+ }
+ return tags;
+ }
+
+ static private Collection<String> createClasses( final Collection<IdentifiedAnnotation> annotations ) {
+ return annotations.stream()
+ .map( HtmlTextWriter::getSemanticNames )
+ .flatMap( Collection::stream )
+ .distinct()
+ .sorted()
+ .collect( Collectors.toList() );
+ }
+
+
+ // // Can do something like
+// // Patient has a <a class="affirmed finding" id="finding1" href="#site9">rash</a> on his <textspan class="anatomy" id="site9">elbow</textspan>.
+// // #site9:target { font-weight: bold; }
+//
+// // Can change background of b when hovering over a :
+// // #a:hover ~ #b { background: #ccc }
+// // iff b is after a. Can not change a before b by hovering over b
+
+
+ static private String addBackgroundLink( final String idName, final String color ) {
+ return "onmouseover=\"linkBg(" + idName + "," + color + ")\" onmouseout=\"linkBg(" + idName + ",white)\"";
+ }
+
+
+ static private String getCssLink( final String filePath ) {
+ return "<link rel=\"stylesheet\" href=\"" + filePath + "\" type=\"text/css\" media=\"screen\">";
+ }
+
+
+ static private String getHeader() {
+ return "<!DOCTYPE html>\n" +
+ "<html>\n" +
+ "<body>\n";
+ }
+
+ static private String getFooter() {
+ return "</body>\n" +
+ "</html>\n";
+ }
+
+
+ // CSS
+
+
+ // javascript
+
+ static private String startJavascript() {
+ return "<script type=\"text/javascript\">";
+ }
+
+ static private String endJavascript() {
+ return "</script>";
+ }
+
+ static private String getLinkBackgrounds() {
+ return " function linkBg(id,color) {\n" +
+ " document.getElementById(id).style.backgroundColor = color;\n" +
+ " }\n";
+ }
+
+
+}