状態遷移表をPlantUMLスクリプトに変換する

Excel上に記述した状態遷移表をF#プログラムでPlantUMLスクリプトに変換する。

コンテキスト

f:id:borsalino:20161010084845p:plain

@startuml
object "電源スイッチ" as obj1 {
  状態={オフ,オン}
}
object "強弱スイッチ" as obj2 {
  状態={弱,強}
}
object "制御" as obj3
object "ファン" as obj4{
  状態={弱回転,強回転}
}
obj1 --> obj3 : 操作:{オフ,オン}
obj2 --> obj3 : 操作:{弱,強}
obj3 --> obj4 : 操作:{弱回転,強回転}
@enduml

状態遷移表

f:id:borsalino:20161010161329p:plain

PlantUML

@startuml
S0:スイッチ:オフ
S0:レベル:弱
S0:ファン:停止
S1:スイッチ:オフ
S1:レベル:強
S1:ファン:停止
S2:スイッチ:オン
S2:レベル:弱
S2:ファン:弱回転
S3:スイッチ:オン
S3:レベル:強
S3:ファン:強回転
[*]-->S0
'S0-->×:オフ
S0-->S2:オン
'S0-->×:弱
S0-->S1:強
'S1-->×:オフ
S1-->S3:オン
S1-->S0:弱
'S1-->×:強
S2-->S0:オフ
'S2-->×:オン
'S2-->×:弱
S2-->S3:強
S3-->S1:オフ
'S3-->×:オン
S3-->S2:弱
'S3-->×:強
@enduml

状態遷移図

f:id:borsalino:20161005213159p:plain

変換ツール

open Microsoft.Office.Interop.Excel
open System.Runtime.InteropServices

type CellValue = string
type CellToValue = System.Object -> CellValue
type ColumnsToValues = Range -> CellValue[]
type RowsToValues = Range -> CellValue[]
type MatrixToValues = Range -> CellValue[][]

type State = CellValue
type Variable = CellValue
type Value = CellValue
type Action = CellValue
type Trans = CellValue

type StateExpPart = string
type TransExpPart = string
type StateExp = string
type InitTransExp = string
type TransExp = string
type Script = string 

type ToStateExpPart = State*Variable*Value -> StateExpPart
type ToTransExpPart = State*Action*Trans -> TransExpPart
type ToStateExp = State[]*Variable[]*Value[] -> StateExp
type ToTransExp = State[]*Action[]*Trans[] -> TransExp
type ToScript = StateExp*InitTransExp*TransExp -> Script

let CreateRange (cell:Range, rows:int, columns:int) =
  cell.Address() + ":" + cell.Offset(rows-1,columns-1).Address()
let CellToValue (cell:System.Object):CellValue = (cell:?>Range).Value2:?>CellValue
let ColumnsToValues (range:Range) =
  [|for i in 1..range.Columns.Count do yield (range.[1,i] |> CellToValue)|]
let RowsToValues (range:Range):CellValue[] =
  [|for i in 1..range.Rows.Count do yield (range.[i,1] |> CellToValue)|]
let MatrixToValues (range:Range):CellValue[][] =
  [|for i in 1..range.Rows.Count do 
      yield [|for j in 1..range.Columns.Count do 
                yield (range.[i,j] |> CellToValue)|]|]
                                
let ToStateExpPart (state,variable,value):StateExpPart =
  sprintf"%s:%s:%s" state variable value
let ToTransExpPart (state,action,trans):TransExpPart =
  let exp = sprintf"%s-->%s:%s" state trans action
  match trans with
  |"×" -> "'" + exp
  |_ -> exp
let ToStateExp ((states:string[]), (variables:string[]), (values:string[][])):StateExp =
  [|for i in 0..states.Length-1 do
      for j in 0..variables.Length-1 do
        yield ((states.[i],variables.[j],values.[i].[j])|>ToStateExpPart)|] 
    |> String.concat "\n"
let ToTransExp ((states:string[]),(actions:string[]),(transitions:string[][])):TransExp=
  [|for i in 0..states.Length-1 do
      for j in 0..actions.Length-1 do
        yield ((states.[i],actions.[j],transitions.[i].[j])|>ToTransExpPart)|] 
    |> String.concat "\n"
let ToScript (stateExp, transExp):Script =
  [|"@startuml";stateExp;"[*]-->S0";transExp;"@enduml"|]
    |> String.concat "\n"

let excel = ApplicationClass(Visible = false)
let dir = System.IO.Directory.GetCurrentDirectory() 
let workbooks = excel.Workbooks.Open(dir + @"\sample.xlsx")
let worksheet = workbooks.Sheets.["Sheet1"] :?> Worksheet

let StateRange = worksheet.Range("A2:A5") // .Range("State")
let VariableRange = worksheet.Range("B1:D1") // .Range("Variable")
let ActionRange = worksheet.Range("E1:H1") // .Range("Action")
let ValueRange =
  (VariableRange.[2,1]:?>Range,
   StateRange.Rows.Count,
   VariableRange.Columns.Count) |> CreateRange
let TransitionsRange =
  (ActionRange.[2,1]:?>Range,
   StateRange.Rows.Count,
   ActionRange.Columns.Count) |> CreateRange
    
let states = StateRange |> RowsToValues
let variables = VariableRange |> ColumnsToValues
let actions = ActionRange |> ColumnsToValues
let values = worksheet.Range(ValueRange) |> MatrixToValues
let transitions = worksheet.Range(TransitionsRange) |> MatrixToValues

let script =
  ((states,variables,values) |> ToStateExp,
   (states,actions,transitions) |> ToTransExp) 
      |> ToScript

excel.Quit()
excel |> Marshal.ReleaseComObject |> ignore
  
System.IO.File.WriteAllText("plantuml.txt",script)