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.
Files changed (2) hide show
  1. package/index.ts +82 -97
  2. 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 // 読み込んだ XML ファイルの内容(生の文字列)
15
- parsedJson: any // fast-xml-parser によるパース結果(可変な構造のため any)
16
- pages: Record<string, string> // ルートパス => 生成した HTML のマップ
17
- name: string // サイト/アプリケーション名(<title> に利用)
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
- * XML を解析して pages に HTML を格納する。
30
- * - fast-xml-parser を使って XML を JSON に変換
31
- * - Page が配列か単一かで処理を分岐
32
- * - Content の各タグに対して文字列・配列・オブジェクトに応じて HTML を生成
33
- * - `{...}` の中身は一時ファイル runs.js に書き出し node で実行、その出力で置換する(元の実装に合わせた動作)
34
- *
35
- * 注意: 現状は評価のために runs.js を書き込んで node で実行します。外部入力を評価すると危険です。
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
- // Root.Page が配列かどうかで分岐
45
- if (Array.isArray(this.parsedJson["Root"]["Page"])) {
46
- this.parsedJson["Root"]["Page"].forEach((page: any) => {
47
- const route = page['@_route']
48
-
49
- // Content の各 tag を HTML に変換
50
- const generatedhtml = Object.keys(page['Content']).map(tags => {
51
- // タグの値
52
- let value = page['Content'][tags]
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
- return `<${tags}>${value}</${tags}>`
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
- * 簡易サーバを起動し、生成済み pages をルーティングする
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-riner",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",