2007年7月28日土曜日

(X)HTMLコンバータ (7) スクリプトの変換

HSP3のスクリプトを解析して(X)HTMLへ変換するスクリプト。これでバージョン1.0とします。
このスクリプト内で説明されていない変数などもありますが、変換の流れ程度はわかっていただけるかもしれません。

命令やラベルや数値などスペースを含まないものはstrmidで一気に解析してしまい、コメントや文字列などスペースを含むことができるものはpeekで1バイトずつ(オートマトンのように)変換しています。#uselib "kernel32.dll"
    #cfunc global IsDBCSLeadByteEx "IsDBCSLeadByteEx" sptr, sptr

#const global CP_ACP      0
#const global EXPAND_SIZE 1024

#const global CR   0x0D
#const global LF   0x0A
#const global CRLF 0x0A0D

#enum STATE_NORMAL = 1
#enum STATE_COMMENT
#enum STATE_MULTILINE_COMMENT
#enum STATE_STRINGS
#enum STATE_MULTILINE_STRINGS

#module
#deffunc setConvertMode int i
    convertMode = i
    return

#deffunc addString str s, local l
    l = strlen( s )
    if ( rTextSize@ <= rTextPos@ + l ) {
        // バッファをオーバーしてしまうので、拡張する
        rTextSize@ += EXPAND_SIZE
        memExpand rText@, rTextSize@
    }
    poke rText@, rTextPos@, s
    rTextPos@ += l
    return

// 単独タグ
#defcfunc _aloneTag str tagName, str className
    if ( convertMode == REPLACEMODE_XHTML ) {
        if ( className == "" ) {
            return "<" + tagName + " />"
        } else {
            return "<" + tagName + " class=\"" + className + "\" />"
        }
    } else {
        if ( className == "" ) {
            return "<" + tagName + ">"
        } else {
            return "<" + tagName + " class=\"" + className + "\">"
        }
    }

// 終了タグ
#defcfunc endTag str tagName
    return "</" + tagName + ">"

// 開始タグ
#defcfunc _startTag str tagName, str className, str idName, local s
    if ( idName == "" ) {
        s = ""
    } else {
        s = " id=\"" + idName + "\""
    }
    if ( className == "" ) {
        return "<" + tagName + s + ">"
    } else {
        return "<" + tagName + " class=\"" + className + "\"" + s + ">"
    }

// 文字がスペースかどうか(キーワードとして利用できない文字かどうか)調べる
#defcfunc is_space int iTarget, local iResult
    if IsDBCSLeadByteEx( CP_ACP, iTarget )     : return 0
    if ( '0' <= iTarget ) & ( iTarget <= '9' ) : return 0
    if ( 'a' <= iTarget ) & ( iTarget <= 'z' ) : return 0
    if ( 'A' <= iTarget ) & ( iTarget <= 'Z' ) : return 0
    if ( iTarget == '_' ) : return 0
    return 1

// 文字が数字かどうか調べる
#defcfunc is_number int iTarget
    return ( '0' <= iTarget ) & ( iTarget <= '9' )

#global
#define ctype aloneTag( %1, %2 = "" ) _aloneTag( %1, %2 )
#define ctype startTag( %1, %2 = "", %3 = "" ) _startTag( %1, %2, %3 )

*convertText
    setConvertMode replaceMode
    gosub *loadDictionary       // 変換用データベースの準備
    gosub *beforeConvert        // 変換の準備
    gosub *convert              // 変換の実行
    gosub *afterConvert         // 変換の後処理
    gosub *deleteDictionary     // データベースの削除
    return

// 変換の準備
*beforeConvert
    textSize = strlentext )
    rTextSize = textSize
    sdim rText, rTextSize       // ReplacedText

    if ( sandwichMode == 1 ) & ( sandwichTag != "" ) {
        if ( addIdMode ) {
            // コマンドライン(ファイル名が格納されているはず)から
            // ファイル名を取り出してidとする
            rText = startTag( sandwichTag, sandwichClass, getpathdir_cmdline, 8 ) )
        } else {
            rText = startTag( sandwichTag, sandwichClass )
        }
    }
    rTextPos = strlen( rText )

    x = 0
    firstCharOfArea = 1    // ラベルの判定に使用
    state = STATE_NORMAL

    return

// 変換の実行
*convert
    repeat
        textPos = cnt
        c = peektext, textPos )
        switch c
        case 0
            break
            swbreak
        case CR : case LF
            // 改行
            gosub *startNewLine
            if ( strmidtext, textPos, 2 ) == "\n" ) {
                continue textPos + 2
            }
            swbreak
        case '/'
            if ( strmidtext, textPos, 2 ) == "/*" ) & ( state == STATE_NORMAL ) {
                // 複数行コメント開始
                gosub *startMultiLineComment
                addString "/*" : x += 2
                continue textPos + 2
            }
            if ( strmidtext, textPos, 2 ) == "//" ) & ( state == STATE_NORMAL ) {
                // 単一行コメント開始
                gosub *startSingleLineComment
                addString "//" : x += 2
                continue textPos + 2
            }
            addString "/" : x++
            swbreak
        case '*'
            if ( firstCharOfArea ) & ( state == STATE_NORMAL ) {
                gosub *firstCharFinished
                gosub *addLabel
                continue textPos
            }
            if ( strmidtext, textPos, 2 ) == "*/" ) & ( state == STATE_MULTILINE_COMMENT ) {
                // 複数行コメントの終了
                addString "*/" : x += 2
                gosub *endMultiLineComment
                continue textPos + 2
            }
            addString "*" : x++
            swbreak
        case ';'
            if ( state == STATE_NORMAL ) {
                // 単一行コメント開始
                gosub *startSingleLineComment
            }
            addString ";" : x++
            swbreak
        case '{'
            if ( strmidtext, textPos, 2 ) == "{\"" ) & ( state == STATE_NORMAL ) {
                // 複数行文字列の開始
                gosub *startMultiLineStrings
                addString "{\"" : x += 2
                continue textPos + 2
            }
            addString "{" : x++
            swbreak
        case '"'
if ( state == STATE_NORMAL ) {
// 単一行文字列開始
gosub *startSingleLineStrings
addString "
\"" : x++
                continue
            }
            if ( state == STATE_STRINGS ) {
                // 単一行文字列終了
                addString "\"" : x++
                gosub *endSingleLineStrings
                continue
            }
            if ( strmidtext, textPos, 2 ) == "\"}" ) & ( state == STATE_MULTILINE_STRINGS ) {
                addString "\"}"
                gosub *endMultiLineStrings
                continue textPos + 2
            }
            addString "\"" : x++
            swbreak
        case '\t'
            if ( replaceTabs ) & ( state != STATE_STRINGS ) & ( state != STATE_MULTILINE_STRINGS ) {
                // タブをスペースへ変換する
                if replaceSpaces {
                    s = "&nbsp;"
                } else {
                    s = " "
                }
                repeat tabWidth - ( x \ tabWidth )
                    addString s : x++
                loop
            } else {
                addString "\t" : x += tabWidth - ( x \ tabWidth )
            }
            swbreak
        case ' '
            if replaceSpaces {
                // 半角スペースを変換(文字列中・コメント中でも実行)
                addString "&nbsp;"
            } else {
                addString " "
            }
            x++
            swbreak
        case ':'
            // 文の区切れ
            addString ":" : x++
            if ( state == STATE_NORMAL ) : gosub *endLine
            swbreak
        case ',' : case '='
            // パラメータの区切れ
            addString strf"%c", c ) : x++
            if ( state == STATE_NORMAL ) : gosub *endArea
            swbreak
        case '\\'
            if ( state == STATE_STRINGS ) | ( state == STATE_MULTILINE_STRINGS ) {
                if ( strmidtext, textPos, 2 ) == "\\\\" ) | ( strmidtext, textPos, 2 ) == "\\\"" ) {
                    addString strmidtext, textPos, 2 ) : x += 2
                    continue textPos + 2
                }
            }
            addString "\\" : x++
            swbreak
        case '&' : addString "&amp;"  : x++ : swbreak
        case '<' : addString "&lt;"   : x++ : swbreak
        case '>' : addString "&gt;"   : x++ : swbreak
        default
            // 命令・関数などキーワードの可能性
            if IsDBCSLeadByteEx( CP_ACP, c ) {
                // 2バイト文字の場合

                // 現在のバージョンでは変数をマークアップしないので、
                // このように扱ってもOK。
                addString strmidtext, textPos, 2 ) : x += 2
                continue textPos + 2
            }
            // 1バイト文字の場合
            if ( state == STATE_NORMAL ) {
                // 文字列中やコメント中でなければ、数値や命令・関数としてマークアップできるかどうか調べる
                if ( is_number( c ) ) | ( c == '%' ) | ( c == '$' ) {
                    // 数値の挿入
                    gosub *addNumber
                    continue textPos
                }
                if ( is_space( c ) == 0 ) | ( c == '#' ) {
                    // 命令・プリプロセッサ・関数etc.の挿入
                    gosub *addWord
                    continue textPos
                }
            }
            // 命令などを構成する文字列ではなかった場合(@,|,(,)など)
            gosub *firstCharFinished
            addString strf"%c", c ) : x++
            swbreak
        swend
    loop
    return

// *****************************************************************

// 変換終了後処理
*afterConvert
    if ( sandwichMode == 1 ) & ( sandwichTag != "" ) {
        addString endTag( sandwichTag )
    }
    addString "\n"

    text = rText
    sdim rText, 4

    return

// 変数の処理
*firstCharFinished
    // 空白でない文字を取り出した場合
    firstCharOfArea = 0
    return

*endLine
    // (マルチステートメントを含む論理的な)行の終了に伴う変数の変化
*endArea
    // パラメータの終了に伴う変数の変化
    firstCharOfArea = 1
    return

// *****************************************************************

// 単行コメントの開始
*startSingleLineComment
    state = STATE_COMMENT
    if markupComments {
        addString startTag( "span", CLASSNAME_COMMENT )
    }
    return

// コメント行を終了
*endSingleLineComment
    state = STATE_NORMAL
    if ( markupComments ) {
        addString endTag( "span" )
    }
    return

// 複数行コメントの開始
*startMultiLineComment
    state = STATE_MULTILINE_COMMENT
    if ( markupComments ) {
        addString startTag( "span", CLASSNAME_COMMENT )
    }
    return

// 複数行コメントの終了
*endMultiLineComment
    state = STATE_NORMAL
    if ( markupComments ) {
        addString endTag( "span" )
    }
    return

// 単行文字列の開始
*startSingleLineStrings
    state = STATE_STRINGS
    if ( markupStrings ) {
        addString startTag( "span", CLASSNAME_STRING )
    }
    return

// 単行文字列の終了
*endSingleLineStrings
    state = STATE_NORMAL
    if ( markupStrings ) {
        addString endTag( "span" )
    }
    return

// 複数行文字列の開始
*startMultiLineStrings
    state = STATE_MULTILINE_STRINGS
    if ( markupStrings ) {
        addString startTag( "span", CLASSNAME_STRING )
    }
    return

// 複数行文字列の終了
*endMultiLineStrings
    state = STATE_NORMAL
    if ( markupStrings ) {
        addString endTag( "span" )
    }
    return

// *****************************************************************

// textPosの位置以降をラベルとして挿入
*addLabel
    lLabel = textSize - textPos
    repeat textSize - textPos - 1, textPos + 1
        if ( textSize <= cnt ) {
            lLabel = textSize - textPos
            break
        }
        j = peektextcnt )
        if ( IsDBCSLeadByteEx( CP_ACP, j ) ) : continue cnt + 2
        if ( cnt == textPos + 1 ) & ( is_number( j ) ) {
            lLabel = cnt - textPos
            break
        }
        if is_space( j ) {
            lLabel = cnt - textPos
            break
        }
    loop
    if ( markupLabels ) & ( 1 < lLabel ) {
        addString startTag( "span", CLASSNAME_LABEL )
    }
    addString strmidtext, textPos, lLabel )
    textPos += lLabel : x += lLabel
    if ( markupLabels ) & ( 1 < lLabel ) {
        addString endTag( "span" )
    }
    return

// textPosの位置以降を数字として挿入
*addNumber
    if ( markupNumbers ) {
        addString startTag( "span", CLASSNAME_NUMBER )
    }
    repeat -1, 1
        j = peektext, textPos + cnt )
        if is_number( j ) | ( j == '.' ) {
            continue
        }
        if ( cnt == 1 ) : if ( peektext, textPos ) == '0' ) & (( j == 'x' ) | ( j == 'b' )) {
            continue
        }
        if ( j == 'e' ) | ( j == 'E' ) {
            if ( textPos + cnt + 1 < textSize ) {
                n = peektext, textPos + cnt + 1 )
                if ( n == '+' ) | ( n == '-' ) : continue cnt + 2
            }
            continue
        }
        lNumber = cnt
        break
    loop
    addString strmidtext, textPos, lNumber )
    textPos += lNumber : x += lNumber
    if ( markupNumbers ) {
        addString endTag( "span" )
    }
    return

// 命令・関数・システム変数など
*addWord
    repeat -1, 1
        if textPos + cnt >= textSize {
            dcKey = strmidtext, textPos, cnt )
            break
        }
        if is_space( peektext, textPos + cnt ) ) {
            dcKey = strmidtext, textPos, cnt )
            break
        }
    loop

    dc -> "Exists" dcKey
    if ( vret != 0 ) {
        // 辞書に載っている文字列は適切なタグで囲む
        dcVal = dc( "Item", dcKey )
        if ( dcVal == DCVAL_COMMAND ) & ( markupCommands == 1 ) {
            addString startTag( "span", CLASSNAME_COMMAND )
        }
        if ( dcVal == DCVAL_FUNCTION ) & ( markupFunctions == 1 ) {
            addString startTag( "span", CLASSNAME_FUNCTION )
        }
        if ( dcVal == DCVAL_SYSVAL ) & ( markupSysvals == 1 ) {
            addString startTag( "span", CLASSNAME_SYSVAL )
        }
        if ( dcVal == DCVAL_PREPROCESSOR ) & ( markupPreprocessors == 1 ) {
            addString startTag( "span", CLASSNAME_PREPROCESSOR )
        }
        if ( dcVal == DCVAL_MACRO ) & ( markupMacros == 1 ) {
            addString startTag( "span", CLASSNAME_MACRO )
        }
        addString dcKey
        if (( dcVal == DCVAL_COMMAND ) & ( markupCommands == 1 )) | (( dcVal == DCVAL_FUNCTION ) & ( markupFunctions == 1 )) {
            addString endTag( "span" )
        }
        if (( dcVal == DCVAL_SYSVAL ) & ( markupSysvals == 1 )) | (( dcVal == DCVAL_PREPROCESSOR ) & ( markupPreprocessors == 1 )) {
            addString endTag( "span" )
        }
        if (( dcVal == DCVAL_MACRO ) & ( markupMacros == 1 )) {
            addString endTag( "span" )
        }
    } else {
        // 辞書に載っていない文字列はそのまま
        addString dcKey
    }
    x += strlen( dcKey )
    gosub *firstCharFinished
    if ( dcVal == DCVAL_COMMAND ) : gosub *endArea
    textPos += strlen( dcKey )
    return

// *****************************************************************

// 新しい行の開始(マルチステートメントを除く、物理的に新しい行)
*startNewLine
    if ( state == STATE_COMMENT ) : gosub *endSingleLineComment
;   if ( state == STATE_STRINGS ) : state == STATE_NORMAL // 本来は必要ないはず

    if addBrTags {
        addString aloneTag( "br" )
    }
    addString "\n" : x = 0
    gosub *endLine
    return

// *****************************************************************

// 辞書の読み込み
*loadDictionary
    newcom dc, "Scripting.Dictionary"
    comres vret
    dc( "compareMode" ) = 1   // 大文字小文字を区別しない
    dcKey = 0

    dcList = DCFILE_FUNCTIONS, DCFILE_SYSVALS, DCFILE_COMMANDS, DCFILE_PREPROCESSORS, DCFILE_MACROS
    dcVal  = DCVAL_FUNCTION,   DCVAL_SYSVAL,   DCVAL_COMMAND,   DCVAL_PREPROCESSOR,   DCVAL_MACRO

    chdir dir_source + "/" + SAVE_FOLDER
    notesel note
    repeat length( dcList )
        exist dcList( cnt )
        if ( strsize < 0 ) : continue
        noteload dcList( cnt )
        _cnt = cnt
        repeat notemax
            noteget s, cnt
            if ( s != "" ) {
                dc -> "Add" s, dcVal( _cnt )
            }
        loop
    loop
    sdim note, 4
    return

// 辞書の削除
*deleteDictionary
    delcom dc
    return


ラベルや変数をローワーキャメルケースで書いてきたのですが、やはりHSPユーザはアンダースコア区切りが多いようなのでそちらに変えようかな、と思っています。

2 件のコメント:

Unknown さんのコメント...

 あのー,なんか,実行しようとしてもエラーがでて実行できないのですが,どうしたらよろしいのでしょうか。
「#Error 6 in line 506 (???)
-->パラメータの型が違います」

eller さんのコメント...

確かに動かないですね。スクリプトの一部を切り出す際に失敗していたようです。失礼しました。

他の個所に問題はないと思いますので、該当部分だけご自分で改良していただくのが手っ取り早いと思います。