概要
GoFのデザインパターンのInterpreterパターンについて。
任意の文法規則をクラスで表します。
汎用言語を使用して、特定分野の用途に絞ったミニ言語(DSL)を作成する際などに利用される。
問題領域に近く・処理効率がよく・保守性の高いプログラムの作成が可能となる。
登場人物
AbstractExpression = 命令
TerminalExpression = 具象命令(終端)
NonterminalExpression = 具象命令(終端以外)
Context = 状況、文脈
Client = 利用者
実装サンプル
サンプル概要
以下のルールで記述されたプレーンテキストをもとに
HTML5のセクション構造に対応したWebページを生成する。
今回は再帰構造はなしにします。
・WebページはHeadとBodyを持ちます。Bodyはセクション要素を0個からn個持つことが出来ます。 ・Headにはエンコードの設定、ページのタイトル、CSSのインクルードを持ちます。 ・エンコード名、ページのタイトル、CSSのインクルード設定は任意のテキストで持ちます。 ・セクション要素はSection,Article,Nav,Header,Footerのどれかです。 ・Sectionタグであらわされる1つのセクションは開始タグ、属性、Sectionの中身。閉じタグを持ちます。 属性は属性名と属性値のセットからなり、0個からn個持つことが出来ます。 Sectionの中身は任意のテキストおよびHTMLのタグを0個からN個持ちます。 ・テキストは任意の文字列です ・html-tagは任意のタグです。 ・Article、Aside、Nav、Header、FooterはSectionと同じ構造です
▼出力ファイルのBNF定義
<html5-section-page> ::= "<!DOCTYPE HTML><html><page-head><body>" [<section-factor>{<section-factor>}] "</body></html>" <page-head> ::= "<head>" <encode-type> <page-title> <css-include> "</head>" <encode-type> ::= <meta charset=\"" <text> "\" />" <css-include> ::= <link rel=\"stylesheet\" type=\"text/css\" href=\"" <text> ".css\" /> <page-title> ::= "<title>" <text> "</title>" <section-factor> ::= <section> | <article> | <nav> | <aside> | <header> | <footer> <section> ::= "<section" [<section-attribute>{attribute}] ">" <section-contents> "</section>" <section-attribute> ::= " " <section-attribute-name> "=\"" <section-attribute-value> "\"" <section-contents> ::= <text> | <html-tag> <article> ::= "<article" [<article-attribute>{attribute}] ">" <article-contents> "</article>" <article-attribute> ::= " " <article-attribute-name> "=\"" <article-attribute-value> "\"" <nav> ::= "<nav" [<nav-attribute>{attribute}] ">" <nav-contents> "</nav>" <nav-attribute> ::= " " <nav-attribute-name> "=\"" <nav-attribute-value> "\"" <nav-contents> ::= <text> | <html-tag> <aside> ::= "<aside" [<aside-attribute>{attribute}] ">" <aside-contents> "</aside>" <aside-attribute> ::= " " <aside-attribute-name> "=\"" <aside-attribute-value> "\"" <aside-contents> ::= <text> | <html-tag> <header> ::= "<header" [<header-attribute>{attribute}] ">" <header-contents> "</header>" <header-attribute> ::= " " <header-attribute-name> "=\"" <header-attribute-value> "\"" <header-contents> ::= <text> | <html-tag> <footer> ::= "<footer" [<footer-attribute>{attribute}] ">" <footer-contents> "</footer>" <footer-attribute> ::= " " <footer-attribute-name> "=\"" <footer-attribute-value> "\"" <footer-contents> ::= <text> | <html-tag> <text> :== 任意の文字列 <html-tag> :== 任意のHTMLタグ
上記のHTML5のWebページを生成するにあたって、以下のミニ言語を定義します。
▼ミニ言語のBNF定義
<html5-section-page> ::= <encode-type> <page-title> <css-directory> <css-filename> [<section-factor>{<section-factor>}] <encode-type> ::= "et=" <text> "\n" <page-title> ::= "pt=" <text> "\n" <css-directory> ::= "cd=" <text> ",cf=" <text> "\n" <section-factor> ::= "sf=" <section-factor-type>" [<section-factor-attribute>{<section-factor-attribute>}] [", sfc=" <text> {", sfc=" <text>}] <section-factor-type> ::= "section" | "article" | "nav" | "aside" | "header" | "footer" <section-factor-attribute> ::= ",sfa=" <attribute-name> "<<" <attribute-value> "|"{<attribute-name> "<<" <attribute-value> "|"} <attribute-name> ::= 任意の属性名 <attribute-value> ::= 任意の属性値 <section-factor-contents> ::= "sfc=" <text> | "{" <section-factor> "}" | <html-tag> <text> :== 任意の文字列
登場人物
AbstractExpression = Html5GeneratorExpression : 命令
NonterminalExpression = Html5EncodeTypeExpression : エンコード設定。具象命令(終端以外)
NonterminalExpression = Html5HeadStartExpression : Headタグの開始。具象命令(終端以外)
NonterminalExpression = Html5HeadEndExpression : Headタグの終了。具象命令(終端以外)
NonterminalExpression = Html5BodyStartExpression : Bodyタグの開始。具象命令(終端以外)
NonterminalExpression = Html5BodyEndExpression : Bodyタグの終了。具象命令(終端以外)
NonterminalExpression = Html5PageTitleExpression : タイトル。具象命令(終端以外)
NonterminalExpression = Html5CssExpression : CSSファイル名とファイルパス。具象命令(終端以外)
NonterminalExpression = Html5SectionFactorExpression : セクション要素のタイプ、名前、値、コンテンツ(再帰可能)。具象命令(終端以外)
XXX = Html5Parser : 各命令を解析
Context = Html5Context : 状況、文脈。命令全体の保持
Client = main : 利用者
サンプルコード
Html5GeneratorExpression
# encoding: Shift_JIS =begin rdoc = Html5GeneratorExpressionクラス =end class Html5GeneratorExpression NOT_OVERRIDE = 'not override error' attr_accessor :expression def initialize(expression) @expression = expression end def parse() raise NOT_OVERRIDE end end
Html5EncodeTypeExpression
# encoding: Shift_JIS require_relative './html5_generator_expression' =begin rdoc = Html5EncodeTypeExpressionクラス =end class Html5EncodeTypeExpression < Html5GeneratorExpression ENCODE_VALUE_INDEX = 1 def parse() return "\t\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=#{@expression.split('=')[ENCODE_VALUE_INDEX]}\" />" end end
Html5HeadStartExpression
# encoding: Shift_JIS require_relative './html5_generator_expression' =begin rdoc = Html5HeadStartExpressionクラス =end class Html5HeadStartExpression < Html5GeneratorExpression def parse() return "\t<head>" end end
Html5HeadEndExpression
# encoding: Shift_JIS require_relative './html5_generator_expression' =begin rdoc = Html5HeadEndExpressionクラス =end class Html5HeadEndExpression < Html5GeneratorExpression def parse() return "\t<\/head>" end end
Html5BodyStartExpression
# encoding: Shift_JIS require_relative './html5_generator_expression' =begin rdoc = Html5BodyStartExpressionクラス =end class Html5BodyStartExpression < Html5GeneratorExpression def parse() return "\t<body>" end end
Html5BodyEndExpression
# encoding: Shift_JIS require_relative './html5_generator_expression' =begin rdoc = Html5BodyEndExpressionクラス =end class Html5BodyEndExpression < Html5GeneratorExpression def parse() return "\t<\/body>" end end
Html5PageTitleExpression
# encoding: Shift_JIS require_relative './html5_generator_expression' =begin rdoc = Html5PageTitleExpressionクラス =end class Html5PageTitleExpression < Html5GeneratorExpression TITLE_VALUE_INDEX = 1 def parse() return "\t\t<title>#{@expression.split('=')[TITLE_VALUE_INDEX]}</title>" end end
Html5CssExpression
# encoding: Shift_JIS require_relative './html5_generator_expression' =begin rdoc = Html5CssExpressionクラス =end class Html5CssExpression < Html5GeneratorExpression CSS_FOLDER = 0 CSS_FILENAME = 1 CSS_VALUE_INDEX = 1 def parse() css_attribute_list = @expression.split(',') css_folder = css_attribute_list[CSS_FOLDER].split('=')[CSS_VALUE_INDEX] css_filename = css_attribute_list[CSS_FILENAME].split('=')[CSS_VALUE_INDEX] return "\t\t<link rel=\"stylesheet\" type=\"text/css\" href=\"#{css_folder}#{css_filename}.css\" />" end end
Html5SectionFactorExpression
# encoding: Shift_JIS require_relative './html5_generator_expression' =begin rdoc = Html5SECTION_FACTORExpressionクラス =end class Html5SectionFactorExpression < Html5GeneratorExpression SECTION_FACTOR_TYPE = 0 SECTION_FACTOR_ATTRIBUTE = 1 SECTION_FACTOR_CONTENTS = 2 SECTION_FACTOR_NAME_INDEX = 0 SECTION_FACTOR_VALUE_INDEX = 1 def parse() section_factor_attribute_list = @expression.split(',') section_factor_type = section_factor_attribute_list[SECTION_FACTOR_TYPE].split('=')[SECTION_FACTOR_VALUE_INDEX] section_factor_attribute_lists = section_factor_attribute_list[SECTION_FACTOR_ATTRIBUTE].split('=')[SECTION_FACTOR_VALUE_INDEX] section_factor_contents = section_factor_attribute_list[SECTION_FACTOR_CONTENTS].split('=')[SECTION_FACTOR_VALUE_INDEX] html="" html << "\t\t<#{section_factor_type}" section_factor_attribute_lists.split('|').each {|section_factor_attribute_list| attribute = section_factor_attribute_list.split('<<') html << " #{attribute[SECTION_FACTOR_NAME_INDEX]}='#{attribute[SECTION_FACTOR_VALUE_INDEX]}'" } html << ">" html << section_factor_contents html << "</#{section_factor_type}>" return html end end
Html5Parser
# encoding: Shift_JIS require_relative './html5_generator_expression' require_relative './html5_encode_type_expression' require_relative './html5_page_title_expression' require_relative './html5_head_start_expression' require_relative './html5_head_end_expression' require_relative './html5_body_start_expression' require_relative './html5_body_end_expression' require_relative './html5_css_expression' require_relative './html5_section_factor_expression' =begin rdoc = Html5Parserクラス =end class Html5Parser attr_accessor :expression_list # headタグ開始 HS="hs" # headタグ終了 HE="he" # エンコードタイプ ET="et" # ページタイトル PT="pt" # CSSディレクトリ CD="cd" # Bodyタグ開始 BS="bs" # Bodyタグ終了 BE="be" # セクション要素 SF="sf" def initialize(expressions) @expression_list=Array.new expressions.split("\n").each {|expression| case expression.split('=')[0] when HS @expression_list.push Html5HeadStartExpression.new(expression) when ET @expression_list.push Html5EncodeTypeExpression.new(expression) when PT expression_list.push Html5PageTitleExpression.new(expression) when CD expression_list.push Html5CssExpression.new(expression) when HE @expression_list.push Html5HeadEndExpression.new(expression) when BS @expression_list.push Html5BodyStartExpression.new(expression) when SF expression_list.push Html5SectionFactorExpression.new(expression) when BE @expression_list.push Html5BodyEndExpression.new(expression) end } end def parse() html= << "EOS" <!DOCTYPE HTML> <html lang="ja-JP"> EOS @expression_list.each {|expression| html << "\t\t" html << expression.parse html << "\n" } html << '</html>' return html end end
Html5Context
# encoding: Shift_JIS require_relative './html5_parser' =begin rdoc = Html5Contextクラス =end class Html5Context attr_accessor :expressions def initialize(expressions) @expressions = expressions end def parse() html5_parser = Html5Parser.new(@expressions) return html5_parser.parse end end
main
# encoding: Shift_JIS require_relative './html5_context' input =<<"EOS" hs et=UTF-8 pt=hello html5 cd=./,cf=hello_html5 he bs sf=section,sfa=id<<section1|class<<section,sfc=section_contents1<br />section_contents2 sf=article,sfa=id<<article1|class<<article,sfc=article_contents sf=nav,sfa=id<<nav1|class<<nav,sfc=nav_contents sf=aside,sfa=id<<aside1|class<<aside,sfc=aside_contents sf=header,sfa=id<<header1|class<<header,sfc=header_contents sf=footer,sfa=id<<footer1|class<<footer,sfc=footer_contents be EOS html5_context = Html5Context.new(input) html = html5_context.parse puts html
section.section { background-color:skyblue; } article.article { background-color:yellow; } nav.nav { background-color:green; } aside.aside { background-color:gray; } header.header { background-color:#115533; } footer.footer { background-color:#118844; }
出力結果HTML
<!DOCTYPE HTML> <html lang="ja-JP"> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> <title>hello html5</title> <link rel="stylesheet" type="text/css" href="./hello_html5.css" /> </head> <body> <section id='section1' class='section'>section_contents1<br />section_contents2</section> <article id='article1' class='article'>article_contents</article> <nav id='nav1' class='nav'>nav_contents</nav> <aside id='aside1' class='aside'>aside_contents</aside> <header id='header1' class='header'>header_contents</header> <footer id='footer1' class='footer'>footer_contents</footer> </body> </html>