node-riner 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +82 -97
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -7,15 +7,13 @@ import { execSync } from 'node:child_process'
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Ride クラス
|
|
10
|
-
* - XML をパースしてページごとの HTML を生成し、簡易サーバで配信する機能を備える。
|
|
11
|
-
* - 実行時に XML 内の `{...}` を runs.js に書き出して node で評価する仕組みを使用している(既存の挙動を維持)。
|
|
12
10
|
*/
|
|
13
11
|
export default class Ride {
|
|
14
|
-
F: string
|
|
15
|
-
parsedJson: any
|
|
16
|
-
pages: Record<string, string>
|
|
17
|
-
name: string
|
|
18
|
-
defaultinfo: string
|
|
12
|
+
F: string
|
|
13
|
+
parsedJson: any
|
|
14
|
+
pages: Record<string, string>
|
|
15
|
+
name: string
|
|
16
|
+
defaultinfo: string
|
|
19
17
|
|
|
20
18
|
constructor(name: string) {
|
|
21
19
|
this.F = ""
|
|
@@ -26,13 +24,60 @@ export default class Ride {
|
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
|
-
*
|
|
30
|
-
* -
|
|
31
|
-
* -
|
|
32
|
-
* -
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
* 値を再帰的に HTML に変換するユーティリティ
|
|
28
|
+
* - 文字列: `{...}` を評価して置換(既存挙動を維持)
|
|
29
|
+
* - 配列: 各要素を <item> でラップして連結
|
|
30
|
+
* - オブジェクト: 各キーをタグ名として再帰的にレンダリング
|
|
31
|
+
*/
|
|
32
|
+
private renderValue(value: any): string {
|
|
33
|
+
// null / undefined
|
|
34
|
+
if (value === null || value === undefined) return ''
|
|
35
|
+
|
|
36
|
+
// 文字列処理({...} の評価を含む)
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
// 複数マッチにも対応
|
|
39
|
+
const matches = value.match(/{.*?}/g)
|
|
40
|
+
if (matches) {
|
|
41
|
+
let newValue = value
|
|
42
|
+
matches.forEach(m => {
|
|
43
|
+
const code = m.slice(1, -1) // 中身を取り出す
|
|
44
|
+
try {
|
|
45
|
+
// 既存の実装を踏襲して runs.js に書き出して node で実行
|
|
46
|
+
writeFileSync("./runs.js", code)
|
|
47
|
+
const out = execSync("node runs.js").toString()
|
|
48
|
+
newValue = newValue.replace(m, out)
|
|
49
|
+
} catch (e) {
|
|
50
|
+
// 評価失敗時は空文字に置換(または元の {..} のままにする選択も可能)
|
|
51
|
+
newValue = newValue.replace(m, '')
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
return newValue
|
|
55
|
+
}
|
|
56
|
+
return value
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 配列: 各要素を再帰的に処理して <item> でラップ
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
return value.map((item: any) => `<item>${this.renderValue(item)}</item>`).join('')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// オブジェクト: キーごとにタグ化して再帰処理
|
|
65
|
+
if (typeof value === 'object') {
|
|
66
|
+
return Object.keys(value).map(key => {
|
|
67
|
+
// fast-xml-parser の場合、テキストノードはキーが '#text' や '_text' の可能性があるので考慮
|
|
68
|
+
if (key === '#text' || key === '_text') {
|
|
69
|
+
return this.renderValue(value[key])
|
|
70
|
+
}
|
|
71
|
+
return `<${key}>${this.renderValue(value[key])}</${key}>`
|
|
72
|
+
}).join('')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// その他(数値や真偽値など)はそのまま文字列化
|
|
76
|
+
return String(value)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* XML を解析して pages に HTML を格納
|
|
36
81
|
*/
|
|
37
82
|
returnhtml(): string {
|
|
38
83
|
const options = { ignoreAttributes: false }
|
|
@@ -41,109 +86,52 @@ export default class Ride {
|
|
|
41
86
|
try {
|
|
42
87
|
this.parsedJson = parser.parse(this.F)
|
|
43
88
|
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (typeof value === "string" && typeof value.match === "function") {
|
|
56
|
-
const match = value.match(/{.*}/g)
|
|
57
|
-
if (match) {
|
|
58
|
-
let matched: string = match[0]
|
|
59
|
-
matched = matched.replace("{", "").replace("}", "")
|
|
60
|
-
// runs.js に書き出して node で実行(既存の実装を保持)
|
|
61
|
-
writeFileSync("./runs.js", matched)
|
|
62
|
-
const out = execSync("node runs.js").toString()
|
|
63
|
-
value = value.replace(/{.*}/, out)
|
|
64
|
-
// 更新しておく
|
|
65
|
-
page['Content'][tags] = value
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// オブジェクト(ネストされたタグ群) -> タグでラップして出力
|
|
70
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
71
|
-
const inner = Object.keys(value).map(key => {
|
|
72
|
-
return `<${key}>${value[key]}</${key}>`
|
|
73
|
-
}).join('')
|
|
74
|
-
const gene = `<${tags}>${inner}</${tags}>`
|
|
75
|
-
return gene
|
|
76
|
-
}
|
|
77
|
-
// 配列 -> item タグで列挙
|
|
78
|
-
else if (Array.isArray(value)) {
|
|
79
|
-
const gene = `<${tags}>` + value.map((item: any) => {
|
|
80
|
-
return `<item>${item}</item>`
|
|
81
|
-
}).join('') + `</${tags}>`
|
|
82
|
-
return gene
|
|
83
|
-
}
|
|
84
|
-
// 文字列等 -> 単純にタグでラップ
|
|
85
|
-
else {
|
|
86
|
-
return `<${tags}>${value}</${tags}>`
|
|
87
|
-
}
|
|
88
|
-
}).join('')
|
|
89
|
-
|
|
90
|
-
// ページを格納(タイトルを付与)
|
|
91
|
-
this.pages[route] = `<title>${this.name}</title>` + generatedhtml
|
|
92
|
-
});
|
|
93
|
-
} else {
|
|
94
|
-
// Page が単一オブジェクトの場合の処理
|
|
95
|
-
const page = this.parsedJson["Root"]["Page"]
|
|
96
|
-
const route = page['@_route']
|
|
97
|
-
|
|
98
|
-
const generatedhtml = Object.keys(page['Content']).map(tags => {
|
|
99
|
-
let value = page['Content'][tags]
|
|
100
|
-
|
|
101
|
-
if (typeof value === "string" && typeof value.match === "function") {
|
|
102
|
-
const match = value.match(/{.*}/g)
|
|
103
|
-
if (match) {
|
|
104
|
-
let matched: string = match[0]
|
|
105
|
-
matched = matched.replace("{", "").replace("}", "")
|
|
106
|
-
writeFileSync("./runs.js", matched)
|
|
107
|
-
const out = execSync("node runs.js").toString()
|
|
108
|
-
value = value.replace(/{.*}/, out)
|
|
109
|
-
page['Content'][tags] = value
|
|
110
|
-
}
|
|
111
|
-
}
|
|
89
|
+
const root = this.parsedJson?.Root
|
|
90
|
+
if (!root) return ""
|
|
91
|
+
|
|
92
|
+
const pages = root["Page"]
|
|
93
|
+
if (!pages) return ""
|
|
94
|
+
|
|
95
|
+
// Page が配列か単一かを吸収して配列化
|
|
96
|
+
const pageArray = Array.isArray(pages) ? pages : [pages]
|
|
97
|
+
|
|
98
|
+
pageArray.forEach((page: any) => {
|
|
99
|
+
const route = page['@_route'] || '/'
|
|
112
100
|
|
|
113
|
-
|
|
101
|
+
// Content が存在しない場合は空にする
|
|
102
|
+
const content = page['Content'] || {}
|
|
103
|
+
|
|
104
|
+
// 各タグを再帰的にレンダリングして結合
|
|
105
|
+
const generatedhtml = Object.keys(content).map(tag => {
|
|
106
|
+
const value = content[tag]
|
|
107
|
+
const inner = this.renderValue(value)
|
|
108
|
+
return `<${tag}>${inner}</${tag}>`
|
|
114
109
|
}).join('')
|
|
115
110
|
|
|
116
|
-
//
|
|
111
|
+
// ページを格納(タイトルを付与)
|
|
117
112
|
this.pages[route] = `<title>${this.name}</title>` + generatedhtml
|
|
118
|
-
}
|
|
113
|
+
})
|
|
119
114
|
} catch (e) {
|
|
120
|
-
// パースエラーなどの簡易ログ
|
|
121
115
|
console.error("Whoops, an error occurred during analysis : " + e)
|
|
122
116
|
}
|
|
123
117
|
|
|
124
|
-
// 現状の戻り値は未使用のため空文字を返す(将来は生成 HTML を返す等に変更可能)
|
|
125
118
|
return ""
|
|
126
119
|
}
|
|
127
120
|
|
|
128
121
|
/**
|
|
129
|
-
* Ride メソッド(ファイル読み込みと HTML
|
|
130
|
-
* @param name ファイル名(相対パス)
|
|
122
|
+
* Ride メソッド(ファイル読み込みと HTML 生成)
|
|
131
123
|
*/
|
|
132
124
|
Ride(name: string) {
|
|
133
|
-
// __dirname から上に遡ってファイルを読んでいる既存の記述を維持
|
|
134
125
|
this.F = readFileSync(path.join(__dirname, "../../", name), { encoding: "utf-8"})
|
|
135
126
|
this.returnhtml()
|
|
136
127
|
}
|
|
137
128
|
|
|
138
129
|
/**
|
|
139
|
-
*
|
|
140
|
-
* @param port 起動ポート
|
|
141
|
-
* @returns express アプリインスタンス(テスト用途などで返す)
|
|
130
|
+
* 簡易サーバを起動
|
|
142
131
|
*/
|
|
143
132
|
serve(port: number) {
|
|
144
133
|
const app = exp();
|
|
145
134
|
|
|
146
|
-
// pages のキーをルートとして登録
|
|
147
135
|
Object.keys(this.pages).forEach(route => {
|
|
148
136
|
app.get(route, (req, res) => {
|
|
149
137
|
res.send(this.pages[route])
|
|
@@ -157,9 +145,6 @@ export default class Ride {
|
|
|
157
145
|
return app
|
|
158
146
|
}
|
|
159
147
|
|
|
160
|
-
/**
|
|
161
|
-
* 設定用(未実装)
|
|
162
|
-
*/
|
|
163
148
|
config(options: object) {
|
|
164
149
|
// 将来の拡張ポイント
|
|
165
150
|
}
|