ต้องการให้ Apache Forrest Application สามารถ
transform xdoc ในส่วนของ java source
ให้อยู่ในรูปสีสรรสดสวย
(เดิมใน xdoc มี tag ที่ชื่อ source
อยู่แล้ว แต่ไม่ได้ทำ highlight ให้)
วิธีการที่ทำได้มีอยู่ 2 ทางหลักๆ
- Customize Project 's Pipeline
- Customize Forrest 's Pipeline ที่ forrest installation directory โดยตรง
วิธีแรกดูดีสุด
โดยในคู่มือ forrest บอกไว้ว่า เราสามารถ
customize pipeline processing เฉพาะใน Document Project ของเรา
ผ่านทาง /src/documentation/sitemap.xmap ได้
โดยทำได้ 2 แบบคือ
พยายามจะทำแบบที่ 2 แต่ไม่ได้สักที
ก็เลยเปลี่ยนใจ ย้ายไป customize ที่ตัว
forrest configuration โดยตรงเลย
เริ่มจาก file /$FORREST_HOME/main/webapp/sitemap.xmap
มันจะมีการ mapping url ไว้ดังนี้
<map:match pattern="*.html">
<map:aggregate element="site">
<map:part src="cocoon:/skinconf.xml"/>
<map:part src="cocoon:/build-info"/>
<map:part src="cocoon:/tab-{0}"/>
<map:part src="cocoon:/menu-{0}"/>
<map:part src="cocoon:/body-{0}"/>
</map:aggregate>
<map:call resource="skinit">
<map:parameter name="type" value="site2xhtml"/>
<map:parameter name="path" value="{0}"/>
</map:call>
</map:match>
นั่นคือหลังจากได้ url ที่ลงท้ายด้วย .html เข้ามา
เจ้า forrest ก็จะแตกออกเป็น 5 dispatch ย่อย ที่จะ
แจกไปให้แต่ละส่วนทำงาน เช่น ส่วน tab, ส่วน menu
จากนั้นค่อย aggregate รวมกันแล้วส่งไป render อีกที
กรณีของเรา ก็คือต้องการ customize ส่วน body
จากของเดิมที่เป็นดังนี้
<map:match pattern="**body-*.html">
<map:generate src="cocoon:/{1}{2}.xml"/>
<map:transform type="idgen"/>
<map:transform type="xinclude"/>
<map:transform type="linkrewriter" src="cocoon:/{1}linkmap-{2}.html"/>
<map:transform src="resources/stylesheets/declare-broken-site-links.xsl" />
<map:call resource="skinit">
<map:parameter name="type" value="document2html"/>
<map:parameter name="path" value="{1}{2}.html"/>
<map:parameter name="notoc" value="false"/>
</map:call>
</map:match>
โดยสิ่งที่เราจะเพิ่มเข้าไปก็คือ หลังจากที่ generator ได้ file xdoc
มาแล้ว เราจะเข้าไป intercept sax event
เพื่อที่ว่าจะได้ทำการ parse java source ให้อยู่ในรูป
xml เสียก่อน จากนั้นก็จะส่งผ่านไปให้ xslt
ทำการ render ให้อยู่ในรูปแบบที่ต้องการ
<map:match pattern="**body-*.html">
<map:generate src="cocoon:/{1}{2}.xml"/>
<!-- parse java source to xml form -->
<map:transform type="java2html" />
<map:transform type="idgen"/>
<map:transform type="xinclude"/>
<map:transform type="linkrewriter" src="cocoon:/{1}linkmap-{2}.html"/>
<!-- render xml syntax tree to html tag -->
<map:transform src="resources/stylesheets/javahighlight.xsl"/>
<map:transform src="resources/stylesheets/declare-broken-site-links.xsl" />
<map:call resource="skinit">
<map:parameter name="type" value="document2html"/>
<map:parameter name="path" value="{1}{2}.html"/>
<map:parameter name="notoc" value="false"/>
</map:call>
</map:match>
ตัว transform type "java2html" จะทำหน้าที่แปลง
javasource ให้อยู่ในรูป xml โดยมี tag แปะตาม
ประเภทของ token
เช่น
package pok.test;
import java.util.ArrayList;
ก็จะถูกแปลงเป็น
<javakeyword>package</javakeyword pok.test;
<javakeyword>import</javakeyword> java.util.<java_type>ArrayList</java_type>;
ส่วน transform อีกตัวจะทำหน้าที่ apply style sheet "javahighlight.xsl"
เพื่อ render ให้อยู่ในรูปแบบ html ที่ต้องการ
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<!-- the obligatory copy-everything -->
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="jshl">
<pre class="hl">
<xsl:apply-templates mode="javasource"/>
</pre>
</xsl:template>
<xsl:template match="java_plain" mode="javasource">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="java_keyword" mode="javasource">
<span class="S_KEYWORD1"><xsl:apply-templates/></span>
</xsl:template>
...
วิธีการ implement transformer
เขียน class Java2HtmlTransformer โดย extends AbstractSaxTransformer
package pok.cocoon.transformer;
import java.io.StringBufferInputStream;
import org.apache.cocoon.transformation.AbstractSAXTransformer;
import org.apache.cocoon.xml.AttributesImpl;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
public class Java2HtmlTransformer extends AbstractSAXTransformer {
private static final String TAG_NAME = "javasource";
public void endElement(String uri, String name, String raw)
throws SAXException {
if (name.equals(TAG_NAME)) {
String srcStr = this.endTextRecording();
JavaXmlRenderer render = new JavaXmlRenderer();
sendStartElementEvent("jshl");
try {
render.highlight(new StringBufferInputStream(srcStr), this);
} catch (Throwable e) {
e.printStackTrace();
}
sendEndElementEvent("jshl");
} else {
super.endElement(uri, name, raw);
}
}
public void startElement(String uri, String name, String raw,
Attributes attr) throws SAXException {
if (name.equals(TAG_NAME)) {
this.startTextRecording();
} else {
super.startElement(uri, name, raw, attr);
}
}
}
หลังจากเขียน class แล้ว เราต้องบอกให้ Forrest
รู้จัก transformer นี้โดยการเพิ่ม config เข้าไปใน sitemap.xmap
<map:transformers>
<map:transformer name="java2html"
src="pok.cocoon.transformer.Java2HtmlTransformer"
logger="pok"
/>
</map:transformers>
ลักษณะการทำงานของ Java2HtmlTransformer ก็คือเราจะสนใจ sax event
เฉพาะส่วน startElement กับ endElement
กรณีที่พบ tag <javasource> เราก็จะทำการ
record text ที่เกิดขึ้นระหว่าง tag เปิด กับ tag ปิด
และทำการส่งไปให้ JavaXmlRenderer ทำการ parse text ในส่วนนี้
ส่วน Class JavaXmlRenderer ใช้ source code ตัวอย่างจาก
project JHighlight
package pok.cocoon.transformer;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import org.apache.cocoon.transformation.AbstractSAXTransformer;
import com.uwyn.jhighlight.highlighter.JavaHighlighter;
import com.uwyn.jhighlight.tools.StringUtils;
public class JavaXmlRenderer {
public void highlight(InputStream in, AbstractSAXTransformer sax) throws Exc
eption {
JavaHighlighter highlighter = new JavaHighlighter();
highlighter.ASSERT_IS_KEYWORD = true;
Reader isr;
isr = new InputStreamReader(in);
BufferedReader r = new BufferedReader(isr);
String line;
String token;
int length;
int style;
String css_class;
String previous_class = null;
int previous_style = 0;
while ((line = r.readLine()) != null)
{
line += "\n";
line = StringUtils.convertTabsToSpaces(line, 4);
// should be optimized by reusing a custom LineReader class
Reader lineReader = new StringReader(line);
highlighter.setReader(lineReader);
int index = 0;
while (index < line.length())
{
style = highlighter.getNextToken();
length = highlighter.getTokenLength();
token = line.substring(index, index + length);
if (style != previous_style)
{
css_class = getCssClass(style);
if (css_class != null)
{
if (previous_class != null)
{
sax.sendEndElementEvent(previous_class);
}
sax.sendStartElementEvent(css_class);
previous_class = css_class;
previous_style = style;
}
}
sax.sendTextEvent(token);
index += length;
}
}
}
protected String getCssClass(int style)
{
switch (style)
{
case JavaHighlighter.PLAIN_STYLE:
return "java_plain";
case JavaHighlighter.KEYWORD_STYLE:
return "java_keyword";
case JavaHighlighter.TYPE_STYLE:
return "java_type";
case JavaHighlighter.OPERATOR_STYLE:
return "java_operator";
case JavaHighlighter.SEPARATOR_STYLE:
return "java_separator";
case JavaHighlighter.LITERAL_STYLE:
return "java_literal";
case JavaHighlighter.JAVA_COMMENT_STYLE:
return "java_comment";
case JavaHighlighter.JAVADOC_COMMENT_STYLE:
return "java_javadoc_comment";
case JavaHighlighter.JAVADOC_TAG_STYLE:
return "java_javadoc_tag";
}
return null;
}
}
โดยหลักการทำงาน ก็คือ JavaXmlRenderer จะเรียกใช้ scanner ที่ชื่อ
JavaHighlighter (implement ด้วย Flex)
ทำการ scan ทีละบรรทัด
ผลลัพท์ที่ได้ จะได้ style กับ token ออกมา
จากนั้น ก็จะทำการส่ง sax event ออกไป
โดยมี tag name ตามรูปแบบ style ที่ได้มาจาก scanner
ตัวอย่างการนำไปใช้
<javasource>
package pok.test;
import java.util.ArrayList;
/**
* this is <code>test</code>
* @param hi
*/
public class Main implements java.io.Serializable {
public static void main(String[] args) {
System.out.println("helloWorld");
if (1 != 2) {
if (1 > 3) {
// pok test
}
}
}
}
</javasource>
ผลลัพท์ที่ได้
สรุป ปัญหาของการ customize forrest หรือ cocoon framework ก็คือ
เกิดจากเจ้าตัว cocoon framework ที่ค่อนข้างจะซับซ้อนพอสมควร
แถมมันยังใช้ IOC container ที่ชื่อ Avalon Excalibur
ยิ่งทำให้การทำความเข้าใจ source code ต่างๆ ต้องใช้เวลามาก
นอกจากนี้ ยังมีประเด็นความเข้าใจในส่วนของการ parse xml Document
ที่มักจะมีปัญหาเรื่อง namespace เสมอ
No comments:
Post a Comment