Skip to content

Commit 4161619

Browse files
committed
Support HTML, TABLE and IMG display - Dynamic form in progress
1 parent 8e635e1 commit 4161619

File tree

7 files changed

+461
-171
lines changed

7 files changed

+461
-171
lines changed

notebook/r/note.json

Lines changed: 326 additions & 125 deletions
Large diffs are not rendered by default.

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@
111111
</modules>
112112

113113
<properties>
114-
<rscala.version>1.0.6</rscala.version>
115114
<slf4j.version>1.7.10</slf4j.version>
116115
<log4j.version>1.2.17</log4j.version>
117116
<libthrift.version>0.9.2</libthrift.version>
118117
<gson.version>2.2</gson.version>
119118
<guava.version>15.0</guava.version>
119+
<rscala.version>1.0.6</rscala.version>
120120

121121
<PermGen>64m</PermGen>
122122
<MaxPermGen>512m</MaxPermGen>

spark/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<url>http://zeppelin.incubator.apache.org</url>
3636

3737
<properties>
38+
<jsoup.version>1.8.2</jsoup.version>
3839
<mockito.version>1.10.19</mockito.version>
3940
<powermock.version>1.6.4</powermock.version>
4041
<spark.version>1.4.1</spark.version>
@@ -235,7 +236,7 @@
235236
<dependency>
236237
<groupId>org.jsoup</groupId>
237238
<artifactId>jsoup</artifactId>
238-
<version>1.8.2</version>
239+
<version>${jsoup.version}</version>
239240
</dependency>
240241

241242
<dependency>

spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717

1818
package org.apache.zeppelin.spark;
1919

20+
import static org.apache.zeppelin.spark.ZeppelinRDisplay.render;
21+
2022
import com.fasterxml.jackson.databind.JsonNode;
2123
import com.fasterxml.jackson.databind.ObjectMapper;
22-
import org.apache.commons.codec.binary.Base64;
2324
import org.apache.zeppelin.interpreter.*;
2425
import org.apache.zeppelin.scheduler.Scheduler;
2526
import org.apache.zeppelin.scheduler.SchedulerFactory;
@@ -106,12 +107,14 @@ public InterpreterResult interpret(String lines, InterpreterContext contextInter
106107
zeppelinR().eval(".zres <- knit2html(text=.zcmd)");
107108
String html = zeppelinR().getS0(".zres");
108109

109-
html = format(html, imageWidth);
110+
RDisplay rDisplay = render(html, imageWidth);
110111

111112
return new InterpreterResult(
112-
InterpreterResult.Code.SUCCESS,
113-
InterpreterResult.Type.HTML,
114-
html);
113+
rDisplay.code(),
114+
rDisplay.type(),
115+
rDisplay.content()
116+
);
117+
115118

116119
} catch (Exception e) {
117120
logger.error("Exception while connecting to R", e);
@@ -124,43 +127,6 @@ public InterpreterResult interpret(String lines, InterpreterContext contextInter
124127
}
125128
}
126129

127-
/*
128-
* Ensure we return proper HTML to be displayed in the Zeppelin UI.
129-
*/
130-
private String format(String html, String imageWidth) {
131-
132-
Document document = Jsoup.parse(html);
133-
134-
Element body = document.getElementsByTag("body").get(0);
135-
136-
Elements images = body.getElementsByTag("img");
137-
Elements scripts = body.getElementsByTag("script");
138-
139-
if ((images.size() == 0)
140-
&& (scripts.size() == 0)) {
141-
142-
// We are here with a pure text output, let's keep format intact...
143-
144-
return html.substring(
145-
html.indexOf("<body>") + 6,
146-
html.indexOf("</body>")
147-
)
148-
.replace("<p>", "<pre style='background-color: white; border: 0px;'>")
149-
.replace("</p>", "</pre>");
150-
151-
}
152-
153-
// OK, we have more than text...
154-
155-
for (Element image : images) {
156-
image.attr("width", imageWidth);
157-
}
158-
159-
return body.html()
160-
.replaceAll("src=\"//", "src=\"http://")
161-
.replaceAll("href=\"//", "href=\"http://");
162-
}
163-
164130
@Override
165131
public void close() {
166132
zeppelinR().close();

spark/src/main/scala/org/apache/zeppelin/spark/ZeppelinR.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ object ZeppelinR {
8383
|z.get <- function(name) {
8484
| SparkR:::callJMethod(.zeppelinContext, "get", name)
8585
|}
86+
|z.input <- function(name, value) {
87+
| SparkR:::callJMethod(.zeppelinContext, "input", name, value)
88+
|}
8689
|""".stripMargin
8790
)
8891

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.zeppelin.spark
19+
20+
import org.apache.zeppelin.interpreter.InterpreterResult.Code
21+
import org.apache.zeppelin.interpreter.InterpreterResult.Code.{SUCCESS, ERROR}
22+
import org.apache.zeppelin.interpreter.InterpreterResult.Type
23+
import org.apache.zeppelin.interpreter.InterpreterResult.Type.{TEXT, HTML, TABLE, IMG}
24+
import org.jsoup.Jsoup
25+
import org.jsoup.nodes.Element
26+
import org.jsoup.nodes.Document
27+
28+
import scala.collection.JavaConversions._
29+
30+
import scala.util.matching.Regex
31+
32+
case class RDisplay(content: String, `type`: Type, code: Code)
33+
34+
object ZeppelinRDisplay {
35+
36+
val pattern = new Regex("""^ *\[\d*\] """)
37+
38+
def render(html: String, imageWidth: String): RDisplay = {
39+
40+
val document = Jsoup.parse(html)
41+
document.outputSettings().prettyPrint(false)
42+
43+
val body = document.body()
44+
45+
if (body.getElementsByTag("p").isEmpty) return RDisplay(body.html(), HTML, ERROR)
46+
47+
val bodyHtml = body.html()
48+
49+
if (! bodyHtml.contains("<img")
50+
&& ! bodyHtml.contains("<script")
51+
&& ! bodyHtml.contains("%html ")
52+
&& ! bodyHtml.contains("%table ")
53+
&& ! bodyHtml.contains("%img ")
54+
) {
55+
return textDisplay(body)
56+
}
57+
58+
if (bodyHtml.contains("%table")) {
59+
return tableDisplay(body)
60+
}
61+
62+
if (bodyHtml.contains("%img")) {
63+
return imgDisplay(body)
64+
}
65+
66+
return htmlDisplay(body, imageWidth)
67+
68+
}
69+
70+
private def textDisplay(body: Element): RDisplay = {
71+
RDisplay(body.getElementsByTag("p").get(0).html(), TEXT, SUCCESS)
72+
}
73+
74+
private def tableDisplay(body: Element): RDisplay = {
75+
val p = body.getElementsByTag("p").get(0).html.replace("“%table " , "").replace("", "")
76+
val r = (pattern findFirstIn p).getOrElse("")
77+
val table = p.replace(r, "").replace("\\t", "\t").replace("\\n", "\n")
78+
RDisplay(table, TABLE, SUCCESS)
79+
}
80+
81+
private def imgDisplay(body: Element): RDisplay = {
82+
val p = body.getElementsByTag("p").get(0).html.replace("“%img " , "").replace("", "")
83+
val r = (pattern findFirstIn p).getOrElse("")
84+
val img = p.replace(r, "")
85+
RDisplay(img, IMG, SUCCESS)
86+
}
87+
88+
private def htmlDisplay(body: Element, imageWidth: String): RDisplay = {
89+
90+
var div = new String()
91+
92+
for (element <- body.children) {
93+
94+
val eHtml = element.html()
95+
var eOuterHtml = element.outerHtml()
96+
97+
eOuterHtml = eOuterHtml.replace("“%html " , "").replace("", "")
98+
99+
val r = (pattern findFirstIn eHtml).getOrElse("")
100+
101+
div = div + eOuterHtml.replace(r, "")
102+
103+
}
104+
105+
val content = div
106+
.replaceAll("src=\"//", "src=\"http://")
107+
.replaceAll("href=\"//", "href=\"http://")
108+
109+
body.html(content)
110+
111+
for (image <- body.getElementsByTag("img")) {
112+
image.attr("width", imageWidth)
113+
}
114+
115+
RDisplay(body.html, HTML, SUCCESS)
116+
117+
}
118+
119+
}

spark/src/test/java/org/apache/zeppelin/spark/SparkRInterpreterTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class SparkRInterpreterTest {
4646
private static final Logger LOGGER = LoggerFactory.getLogger(SparkRInterpreterTest.class);
4747

4848
private static final String MOCK_RSCALA_RESULT = "<body><p> Mock R Result </p></body>";
49-
private static final String MOCK_R_INTERPRETER_RESULT = "<pre style='background-color: white; border: 0px;'> Mock R Result </pre>";
49+
private static final String MOCK_R_INTERPRETER_RESULT = "Mock R Result";
5050

5151
private static InterpreterContext context;
5252
private static InterpreterGroup intpGroup;
@@ -64,7 +64,7 @@ public void testSuccess() throws Exception {
6464
InterpreterResult ret = sparkRInterpreter.interpret(MOCK_RSCALA_RESULT, context);
6565
assertEquals(InterpreterResult.Code.SUCCESS, ret.code());
6666
assertEquals(MOCK_R_INTERPRETER_RESULT, ret.message());
67-
assertEquals(InterpreterResult.Type.HTML, ret.type());
67+
assertEquals(InterpreterResult.Type.TEXT, ret.type());
6868
}
6969

7070
private static void initInterpreters() {

0 commit comments

Comments
 (0)