mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
Support HTML, TABLE and IMG display - Dynamic form in progress
This commit is contained in:
parent
8e635e1f58
commit
41616194ac
7 changed files with 461 additions and 171 deletions
File diff suppressed because one or more lines are too long
2
pom.xml
2
pom.xml
|
|
@ -111,12 +111,12 @@
|
|||
</modules>
|
||||
|
||||
<properties>
|
||||
<rscala.version>1.0.6</rscala.version>
|
||||
<slf4j.version>1.7.10</slf4j.version>
|
||||
<log4j.version>1.2.17</log4j.version>
|
||||
<libthrift.version>0.9.2</libthrift.version>
|
||||
<gson.version>2.2</gson.version>
|
||||
<guava.version>15.0</guava.version>
|
||||
<rscala.version>1.0.6</rscala.version>
|
||||
|
||||
<PermGen>64m</PermGen>
|
||||
<MaxPermGen>512m</MaxPermGen>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
<url>http://zeppelin.incubator.apache.org</url>
|
||||
|
||||
<properties>
|
||||
<jsoup.version>1.8.2</jsoup.version>
|
||||
<mockito.version>1.10.19</mockito.version>
|
||||
<powermock.version>1.6.4</powermock.version>
|
||||
<spark.version>1.4.1</spark.version>
|
||||
|
|
@ -235,7 +236,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.8.2</version>
|
||||
<version>${jsoup.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@
|
|||
|
||||
package org.apache.zeppelin.spark;
|
||||
|
||||
import static org.apache.zeppelin.spark.ZeppelinRDisplay.render;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.zeppelin.interpreter.*;
|
||||
import org.apache.zeppelin.scheduler.Scheduler;
|
||||
import org.apache.zeppelin.scheduler.SchedulerFactory;
|
||||
|
|
@ -106,12 +107,14 @@ public class SparkRInterpreter extends Interpreter {
|
|||
zeppelinR().eval(".zres <- knit2html(text=.zcmd)");
|
||||
String html = zeppelinR().getS0(".zres");
|
||||
|
||||
html = format(html, imageWidth);
|
||||
RDisplay rDisplay = render(html, imageWidth);
|
||||
|
||||
return new InterpreterResult(
|
||||
InterpreterResult.Code.SUCCESS,
|
||||
InterpreterResult.Type.HTML,
|
||||
html);
|
||||
rDisplay.code(),
|
||||
rDisplay.type(),
|
||||
rDisplay.content()
|
||||
);
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Exception while connecting to R", e);
|
||||
|
|
@ -124,43 +127,6 @@ public class SparkRInterpreter extends Interpreter {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure we return proper HTML to be displayed in the Zeppelin UI.
|
||||
*/
|
||||
private String format(String html, String imageWidth) {
|
||||
|
||||
Document document = Jsoup.parse(html);
|
||||
|
||||
Element body = document.getElementsByTag("body").get(0);
|
||||
|
||||
Elements images = body.getElementsByTag("img");
|
||||
Elements scripts = body.getElementsByTag("script");
|
||||
|
||||
if ((images.size() == 0)
|
||||
&& (scripts.size() == 0)) {
|
||||
|
||||
// We are here with a pure text output, let's keep format intact...
|
||||
|
||||
return html.substring(
|
||||
html.indexOf("<body>") + 6,
|
||||
html.indexOf("</body>")
|
||||
)
|
||||
.replace("<p>", "<pre style='background-color: white; border: 0px;'>")
|
||||
.replace("</p>", "</pre>");
|
||||
|
||||
}
|
||||
|
||||
// OK, we have more than text...
|
||||
|
||||
for (Element image : images) {
|
||||
image.attr("width", imageWidth);
|
||||
}
|
||||
|
||||
return body.html()
|
||||
.replaceAll("src=\"//", "src=\"http://")
|
||||
.replaceAll("href=\"//", "href=\"http://");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
zeppelinR().close();
|
||||
|
|
|
|||
|
|
@ -83,6 +83,9 @@ object ZeppelinR {
|
|||
|z.get <- function(name) {
|
||||
| SparkR:::callJMethod(.zeppelinContext, "get", name)
|
||||
|}
|
||||
|z.input <- function(name, value) {
|
||||
| SparkR:::callJMethod(.zeppelinContext, "input", name, value)
|
||||
|}
|
||||
|""".stripMargin
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.zeppelin.spark
|
||||
|
||||
import org.apache.zeppelin.interpreter.InterpreterResult.Code
|
||||
import org.apache.zeppelin.interpreter.InterpreterResult.Code.{SUCCESS, ERROR}
|
||||
import org.apache.zeppelin.interpreter.InterpreterResult.Type
|
||||
import org.apache.zeppelin.interpreter.InterpreterResult.Type.{TEXT, HTML, TABLE, IMG}
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
import scala.util.matching.Regex
|
||||
|
||||
case class RDisplay(content: String, `type`: Type, code: Code)
|
||||
|
||||
object ZeppelinRDisplay {
|
||||
|
||||
val pattern = new Regex("""^ *\[\d*\] """)
|
||||
|
||||
def render(html: String, imageWidth: String): RDisplay = {
|
||||
|
||||
val document = Jsoup.parse(html)
|
||||
document.outputSettings().prettyPrint(false)
|
||||
|
||||
val body = document.body()
|
||||
|
||||
if (body.getElementsByTag("p").isEmpty) return RDisplay(body.html(), HTML, ERROR)
|
||||
|
||||
val bodyHtml = body.html()
|
||||
|
||||
if (! bodyHtml.contains("<img")
|
||||
&& ! bodyHtml.contains("<script")
|
||||
&& ! bodyHtml.contains("%html ")
|
||||
&& ! bodyHtml.contains("%table ")
|
||||
&& ! bodyHtml.contains("%img ")
|
||||
) {
|
||||
return textDisplay(body)
|
||||
}
|
||||
|
||||
if (bodyHtml.contains("%table")) {
|
||||
return tableDisplay(body)
|
||||
}
|
||||
|
||||
if (bodyHtml.contains("%img")) {
|
||||
return imgDisplay(body)
|
||||
}
|
||||
|
||||
return htmlDisplay(body, imageWidth)
|
||||
|
||||
}
|
||||
|
||||
private def textDisplay(body: Element): RDisplay = {
|
||||
RDisplay(body.getElementsByTag("p").get(0).html(), TEXT, SUCCESS)
|
||||
}
|
||||
|
||||
private def tableDisplay(body: Element): RDisplay = {
|
||||
val p = body.getElementsByTag("p").get(0).html.replace("“%table " , "").replace("”", "")
|
||||
val r = (pattern findFirstIn p).getOrElse("")
|
||||
val table = p.replace(r, "").replace("\\t", "\t").replace("\\n", "\n")
|
||||
RDisplay(table, TABLE, SUCCESS)
|
||||
}
|
||||
|
||||
private def imgDisplay(body: Element): RDisplay = {
|
||||
val p = body.getElementsByTag("p").get(0).html.replace("“%img " , "").replace("”", "")
|
||||
val r = (pattern findFirstIn p).getOrElse("")
|
||||
val img = p.replace(r, "")
|
||||
RDisplay(img, IMG, SUCCESS)
|
||||
}
|
||||
|
||||
private def htmlDisplay(body: Element, imageWidth: String): RDisplay = {
|
||||
|
||||
var div = new String()
|
||||
|
||||
for (element <- body.children) {
|
||||
|
||||
val eHtml = element.html()
|
||||
var eOuterHtml = element.outerHtml()
|
||||
|
||||
eOuterHtml = eOuterHtml.replace("“%html " , "").replace("”", "")
|
||||
|
||||
val r = (pattern findFirstIn eHtml).getOrElse("")
|
||||
|
||||
div = div + eOuterHtml.replace(r, "")
|
||||
|
||||
}
|
||||
|
||||
val content = div
|
||||
.replaceAll("src=\"//", "src=\"http://")
|
||||
.replaceAll("href=\"//", "href=\"http://")
|
||||
|
||||
body.html(content)
|
||||
|
||||
for (image <- body.getElementsByTag("img")) {
|
||||
image.attr("width", imageWidth)
|
||||
}
|
||||
|
||||
RDisplay(body.html, HTML, SUCCESS)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ public class SparkRInterpreterTest {
|
|||
private static final Logger LOGGER = LoggerFactory.getLogger(SparkRInterpreterTest.class);
|
||||
|
||||
private static final String MOCK_RSCALA_RESULT = "<body><p> Mock R Result </p></body>";
|
||||
private static final String MOCK_R_INTERPRETER_RESULT = "<pre style='background-color: white; border: 0px;'> Mock R Result </pre>";
|
||||
private static final String MOCK_R_INTERPRETER_RESULT = "Mock R Result";
|
||||
|
||||
private static InterpreterContext context;
|
||||
private static InterpreterGroup intpGroup;
|
||||
|
|
@ -64,7 +64,7 @@ public class SparkRInterpreterTest {
|
|||
InterpreterResult ret = sparkRInterpreter.interpret(MOCK_RSCALA_RESULT, context);
|
||||
assertEquals(InterpreterResult.Code.SUCCESS, ret.code());
|
||||
assertEquals(MOCK_R_INTERPRETER_RESULT, ret.message());
|
||||
assertEquals(InterpreterResult.Type.HTML, ret.type());
|
||||
assertEquals(InterpreterResult.Type.TEXT, ret.type());
|
||||
}
|
||||
|
||||
private static void initInterpreters() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue