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

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

WebフレームワークのhtmlSrvクラスを少々改造

パラメータでどのパーサを使うかを選択できるようにします。

val sw = request.getParameter(“sw”)によってURLのパラメータを読み取ります。

 

	def gen(request : HttpServletRequest, response : HttpServletResponse) : Unit = {
		logger.debug("htmlSrv ################# start")
		println("htmlSrv ################# start")
		var htmlFile = ""
		request.setCharacterEncoding("utf-8")
		val sw = request.getParameter("sw")
   		val url = request.getRequestURL
		val uri = request.getRequestURI
		var spth:String = request.getServletPath
		// URLからパラメータ情報を取り出す
		spth = spth.replace("/","")
		val ur = uri.split("/")
		var fg = false
		ur.foreach({v =>
			if(fg){
				htmlFile += "/" + v
			}
			if(v.equals(spth))
				fg = true
		})
		logger.debug("uri="+uri+" htmlFile="+htmlFile)
		response.setContentType("text/html; charset=utf-8")
		var out = response.getWriter

		val ssp = sw match {
		  case null => new StraightSp(request, response, htmlPath + htmlFile)	// ファイルを読み込み、そのまま出力
		  case "1" => new XmlSp(request, response, htmlPath + htmlFile)			// HTMLファイルをXMLローダで読み込み、文字列変換して出力
		  case "2" => new Htmlparse2Sp(request, response, htmlPath + htmlFile)	// HTMLパーサーの解析結果を、文字列変換して出力
		  case "3" => new HtmlparseSp(request, response, htmlPath + htmlFile)	// HTMLパーサーの解析結果を、文字列変換して出力
		  case "4" => new Ssp(request, response, htmlPath + htmlFile)			// SEEDOによるクラス生成し、その出力結果を置換して出力
		  case _ => null
		}

   		val re = if(ssp.html != null){ssp.toString}else{message}
		out.println(re)	// output html data
		out.flush
		out.close
		logger.debug(" #### End")
		println(" #### End")
	}

 

テスト用のHTMLファイルにはtb関数の引数付きを追加します。
引数は、整数、実数、アスキー文字列、日本語文字列です。

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

結果

URLは、以下のようになります。
TOMCATのプロジェクトはtoroです。

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

tb関数が引数の型により呼び出されていることが確認できます。

Web フレームワークを作る!!  3日目 #3

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

ページ置換

いよいよ完成です。

段階的プログラミングの最終版です。

nu.validator.htmlparserを使って、HTMLファイルをパースします。

ReplaceHandlerクラスのなかでHTMLのなかのタグを置換します。

その情報は、ExecTagクラスによって保持します。

package ews

import java.io._
import javax.servlet.http._
import scala.xml._
import scala.collection.JavaConversions._
import nu.validator.htmlparser.common.XmlViolationPolicy
import nu.validator.htmlparser.sax.{HtmlParser,XmlSerializer}
import nu.validator.htmlparser.test.SystemErrErrorHandler
/**
 * The dynamic page is generated on the static HTML page.
 * htmlPath:URL
 */
class Ssp(request : HttpServletRequest, response : HttpServletResponse, htmlPath : String) extends TemplSp {
	var parsed:String = null	//
	// parse HTML file
	private def change(e:InputSource) : String = {
		println("Ssp parse")
	    val pMap = new java.util.HashMap[String,Array[String]]
	    request.getParameterMap.foreach(v => pMap.put(v._1,v._2))	// copy from getParameterMap to HashMap
		val ck = new ReplaceHandler(pMap)	// set ServletRequest getParameterMap function to handler
		val hp = new HtmlParser
		hp.setContentHandler(ck)
		hp.parse(e)
		if(ck != null)
			return ck.toString.replaceAll("","")	// change 
 tag
		else
			return null
	}
	// replace HTML
	def html : String = {
		val htmlFile = new java.io.File(htmlPath)
		if(htmlFile.exists && htmlPath.toLowerCase.endsWith(".html")){
			val input:BufferedInputStream = new BufferedInputStream(new FileInputStream(htmlFile))	// open HTML file
			val inputSource = new InputSource(input)
			parsed = this.change(inputSource)
			return parsed
		}
		null	// There is no file.
	}
	override def toString :String = parsed
}

まずは、通常のハンドラのコード。
ハンドラクラスの中で使うSAXによるタグ解析用のハンドラ関数です。

  • startDocument 未使用
  • endDocument 未使用
  • startElement タグの開始
  • endElement タグの終了
  • characters 要素など

以下ががシンプルなハンドラのコード

クラス構成を示すものなので、このReplaceHandlerクラスにはコンストラクタの引数がないです。

package ews
import java.util.HashMap
import org.xml.sax.helpers.DefaultHandler
import org.xml.sax.Attributes
import scala.collection.JavaConversions._

class ReplaceHandler extends DefaultHandler {
  var buf = new StringBuffer
  override def toString :String = buf.toString
  //
  override def  startDocument :Unit ={	/*println("startDocument")*/  }
  override def  endDocument :Unit ={	/*println("endDocument")*/  }
  override def startElement(uri: String, localName: String, qName: String,attrs: Attributes): Unit = {
	var tag = ""
	buf.append(tag)
  }
  override def endElement(uri: String, localName: String, qName: String): Unit = {
	buf.append("")
  }
  override def characters(ch:Array[Char],start:Int,length:Int) : Unit = {
    var tag:String = ""
    for(i

このコードをベースにWebフレームワーク用のハンドラクラスは以下のようになります。

class=”seedo:属性があるタグが入れ子になっても処理できるように
stackChange によって置換情報を保持します。

コードは複雑に見えますが、処理としては、

  1. startElement関数でタグ属性を探す
  2. endElementでスタートタグと対になるタグを識別
  3. ec.exec(“setTag”,stackChange.top.getBufChange.toString)により置換元のタグをセット
  4. ec.exec(“setParameterMap”,pMap)によりパラメータをセット
  5. ExecClassを生成してexecStr関数を呼び出す
  6. タグを置換
package ews

import java.util.HashMap
import org.xml.sax.helpers.DefaultHandler
import org.xml.sax.Attributes
import scala.collection.JavaConversions._
import scala.collection.mutable.Stack
import seedo._

class ReplaceHandler(pMap:HashMap[String,Array[String]]) extends DefaultHandler {
  var buf = new StringBuffer		// Original tag is maintained.
  val stack = new Stack[String]			// For indent inspection of tag
  val stackChange = new Stack[ExecTag]	// For substitution

  override def toString :String = buf.toString
  //
  override def  startDocument :Unit ={	/*println("startDocument")*/  }
  override def  endDocument :Unit ={	/*println("endDocument")*/  }
  override def startElement(uri: String, localName: String, qName: String,attrs: Attributes): Unit = {
	stack.push(qName)
	var tag = "= 3) {	// It doesn't substitute it if there are neither a class name nor a function name.
				stackChange.push(new ExecTag(
						stack.length
						,qName
						,execCommand
						,new StringBuffer	// The tag to be substituted is maintained.
				))
				fullFag = true
			} else {
				println("$$$$$$$$$$ execClass Warning " + attrName + "='" + attrValue + "'")
			}
		}
	}
	tag += ">"
	if(!stackChange.isEmpty){
		stackChange.top.getBufChange.append(tag)
	} else {
		buf.append(tag)
	}
	if(fullFag == true){
		stackChange.top.setStartTagFull(tag)
	}
  }
  override def endElement(uri: String, localName: String, qName: String): Unit = {
    val endTag = ""
    if(stack.top.equals(qName)){	// 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 && qName.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=" + qName)
    }
  }
  override def characters(ch:Array[Char],start:Int,length:Int) : Unit = {
    var tag:String = ""
    for(i

結果

結果はこのようになります。

いかがでしょうか。

3日間でWebフレームを作りきりました。

このWebフレームは、最低限の機能しかありませんが、
このコードで誰でもWebフレームを作ることができます。

このコードを発展させて、国産のScala Webフレームワークに育てばいいと考えています。

いっしょに開発しようと思われる方、フレームワークを作りましょう。

ライセンス

ここで公開したソースコードは、LAMPによるライセンスとします。

/*
 * SeeDo
 *  eWave Solutions Inc.(c) 2000-2011, LAMP
 */

Web フレームワークを作る!!  3日目 #2

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

動的ページ生成

class=”seedo:ews.HelloWorld:tb” 属性を実行する機能の実装です。

ExecClassクラスは、フルパスのクラス名をコンストラクタにして生成します。
Class.forName によってクラスを特定して、
newInstance.asInstanceOf[AnyRef] によってオブジェクトを生成します。

この処理の流れはJavaのリフレクションと同じ。

このコードだとクラスローダーを使って任意のクラス名の文字列からClassインスタンスを取得することはしていないので、実行したいクラスは予めクラスファイルかJarファイルをTOMCATに設定します。

最初のコードはシンプルにしたいのでクラスローダーの機能は省略。

package ews
import java.lang.reflect.{Field,InvocationTargetException,Method,Type}
import java.math.BigDecimal
import java.util.{Calendar,Date}
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 MyClass
	/**
	 * 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 => {}
	  }
	  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])
		 return method.invoke(Obj, v)
	    } 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])
		 return method.invoke(Obj, v)
	    } 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])
		 return method.invoke(Obj, v)
	    } 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])
		 return method.invoke(Obj, v)
	    } 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])
		 return method.invoke(Obj, v)
	    } else {	// other class
		 var valClass:Class[_] = value.asInstanceOf[AnyRef].getClass
		 val method = clazz.getMethod(methodName, valClass)
		 return method.invoke(Obj, value.asInstanceOf[AnyRef])
	    }
	}
	/**
	 * 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)
			}
		}
	}
}

実行させたい関数を呼び出すためのexec関数は、

  1. 関数名だけで引数なしの場合は、def exec(methodName:String)
  2. 引数ありの場合は、def exec(methodName:String,value:Any)

引数なしのexec関数のコードには、例外処理のコードが追加されて、きちんと実装しています。

getMethodinvokeで例外が発生した場合、
特に、InvocationTargetExceptionが発生したときは、
getCause()メソッドでターゲットのクラスからの例外を取得して、
throw new Exception(e.getCause)します。

def exec(methodName:String,value:Any)にも、
この例外処理を入れないといけません。このコードではとりあえず省略。

HTMLパーサしたときに、このクラスを使う場合は、def execStr(methodName:String,value:String) を使います。

execStr関数は、タグ属性で指定された引数文字列から、型、この場合、数字であれば
数値型の型変換します。

  1. Int 整数
  2. Long 整数
  3. Double 実数

型変換の処理は、toIntとかtoLongとかtoDoubleを呼び出して例外でなければ、その数値型で実行して、
例外が発生すれば、文字列として扱うためにString型で実行するという簡単なしかけです。

HelloWorldクラスのtb関数には、引数なし、Double引数、String引数の3つのtb関数を
定義しているので、もし、Intとして成立する数字文字列をタグ属性に書いてしまうと、
HelloWorldクラスには、そんな関数ないっていうことで例外が発生します。
次のLongもないので例外発生し、DoubleではOKとなります。

当然、タグ属性にtb関数以外の関数名が指定されても、そんな関数はないと例外発生となります。

ExecClassクラスの単体テスト用のコードを作ってテストします。

package ews
import org.scalatest.junit.{JUnitSuite,ShouldMatchersForJUnit}
import org.junit.{Test,Before}
import scala.xml._

class ExecClassTest extends JUnitSuite with ShouldMatchersForJUnit{
	var obj:ExecClass = null
	@Before def initialize() {
		obj = new ExecClass("ews.HelloWorld")
	}
	@Test def exec {
		println("Start ---------")
		var result:AnyRef = null
		try {
			result = obj.exec("tb")
		} catch {
		 case e:Exception => {
            println("Exception "+e.getMessage)
		 }
		}
		println(result)
		println(obj.execStr("tb","abcdefg"))
		println(obj.execStr("tb","3.14"))
		println(obj.execStr("tb","2001/1/1"))
		println(obj.execStr("tb","2011/8/20 12:9:59"))
 		println(obj.execStr("tb","2"))
		println(obj.execStr("tb","99999999999999"))
		println("End ---------")
	}
}

このコードを実行できるようにするには、JUNITScalatest の2つのJarファイルをビルドパスに追加します。

私の環境では、以下のバージョンを使っていますが、最新のものでもテストできると思います。

  • junit-4.9b3.jar
  • scalatest-1.2.jar

テストを実行した結果です。

Start ---------
HelloWorld Sun Aug 21 16:16:33 JST 2011
HelloWorld String [abcdefg] Sun Aug 21 16:16:33 JST 2011
HelloWorld Double [3.14] Sun Aug 21 16:16:33 JST 2011
HelloWorld String [2001/1/1] Sun Aug 21 16:16:33 JST 2011
HelloWorld String [2011/8/20 12:9:59] Sun Aug 21 16:16:33 JST 2011
HelloWorld Double [2.0] Sun Aug 21 16:16:33 JST 2011
HelloWorld Double [9.9999999999999E13] Sun Aug 21 16:16:33 JST 2011
End ---------

このtb関数で実行されたかは、HelloWorldの隣に型が出力され、型変換された結果が表示されます。
tb関数では、されに new Date で現在の時刻が表示されています。
いましがたテスト実行したので今日の日時です。

引数なしのtb関数呼び出しでは、例外発生したときに出力するようになっているので、
HelloWorldクラス側で、例外発生させるために、null 変数にtoStirng するような
コードを追加すればテストできます。

いよいよ次は、HTMLパーサによって解析したTAGからclass属性を探して、
TAG置換するコードとなります。

Web フレームワークを作る!!  3日目

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

お決まりの HelloWorld を作ります

HelloWorldを表示する HTMLファイルの中身は、

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

spanタグの class=”seedo:ews.HelloWorld:tb” という属性がポイント。

spanタグが、ews.HelloWorld.scala のクラスを生成して置き換わります。

class属性の表記法は、: が区切り文字。

  1. 最初の seedo は、動的ページに置き換わるタグを示します。
    spanタグの中に別のタグがあったとしても、全部置き換えます。
  2. 2番目のews.HelloWorld は動的ページのためのクラス。
    Scala でも、Java でもOK
    クラス名はパッケージ名を含むフルパス。
  3. 3番目のtb は、ews.HelloWorldクラスの実行させる関数。
    この場合は、tb関数。引数はなし。
  4. tb関数に引数を持たせたい場合は、4番目に引数を記述します。
    たとえば、以下のように。class=”seedo:ews.HelloWorld:tb:3.14″

この処理を実装するには、2つの機能が必要になります。

  1. ews.HelloWorldクラスを生成して、tb関数を呼び出すこと
  2. tb関数が生成した文字列を、元のタグと置換すること

ews.HelloWorldクラスの実装は

package ews
import java.util.{HashMap,Date}
import scala.collection.JavaConversions._
/**
 *  A class that's instantiated early and run.  It allows the application
 * to modify Seedo's environment
 */
class HelloWorld {
  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 = {
    "HelloWorld " + new Date
  }
  def tb(no:Double) : String = {
    "HelloWorld Double [" + no + "] " + new Date
  }
  def tb(no:String) : String = {
    "HelloWorld String [" + no + "] " + new Date
  }
}

クラスの中には、2つの変数があります。

var tag:String は、置換元となるタグがセットされます。
もし、置換元をHelloWorldクラス側で参照して処理したい場合に使います。

var parameterMap:HashMap[String,Array[String]] は、
Servletのリクエストから渡されてきたパラメータのすべてを参照するためのものです。
これがないと、動的ページを生成する条件やデータが渡せなくなりますから。
型の仕様は、ServletのparameterMapを参照してください。

この変数に対して、setter関数が定義されます。

次に、関数
この例では3つの関数が定義されています。

関数名はともに tb です。返り値はString型。

  1. 1つ目は、引数なしのtb関数
  2. 2つ目は、引数にDouble型を取るもの
  3. 3つ目は、引数にString型を取るもの

HTMLファイルの例では、引数なしのtb関数が呼び出されます。

Web フレームワークを作る!!  2日目

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

TOMCATとScalaとの関係ができあがったところで、
このWeb Framework についての仕様を決めましょう。

LiftApache Wicket のようにHTMLファイルをベースにします。

その理由は、

    1. WebページのデザインはやはりプロであるWebデザイナが行うことが重要。
      機能的に優れていても見た目や構成がダメならページを見てもらえない。
    1. ページを表示するディバイスがPCのブラウザだけではなく、
      スマートフォンや携帯電話、タブレットと多様化し、
      各ディバイスに最適なデザインはWebデザイナが分担して作業を進める。
    1. 頻繁にページを更新することはもはや日常茶飯事。
      ページ更新するたびにTOMCATを再起動できない。
    1. Webページをデザインするツールは動的ページのことなど考えていない。
      メニューやお知らせもデザインツールで簡単に生成でき、そのままFTPでWebサイトへ更新できる。
  1. 既存Webサイトからの移行にいちいちコンバート作業なんてしてられない。

ということで、

「HTMLファイルありき」がこのWeb Framework第一の方針です。

次に、運用の観点から

TOMCATなどJavaVMを使ったWebサーバの弱点はメモリー管理です。

運用経験がある担当者であれば一度はOutOfMemoryError すなわちメモリ不足に陥ったことがあるでしょう。
メモリのガーベージコレクト(GC)に時間がかかるようになったりすると、
JVMのGCのパラメータをいろいろ変えたり、64ビット版JDKへ移行するためにプラットフォームを32Bから64Bに入れ替えたりと大変です。

JVMにはPermanent領域と呼ばれるヒープ領域があり、クラス定義やメソッド、フィールドなどのメタデータが格納されます。
JSPでページ構成を作るとこの領域に格納されます。Servletも同じ。
Permanent領域を抑えるためには、クラスの数を少なくすること。

1日目のServletを1つにする理由はここにあります。
ページが増えてもクラスが増えないようにすることです。
JSPだとページまるごとクラスとなるのでかなりクラスが大きくなります。
HTMLファイルを読み込み、動的に情報を表示する領域のためのオブジェクトによって動的ページを生成して、
元のHTMLファイルを置換するという方式です。

これが第2の方針

JSPのように最初のアクセスでコンパイルの必要もなく、
Apache httpd のようにDocumentRootディレクトリにHTMLファイルを置くだけです。

このプログラムのしくみはいたって簡単。

    1. Servletのパラメータで指定されたHTMLファイルを読みこんで
    1. toString関数でString型にする

プログラムは積み上げて構築していくものという考えなのでスタートはここから。

package ews
import java.io._
import javax.servlet.http._
/**
 * HTML file -> read -> String ----> Servlet response output
 */
class StraightSp (request : HttpServletRequest, response : HttpServletResponse, htmlPath : String) extends TemplSp {
    var parsed:String = ""
    // replace HTML
    def html : String = {
      try {
        val htmlFile = new java.io.File(htmlPath)
        if(htmlFile.exists && htmlPath.toLowerCase.endsWith(".html")){
            // open HTML file
            val bis = new InputStreamReader(new FileInputStream(htmlFile),"UTF-8")
            val dis = new BufferedReader(bis)
            while(dis.ready) {
                val str = dis.readLine
                parsed += str
            }
        }
        return parsed
      } catch {
        case e:IOException => {
            println("IOException "+e.getMessage)
        }
      }
      null  // There is no file.
    }
    override def toString :String = parsed
}

しかし、これではTOMCATのプロジェクトディレクトリにHTMLファイルを置くのと同じ

2つ目のプログラム。HTMLファイルをXMLローダで読み込んでみる。

package ews
import javax.servlet._
import javax.servlet.http._
import scala.xml._
/**
 * HTML file -> XML.loadFile
 */
class XmlSp (request : HttpServletRequest, response : HttpServletResponse, htmlPath : String) extends TemplSp {
	var parsed:String = null
	var xml:scala.xml.Node = null
	// replace HTML
	def html : String = {
	  try {
		val htmlFile = new java.io.File(htmlPath)
		if(htmlFile.exists && htmlPath.toLowerCase.endsWith(".html")){
			xml = XML.loadFile(htmlPath)
			if(xml != null)
			  parsed = xml.toString
		}
		return parsed
	  } catch {
	    case e:Exception => {
			println("Exception "+e.getMessage)
		}
	  }
	  null	// There is no file.
	}
	override def toString :String = parsed
}

このプログラムの仕様は、HTMLファイルをscalaのXML.loadFile で読みこんでしまう。
これは少々無謀。

とうぜん、HTMLはXMLではないので既存のHTMLファイルを読めばエラーの嵐。

問題個所は、空白文字 &nbsp;

<BR>や<HR>、<IMG>のように単独で記述するタグ。

&nbsp;


scala が XML を型として使えるといえども、HTMLを扱うには問題あり。

こうなると、HTMLパーサを導入することが妥当な選択

2つのHTMLパーサを試してみます。

javax.swing.text.html.parsernu.validator.htmlparser

javax.swing.text.html.parserでは、

package ews

import java.io._
import javax.servlet.http._
import javax.swing.text._
import javax.swing.text.html._
import javax.swing.text.html.parser._
import javax.swing.text.html.parser.ParserDelegator
import scala.xml._
/**
 * HtmlParse
 */
class Htmlparse2Sp (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")){
			try {
				val input = new InputStreamReader (new FileInputStream(htmlFile), "UTF-8")	// open HTML file
				val cb = new ParserCallback2
				val pd = new ParserDelegator
				pd.parse(input, cb, true)
				input.close
				parsed = cb.toString
				return parsed
			} catch {
			  case e:IOException => {
				  println("Exception "+e.getMessage)
			  }
			}
		}
		null	// There is no file.
	}
}

ハンドラクラスは以下のとおり

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={
		buf.append("

nu.validator.htmlparserでは、

package ews
import java.io._
import javax.servlet.http._
import scala.xml._
import scala.xml.parsing.{ConstructingParser,NoBindingFactoryAdapter}
import nu.validator.htmlparser.common.XmlViolationPolicy
import nu.validator.htmlparser.sax.{HtmlParser,XmlSerializer,HtmlSerializer}
import nu.validator.htmlparser.test.SystemErrErrorHandler
/**
 * HtmlParse
 */
class HtmlparseSp (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")){
			try {
				val input = new BufferedInputStream(new FileInputStream(htmlFile))	// open HTML file
				val inputSource = new InputSource(input)
				val parser = new HtmlParser
				parser.setNamePolicy(XmlViolationPolicy.ALLOW)
				val saxer = new NoBindingFactoryAdapter
				parser.setContentHandler(saxer)
				parser.setErrorHandler(new SystemErrErrorHandler)
				parser.parse(inputSource)
				parsed = saxer.rootElem.toString.replaceAll("<br></br>","<br>")
				return parsed
			} catch {
			  case e:IOException => {
				  println("Exception "+e.getMessage)
			  }
			}
		}
		null	// There is no file.
	}
}

このHTMLパーサはLiftでも使われているとのこと。

2つのHTMLパーサを試してみましたが、

いよいよ、
ハンドラクラスでの動的ページ生成のロジックの組み込みとなります。

Web フレームワークを作る!!  1日目

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

まずは、TOMCATScalaとの関係を築くことから。

Servletのソースコードです。

package ews
import org.apache.log4j.Logger
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 = ""
		request.setCharacterEncoding("utf-8")
   		val url = request.getRequestURL
		val uri = request.getRequestURI
		var spth:String = request.getServletPath
		// Parametrical information is taken out of URL.
		spth = spth.replace("/","")
		val ur = uri.split("/")
		var fg = false
		ur.foreach({v =>
			if(fg){
				htmlFile += "/" + v
			}
			if(v.equals(spth))
				fg = true
		})
		logger.debug("uri="+uri+" htmlFile="+htmlFile)
		response.setContentType("text/html; charset=utf-8")
		var out = response.getWriter

		val ssp = new StraightSp(request, response, htmlPath + htmlFile)	// It outputs including reading a file.
//		val ssp = new XmlSp(request, response, htmlPath + htmlFile)			// HTMLファイルをXMLローダで読み込み、文字列変換して出力
//		val ssp = new HtmlparseSp(request, response, htmlPath + htmlFile)	// HTMLパーサーの解析結果を、文字列変換して出力
//		val ssp = new Ssp(request, response, htmlPath + htmlFile)			// SEEDOによるクラス生成し、その出力結果を置換して出力

   		val re = if(ssp.html != null){ssp.toString}else{message}
		out.println(re)	// output html data
		out.flush
		out.close
		logger.debug(" #### End")
		println(" #### End")
	}
}

このコードは、ScalaでServletを書いているので、TOMCATでも問題なく動きます。

これをTOMCATで動かすためには、

プロジェクトを作ります。

このあたりの手順はTOMCAT開発のページがいろいろあると思いますし、書店のコンピュータのコーナにも解説本があるでしょうし省略。

大事なことは、
WEB-INFフォルダにある、web.xml ファイルにServletの定義を追加します。
TOMCATを使ってStrutsした経験があればわかってもらえるでしょう。

		html
		ews.htmlSrv

html
C:\tomcat7_scala\webapps\toro\WEB-INF\html

		1

		html
		/html/*

Servletの名前は、とりあえず、html 。
クラスは先のプログラムの、ews.htmlSrv です。
初期化パラメータに、HTMLファイルを置くディレクトリを定義します。

TOMCATは、Version 7.0.19。
8月20日現在は、Version 7.0.20がリリースされていますが、
それでも動くでしょう。
忘れてならない Scala 2.9.0

JDKは、JAVA SE7。 JAVA SE6でも問題なし。
WEB-INFディレクトリの下にlibディレクトリを作成して、ここに置く必要なJarファイルは、

  • log4j-1.2.15.jar
  • scala-library.jar

web.xmlファイルの定義に戻って、

Servletマッピングは、/html/* です。
この * には大いに意味ありです。

というのは、新しいページを追加するたびに、web.xml ファイルにServletの定義を追加したくないから。

ソースコードの中の、
request.getServletPath によって返ってくるパスをパラメータとして使おうという作戦です。

URLが

http://www.ewavesolutions.com/hoge/html/abc/index.html

なら、/abc/index.html はServletの引数になります。

URLだけ見ると、静的なHTMLファイルで構成するURLの見えてしまいます。

http://www.ewavesolutions.com/hoge/html/abc/index.html?xyz=123456789&id=KABA

としてURLを作ってもOKです。
Scalaプログラムの中は、

Servletの初期化処理は、

override def init(config : ServletConfig)
ここでは、web.xml で定義した init-param の、html の値を読み出します。
HTMLファイルが置かれるパスです。

doGet関数 と doPost関数は、おなじみの関数。

HTMLコンテンツを生成する gen関数を定義します。
この中では、

val ssp という変数に、Servletのリクエストとレスポンス、HTMLファイルのあるフルパスを引数にしてたクラスを定義します。

このクラスをNewして、

  1. ssp.html関数を呼び出してHTMLファイルを読み出し
  2. ssp.toString関数で、String化します。
  3. それを、 response.getWriter関数で出力すればレスポンスの出来上がり。

クラスの仕様はこんな感じ

package ews

trait TemplSp {
  def html : String
  override def toString :String
}

最後に、テスト用に org.apache.log4j.Logger でログ出力するようにします。

Web フレームワークを作る!! by Scala

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

Webフレームワークについては、Javaを使っていたころは(いまでも仕事はJava ! )、
JSP+Servletという仕組みに独自のフレームワークを作って開発していました。

TOMCATを使い始めた2000年には、Webフレームワーク自身が存在していません。

Struts1.0 が、Craig R.McClanahanによって2000年5月に開始され 2001年7月にリリースですから。

TOMCATのJSP仕様書にTaglibの記述ありましたから、TOMCATの開発者はStrutsを意識していたのでしょう。
しかし、StrutsのMVCモデルはいいとして独自のフレームワークを捨ててStrutsを採用する気にはなりませんでした。

  • なんで、プログラム開発するのにXMLなの?
  • ネーミングルールでモデルが関係されるとEclipsでソースコードで関数追跡できない。
  • HTMLやJavaScriptを覚えれるだけでも大変なのにTaglibを覚えるの。
  • スクラッチからの開発は素晴らしいけど、いったん画面遷移や仕様変更されると結局作り直し。

Webフレームワーク

それから10年、世の中にはいろいろなWebフレームワークがリリースされ、比較サイトには独自の尺度でランキングしています。

最近の流行は、Spring MVC, GWT, Ruby on Rails, Wicket のようです。

Struts2SAStrutsもありますね。

Scalaでは、Liftが実績からダントツですが、scalatra、sweetscala、Bowlerなど新興フレームも元気な感じです。

ここでもLiftを評価し、Spring MVC も Java 言語開発の観点から評価してました。

先日も Apache Wicket を見ていたのですが、いまさらJavaかなとも思うしHTMLページ単位にクラスを作るという制約?!や相変わらず Taglib のような独自タグから脱却できない仕様にがっかり。

Liftは、HTML以外のタグを排除するという仕様に感動。

でも、Scalaクラスのプログラムが私のScalaの理解度では何を書いていいのかが直感的にわからない。
lazy val の遅延評価の価値がいまいちピンときていないからでしょう。
JettyじゃなくてやっぱりTOMCATが使いたい。

進む道とは、

これまでのプログラム経験から悟ったことは、

Simple is best

それはシステムがトラブルしているときに、

  • 怒っているお客さん。
  • 何とかしてと懇願するお客さん。
  • もうお出入り禁止と言い放つお客さん。

を前にして、他人の書いたプログラムを追跡して、原因を調べ、パッチを当てるという作業サイクルでは、いくらエレガントな仕様も理解できなきゃどうにもならないということからです。

自分で設計してプログラムを書けば、ブラックボックスはない。
MS-DOSやUNIXのC言語で開発していたころは標準ライブラリしかなかったですから。

でも、JavaEEでビジネスシステムをガンガン開発していたエンジニアや、有名な日本のWebフレームワークの開発者が

「もう、フレームワークの時代ではない」

というブログを読むととっても悲しい気分です。

ということで、軽量Webフレームワークを3日で作ろうというプロジェクト、というより企画です。

Scala (Java) とHTMLだけ。

HTMLは、Webデザイナのお仕事として完全おまかせ。
開発者は、動的Webページに必要な機能を定義して、そのパーツはひたすらScalaかJavaで書けばよし。

という仕様です。

Ruby on Railsのキャッチフレーズが、

  • フルスタックなWebフレームワーク
  • CoCというシンプルな設計思想
  • RJBを使うと、Ajaxと親和性が高い
  • RestfulなWebフレームワーク

なら、こちらは、Webフレームワークの中身が完全理解できる というのがキャッチです。

まずは、TOMCATとScalaの結合から。

Scala Iterator for JDBC

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

JDBCドライバをScalaのIteratorで皮を被せた実装です。

これは、Seedoフレームワークの中心的な部分です。
Javaで実装したSeedoフレームワークでは、Iterator部分が別Classとなる実装でしたが、Scalaではすっきりと1つのClassの中に記述できてしまいます。

内容はJDBCドライバの関数を生で記述したことがある人であればすぐに理解できるものです。
最近は、JDBCドライバの関数で直接コードを書く機会がないと思いますが、お使いのフレームワークの中身とこれとは似たようなものでしょう。

prepareStatement関数を使ったgetObjectによってレコードカラムのデータを取得するコーディングです。

	def executeQueryIterator = new Iterator[Array[Any]] {
		if(sqlMarker != rdbms._SELECT_){
			throw new Exception("SQL sentence should be SELECT phrase")
		}
		var pstmt:PreparedStatement = con.prepareStatement(getSql)
		var parameterIndex = 1
		if (parameters != null) {
			for (i <- 0 to parameters.length - 1) {
				pstmt.setObject(parameterIndex, parameters(i))
				parameterIndex += 1
			}
		}
		var rs:ResultSet = pstmt.executeQuery
		var orsmd:ResultSetMetaData = rs.getMetaData
		var numColumns = orsmd.getColumnCount
		columnNameList = new Array[String](numColumns) // make array for column name
		for (i <- 1 to numColumns) {		// get column name
			columnNameList(i-1) = orsmd.getColumnName (i)
		}
	
		override def hasNext : Boolean = {
			try {
				val r = rs.next	// read next record
				if(!r)
					close
				return r
			} catch {
				case e:SQLException => {
					e.printStackTrace
				}
			}
			return false
		}
		override def next : Array[Any] = {
			val rec = new Array[Any](numColumns) // make array for one record
			var i = 0
			for (j <- 1 to numColumns) {
				val tp = orsmd.getColumnType (j)
				try {
					rec(i) = rs.getObject(j)
				} catch {
					case e:SQLException => {
						errorMessage = "Warning "+e.getMessage
						System.err.println(errorMessage)
						close
					}
				}
				i += 1
			}
			rec
		}
		def close :Unit = {
			try {
				if (pstmt != null) {
					pstmt.close
					pstmt = null
				}
				if (rs != null){
					rs.close
					rs = null
				}
			} catch {
				case e:SQLException => {
					e.printStackTrace
				}
			}
		}
		clear
	}



override def hasNext では、rs.next を呼び出しています。

rs.hasNext や rs.isLast や rs.last が使えるといいのですが、
Oracleデータベースでは、prepareStatement関数が、TYPE_SCROLL_INSENSITIVEでないResultSetを作るため例外を発してしまいます。PostgreSQLのJDBCドライバでは問題はないようです。


次のscalatestによるテストコードは、Seedoフレームワークのテスト用のものです。

import org.scalatest.junit.{JUnitSuite,ShouldMatchersForJUnit}
import org.junit.{Test,Before,After,BeforeClass,AfterClass}
import seedo._
import seedo.database._
import scott.bean._

class testDB extends JUnitSuite with ShouldMatchersForJUnit{
	var drivrName = "oracle.jdbc.driver.OracleDriver"
	var dsn = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL"
	var user ="scott"
	var passwd ="tiger"
	var db:Db = _

	@Before def initialize() {
	  println("Start")
	  db = new Db(drivrName,dsn,user,passwd)
	}
	@Test def verifyTable() { // Uses JUnit-style assertions & Uses ScalaTest assertions
	  db.select("*").from("EMP").where("empno >= ?",7700).orderBy("ename")
	  println(db.toString)
	  println(db.getSql)
	  println(" --- Bean SELECTした結果をBEANに格納してArrayで返す")
	  db.executeQuery(classOf[EMP]).foreach({c =>
	 	  println(c.toString)
	  })
	  println(" --- ColumnNameList")
	  db.getColumnNameList.foreach(c =>{
	 	  print(" " + c)
	  })
	  println
	  db.select("EMPNO,ENAME,HIREDATE").from("EMP").where("empno >= ?",7700).orderBy("EMPNO")
	  println(db.toString)
	  println(db.getSql)
	  println(" --- Bean SELECTした結果をBEANに格納して返すIteratorによる実装。foreachにて出力はXML")
	  db.executeQueryIterator(classOf[EMP]).foreach({c =>
	 	  println(c.toXml)
	  })
	  println(" --- ColumnNameList")
	  db.getColumnNameList.foreach(c =>{
	 	  print(" " + c)
	  })
	  println
	  db.select("EMPNO,ENAME,HIREDATE").from("EMP").where("empno >= ?",7700).orderBy("EMPNO")
	  println(db.toString)
	  println(db.getSql)
	  println(" --- Bean SELECTした結果をBEANに格納して返すIteratorによる実装。while句でhasNext、next関数にて出力はJson")
	  val it = db.executeQueryIterator(classOf[EMP])
	  while(it.hasNext){
		  println(it.next.toJson)
	  }
	  println(" --- ColumnNameList")
	  db.getColumnNameList.foreach(c =>{
	 	  print(" " + c)
	  })
	  println
	  db.select("EMPNO,ENAME,HIREDATE").from("EMP").where("empno >= ?",7700).orderBy("EMPNO")
	  println(db.toString)
	  println(db.getSql)
	  println(" --- Array SELECTした結果をArray[Any]に格納して返すIteratorによる実装。while句でhasNext、next関数にて出力はtoString")
	  val list2 = db.executeQueryIterator
	  while(list2.hasNext){
		  list2.next.foreach(v =>print(v.toString + " "))
		  println
	  }
	  println(" --- ColumnNameList")
	  db.getColumnNameList.foreach(c =>{
	 	  print(" " + c)
	  })
	  println

	  println(" --- End Sql")
	}
	@After def close() {
	  if(db != null)
		  db.close
	  println("End")
	}
}

テスト結果は、以下の通り。

Start
MissingResourceException:Can't find bundle for base name env, locale 

JDBC Driver:Oracle JDBC driver Version:11.1.0.7.0-Production
Database:Oracle Version:11.2
select * from EMP where empno >= ? order by ename [7700]
select * from EMP where empno >= ? order by ename
 --- Bean SELECTした結果をBEANに格納してArrayで返す
7876,ADAMS,CLERK,7788,1987-05-23 00:00:00.0,1100,null,20
7782,CLARK,MANAGER,7839,1981-06-09 00:00:00.0,2450,null,10
7902,FORD,ANALYST,7566,1981-12-03 00:00:00.0,3000,null,20
7900,JAMES,CLERK,7698,1981-12-03 00:00:00.0,950,null,30
7839,KING,PRESIDENT,null,1981-11-17 00:00:00.0,5000,null,10
7934,MILLER,CLERK,7782,1982-01-23 00:00:00.0,1300,null,10
7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,null,20
7844,TURNER,SALESMAN,7698,1981-09-08 00:00:00.0,1500,0,30
 --- ColumnNameList
 EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
select EMPNO,ENAME,HIREDATE from EMP where empno >= ? order by EMPNO [7700]
select EMPNO,ENAME,HIREDATE from EMP where empno >= ? order by EMPNO
 --- Bean SELECTした結果をBEANに格納して返すIteratorによる実装。foreachにて出力はXML
<EMP><EMPNO>7782</EMPNO><ENAME>CLARK</ENAME><JOB></JOB><MGR></MGR><HIREDATE>1981-06-09 00:00:00.000</HIREDATE><SAL></SAL><COMM></COMM><DEPTNO></DEPTNO></EMP>
<EMP><EMPNO>7788</EMPNO><ENAME>SCOTT</ENAME><JOB></JOB><MGR></MGR><HIREDATE>1987-04-19 00:00:00.000</HIREDATE><SAL></SAL><COMM></COMM><DEPTNO></DEPTNO></EMP>
<EMP><EMPNO>7839</EMPNO><ENAME>KING</ENAME><JOB></JOB><MGR></MGR><HIREDATE>1981-11-17 00:00:00.000</HIREDATE><SAL></SAL><COMM></COMM><DEPTNO></DEPTNO></EMP>
<EMP><EMPNO>7844</EMPNO><ENAME>TURNER</ENAME><JOB></JOB><MGR></MGR><HIREDATE>1981-09-08 00:00:00.000</HIREDATE><SAL></SAL><COMM></COMM><DEPTNO></DEPTNO></EMP>
<EMP><EMPNO>7876</EMPNO><ENAME>ADAMS</ENAME><JOB></JOB><MGR></MGR><HIREDATE>1987-05-23 00:00:00.000</HIREDATE><SAL></SAL><COMM></COMM><DEPTNO></DEPTNO></EMP>
<EMP><EMPNO>7900</EMPNO><ENAME>JAMES</ENAME><JOB></JOB><MGR></MGR><HIREDATE>1981-12-03 00:00:00.000</HIREDATE><SAL></SAL><COMM></COMM><DEPTNO></DEPTNO></EMP>
<EMP><EMPNO>7902</EMPNO><ENAME>FORD</ENAME><JOB></JOB><MGR></MGR><HIREDATE>1981-12-03 00:00:00.000</HIREDATE><SAL></SAL><COMM></COMM><DEPTNO></DEPTNO></EMP>
<EMP><EMPNO>7934</EMPNO><ENAME>MILLER</ENAME><JOB></JOB><MGR></MGR><HIREDATE>1982-01-23 00:00:00.000</HIREDATE><SAL></SAL><COMM></COMM><DEPTNO></DEPTNO></EMP>

 --- ColumnNameList
 EMPNO ENAME HIREDATE
select EMPNO,ENAME,HIREDATE from EMP where empno >= ? order by EMPNO [7700]
select EMPNO,ENAME,HIREDATE from EMP where empno >= ? order by EMPNO
 --- Bean SELECTした結果をBEANに格納して返すIteratorによる実装。while句でhasNext、next関数にて出力はJson
{"EMP" : {"EMPNO" : "7782","ENAME" : "CLARK","HIREDATE" : "1981-06-09 00:00:00.0"}}
{"EMP" : {"EMPNO" : "7788","ENAME" : "SCOTT","HIREDATE" : "1987-04-19 00:00:00.0"}}
{"EMP" : {"EMPNO" : "7839","ENAME" : "KING","HIREDATE" : "1981-11-17 00:00:00.0"}}
{"EMP" : {"EMPNO" : "7844","ENAME" : "TURNER","HIREDATE" : "1981-09-08 00:00:00.0"}}
{"EMP" : {"EMPNO" : "7876","ENAME" : "ADAMS","HIREDATE" : "1987-05-23 00:00:00.0"}}
{"EMP" : {"EMPNO" : "7900","ENAME" : "JAMES","HIREDATE" : "1981-12-03 00:00:00.0"}}
{"EMP" : {"EMPNO" : "7902","ENAME" : "FORD","HIREDATE" : "1981-12-03 00:00:00.0"}}
{"EMP" : {"EMPNO" : "7934","ENAME" : "MILLER","HIREDATE" : "1982-01-23 00:00:00.0"}}
 --- ColumnNameList
 EMPNO ENAME HIREDATE
select EMPNO,ENAME,HIREDATE from EMP where empno >= ? order by EMPNO [7700]
select EMPNO,ENAME,HIREDATE from EMP where empno >= ? order by EMPNO
 --- Array SELECTした結果をArray[Any]に格納して返すIteratorによる実装。while句でhasNext、next関数にて出力はtoString
7782 CLARK 1981-06-09 00:00:00.0
7788 SCOTT 1987-04-19 00:00:00.0
7839 KING 1981-11-17 00:00:00.0
7844 TURNER 1981-09-08 00:00:00.0
7876 ADAMS 1987-05-23 00:00:00.0
7900 JAMES 1981-12-03 00:00:00.0
7902 FORD 1981-12-03 00:00:00.0
7934 MILLER 1982-01-23 00:00:00.0
 --- ColumnNameList
 EMPNO ENAME HIREDATE
 --- End Sql
End

Scala関数:漢数字 <-> アラビア数字へ変換

Posted 2011年7月27日 by
Tagged As: | Categories: Scala | No Comments

アラビア数字から漢数字に変換と漢数字からアラビア数字へ変換するオブジェクトを書いてみました。
日本の漢字文化ならではの関数です。

アラビア数字から漢数字に変換する関数は、for文を使わずに再帰呼び出しでScala風です。
Listは辞書を作るにはたいへん便利です。

 

 


package seedo
import scala.collection.mutable.HashMap
// アラビア数字 <-> 漢数字 変換
object Kansuji {
  // 変換規則
	val ji = List[(Char,Long,Char)](('〇',0L,'A'),('一',1L,'A'),('二',2L,'A')
	,('三',3L,'A'),('四',4L,'A'),('五',5L,'A'),('六',6L,'A'),('七',7L,'A')
	,('八',8L,'A'),('九',9L,'A'),('零',0L,'A'),('壱',1L,'A'),('弐',2L,'A')
	,('参',3L,'A'),('肆',4L,'A'),('伍',5L,'A'),('陸',6L,'A'),('質',7L,'A')
	,('捌',8L,'A'),('玖',9L,'A'),('零',0L,'A'),('壹',1L,'A'),('貳',2L,'A'),('參',3L,'A')
	,('0',0L,'A'),('1',1L,'A'),('2',2L,'A'),('3',3L,'A'),('4',4L,'A')
	,('5',5L,'A'),('6',6L,'A'),('7',7L,'A'),('8',8L,'A'),('9',9L,'A')
	,('0',0L,'A'),('1',1L,'A'),('2',2L,'A'),('3',3L,'A'),('4',4L,'A')
	,('5',5L,'A'),('6',6L,'A'),('7',7L,'A'),('8',8L,'A'),('9',9L,'A')
    // 桁
    ,('十',10L,'T'),('百',100L,'T'),('千',1000L,'T'),('拾',10L,'T'),('佰',100L,'T')
    ,('仟',1000L,'T'),('十',10L,'T'),('陌',100L,'T'),('阡',1000L,'T')
    // 桁
    ,('万',10000L,'M'),('億',100000000L,'M'),('兆',1000000000000L,'M')
    ,('萬',10000L,'M'),('京',10000000000000000L,'M')
    ,(',',0L,'U'),(',',0L,'U'))

    val dic = new HashMap[Char,(Char,Long,Char)]	// 漢数字の辞書
    ji.foreach(v => dic.put(v._1,v))
    val keta4 = Array("", "十", "百", "千")
    val keta = Array("", "万", "億", "兆", "京")
    val ketaOld4 = Array("", "拾", "佰", "仟")	// (旧漢字)
    val ketaOld = Array("", "萬", "億", "兆", "京") // (旧漢字)
    // 漢数字に変換
    def toKansuji (num:Long) : String = {
	  var result = ""
	  var back = ""
	  var tmp_div = 0	//整数除算
	  val su = toArabicKansuji(num).reverse
	  for(i <- 0 to su.length - 1){
	    val c = su.charAt(i)
		if(c != '〇') {
			val tmp_mod = i % 4	//余り
			if (tmp_mod == 0) {	//大きな桁
				tmp_div = (i + 1) / 4	//整数除算
				result = c + keta(tmp_div) + result
			} else {	//"十", "百", "千"の桁
			  if(c == '一'){		// 一を省略
				result = keta4(tmp_mod) + back + result
			  } else {
				result = c + keta4(tmp_mod) + back + result
			  }
			}
			back = ""
		  }else{
			if ((i % 4) == 0) {	//大きな桁
				tmp_div = (i + 1) / 4	//整数除算
				back = keta(tmp_div)
			}
		  }
	  }
	  result
	}
    // 漢数字(旧漢字)に変換
    def toKansujiOld (num:Long) : String = {
	  var result = ""
	  var back = ""
	  var tmp_div = 0	//整数除算
	  val su = toArabicKansujiOld(num).reverse
	  for(i <- 0 to su.length - 1){
	    val c = su.charAt(i)
		if(c != '〇') {
			val tmp_mod = i % 4	//余り
			if (tmp_mod == 0) {	//大きな桁
				tmp_div = (i + 1) / 4	//整数除算
				result =c + ketaOld(tmp_div) + result
			} else {	//"十", "百", "千"の桁
			  if(c == '壱'){	// 壱を省略
				result = ketaOld4(tmp_mod) + back + result
			  } else {
				result = c + ketaOld4(tmp_mod) + back + result
			  }
			}
			back = ""
		}else{
			if ((i % 4) == 0) {	//大きな桁
				tmp_div = (i + 1) / 4	//整数除算
				back = ketaOld(tmp_div)
			}
		}
	  }
	  result
	}
    // 整数をアラビア数字表記の漢数字に変換 再帰呼び出し
    def toArabicKansuji (num:Long) : String = {
      if(num == 0) return "零"
	  var number = (num % 10) match {
	    case 0 =>	"〇"
	    case 1 =>	"一"
	    case 2 =>	"二"
	    case 3 =>	"三"
	    case 4 =>	"四"
	    case 5 =>	"五"
	    case 6 =>	"六"
	    case 7 =>	"七"
	    case 8 =>	"八"
   	    case 9 =>	"九"
   	    case _ =>	""
	  }
	  if(num > 9L) return toArabicKansuji(num / 10) + number
	  number
	}
    // 整数をアラビア数字表記の漢数字(旧漢字)に変換 再帰呼び出し
    def toArabicKansujiOld (num:Long) : String = {
      if(num == 0) return "零"
	  var number = (num % 10) match {
	    case 0 =>	"〇"
	    case 1 =>	"壱"
	    case 2 =>	"弐"
	    case 3 =>	"参"
	    case 4 =>	"四"
	    case 5 =>	"伍"
	    case 6 =>	"六"
	    case 7 =>	"七"
	    case 8 =>	"八"
   	    case 9 =>	"九"
   	    case _ =>	""
	  }
	  if(num > 9L) return toArabicKansujiOld(num / 10) + number
	  number
	}
    // 整数(Int)をアラビア数字表記の漢数字に変換
    def toArabicKansuji (num:Int) : String = {
	  toArabicKansuji(num.toLong)
	}
    // 漢数字を整数に変換
    def toArabicNumerals (num:String) : Long = {
	  var result = 0L		// 変換結果
	  var resultArabic = 0L	// アラビア数字表記の漢数字の変換結果
	  var format = 0		// 0は未確定状態、アラビア数字表記の漢数字の場合は1、2は漢数字、3は万・億・兆・京と〇から九までの漢数字
	  var backSuji = -1L	// 1つ前の数字
	  var back4 = 0L		// 千・万・十の時のバッファ
	  var backKeta = 0L		// 万・億・兆・京の1つ前の桁
	  var backKeta4 = 0L	// 千・百・十の1つ前の桁
	  var four = 0L		// 〇から九までの漢数字が四桁以下の連続の場合のバッファ
	  var current = -1L 
	  for(i <- 0 to num.length - 1){
	    val c = num.charAt(i)
//	    println(" #" + i + " [" + c + "] format=" + format + " backSuji="+backSuji+ " back4="+back4+ " four="+ four + " result=" + result )
	    val r = dic.get(c) match {
	      case Some(f) => f
	      case _ => ('0',0L,'X')	// 漢数字を構成する文字列がない
	    }
	    current = r._2
	    r._3 match {
	      case 'A' => {
	    		resultArabic = current + (resultArabic * 10L)
	    		four = current + (four * 10L)
	    		if(format != 1 && four > 9999){
	    			return -8L	// 4桁以上は構文エラー
	    		}
	    		if(i > 0){
	    			if(format == 0 && backKeta == 0L){
	    				format = 1	// フォーマット確定
	    			}
	    		}
	    		backSuji =current // 一つ前として記録
	      }
	      case 'T' => {	// 千・百・十 漢数字の4桁バッファの処理
	    	  format = 2
	    	  if(backKeta4 != 0L && backKeta4 <= current){
	    		  return -5L	// 桁が1つ前の桁よりも大きいか同じなら構文エラー
	    	  }
	    	  if(backSuji > 0L){
	    		  back4 += backSuji * current // 千・百・十の前に数字がある
	    	  } else {
	    		  back4 += current // 千・百・十が単独
	    	  }
	          backKeta4 = current
	          backSuji = -1L	// 初期化
	          four = 0L		// 初期化 〇から九までの漢数字が四桁以下の連続の場合のバッファ
	      }
	      case 'M' => {	// 万・億・兆・京
	        if(backSuji == -1L && back4 == 0L){
	        	return -7L	// 1つ前が数字でないか、千・百・十でないときは構文エラー 
	        }
        	if(backKeta != 0L && backKeta <= current){
        	  return -4L	// 現在の桁が1つ前の桁よりも大きいか同じなら構文エラー
        	}
        	if(format == 2){
	        	if(backSuji > 0L){
		        	back4 += backSuji
	        	}
	        	result += (back4 * current)
        	} else if(four > 0L){
        		if(four > 9999L){	// 4桁以上なら構文エラー
        		  return -8L
        		}
	        	result += (four * current)
	        	format = 3	// 書式が確定
        	}
        	backKeta = current	// 一つ前の文字として保存
        	backKeta4 = 0L	// 初期化
	        backSuji = -1L	// 初期化
	        back4 = 0L		// 初期化
	        four = 0L		// 初期化 〇から九までの漢数字が四桁以下の連続の場合のバッファ
	      }
	      case 'U' =>
	      case _ => return -9L	// 漢数字ではない
	    }
	  }
	  if(format == 2){// 千・万・十の4桁後処理
		  if(backSuji > 0L)
			  back4 += backSuji
		  result += back4
	  } else {
	   	  result += four
	  }
	  if(format == 1)	// アラビア数字表記の漢数字の変換結果
	      return resultArabic
	  result
	}
}



下記は、Scala Testを使って上記オブジェクトをテストするクラスです。

最初のテストは、アラビア数字からアラビア数字風漢数字(一二三四五六〇〇)、
漢数字(千二百三十四万五千六百)、旧漢字(壱千弐百参拾四萬五千六百)を変換して
再びアラビア数字に変換するというものです。

次のテストは、Loopでアラビア数字を回して、アラビア数字から漢数字、漢数字から再びアラビア数字の戻してます。

最後のテストは、漢数字からアラビア数字を変換する関数で、正常系と異常系をテストケースにします。

テストケースのデータもListで定義すると便利。


package test
import org.scalatest.junit.JUnitSuite
import org.scalatest.junit.ShouldMatchersForJUnit
import org.junit.{Test,Before}
import seedo._

class testKansuji extends JUnitSuite with ShouldMatchersForJUnit{
	val test = List[(String,Long)](
       ("千二百三十四兆五千六百七十八億九千十二万三千四百五十六",1234567890123456L)
      ,("千百三十四兆千百十八億九千十二万三千四百五十六",1134111890123456L)
      ,("二億三千九十万十二",230900012L)
      ,("二億三千九千万十二",-5L)		// 桁が1つ前の桁よりも大きいか同じ
      ,("二億三千九百万八十",239000080L)
      ,("二億三千九十万十二兆",-4L)	// 億の単位の後に兆の単位がある
      ,("億三千九十万十二",-7L)		// 億の前に数字がない
      ,("一二三四五六七八九〇一二三",1234567890123L)
      ,("一,二三四,五六七,八九〇,一二三",1234567890123L)
      ,("一二三四五六七八九〇万一二三",-8L)		// 4桁以上なら構文エラー
      ,("一万二三四五六七八九〇一二三",-8L)		// 4桁以上なら構文エラー 
      ,("〇〇二三四五六七八九〇一二三",234567890123L)
      ,("〇万〇二三四五六七八九〇一二三",-8L)	// 4桁以上なら構文エラー 
      ,("万〇二三四五六七八九〇一二三",-7L)		// 万の前に数字がない
      ,("〇"		,0L)
      ,("壱"		,1L)
      ,("弐"		,2L)
      ,("参"		,3L)
      ,("拾"		,10L)
      ,("阡"		,1000L)
      ,("壱萬"		,10000L)
      ,("参億"		,300000000L)
      ,("参億四"		,300000004L)
      ,("参億四十三"	,300000043L)
      ,("672兆1,293億"	,672129300000000L)
      ,("92兆2,292億346",92229200000346L)
      ,("漢数字"		,-9L)	// 漢数字ではない
      ,("百日二左衛門",-9L)	// 漢数字ではない
    )
    val test2 = List[(Long,(String,String,String))](
     (10000L ,("一〇〇〇〇","一万","壱萬"))
     ,(100000000L ,("一〇〇〇〇〇〇〇〇","一億","壱億"))
     ,(100000L ,("一〇〇〇〇〇","十万","拾萬"))
     ,(1234567890L ,("一二三四五六七八九〇","十二億三千四百五十六万七千八百九十","拾弐億参仟四佰伍拾六萬七仟八佰九拾"))
    ,(1234567893L ,("一二三四五六七八九三","十二億三千四百五十六万七千八百九十三","拾弐億参仟四佰伍拾六萬七仟八佰九拾参"))
     ,(1111L ,("一一一一","千百十一","仟佰拾壱"))
     ,(11111111111111111L ,("一一一一一一一一一一一一一一一一一"
         ,"一京千百十一兆千百十一億千百十一万千百十一","壱京仟佰拾壱兆仟佰拾壱億仟佰拾壱萬仟佰拾壱"))
     ,(0L ,("零","零","零"))
     ,(-0L ,("零","零","零"))
    )

	@Before def initialize() {
	}
	@Test def toArabicKansuji() { //
		println("TEST toArabicKansuji")
		test2.foreach({v =>
			val sample = v._1
			println(sample)
			val resultvalue = Kansuji.toArabicKansuji(sample)
			val resultvalue2 = Kansuji.toKansuji(sample)
			val resultvalue3 = Kansuji.toKansujiOld(sample)
			println(v._2._1 + " -> [" + resultvalue + "]" + v._2._1.length + "/" + resultvalue.length)
			v._2._1 should be (resultvalue)
			sample should be (Kansuji.toArabicNumerals(v._2._1))
			println(v._2._2 + " -> [" + resultvalue2 + "]" + v._2._2.length + "/" + resultvalue2.length)
			v._2._2 should be (resultvalue2)
			sample should be (Kansuji.toArabicNumerals(v._2._2))
			println(v._2._3 + " -> [" + resultvalue3 + "]" + v._2._3.length + "/" + resultvalue3.length)
			v._2._3 should be (resultvalue3)
			sample should be (Kansuji.toArabicNumerals(v._2._3))
			println
		})
	}
	@Test def toArabicKansujiLoop() { //
		println("TEST toArabicKansuji Loop Start " + new java.util.Date)
		var i:Long = 0L
		var num,num2,num3 = 0L
		var up = 1L
		var ch = 100000L
		while(i < 999999999999999999L) {
			val resultvalue = Kansuji.toArabicKansuji(i)
			val resultvalue2 = Kansuji.toKansuji(i)
			val resultvalue3 = Kansuji.toKansujiOld(i)
			num = Kansuji.toArabicNumerals(resultvalue)
			i should be (num)
			num2 = Kansuji.toArabicNumerals(resultvalue2)
			if(i != num2)
			  println("TEST toArabicKansuji " + i + " <> " + num2 + " [" +resultvalue2 + "]")
			i should be (num2)
			num3 = Kansuji.toArabicNumerals(resultvalue3)
			if(i != num3)
			  println("TEST toArabicKansuji " + i + " <> " + num3 + " [" +resultvalue3 + "]")
			i should be (num3)
			if((i % ch) == 0) {
				println("TEST toArabicKansuji " + i + " " + new java.util.Date)
				up *= 10L
				ch *= 10L
			}
			i += up
		}
		println("TEST toArabicKansuji End " + new java.util.Date)
	}
	@Test def toArabicNumerals() { //
		println("TEST toArabicNumerals")
		test.foreach({v =>
			val resultvalue = Kansuji.toArabicNumerals(v._1)
			val sa = resultvalue - v._2
			println(v + " -> " + resultvalue  + " sa=" + sa)
			sa should be (0)
		})
		println
	}

}

scalaでXML属性の追加・置換

Posted 2011年7月22日 by
Tagged As: | Categories: Scala | No Comments

scalaでXMLタグの置換を行って、次にXMLタグの属性を追加したり、置換するプログラムです。

import scala.xml._
import scala.xml.transform._

import scala.xml._
import scala.xml.transform._

class ReplacementAttributeRewriteRule(x:String) extends RewriteRule {
	override def transform(node: Node): Seq[Node] = node match {
       case Elem(prefix, "question", attribs, scope, content@_*)  => 
Elem(prefix, "question", attribs append Attribute(None, "defaultdata", Text(x), scala.xml.Null) , scope, content:_*)
       case other => other
	}
}

処理前

<question defaultdata="漢字">質問その1</question>
<question>質問その2</question>

関数呼び出しの引数に「英語」をセットして呼び出すと

処理後

<question defaultdata="英語">質問その1</question>
<question defaultdata="英語">質問その2</question>

scalaでXMLタグの置換を行う

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

スマートフォン向けに会話形式の入力フォームを開発するためにscalaでプログラムを書き始めました。XMLで会話内容を定義して会話画面を作る仕様です。

ブラウザにAjaxにてXMLで定義した会話データを送信するめに、
XMLの特定のタグを置換しようと、scala標準ライブラリのRewriteRuleとRuleTransformerを使ってプログラムを書いてみた。

Blog「devneko is not cat.」のコードを参考にしてRewriteRuleに置換ルールをクラスを定義し、RuleTransformerでRewriteRuleに基づいて置換処理するもの。

package sc
import scala.xml._
import scala.xml.transform._

class ReplacementTagRewriteRule(x:String) extends RewriteRule {
	override def transform(node:Node): Seq[Node] = node match {
		case e @ Elem(_,"msg",_,_,_*) =>  <msg>{x}</msg>
		case other => other
	}
}

置換したいXMLは、<msg>タグの内容を <retmsg>タグの内容に置換したXMLを生成します。

package sc
import scala.xml._
import scala.xml.transform._
object testSA {
  def main(args: Array[String]): Unit = {
    val defXml = <question id="START">
			<msg>あなたのことを知りたいの</msg>
			<choise>
				<item result="yes" value="いいよ"/>
				<item result="no" value="教えない"/>
				<item result="why" value="どうして?"/>
			</choise>
			<ans hantei="match">
				<select result="yes" next="2">はい,いいよ,OK,YES</select>
				<select result="no" next="exit">教えない,いや,いいえ,NG,NO</select>
				<select result="other" next="again">
					<retmsg>「はい」か「いいえ」で答えてください。</retmsg>
				</select>
				<select result="why" next="again">どうして?
					<retmsg>プレゼントを送りたいから</retmsg>
				</select>
			</ans>
		</question>
    	val retmsg = defXml \ "ans" \ "select" \ "retmsg"
    	var message:String = null
    	for(msg <- retmsg){message = msg.text}
    	var node = new RuleTransformer(new ReplacementTagRewriteRule(message))(defXml)
    	println(node)
  }
}

実行すると、<msg>タグの内容が「プレゼントを送りたいから」に置換されます。

 <question id="START">
			<msg>;プレゼントを送りたいから</msg>
			<choise>
				<item result="yes" value="いいよ"/>
				<item result="no" value="教えない"/>
				<item result="why" value="どうして?"/>
			</choise>
			<ans hantei="match">
				<select result="yes" next="2">はい,いいよ,OK,YES</select>
				<select result="no" next="exit">教えない,いや,いいえ,NG,NO</select>
				<select result="other" next="again">
					<retmsg>「はい」か「いいえ」で答えてください。</retmsg>
				</select>
				<select result="why" next="again">どうして?
					<retmsg>プレゼントを送りたいから</retmsg>
				</select>
			</ans>
		</question>