Eclipse(Version: Indigo Release)でScala IDE(2.9.1.FINAL)

Posted 2011年9月3日 by
Tagged As: | Categories: Scala | No Comments

EclipseがIndigoになり、Scalaも2.9.1になったところで、
環境を一新しました。

Eclipse は、以下のURLにてEclipse IDE for Java Developersをダウンロード。

http://www.eclipse.org/downloads/

Eclipseのインストールは親切なサイトを参考にしてください。
Javaは、JKD7がリリースされていのでインストールします。

ここからがScalaIEDの設定手順です。

はじめてScalaの環境を作った時に助けになったサイトのように
EclipsにScalaIDEをインストールした画面を貼り付けます。

ScalaIDEは、http://download.scala-ide.org/にあります。

Eclipseのメニューの右端のヘルプ(Help)Install New Software…を選択して、

画面のように以下のURLを設定します。Scala2.9.1のファイナルです。

Update site URL: http://download.scala-ide.org/nightly-update-wip-experiment-2.9.1.final




詳細はここに掲載します。


設定が完了して、
EclipseのメニューでFile → Newで新規プロジェクトでScalaのプロジェクトが表示されれば問題なし。
Scalaのプロジェクトを作ると、Scala Library[2.9.1.final]と表示されます。

Scala IEDも安定してます。

しかし、Scalaのソースファイルをいっぱい作り始めると、1つのソースファイルの変更によってプロジェクト全体が再ビルドしているような感じです。
それと、LibのJARファイルを更新したときはメニューのプロジェクトでClean…をしないと動きが安定してません。

Clean…とリフレッシュは動作が違うのですね。Cleanしたからいいと思っていると、
ソースコードを上書きすると、F5しろとEclipseに怒られます。

Ruby on Rails のModel classのようにDB操作する

Posted 2011年8月31日 by
Tagged As: | Categories: Scala | No Comments

Ruby on Rails

平均的な Ruby on Rails プログラムは SQL コードがあったとしてもその量は極めて限られています。

Ruby on Rails はモデル・ビュー・コントローラー (MVC) パターンをベースにしていることから、大半の Rails アプリケーションは 3 つの構成部分にはっきりと分かれます。
アプリケーションのデータを管理するために必要な振る舞いを受け持つのがモデル(M)です。

一般的に、Ruby on Rails アプリケーションではモデルとデータベース・テーブルの間に 1 対 1 の関係によって実装される傾向にあります。これはデータベースを使う上で重要なポイントです。

この方針は、アプリケーションの構造をシンプルにします。

Ruby on Rails がデフォルトでモデルのデータベース操作に使用するのが、 ActiveRecord というオブジェクト・リレーション・マッピング (ORM) です。



Rails アプリケーション

Ruby on Rails 開発者のスタイルとスタンスは、ActiveRecord が多くの処理を極めて簡単に記述できるため、Rails 開発者は「反 SQL」のスタンスを取りがちです。
このようなスタンスの Rails 開発者は、SQL のほうが理にかなっているとしても、SQL を使いたがりません。



Scala DB アプリケーション

ScalaでDB操作するシステムを開発するとき、
SQL文を意識してプログラムを書くのか、Rails アプリケーションの開発者のように反SQLのスタイルで書くのかは、データベースのスキルに依存します。

ScalaをRuby on Railsのように使いたのであれば、
SQLをこれから覚えるよりは、新しいサービスをとりあえず開発してリリースする方を優先するというスタンスでしょう。




データベースに対するActiveRecordの戦略は、DBのスキーマ情報からモデルクラスを生成することにつきます。
とすれば、Scalaプログラムも同じ生成機能があれば、Ruby on Rails 開発者とのハンディは帳消しできます。

Javaプログラマーは、JavaBeanによってビジネスアプリケーションを書くことに慣れています。
しかし、操作となると DAO (Data Access Object) で相変わらずSQL文と格闘。

ScalaにおいてモデルとなるBeanクラスと、find,create,update,delete操作系関数が生成できれば、いままでこの領域のコードを記述していたよりプログラマーが不要になります。
少人数のプログラマーでより多くの機能をより短時間に生み出すことができると主張するRuby on Rails 開発者と同じです。
結果、ソフトウェア開発費あたりのビジネス価値が増大することに他なりません。



Scalaで使えるBeanクラスは、Seedoフレームワークですでに生成することができるので、操作系のクラスも生成してみよと試みようと次のようなコードを書いてみました。

EMP表を操作するプログラムです。



package ews.servlet.ajax

import scala.collection.mutable.{HashMap,LinkedHashMap,HashSet,ArrayBuffer}
import seedo._
import seedo.database._
import scott.bean._
/**
 * The EMP table is operated. 
 */
class User {
	var key:String = null
	var column:String = "*"
	var results:ArrayBuffer[EMP] = null
	// DELET
	def remove(userids:Int*) : Int = {
	  var db:Db=null
	  var si = -1
	  try {
		  db = new Db
		  var sql:DL = db.deleteFrom("emp")
		  if(userids != null) {
			  var str = ""
			  var i = 0
			  userids.foreach(v =>{
				if(i != 0){
				  str += ","
				}
				str += "?"
				i += 1
			  })
			  sql.where("empno in ("+str+")",userids:_*)
		  }
		  si = db.execute.getResultSize
		  db.commit
	  } catch {
		case e:Exception => {
		  db.rollback
		  println(e.getMessage)
		  throw e
		}
	  } finally {
		  db.close
	  }
	  si
	}
	def remove(userids:List[Int]) : Int = {
	  var db:Db=null
	  var si = -1
	  try {
		  db = new Db
		  var sql:DL = db.deleteFrom("emp")
		  if(userids != null) {
			  var str = ""
			  var i = 0
			  userids.foreach(v =>{
				if(i != 0){
				  str += ","
				}
				str += "?"
				i += 1
			  })
			  sql.where("empno in ("+str+")",userids:_*)
		  }
		  si = db.execute.getResultSize
		  db.commit
	  } catch {
		case e:Exception => {
		  db.rollback
		  println(e.getMessage)
		  throw e
		}
	  } finally {
		  db.close
	  }
	  si
	}
	def remove(user:EMP) : Int = {
	  var db:Db=null
	  var si = -1
	  try {
		  db = new Db
		  db.deleteBean(user)
		  si = db.execute.getResultSize
		  db.commit
	  } catch {
		case e:Exception => {
		  db.rollback
		  println(e.getMessage)
		  throw e
		}
	  } finally {
		  db.close
	  }
	  si
	}

	// INSERT
	def create(user:EMP) : Unit = {
	  var db:Db=null
	  try {
		  db = new Db
		  db.insertBean(user)
		  val si = db.execute.getResultSize
		  db.commit
	  } catch {
		case e:Exception => {
		  db.rollback
		  println(e.getMessage)
		  throw e
		}
	  } finally {
		  db.close
	  }
	}
	// UPDATE
	def update(user:EMP) : Unit = {
	  var db:Db=null
	  try {
		  db = new Db
		  db.updateBean(user)
		  val si = db.execute.getResultSize
		  db.commit
	  } catch {
		case e:Exception => {
		  db.rollback
		  println(e.getMessage)
		  throw e
		}
	  } finally {
		  db.close
	  }
	}

	// SELECT
	def findAll : ArrayBuffer[EMP] = {
	  val db = new Db
	  if(column==null)
		column = "*"
	  var sql:DL = db.select(column).from("emp")
	  if(key != null) {
		  sql.orderBy(key)
	  }
	  results = db.executeQuery(classOf[EMP])
	  db.close
	  return results
	}
	def find(userids:Int*) : ArrayBuffer[EMP] = {
	  val db = new Db
	  if(column==null)
		column = "*"
	  var sql:DL = db.select(column).from("emp")
	  if(userids != null) {
		  var str = ""
		  var i = 0
		  userids.foreach(v =>{
			if(i != 0){
			  str += ","
			}
			str += "?"
			i += 1
		  })
		  sql.where("empno in ("+str+")",userids:_*)
	  }
	  if(key != null) {
		  sql.orderBy(key)
	  }
	  results = db.executeQuery(classOf[EMP])
	  db.close
	  return results
	}
	def find(userids:List[Int]) : ArrayBuffer[EMP] = {
	  val db = new Db
	  if(column==null)
		column = "*"
	  var sql:DL = db.select(column).from("emp")
	  if(userids != null) {
		  var str = ""
		  var i = 0
		  userids.foreach(v =>{
			if(i != 0){
			  str += ","
			}
			str += "?"
			i += 1
		  })
		  sql.where("empno in ("+str+")",userids:_*)
	  }
	  if(key != null) {
		  sql.orderBy(key)
	  }
	  results = db.executeQuery(classOf[EMP])
	  db.close
	  return results
	}
	
	def getUser(userid:Int) : EMP = {
	  val db = new Db
	  if(column==null)
		column = "*"
	  var sql:DL = db.select(column).from("emp")
	  sql.where("empno=?",userid)
	  if(key != null) {
		  sql.orderBy(key)
	  }
	  results = db.executeQuery(classOf[EMP])
	  db.close
	  return results(0)
	}
	def setSortkey(key:String) :Unit = {
	  this.key = key
	}
	def setColumn(col:String) :Unit = {
	  this.column = col
	}
}



このコードは、モデルとしてBeanと操作関数を別々のクラスとして実装しています。
その理由は、Active RecordsとしてSQLの特徴である集合として複数レコードを扱いたいからです。

しかし、単数としてActiveRecordを扱うのであれば、EMPクラスを継承という形に。
あるいは、ArrayBuffer[EMP]をメンバーにする、いろいろあります。

まだまだ、ActiveRecordのレベルにはありませんが、
Scalaの可変長引数を使ってEMP表のプライマリキーのカラムのEMPNOを引数にして操作します。
EMPNOを要素にしたListを引数にして、可変長引数と同様の操作ができるコードも合わせて書いてみました。



find関数では、データベースをSELECTした結果のレコードデータは、BeanクラスのオブジェクトをArrayBufferに格納して返します。

INSERTするデータもBeanに格納すればいい。
EMPNOによってSELECT、UPDATE、DELETEも問題なし。


問題は、条件指定したSELECT、UPDATE、DELETEする操作です。
リストで条件を記述するようにすればいいと考えているのですが、
実際にScalaでコードを書くプログラマに受け入れられる仕様となるのでしょうか?

Web フレームワークを作る!! HTML Parserの差し替え

Posted by
Tagged As: | Categories: Framework, Scala | No Comments

HTMLパーサの差し替え

org.htmlparser.saxのバージョンは2.0をタグ拡張したバージョンにするため
以下のハンドラコードにを切り替える。

class Htmlparse5Spは、class Htmlparse4Spのハンドラを
生成しているところを、

val cb = new ParserCallback4 から val cb = new ParserCallback5 に書き換えればよい。

これで、オリジナルのHTMLファイルのScriptタグのなかにあるJavascriptのコードがなくならない。

このコードについて、このページを掲載してからいろいろとテストしたところ、
タグの再現性がないことを確認しました。

再現しない理由は、

  • HTML parserが解析しないタグが他にもあること。
  • タグの開始と終了が完全でないとエラーとなる。これは、ソースコードにあるstackでタグ関係をチェックしているために対が崩れるとエラーとしていることです。この場合このコードは不要です。

対応は、
HTML parserのオリジナルのコードにさらに定義されていないタグのクラス定義を追加し、
stackのタグ対チェックを外すことです。

これ以外にも、処理ロッジを変更しているので、新しいコードが完成次第リリースしますので、お試しください。

以下のコードはさらに差し替えになります。

package ews.servlet.sp
import java.util.HashMap
import org.xml.sax._
import org.xml.sax.helpers.DefaultHandler
import scala.collection.JavaConversions._
import scala.collection.mutable.Stack
import seedo._

class ParserCallback5(pMap:HashMap[String,Array[String]]) extends DefaultHandler {
	var buf = new StringBuffer
	val stack = new Stack[String]			// For indent inspection of tag
	val stackChange = new Stack[ExecTag]	// For substitution
	var locator:Locator = null

	override def toString :String = buf.toString
	override def setDocumentLocator (locator:Locator) :Unit=  {	this.locator = locator }
	override def startDocument ():Unit= {	System.out.println("Start document");}
	override def endDocument():Unit= {	System.out.println("End document");}
	override def startElement ( uri:String,  name:String, qualifiedName:String , attrs:Attributes ):Unit={
		var buflocal = new StringBuffer
		stack.push(name)
		buflocal.append("<"+name)
		var fullFag = false	//
		for (idx <- 0 to attrs.getLength - 1){
			val attributeType = attrs.getType(idx)
			val attrLocalName = attrs.getLocalName(idx)
		  	val attrName = attrs.getQName(idx)
			val attrValue = attrs.getValue(idx)
			if(attrLocalName.length > 0 && !"#text".equals(attrLocalName)){
				buflocal.append(" " + attrLocalName + "=\"" + attrValue + "\"")
				if("class".equals(attrName) && attrValue.startsWith("seedo:")){
					val execCommand = attrValue.split(":")	// Each element is separated.
					if(execCommand.length >= 3) {	// It doesn't substitute it if there are neither a class name nor a function name.
						stackChange.push(new ExecTag(
						stack.length
						,name
						,execCommand
						,new StringBuffer	// The tag to be substituted is maintained.
						))
						fullFag = true
					} else {
						println("$$$$$$$$$$ execClass Warning " + attrName + "='" + attrValue + "'")
					}
				}
			}
		}
		buflocal.append(">")
		if(!stackChange.isEmpty){
			stackChange.top.getBufChange.append(buflocal)
		} else {
			buf.append(buflocal)
		}
		if(fullFag == true){
			stackChange.top.setStartTagFull(buflocal.toString)
		}
	}
	override def endElement (uri:String ,  name:String, qualifiedName:String):Unit={
		val endTag = "</"+name+">"
		if(stack.top.equals(name)){	// It is the same as the tag that became a substitution beginning.
			if(!stackChange.isEmpty) {	// Tag to be substituted inside
			  if(stackChange.top.getStartTagDepth == stack.length && name.equals(stackChange.top.getStartTag) && !stackChange.isEmpty) {
				// Depth of the same stack as tag that became substitution beginning -> tag end
				stackChange.top.getBufChange.append(endTag)
				var changeTag:String = ""
				try{
					val execCommand = stackChange.top.getExecCommand
					var ec:ExecClass = new ExecClass(execCommand(1))		// Object is generated.
					if(ec != null){
						ec.exec("setTag",stackChange.top.getBufChange.toString)	// TAG before it substitutes it is set.
						ec.exec("setParameterMap",pMap)	// GetParameterMap method of ServletRequest to acquire parameter
						if(execCommand.length == 3){	// Argument none
							changeTag = ec.exec(execCommand(2)).toString
						}else if(execCommand.length == 4){	// There is one argument.
							val value = execCommand(3)
							changeTag = ec.execStr(execCommand(2),execCommand(3)).toString
						}
					}
				} catch {
					case e:NoSuchMethodException => {
						println("Exception "+e.getMessage + " NoSuchMethodException!")
					}
					case e:Exception => {
						println("Exception "+e.getMessage + " class or Method not find!")
					}
				}
				var str:String = null
				if(!stackChange.isEmpty){// Tag inside class="seedo"
					str = stackChange.top.getStartTagFull + changeTag + endTag
				}
				stackChange.pop	// The stack for substitution is liberated.
				if(!stackChange.isEmpty){// class="seedo"
					stackChange.top.getBufChange.append(str)
				} else {
					buf.append(str)	//
				}
			  } else {
				stackChange.top.getBufChange.append(endTag)
			  }
			} else {
				buf.append(endTag)
			}
			stack.pop
	//		println("-------------stack.length="+stack.length)
		} else {
			println("$$$$$$$$$$ The correspondence of tag is not taken. startTag(stack.top)=" + stack.top + " endTag=" + qualifiedName)
		}
	}
	override def characters(ch:Array[Char],start:Int,length:Int) : Unit = {
		var str:String = ""
		for(i <- start to length+start-1){
			str += ch(i)
		}
		if(!stackChange.isEmpty){// inside tag class="seedo"
			stackChange.top.getBufChange.append(str)
		} else {
			buf.append(str)
		}
	}
}

package がすべてewsだったので、機能ごとに階層を分ける。
servletから呼び出されるクラスは、package ews.servlet.spに置く。

class htmlSrvは、package ews.servletに置く。

packageの変更により、web.xmlの定義は以下のように修正。

HTMLファイルを置く場所も、テスト用にC:\test_site\htmlに変更する。
この設定を変更すればいろいろなサイトのコンテンツを評価することができます。

またews.servlet.htmlSrvを拡張してファイル転送できるようにしたので、イメージファイルやCSSファイル、JSファイルもこのディレクトリに置くことができます。

web.xml

	<servlet>
		<servlet-name>html</servlet-name>
		<servlet-class>ews.servlet.htmlSrv</servlet-class>
		<init-param>
			<param-name>html</param-name>
			<param-value>C:\test_site\html</param-value>
		</init-param>

		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>html</servlet-name>
		<url-pattern>/html/*</url-pattern>
	</servlet-mapping>

コードが落ち着いてきたらソースコード一式をダウンロードできるようにします。

HTML Parser 評価 org.htmlparser.sax

Posted by
Tagged As: | Categories: Scala | No Comments

HTMLパーサ評価用のソースコード

org.htmlparser.saxのバージョンは2.0を使用。
http://htmlparser.sourceforge.net/

  1. HTMLファイルの読み込みは、FileInputStreamを使うので、コードセットを指定できない。
    文字化けの原因となりえる。
  2. SAXのハンドラはorg.xml.sax.helpers.DefaultHandlerの標準仕様
  3. コメントタグの内容は出力できる。
  4. エラーハンドラがあるので、エラーはここから出力できる。
  5. 2.0では、STRONGタグやHTML5で追加されたタグをパースできない。CENTERタグ,FONTタグなども認識できないです。




と、1でコードセットを指定できないと書きましたが、InputSourceに対してsetEncoding関数でコード指定できます。


val input = new FileInputStream(htmlFile)
val inputSource = new InputSource(input)
inputSource.setEncoding("UTF-8")




htmlparser のソースコードに対して、定義されていないタグを処理できるように拡張する



評価用のソースコードは以下のとおり。

setEncoding関数でコード指定を追加してあります。


package ews.servlet.sp

import java.io._
import javax.servlet.http._
import org.xml.sax.{InputSource,ErrorHandler,Attributes,SAXException,SAXParseException}
import org.xml.sax.helpers.{DefaultHandler,XMLReaderFactory}
import org.htmlparser.sax._

class Htmlparse4Sp (request : HttpServletRequest, response : HttpServletResponse, htmlPath : String) extends TemplSp {
	var parsed:String = null	// 
	override def toString :String = parsed
	// replace HTML
	def html : String = {
		val htmlFile = new java.io.File(htmlPath)
		if(htmlFile.exists && htmlPath.toLowerCase.endsWith(".html")){
			println("Htmlparse4Sp html ---")
			try {
				val input = new FileInputStream(htmlFile)	// open HTML file
				val inputSource = new InputSource(input)
				inputSource.setEncoding("UTF-8")
				val cb = new ParserCallback4
				
				val r = XMLReaderFactory.createXMLReader("org.htmlparser.sax.XMLReader")
				r.setContentHandler(cb)
				r.parse(inputSource)
				println("Htmlparse4Sp parse---")

				input.close
				parsed = cb.toString
				return parsed
			} catch {
			  case e:IOException => {
				  println("Exception "+e.getMessage)
			  }
			}
		}
		null	// There is no file. 
	}
}

class ErrorHandler4 extends ErrorHandler {
		def warning(e:SAXParseException ) :Unit= {
			println("ERROR warning")
			throw new SAXException(e)
		}
		def error(e:SAXParseException) :Unit= {
			println("ERROR error")
			throw new SAXException(e)
		}
		def fatalError(e:SAXParseException) :Unit= {
			println("ERROR fatalError")
			throw new SAXException(e)
		}
		// default constructor
		def ErrorTestHandler :Unit= {
		}
}

ハンドラのソースコード

package ews.servlet.sp

import org.xml.sax._
import org.xml.sax.helpers.DefaultHandler

class ParserCallback4 extends DefaultHandler {
	var buf = new StringBuffer
	var locator:Locator = null
	override def toString :String = buf.toString
	override def setDocumentLocator (locator:Locator) :Unit=  {
	  println("setDocumentLocator")
		this.locator = locator
	}
	override def startDocument ():Unit= {	System.out.println("Start document");}
	override def endDocument():Unit= {	System.out.println("End document");}
	override def startElement ( uri:String,  name:String, qualifiedName:String , attrs:Attributes ):Unit={
		var buflocal = new StringBuffer
		buflocal.append("<"+name)
		for (idx <- 0 to attrs.getLength - 1){
			val attributeType = attrs.getType(idx)
			val attrLocalName = attrs.getLocalName(idx) 
			val attrName = attrs.getQName(idx)
			val attrValue = attrs.getValue(idx)
			if(attrLocalName.length > 0 && !"#text".equals(attrLocalName)){
				buflocal.append(" " + attrLocalName + "=\"" + attrValue + "\"")
			}
		}
		buflocal.append(">")
		buf.append(buflocal)
	}
	override def endElement (uri:String ,  name:String, qualifiedName:String):Unit={
		buf.append("</"+name+">")
	}
	override def characters(ch:Array[Char],start:Int,length:Int) : Unit = {
		var str:String = ""
		for(i <- start to length+start-1){
			str += ch(i)
		}
		buf.append(str)
	}
	override def unparsedEntityDecl(name:String,publicId:String,systemId:String,notationName:String) : Unit = {
		println("unparsedEntityDecl name="+name + " publicId=" + publicId + " systemId="+systemId + " notationName=" + notationName)
	}
	override def startPrefixMapping(prefix:String , uri:String ) : Unit = {
		println("startPrefixMapping prefix="+prefix + " uri=" + uri)
	}
	override def endPrefixMapping(prefix:String) : Unit = {
		println("endPrefixMapping prefix="+prefix)
	}
	override def processingInstruction(target:String, data:String) : Unit = {
		println("processingInstruction target="+target + " data="+data)
	}
	override def skippedEntity(name:String) : Unit = {
		println("skippedEntity name="+name)
	}
}

HTML Parser 評価 org.cyberneko.html.parsers

Posted 2011年8月29日 by
Tagged As: | Categories: Framework, Scala | No Comments

HTMLパーサ評価用のソースコード

org.cyberneko.html.parsersのバージョンは1.9.15を使用。
http://nekohtml.sourceforge.net/index.html

  1. HTMLファイルの読み込みは、InputStreamReaderが使えるので、コードセットを指定することができる。
    文字化けの原因を回避。
  2. SAXのハンドラはorg.xml.sax.helpers.DefaultHandlerの標準仕様
  3. オリジナルにあるコメントタグの内容は出力できない。
  4. エラーハンドラがあるので、エラーはここから出力できる。
  5. タグの出力は<BR>タグを<BR></BR>と出力するので修正。
    HRタグも同様
    IMGタグとINPUTタグのエンドタグは削除。
  6. ネストしたタグの解析処理に不具合があるようで、タグが不完全と判断して余分なタグを出力するため画面が乱れてしまう。

評価用のソースコードは以下のとおり。

package ews
import java.io._
import javax.servlet.http._
import org.xml.sax.helpers._
import org.xml.sax.{InputSource,ErrorHandler,Attributes,SAXException,SAXParseException}
import org.cyberneko.html.parsers._

class Htmlparse3Sp (request : HttpServletRequest, response : HttpServletResponse, htmlPath : String) extends TemplSp {
	var parsed:String = null	//
	override def toString :String = parsed
	// cyberneko 1.9.15
	def html : String = {
		val htmlFile = new java.io.File(htmlPath)
		if(htmlFile.exists && htmlPath.toLowerCase.endsWith(".html")){
			var input:InputStreamReader = null
			try {
				input = new InputStreamReader (new FileInputStream(htmlFile), "UTF-8")	// open HTML file
				val inputSource = new InputSource(input)
				val cb = new ParserCallback3
				val r = new SAXParser
				r.setContentHandler(cb)
				r.setErrorHandler(new ErrorHandler3)
				r.parse(inputSource)
				parsed = cb.toString.replaceAll("<br></br>","<br>")
				.replaceAll("<hr></hr>","<hr>")
				.replaceAll("</input>","")
				.replaceAll("</img>","")
				return parsed
			} catch {
			  case e:IOException => {
				  println("Exception "+e.getMessage)
			  }
			} finally {
				input.close
			}
		}
		null	// There is no file.
	}
}
class ErrorHandler3 extends ErrorHandler {
		def warning(e:SAXParseException ) :Unit= {
			println("ERROR warning")
			throw new SAXException(e)
		}
		def error(e:SAXParseException) :Unit= {
			println("ERROR error")
			throw new SAXException(e)
		}
		def fatalError(e:SAXParseException) :Unit= {
			println("ERROR fatalError")
			throw new SAXException(e)
		}
		// default constructor
		def ErrorTestHandler :Unit= {
		}
}

ハンドラのソースコード

package ews

import org.xml.sax._
import org.xml.sax.helpers.DefaultHandler

class ParserCallback3 extends DefaultHandler {
	var buf = new StringBuffer
	override def toString :String = buf.toString
	override def startDocument ():Unit= {/* System.out.println("Start document")*/}
	override def endDocument():Unit= {/* System.out.println("End document")*/}
	override def startElement ( uri:String,  name:String, qName:String , attrs:Attributes ):Unit={
		var buflocal = new StringBuffer
		buflocal.append("<"+qName.toLowerCase)
		for (idx <- 0 to attrs.getLength - 1){
			val attrName = attrs.getQName(idx)
			val attrValue = attrs.getValue(idx)
			buflocal.append(" " + attrName + "=\"" + attrValue + "\"")
		}
		buflocal.append(">")
		buf.append(buflocal)
	}
	override def endElement (uri:String ,  name:String, qName:String):Unit={
		buf.append("</" + qName.toLowerCase + ">")
	}
	override def characters(ch:Array[Char],start:Int,length:Int) : Unit = {
		var str:String = ""
		for(i <- start to length+start-1){
			str += ch(i)
		}
		buf.append(str)
	}
	override def unparsedEntityDecl(name:String,publicId:String,systemId:String,notationName:String) : Unit = {
		println("unparsedEntityDecl name="+name + " publicId=" + publicId + " systemId="+systemId + " notationName=" + notationName)
	}
	override def startPrefixMapping(prefix:String , uri:String ) : Unit = {
		println("startPrefixMapping prefix="+prefix + " uri=" + uri)
	}
	override def endPrefixMapping(prefix:String) : Unit = {
		println("endPrefixMapping prefix="+prefix)
	}
	override def processingInstruction(target:String, data:String) : Unit = {
		println("processingInstruction target="+target + " data="+data)
	}
	override def skippedEntity(name:String) : Unit = {
		println("skippedEntity name="+name)
	}
}

HTML Parser 評価 javax.swing.text.html.parser

Posted by
Tagged As: | Categories: Framework, Java, Scala | No Comments

HTML Parser 評価

HTMLパーサの主な用途は、クローラを書くときに使ったり、
HTML文書から文書だけを取り出すことでしょう。
あるいは、特定のタグの属性を見て何かするような目的もあります。

しかし、Web frameworkで使う目的は、タグの置換ですから、
オリジナルのHTMLにあって、パースした出力にはタグがなくなっている
ということはあってはなりません。

ここでのHTMLパーサの評価は、
オリジナルのHTMLファイルを読み込んで、
HTMLパーサによってパースした結果をHTMLファイルに戻す。
オリジナルと同じであることが評価されます。

すなわちオリジナルと出力したHTMLファイルの差異が評価ポイントとなります。

評価ポイント

評価ポイントは次のとおり。

  1. タグが未対応
  2. タグの欠落
  3. タグ要素の欠落
  4. タグが閉じていないケースの対応
  5. コメントの扱い
  6. Scriptコードの扱い
  7. XHTML化
  8. HTML5への対応
  9. 文字化け

HTMLパーサ評価用のソースコード

まずは、javax.swing.text.html.parserから。

  1. HTMLファイルの読み込みは、InputStreamReaderが使えるので、コードセットを指定することができる。
    文字化けの原因を回避。
  2. SAXのハンドラは独自。
    コメント用のハンドラがあるのでHTML内のコメントを出力できる。
    コメントを出力できないハンドラが多いなかこれは評価できる。
  3. タグへのパーサからの評価がタグ属性で出力される。
    最初にテストコードを動かしたときにオリジナルにない属性が出力されていたので
    出力しないようにコードを変更する。
  4. タグの出力は比較的素直。<BR>タグを<BR></BR>と出力しない。
package ews

import java.io._
import javax.servlet.http._
import javax.swing.text._
import javax.swing.text.html._
import javax.swing.text.html.parser._
/**
 * HtmlParse
 *
 * javax.swing.text.html should not be used.
 * Because it is not thread safe.
 */
class Htmlparse2Sp (request : HttpServletRequest, response : HttpServletResponse, htmlPath : String) extends TemplSp {
	var parsed:String = null	//
	override def toString :String = parsed
	// javax.swing.text.html.parser
	def html : String = {
		val htmlFile = new java.io.File(htmlPath)
		if(htmlFile.exists && htmlPath.toLowerCase.endsWith(".html")){
			var input:InputStreamReader = null
			try {
				input = new InputStreamReader (new FileInputStream(htmlFile), "UTF-8")	// open HTML file
				val cb = new ParserCallback2
				val pd = new ParserDelegator
				pd.parse(input, cb, true)
				parsed = cb.toString
				return parsed
			} catch {
			  case e:IOException => {
				  println("Exception "+e.getMessage)
			  }
			} finally {
				input.close
			}
		}
		null	// There is no file.
	}
}

ハンドラのソースコード

package ews
import javax.swing.text._
import javax.swing.text.html._

class ParserCallback2 extends HTMLEditorKit.ParserCallback {
	var buf = new StringBuffer
	override def toString :String = buf.toString

	override def handleStartTag(tag:HTML.Tag, attrs:MutableAttributeSet , pos:Int):Unit={
		var ok = true
		var buflocal = new StringBuffer
		buflocal.append("<"+tag.toString)
		val attrNames = attrs.getAttributeNames
		while (attrNames.hasMoreElements) {
			val attrName = attrNames.nextElement
			val attrValue = attrs.getAttribute(attrName)
			if(HTMLEditorKit.ParserCallback.IMPLIED.equals(attrName)){	// error
			  ok = false
			  println("ParserCallback2 [IMPLIED] tag=" + tag.toString + " attrName="+attrName + " attrValue="+attrValue)
			} else
			if(HTML.NULL_ATTRIBUTE_VALUE.equals(attrName)){
			  ok = false
			  println("ParserCallback2 [NULL_ATTRIBUTE_VALUE] tag=" + tag.toString + " attrName="+attrName + " attrValue="+attrValue)
			} else
			if(HTML.Attribute.ENDTAG.equals(attrName)){
			  ok = false
			  println("ParserCallback2 [ENDTAG] tag=" + tag.toString + " attrName="+attrName + " attrValue="+attrValue)
			}
			buflocal.append(" " + attrName + "=\"" + attrValue + "\"")
			if (attrName == StyleConstants.NameAttribute && tag == HTML.Tag.CONTENT) {

			}
		}
		buflocal.append(">")
		if(ok)
			buf.append(buflocal)
	}
	override def handleEndTag(tag:HTML.Tag, pos:Int):Unit={
		buf.append("</"+tag.toString+">\n")
	}
	override def handleSimpleTag(tag:HTML.Tag, attrs:MutableAttributeSet , pos:Int):Unit={
		handleStartTag(tag,attrs,pos)
	}
	override def handleText(ch:Array[Char],length:Int):Unit={
		buf.append(new String(ch))
	}
	override def handleComment(ch:Array[Char],length:Int):Unit={
		val str = new String(ch)
		if(str.trim.length > 0)
			buf.append("<!--\n"+str+"\n-->\n")
	}
	override def handleEndOfLineString(eol:String):Unit={
		buf.append(new String(eol))
	}
	def handleError(ln:Int, errorMsg:String):Unit={
		println("ERROR line number="+ln + " " + errorMsg)
	}
}

Scala Web framework !! update HttpServlet code

Posted by
Tagged As: | Categories: Framework, Scala | No Comments

Web フレームワークを作る!!  1日目で作ったhtmlSrv クラスをアップデートします。

 

Webフレームワークに、実際のサイトで使われている静的なHTMLファイルを読み込ませてみるといろいろと課題が見えてきました。

  1. IMGタグのJPGやGIFのイメージファイルのリクエスト
  2. 同じくCSSやJSファイルのリクエスト
  3. HTMLパーサによるSCRIPTタグの扱い
  4. 同じくコメントの扱い

 

1と2については、各種ファイルへのリクエストは、URLパスのエーリアスで処理しようかと考えたのですが、URLのパスをいちいち変更するのも面倒なので、これらのファイルを直接転送できるようにContentTypeでTEXTでないものはすべて転送で処理するように変更。

3と4については、nu.validator.htmlparserをHTMLパーサに採用しましたが、SCRIPTタグの中身をうまく返せない。こうなるとJavascriptが動かない。

 

HTMLパーサは、いろいろなソフトが使えるのでHTMLパーサを評価する。
評価してHTMLパーサは以下のとおり。

  1. nu.validator.htmlparser
  2. javax.swing.text.html.parser._
  3. org.cyberneko.html.parsers._
  4. org.htmlparser.sax._

 

細かい仕様がまったく違うので、評価コードを書くのは大変でした。
その1つが、inputSourceの引数がまったく異なる。

しかし評価結果は、この4つは共にWebフレームに必要な仕様を満たさない。

    1. nu.validator.htmlparserは先に述べたとおり。
      何か出力を切り替えるスイッチがあるのではないかと思うのだがわからず。
      ソースコード読めばいいだろうけど。

 

    1. javax.swing.text.html.parser._はJAVADOCを読むとSwingライブラリはスレッドセーフではないとある。
      HTMLパーサとしては問題ないんですけど残念。

 

    1. org.cyberneko.html.parsers._は、DLタグのネストをうまく処理できない不具合がある。
      最初、SAXParserをNEWするコードを書くと、コンストラクタがないとコンパイルエラー。
      ネットで調べると2008年にScalaでDOMParserを使おうとして同じエラーに遭遇している人を見つけるが、対応コメントがない。
      ソースを見るがSAXParserには確かにコンストラクタがある。

      よくよくソースコードを見ると、
      import org.apache.xerces.parsers.AbstractSAXParser;とあるではないですか。
      ということは、xercesImpl.jarをLibに追加すれば問題解決。

 

  1. org.htmlparser.sax._は、処理できないタグがある。当然HTML5のタグも処理できない。

    2006年で更新がストップしているので仕方ないことなんでしょう。ソースコードを読むと、タグごとにクラスが定義されている。
    ということは、対応していないタグのクラスを定義すれば。。。。

    SPANクラスを参考にして、未対応のタグとついでにHTML5で追加されたタグのクラスを定義。
    このクラスをNewしているPrototypicalNodeFactoryクラスにタグを追加するとOK。

この改良版org.htmlparser.sax._を使うことで、課題となっていた件を解決することができた。

改良版htmlSrvクラスのソースコード は以下のとおり。

いろいろなHTMLパーサを使って仕様を評価するために、

request.getParameter(“parsersw”)により、以下のURLのように
してHTMLパーサを切り替えることができる。

http://127.0.0.1:8080/toro/html/form/index.html?parsersw=7

package ews
import org.apache.log4j.Logger
import java.io._
import javax.servlet._
import javax.servlet.http._
/**
 * The dynamic page is made by HttpServlet.
 */
class htmlSrv extends HttpServlet {
	lazy val logger = Logger.getLogger(classOf[ewsSrv])
	var context : ServletContext = null
	var htmlPath : String = null
	var message = "Not Found :The requested URL was not found on this server."	// Not Found
	override def init(config : ServletConfig) : Unit = {
		super.init(config)
		context = config.getServletContext
		htmlPath = config.getInitParameter("html")	// WEB-INF/web.xml
	}
	override def doGet(request : HttpServletRequest, response : HttpServletResponse) : Unit = {
		gen(request, response)
	}
	override def doPost(request : HttpServletRequest, response : HttpServletResponse) : Unit = {
		gen(request, response)
	}
	// generate HTML
	def gen(request : HttpServletRequest, response : HttpServletResponse) : Unit = {
		logger.debug("htmlSrv ################# start")
//		println("htmlSrv ################# start")
		var htmlFile = ""
		var parsersw = if(request.getParameter("parsersw") !=null){request.getParameter("parsersw").toInt}else{9}	// change HTML parser
		request.setCharacterEncoding("utf-8")
   		val url = request.getRequestURL
		val uri = request.getRequestURI
		var spth:String = request.getServletPath

		spth = spth.replace("/","")
		val ur = uri.split("/")
		var fg = false
		ur.foreach({v =>
			if(fg){
				htmlFile += "/" + v
			}
			if(v.equals(spth))
				fg = true
		})
		var ct = ContentTypeDef.changeFromFileNameToContentType(htmlFile)
		val sw = ContentTypeDef.getCtype(ct) match {
		  case "text" => if(ct.equals("text/html")){parsersw}else{1}
		  case "image" => 10
		  case "application" => if(ct.equals("application/xml")){2}else{10}
		  case "audio" => 10
		  case "video" => 10
		  case _ => 10
		}
		logger.debug("htmlSrv ############ "+ct+" ##### htmlFile="+htmlFile)
		if(sw != 10){
			val ssp = sw match {
			  // It reads a file, and the content is output as it is.
			  case 1 => new StraightSp(request, response, htmlPath + htmlFile)
			  // The HTML file is read by the Scala XML loader. The character string is converted and XML is output.
			  case 2 => new XmlSp(request, response, htmlPath + htmlFile)
			  // The analytical result of HTML parser is converted in the character string and it outputs it.
			  case 3 => new Htmlparse2Sp(request, response, htmlPath + htmlFile)// javax.swing.text.html.parser._
			  case 4 => new HtmlparseSp(request, response, htmlPath + htmlFile)	// nu.validator.htmlparser._ for TEST
			  case 5 => new ValidatorHtmlparserSp(request, response, htmlPath + htmlFile)	// nu.validator.htmlparser for TEST
			  case 6 => new Htmlparse3Sp(request, response, htmlPath + htmlFile)// org.cyberneko.html.parsers._ for TEST
			  case 7 => new Htmlparse4Sp(request, response, htmlPath + htmlFile)// org.htmlparser.sax._ for TEST
			  case 8 => new Ssp(request, response, htmlPath + htmlFile)			// Dynamic page. use the nu.validator.htmlparser
			  case 9 => new Htmlparse5Sp(request, response, htmlPath + htmlFile)// Dynamic page. use the org.htmlparser.sax._
			}
	   		val re = if(ssp.html != null){
	   				response.setStatus(200)
	   				ssp.toString
	   		  } else {
	   			  	response.setStatus(404,"Not Found")
	   			  	message
	   		  }
	   		ct += "; charset=utf-8"
			response.setContentType(ct)
			val out = response.getWriter
			out.println(re)	// output html data
			out.flush
			out.close
		} else {
			var out = response.getOutputStream
			var stream:FileInputStream = null
			try {
				val binaryFile = new java.io.File(htmlPath + htmlFile)
				if(binaryFile.exists){
					response.setContentType(ct)
					response.setStatus(200)
					stream = new FileInputStream(binaryFile)
					val buf:Array[Byte] = new Array(1024)
					while(stream.available != 0){
						val i = stream.read(buf,0,1024)
						out.write(buf,0,i)
					}
				} else {
					error(404,"Not Found",response)
					val ms = message.getBytes("UTF-8")
		  	   		out.write(ms,0,ms.length)
				}
			} catch {
	        	case e:IOException => {
	        		println("IOException "+e.getMessage)
	        		error(500,"Internal Server Error",response)
	        	}
	        	case e:Exception => {
	        		println("Exception "+e.getMessage)
	        		error(500,"Internal Server Error",response)
	        	}
			} finally {
				out.flush
				out.close
				stream.close
			}
		}
//		logger.debug(" #### End")
	}
	// for error
	def error(status:Int,message:String, response : HttpServletResponse) :Unit = {
		response.setContentType("text/html")
		response.setStatus(status,message)
	}
}

HTMLパーサの評価コードについては引き続き解説します。
お楽しみに。

ContentType

ファイルの拡張子からContentTypeを得るプログラムです。
TOMCATの中で持っているリストと同じ。

このリストも、ネットで調べても出てこない。
参考になれば。

気になるのが、JSファイルの扱い。

applicationかtextかということなんでしょうが、
application/javascriptが正解という見解もあるようですが、
TOMCATではtext/javascriptになってます。

 

package ews

/**
 * ContentType
 */
object ContentTypeDef {
	val ct = List(
	    ("html","text/html"),
	    ("json","application/json"),
        ("abs", "audio/x-mpeg"),
        ("ai", "application/postscript"),
        ("aif", "audio/x-aiff"),
        ("aifc", "audio/x-aiff"),
        ("aiff", "audio/x-aiff"),
        ("aim", "application/x-aim"),
        ("art", "image/x-jg"),
        ("asf", "video/x-ms-asf"),
        ("asx", "video/x-ms-asf"),
        ("au", "audio/basic"),
        ("avi", "video/x-msvideo"),
        ("avx", "video/x-rad-screenplay"),
        ("bcpio", "application/x-bcpio"),
        ("bin", "application/octet-stream"),
        ("bmp", "image/bmp"),
        ("body", "text/html"),
        ("cdf", "application/x-cdf"),
        ("cer", "application/x-x509-ca-cert"),
        ("class", "application/java"),
        ("cpio", "application/x-cpio"),
        ("csh", "application/x-csh"),
        ("css", "text/css"),
        ("dib", "image/bmp"),
        ("doc", "application/msword"),
        ("dtd", "application/xml-dtd"),
        ("dv", "video/x-dv"),
        ("dvi", "application/x-dvi"),
        ("eps", "application/postscript"),
        ("etx", "text/x-setext"),
        ("exe", "application/octet-stream"),
        ("gif", "image/gif"),
        ("gtar", "application/x-gtar"),
        ("gz", "application/x-gzip"),
        ("hdf", "application/x-hdf"),
        ("hqx", "application/mac-binhex40"),
        ("htc", "text/x-component"),
        ("htm", "text/html"),
//        ("html", "text/html"),
        ("hqx", "application/mac-binhex40"),
        ("ief", "image/ief"),
        ("jad", "text/vnd.sun.j2me.app-descriptor"),
        ("jar", "application/java-archive"),
        ("java", "text/plain"),
        ("jnlp", "application/x-java-jnlp-file"),
        ("jpe", "image/jpeg"),
        ("jpeg", "image/jpeg"),
        ("jpg", "image/jpeg"),
        ("js", "text/javascript"),
        ("jsf", "text/plain"),
        ("jspf", "text/plain"),
        ("kar", "audio/x-midi"),
        ("latex", "application/x-latex"),
        ("m3u", "audio/x-mpegurl"),
        ("mac", "image/x-macpaint"),
        ("man", "application/x-troff-man"),
        ("mathml", "application/mathml+xml"),
        ("me", "application/x-troff-me"),
        ("mid", "audio/x-midi"),
        ("midi", "audio/x-midi"),
        ("mif", "application/x-mif"),
        ("mov", "video/quicktime"),
        ("movie", "video/x-sgi-movie"),
        ("mp1", "audio/x-mpeg"),
        ("mp2", "audio/x-mpeg"),
        ("mp3", "audio/x-mpeg"),
        ("mp4", "video/mp4"),
        ("mpa", "audio/x-mpeg"),
        ("mpe", "video/mpeg"),
        ("mpeg", "video/mpeg"),
        ("mpega", "audio/x-mpeg"),
        ("mpg", "video/mpeg"),
        ("mpv2", "video/mpeg2"),
        ("ms", "application/x-wais-source"),
        ("nc", "application/x-netcdf"),
        ("oda", "application/oda"),
        ("odb", "application/vnd.oasis.opendocument.database"),
        ("odc", "application/vnd.oasis.opendocument.chart"),
        ("odf", "application/vnd.oasis.opendocument.formula"),
        ("odg", "application/vnd.oasis.opendocument.graphics"),
        ("odi", "application/vnd.oasis.opendocument.image"),
        ("odm", "application/vnd.oasis.opendocument.text-master"),
        ("odp", "application/vnd.oasis.opendocument.presentation"),
        ("ods", "application/vnd.oasis.opendocument.spreadsheet"),
        ("odt", "application/vnd.oasis.opendocument.text"),
        ("otg", "application/vnd.oasis.opendocument.graphics-template"),
        ("oth", "application/vnd.oasis.opendocument.text-web"),
        ("otp", "application/vnd.oasis.opendocument.presentation-template"),
        ("ots", "application/vnd.oasis.opendocument.spreadsheet-template "),
        ("ott", "application/vnd.oasis.opendocument.text-template"),
        ("ogx", "application/ogg"),
        ("ogv", "video/ogg"),
        ("oga", "audio/ogg"),
        ("ogg", "audio/ogg"),
        ("spx", "audio/ogg"),
        ("faca", "audio/flac"),
        ("anx", "application/annodex"),
        ("axa", "audio/annodex"),
        ("axv", "video/annodex"),
        ("xspf", "application/xspf+xml"),
        ("pbm", "image/x-portable-bitmap"),
        ("pct", "image/pict"),
        ("pdf", "application/pdf"),
        ("pgm", "image/x-portable-graymap"),
        ("pic", "image/pict"),
        ("pict", "image/pict"),
        ("pls", "audio/x-scpls"),
        ("png", "image/png"),
        ("pnm", "image/x-portable-anymap"),
        ("pnt", "image/x-macpaint"),
        ("ppm", "image/x-portable-pixmap"),
        ("ppt", "application/vnd.ms-powerpoint"),
        ("pps", "application/vnd.ms-powerpoint"),
        ("ps", "application/postscript"),
        ("psd", "image/x-photoshop"),
        ("qt", "video/quicktime"),
        ("qti", "image/x-quicktime"),
        ("qtif", "image/x-quicktime"),
        ("ras", "image/x-cmu-raster"),
        ("rdf", "application/rdf+xml"),
        ("rgb", "image/x-rgb"),
        ("rm", "application/vnd.rn-realmedia"),
        ("roff", "application/x-troff"),
        ("rtf", "application/rtf"),
        ("rtx", "text/richtext"),
        ("sh", "application/x-sh"),
        ("shar", "application/x-shar"),
        /*"shtml", "text/x-server-parsed-html",*/
        ("smf", "audio/x-midi"),
        ("sit", "application/x-stuffit"),
        ("snd", "audio/basic"),
        ("src", "application/x-wais-source"),
        ("sv4cpio", "application/x-sv4cpio"),
        ("sv4crc", "application/x-sv4crc"),
        ("svg", "image/svg+xml"),
        ("svgz", "image/svg+xml"),
        ("swf", "application/x-shockwave-flash"),
        ("t", "application/x-troff"),
        ("tar", "application/x-tar"),
        ("tcl", "application/x-tcl"),
        ("tex", "application/x-tex"),
        ("texi", "application/x-texinfo"),
        ("texinfo", "application/x-texinfo"),
        ("tif", "image/tiff"),
        ("tiff", "image/tiff"),
        ("tr", "application/x-troff"),
        ("tsv", "text/tab-separated-values"),
        ("txt", "text/plain"),
        ("ulw", "audio/basic"),
        ("ustar", "application/x-ustar"),
        ("vxml", "application/voicexml+xml"),
        ("xbm", "image/x-xbitmap"),
        ("xht", "application/xhtml+xml"),
        ("xhtml", "application/xhtml+xml"),
        ("xls", "application/vnd.ms-excel"),
        ("xml", "application/xml"),
        ("xpm", "image/x-xpixmap"),
        ("xsl", "application/xml"),
        ("xslt", "application/xslt+xml"),
        ("xul", "application/vnd.mozilla.xul+xml"),
        ("xwd", "image/x-xwindowdump"),
        ("vsd", "application/x-visio"),
        ("wav", "audio/x-wav"),
        ("wbmp", "image/vnd.wap.wbmp"),
        ("wml", "text/vnd.wap.wml"),
        ("wmlc", "application/vnd.wap.wmlc"),
        ("wmls", "text/vnd.wap.wmlscript"),
        ("wmlscriptc", "application/vnd.wap.wmlscriptc"),
        ("wmv", "video/x-ms-wmv"),
        ("wrl", "x-world/x-vrml"),
        ("wspolicy", "application/wspolicy+xml"),
        ("Z", "application/x-compress"),
        ("z", "application/x-compress"),
        ("zip", "application/zip")
    )
    def getCtype(ct:String) : String = {
	  if(ct == null)
	    return null
	  val t = ct.split("/")
	  return t(0)
	}
	def getSuffix(fileName:String):String= {
	    if (fileName == null)
	        return null
	    val point = fileName.lastIndexOf(".")
	    if (point != -1) {
	        return fileName.substring(point + 1).toLowerCase
	    }
	    return fileName.toLowerCase
	}  

	def changeFromFileNameToContentType(fname:String) : String = {
	  val sf = getSuffix(fname)
	  val rt = ct.find(_._1.equals(sf)) match {
	    case Some(f) => f._2
	    case None => null
	  }
	  return rt
	}
}

Scala Web framework !! DB select <- ParameterMap

Posted 2011年8月25日 by
Tagged As: | Categories: Framework, Scala | No Comments

URLの引数によって値を取るケース

以下のURLのようにdeptno=30の値を HelloWorldDb3 クラスへ渡すコードです。

http://127.0.0.1:8080/toro/html/helloworlddb3.html?sw=4&deptno=30

parameterMap変数は、JavaのコンテナクラスのHashMap[String,Array[String]]です。

parameterMap.get(“deptno”) 関数によって、deptnoの引数の値を取り出します。
ScalaのMAPクラスではないので、case ではSome(f)ではなく、クラスマッチングで値を取り出し、
Arrayクラスなので配列の0番目の値を取り出します。
その値を toInt関数でStringから整数Intに変換します。

これをSELECT文の条件として使い、データベースに検索します。

import scala.collection.immutable._
import scala.xml._
import seedo.database._

class HelloWorldDb3 {
	var tag:String = null // Replaced original tag
	def setTag(tg:String): Unit = {tag = tg}
	var parameterMap:HashMap[String,Array[String]] = null // Parameter from request of servlet
	def setParameterMap(pm:HashMap[String,Array[String]]) : Unit =  {parameterMap = pm}

	def recx(record:Array[Any]) :Node =  {
			var nodes  = Queue.empty[Node]
			record.foreach{(fx) => nodes += <TD>{fx}</TD>}
			return <TR>{nodes}</TR>
		}
	// Function one argument
	def tb(sal:Int) : String = {
		val deptno = parameterMap.get("deptno") match {
		  case v:Array[String] => v(0).toInt
		  case null => 10
		}
		var sql:Db = null
		try {
			sql = new Db
			sql.select("EMPNO,ENAME,SAL,DEPTNO,JOB,MGR")
				.from("EMP")
				.where("SAL > ?",sal)
				.or("DEPTNO = ?",deptno)
				.orderBy("EMPNO")
			val re = sql.executeQuery
			var nodes  = Queue.empty[Node]
			re.foreach{(rec) => nodes += recx(rec)}
			val xml = <table border="2">{nodes}</table>
			return "HelloWorldDb3 " + new Date + xml.toString
		} catch {
		  case e:Exception =>{
			throw e
		  }
		} finally {
			if(sql != null)sql.close
		}
	}
}

結果

検索して結果は以下のとおり

deptno引数がなしの場合

http://127.0.0.1:8080/toro/html/helloworlddb3.html?sw=4

HelloWorldDb3 クラスのdeptno値は、引数がない場合は10になります。

deptno引数が30の場合

http://127.0.0.1:8080/toro/html/helloworlddb3.html?sw=4&deptno=30

SELECT文の条件にEMP表のDEPTカラムの値が30のレコードもOR条件で加わるので
レコード件数の数が増えています。

deptno引数が20の場合

http://127.0.0.1:8080/toro/html/helloworlddb3.html?sw=4&deptno=20

deptnoの引数の値によりSELECTされた結果が異なっていることがわかります。

helloworlddb3.htmlファイルの中身は、

<!doctype html>
<HTML>
<HEAD>
<title>Home</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</HEAD>
<BODY>
<h2>Example6</h2>
The current time is
<div class="seedo:ews.HelloWorldDb3:tb:2500">emp</div>.
</BODY>
</HTML>

class属性の値が、
class=”seedo:ews.HelloWorldDb3:tb:2500″となり、
ews.HelloWorldDb3クラスを使用しています。

Scala Web framework !! DB select

Posted by
Tagged As: | Categories: Framework, Scala | No Comments

HelloWorldDb2 クラスに引数を渡して、引数の値をSELECT文の条件にします。

引数の渡し方には2通りあります。
最初の方式は、HTMLファイルのDIVタグのクラス属性に整数値の2500を記述します。

class=”seedo:ews.HelloWorldDb2:tb:2500″

これを、where(“SAL > ?”,sal)としてwhere関数の可変長引数として渡します。
セキュリティ上、SQLインジェクションされないように上記のように記述します。

このようにHTMLファイルの中の固定値で引数が書けます。

JDBCアクセスなので、try と catch で例外処理を捉えます。
finally でsql.close 関数を呼び出すことで、SQLコネクションをコネクションプールに返します。
close関数がないと、コネクションプールを消費し続け、最後にMAX値まで達するとSQLエラーの例外が発生します。

scalaだと、明示的にclose関数を記述しないでも内部でclose関数を呼ぶコードも書けます。
トライしてみてください。

package ews
import java.util.{HashMap,Date}
import scala.collection.JavaConversions._
import scala.collection.immutable._
import scala.xml._
import seedo.database._

class HelloWorldDb2 {
  var tag:String = null // Replaced original tag
  def setTag(tg:String): Unit = {tag = tg}
  var parameterMap:HashMap[String,Array[String]] = null // Parameter from request of servlet
  def setParameterMap(pm:HashMap[String,Array[String]]) : Unit =  {parameterMap = pm}

  def recx(record:Array[Any]) :Node =  {
            var nodes  = Queue.empty[Node]
            record.foreach{(fx) => nodes += <TD>{fx}</TD>}
            return <TR>{nodes}</TR>
        }
  // Function one argument
  def tb(sal:Int) : String = {
    val deptno = 30
    var sql:Db = null
    try {
		sql = new Db
		sql.select("EMPNO,ENAME,SAL,DEPTNO,JOB,MGR")
			.from("EMP")
			.where("SAL > ?",sal)
			.or("DEPTNO = ?",deptno)
			.orderBy("EMPNO")
		val re = sql.executeQuery
		var nodes  = Queue.empty[Node]
		re.foreach{(rec) => nodes += recx(rec)}
		val xml = <table border="1">{nodes}</table>
	    return "HelloWorldDb2 " + new Date + xml.toString
	} catch {
      case e:Exception =>{
        throw e
      }
    } finally {
    	if(sql != null)sql.close
    }
  }
}

HTLMファイルの中身です。

<!doctype html>
<HTML>
<HEAD>
<title>Home</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</HEAD>
<BODY>
<h2>Example5</h2>
The current time is
<div class="seedo:ews.HelloWorldDb2:tb:2500">emp</div>.
</BODY>
</HTML>

結果

結果は以下のとおり

Scala Web framework !! DB test

Posted by
Tagged As: | Categories: Framework, Scala | No Comments

MySQL
Web framework を使ってMySQLにアクセスします。
使用するのはseedoフレームワークとの組み合わせ。
seedoフレームワークは、seedo1.2.2.jarを WEB-INF/libディレクトリのなかに入れる。

WEB-INF/classesディレクトリのなかにenv.properties ファイルを入れる。

DB接続の定義は、MySQLのJDBCドライバを使う。
connection-jndi を off にすることで、seedoフレームワーク独自のコネクションプールを使う。
TOMCATのコネクションプールを使う場合は、connection-jndiはon にして、
jndi-name = jdbc/mysql にする。
TOMCAT/conf/context.xml ファイルにDB接続定義を記述してもよい。

env.properties ファイル

driver = com.mysql.jdbc.Driver
#driver = oracle.jdbc.driver.OracleDriver

dsn = jdbc:mysql://127.0.0.1:3306/seedo?useUnicode=true&characterEncoding=UTF-8
#dsn = jdbc:oracle:thin:@127.0.0.1:1521:ORCL
user = scott
password = tiger

connection-jndi = off
jndi-name = jdbc/mysql
#jndi-name = jdbc/myoracle

EMP表に対してSELECT文を発行します。ENAMEフィールドでソートします。

MySQLにテスト用のDBを作る手順は、過去のBLOGにあるので参考に。

HelloWorldDb1.scala のソースコード

package ews
import java.util.{HashMap,Date}
import scala.collection.JavaConversions._
import scala.collection.immutable._
import scala.xml._
import seedo.database._

class HelloWorldDb1 {
  var tag:String = null // Replaced original tag
  def setTag(tg:String): Unit = {tag = tg}
  var parameterMap:HashMap[String,Array[String]] = null // Parameter from request of servlet
  def setParameterMap(pm:HashMap[String,Array[String]]) : Unit =  {parameterMap = pm}
  // One record -> TD tag
  def recx(record:Array[Any]) :Node =  {
            var nodes  = Queue.empty[Node]
            record.foreach{(fx) => nodes += <TD>{fx}</TD>}
            return <TR>{nodes}</TR>
        }
  // Function without argument
  def tb : String = {
	val sql = new Db
	val re = sql.executeQuery("select * from emp order by ename")
	sql.close
	var nodes  = Queue.empty[Node]
	re.foreach{(rec) => nodes += recx(rec)}
	val xml = <table>{nodes}</table>
    return "HelloWorldDb1 " + new Date + xml.toString
  }
}

HTMLファイルは、helloworlddb1.html

今回は、DIVタグ内の2つのSPANタグの中身は丸ごと、SELECT文の検索結果に置換されます。

<!doctype html>
<HTML>
<HEAD>
<title>Home</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</HEAD>
<BODY>
<h2>Example4</h2>
The current time is
<div class="seedo:ews.HelloWorldDb1:tb">
	<div>The current time is <span class="seedo:ews.HelloWorld3:tb:999999999">now</span>.</div>
	<div>The current time is <span class="seedo:ews.HelloWorld3:tb:こんばんわ">now</span>.</div>
</div>.
</BODY>
</HTML>

結果

URLは、
http://127.0.0.1:8080/toro/html/helloworlddb1.html?sw=4

結果は見ての通り。

Web フレームワークを作る!! test case#2

Posted 2011年8月23日 by
Tagged As: | Categories: Framework, Scala | No Comments

ExecClassクラスの改修

改修項目

  • String型からDate型への変換を追加
  • InvocationTargetExceptionの例外処理
package ews
import java.lang.reflect.{Field,InvocationTargetException,Method,Type}
import java.math.BigDecimal
import java.util.{Calendar,Date}
import java.text.SimpleDateFormat
import java.sql.{CallableStatement, PreparedStatement, ResultSet,Timestamp}
import javax.persistence.{Column,Entity,Id,Table,Lob,UniqueConstraint,SequenceGenerator}
import seedo.Util

/**
 * The function is executed specifying the class.
 */
class ExecClass(className:String
    ) {
	val myClass = Class.forName(className)	// The class is gotten from the class name.
	val Obj = myClass.newInstance.asInstanceOf[AnyRef]	// create Object
    val clazz:Class[_] = Obj.getClass	// Class of Bean
	/**
	 * It executes it specifying the type of the argument of the function.
	 */
	def execStr(methodName:String,value:String) : AnyRef = {
	  try {	// The function with the Int in the argument is called.
		val v = value.toInt
	   	return exec(methodName,v)
	  } catch {
	  	case e:Exception => {}
	  }
	  try {	// The function with the Long in the argument is called.
		val v = value.toLong
		return exec(methodName,v)
	  } catch {
	  	case e:Exception => {}
	  }
	  try {	// The function with the Double in the argument is called.
		val v = value.toDouble
		return exec(methodName,v)
	  } catch {
	  	case e:Exception => {}
	  }
	  val DATE_PATTERN = "yyyy/MM/dd"
      val sdf = new SimpleDateFormat(DATE_PATTERN)
      try {
    	val v = sdf.parse(value)
		return exec(methodName,v)
      } catch {
      	case e:Exception => {}
      }
	  return exec(methodName,value)
	}
	/**
	 * The function with the argument is executed.
	 */
	def exec(methodName:String,value:Any) : AnyRef = {
  	    if(value == null){
	    	val method:Method = clazz.getMethod(methodName)
	    	return method.invoke(Obj)
  	    }
  	    // The function is executed specifying the type of the argument of the function.
	    if(value.isInstanceOf[Int]){// The function with the Int in the argument is called.
	    	val method = clazz.getMethod(methodName, classOf[Int])
	    	val v = new Integer(value.asInstanceOf[Int])
	   		try {
	   			return method.invoke(Obj, v)
	   		} catch {
	   			case e:InvocationTargetException => {
	   				throw new Exception(e.getCause)
	   			}
	   		}
	    } else if(value.isInstanceOf[Long]){// The function with the Long in the argument is called.
	    	val method = clazz.getMethod(methodName, classOf[Long])
	    	val v = new java.lang.Float(value.asInstanceOf[Long])
	   		try {
	   			return method.invoke(Obj, v)
	   		} catch {
	   			case e:InvocationTargetException => {
	   				throw new Exception(e.getCause)
	   			}
	   		}
	    } else if(value.isInstanceOf[Float]){// The function with the Float in the argument is called.
	    	val method = clazz.getMethod(methodName, classOf[Float])
	    	val v = new java.lang.Float(value.asInstanceOf[Float])
	   		try {
	   			return method.invoke(Obj, v)
	   		} catch {
	   			case e:InvocationTargetException => {
	   				throw new Exception(e.getCause)
	   			}
	   		}
	    } else if(value.isInstanceOf[Double]){// The function with the Double in the argument is called.
	    	val method = clazz.getMethod(methodName, classOf[Double])
	    	val v = new java.lang.Double(value.asInstanceOf[Double])
	   		try {
	   			return method.invoke(Obj, v)
	   		} catch {
	   			case e:InvocationTargetException => {
	   				throw new Exception(e.getCause)
	   			}
	   		}
	    } else if(value.isInstanceOf[Char]){// The function with the Char in the argument is called.
	    	val method = clazz.getMethod(methodName, classOf[Char])
	    	val v = new Character(value.asInstanceOf[Char])
	   		try {
	   			return method.invoke(Obj, v)
	   		} catch {
	   			case e:InvocationTargetException => {
	   				throw new Exception(e.getCause)
	   			}
	   		}
	    } else {	// other class
	    	var valClass:Class[_] = value.asInstanceOf[AnyRef].getClass
	    	val method = clazz.getMethod(methodName, valClass)
	   		try {
	   			return method.invoke(Obj, value.asInstanceOf[AnyRef])
	   		} catch {
	   			case e:InvocationTargetException => {
	   				throw new Exception(e.getCause)
	   			}
	   		}
	    }
	}
	/**
	 * The function is executed.
	 */
	def exec(methodName:String) : AnyRef ={
		if(methodName == null)
			return null
		try {
			val method:Method = clazz.getMethod(methodName)
			return method.invoke(Obj)
		} catch {
			case e:InvocationTargetException => {
				throw new Exception(e.getCause)
			}
			case e:Exception => {
				throw new Exception(e)
			}
		}
	}
}

改修したExecClassクラスをテストするためのHelloWorld3 クラス。

  • 引数なしtb関数に置換元を格納するtag変数からspanタグを取り出し、HelloWorld文字列に追加
  • 整数を引数に持つtb関数にパラメータの値を、HelloWorld文字列に追加
  • Date型を引数に持つtb関数を追加
package ews
import java.util.{HashMap,Date}
import scala.collection.JavaConversions._
import scala.xml._
/**
 *  A class that's instantiated early and run.  It allows the application
 * to modify Seedo's environment
 */
class HelloWorld3 {
  var tag:String = null // Replaced original tag
  def setTag(tg:String): Unit = {tag = tg}
  var parameterMap:HashMap[String,Array[String]] = null // Parameter from request of servlet
  def setParameterMap(pm:HashMap[String,Array[String]]) : Unit =  {parameterMap = pm}
  // Function without argument
  def tb : String = {
    var xml : Node = null
    var str : String = ""
    if(tag != null){
      xml = XML.loadString(tag)
      val l = xml \\ "span"
      l.foreach(v=> str += "" + v)
    }
    "HelloWorld " + new Date + str
  }
  def tb(no:Int) : String = {
    var str : String = " parameterMap: "
    if(parameterMap!=null){
    	println(parameterMap.size)
       parameterMap.foreach(v => {
         str += v._1 + "="
         v._2.foreach(w =>
           str += w + ","
         )
       })
    }
    "HelloWorld Int [" + no + "] " + new Date + str
  }
  def tb(no:Double) : String = {
    "HelloWorld Double [" + no + "] " + new Date
  }
  def tb(no:Date) : String = {
    "HelloWorld " + no.getClass + " [" + no + "] " + new Date
  }
  def tb(no:String) : String = {
    "HelloWorld " + no.getClass + " [" + no + "] " + new Date
  }
}

HTMLファイルは、helloworld3.html

<!doctype html>
<HTML>
<HEAD>
<title>Home</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</HEAD>
<BODY>
<h2>Example3</h2>
The current time is
<div class="seedo:ews.HelloWorld3:tb">
	<div>The current time is <span class="seedo:ews.HelloWorld3:tb:999999999">now</span>.</div>
	<div>The current time is <span class="seedo:ews.HelloWorld3:tb:こんばんわ">now</span>.</div>
</div>.
</BODY>
</HTML>

結果