見出しが抽出できない原因を探っていると、ini設定ファイルの読み込みで見出しを指定しない設定になっていた。更に原因を探ると、getBoolでpropから設定を読み込んだときに文字列ではなく数字と判定されていたので常にfalseと判定されていた。なのでstringに変換してから判定するように変更した。
const getBool = (key) => props.get(key) === "1"; const getBool \= (key) => String(props.get(key)) \=== "1";
You:
iniを読み込んだときにconsole.log(coverPage)がfalseとなる
CoverPage: '1',
import { propertiesReader } from 'properties-reader';
const propFileName = "./AozoraEpub3.ini";
props = propertiesReader({ sourceFile: propFileName });
//ini取得ユーティリティ関数
const getString = (key, def = null) =>
props.get(key) ?? def;
const getBool = (key) =>
props.get(key) === "1";
const getInt = (key, def = 0) => {
const v = parseInt(props.get(key));
return isNaN(v) ? def : v;
};
const coverPage = getBool("CoverPage");// 表紙追加
console.log(coverPage)
ChatGPT:
原因は properties-reader が返す値の型です。
props.get(key) は 文字列とは限らず、数値として返る場合があります。
あなたのコードでは
JavaScript const getBool \= (key) => props.get(key) \=== "1";
となっているため、
CoverPage: '1'
が 数値 1 として取得された場合
1 === "1" → false
になってしまいます。
解決方法
方法①(おすすめ:文字列化)
JavaScript const getBool \= (key) => String(props.get(key)) \=== "1";
方法②(数値比較)
JavaScript const getBool \= (key) => props.get(key) \== 1;
ただし == は型変換が入るので①の方が安全です。
方法③(AozoraEpub3互換っぽくする)
AozoraEpub3のiniは 1/0 なのでこうするのが一番安全です。
JavaScript const getBool \= (key) => Number(props.get(key)) \=== 1;
デバッグ確認
一度これを出してみると原因がはっきりします。
JavaScript
console.log(props.get("CoverPage"), typeof props.get("CoverPage"));
例
1 'number'
ついでに安全なユーティリティ
AozoraEpub3移植ならこれが一番安定します。
JavaScript
const getBool \= (key, def \= false) => {
const v \= props.get(key);
if (v \=== undefined || v \=== null) return def;
return Number(v) \=== 1;
};
もしよければ
AozoraEpub3.ini 全互換の IniUtils.js(Java版完全再現)も作れます。
実は properties-reader を使うと いくつか罠があります。
You:
JSに書き換えて public class BookInfo { private boolean isPattern(int num) { ChapterLineInfo chapterLineInfo = this.getChapterLineInfo(num); if (chapterLineInfo == null) return false; return chapterLineInfo.isPattern(); } }
ChatGPT:
Javaのコードは
Java
public class BookInfo
{
private boolean isPattern(int num)
{
ChapterLineInfo chapterLineInfo \= this.getChapterLineInfo(num);
if (chapterLineInfo \== null) return false;
return chapterLineInfo.isPattern();
}
}
なので、ほぼそのまま JavaScript(ES module)に変換できます。
JavaScript
export default class BookInfo {
isPattern(num) {
const chapterLineInfo \= this.getChapterLineInfo(num);
if (chapterLineInfo \== null) return false;
return chapterLineInfo.isPattern();
}
}
さらにJSっぽく簡潔に書く場合
同じ意味ですが短くできます。
JavaScript
export default class BookInfo {
isPattern(num) {
const info \= this.getChapterLineInfo(num);
return info ? info.isPattern() : false;
}
}
Optional chainingを使う場合(Node.jsならこれも可)
JavaScript
export default class BookInfo {
isPattern(num) {
return this.getChapterLineInfo(num)?.isPattern() ?? false;
}
}
もし AozoraEpub3の BookInfo.java をJS完全移植しているなら、
ChapterLineInfo 周りで Java → JS移植時によく起きるバグ箇所がいくつかあります。
(特に Map<Integer, ChapterLineInfo> → JSの配列/Map変換)
必要なら AozoraEpub3の BookInfo.js 安全移植版も出します。
You:
TypeError: chapterLineInfo.isPattern is not a function
ChatGPT:
このエラー
TypeError: chapterLineInfo.isPattern is not a function
は chapterLineInfo が ChapterLineInfo クラスのインスタンスではないときに起きます。
つまり isPattern() メソッドが存在しないオブジェクトです。
Java → JS移植で 一番よくある原因はこの2つです。
原因①:ただのObjectになっている
例えばこういうデータになっている場合
JavaScript
{
pattern: true
}
これは ChapterLineInfoクラスではなく普通のObjectなので
chapterLineInfo.isPattern()
は存在しません。
解決方法①(Javaに近い)
ChapterLineInfo クラスを作る
JavaScript
export default class ChapterLineInfo {
constructor(pattern \= false) {
this.pattern \= pattern;
}
isPattern() {
return this.pattern;
}
}
そして生成時に
JavaScript new ChapterLineInfo(true)
にする必要があります。
原因②(AozoraEpub3移植で一番多い)
Java版
Java Map<Integer, ChapterLineInfo\>
JS移植で
JavaScript
{}
や
JavaScript \[\]
にすると インスタンスが崩れます。
例
JavaScript
this.chapterLineInfo\[num\] \= {
pattern: true
};
これだと ただのObjectです。
解決方法②(安全)
JavaScript this.chapterLineInfo\[num\] \= new ChapterLineInfo(true);
応急処置(動作だけ合わせる)
もしデータ構造を変えたくないなら
JavaScript
isPattern(num) {
const info \= this.getChapterLineInfo(num);
return info?.pattern ?? false;
}
デバッグすると原因がすぐ分かる
これを出してください。
JavaScript const info \= this.getChapterLineInfo(num); console.log(info); console.log(typeof info); console.log(info.constructor.name);
もし
Object
なら クラスが壊れています。
💡 AozoraEpub3移植の場合かなり高確率でこれです
Java
ArrayList<ChapterLineInfo>
↓
JS
\[\]
↓
push時に new を付け忘れる
もしよければ
AozoraEpub3の ChapterLineInfo / BookInfo をJS完全互換で書いた版を出します。
実は この2クラスはJava→JS移植でバグ率トップです。
You:
JSに書き換えて public class Epub3Writer { /** 章を追加 */ public void addChapter(String chapterId, String name, int chapterLevel) { SectionInfo sectionInfo = this.sectionInfos.lastElement(); this.chapterInfos.add(new ChapterInfo(sectionInfo.sectionId, chapterId, name, chapterLevel)); } }
ChatGPT:
Javaコード
Java
public class Epub3Writer
{
/\*\* 章を追加 \*/
public void addChapter(String chapterId, String name, int chapterLevel)
{
SectionInfo sectionInfo \= this.sectionInfos.lastElement();
this.chapterInfos.add(new ChapterInfo(sectionInfo.sectionId, chapterId, name, chapterLevel));
}
}
を JavaScript(ES module)に移植するとこうなります。
JavaScript
export default class Epub3Writer {
/\*\* 章を追加 \*/
addChapter(chapterId, name, chapterLevel) {
const sectionInfo \= this.sectionInfos\[this.sectionInfos.length \- 1\];
this.chapterInfos.push(
new ChapterInfo(sectionInfo.sectionId, chapterId, name, chapterLevel)
);
}
}
変換ポイント
1️⃣ lastElement()
Java
Java this.sectionInfos.lastElement();
JS
JavaScript this.sectionInfos\[this.sectionInfos.length \- 1\]
2️⃣ add()
Java (ArrayList)
Java this.chapterInfos.add(...)
JS (Array)
JavaScript this.chapterInfos.push(...)
少し安全に書く場合(おすすめ)
Java版は 空配列チェックなしですが
JSだと落ちやすいので入れておくと安全です。
JavaScript
addChapter(chapterId, name, chapterLevel) {
if (!this.sectionInfos.length) return;
const sectionInfo \= this.sectionInfos\[this.sectionInfos.length \- 1\];
this.chapterInfos.push(
new ChapterInfo(sectionInfo.sectionId, chapterId, name, chapterLevel)
);
}
AozoraEpub3移植でよくある注意点
sectionInfos はJavaでは
Java Vector<SectionInfo\>
ですがJSでは普通
JavaScript this.sectionInfos \= \[\];
になります。
もし lastElement() をそのまま移植して
JavaScript this.sectionInfos.lastElement()
と書くと undefinedエラーになります。
もし希望あれば
**AozoraEpub3の Epub3Writer.java → JS完全移植版(約2500行)**で
VectorEnumerationBufferedWriterZipOutputStream
の 安全なJS置換パターンもまとめられます。
これをやると 移植バグがかなり減ります。
You:
ReferenceError: ejs:33 31|
32| <% } -%> >> 33| <% if (subcreator!= undefined) { -%> 34|
35| <% } -%> 36| subcreator is not defined
ChatGPT:
このエラー
subcreator is not defined
は EJSテンプレートに subcreator という変数が渡されていないときに発生します。
問題の行:
ejs
<% if (subcreator!= undefined) { -%>
EJSでは 未定義の変数を直接参照すると ReferenceError になります。
解決方法
方法①(最も安全)
typeof を使う
ejs
<% if (typeof subcreator !== "undefined") { -%>
<br/>
<% } -%>
typeof は 未定義でもエラーになりません。
方法②(推奨:AozoraEpub3移植ならこれ)
JS側で 必ず変数を渡す。
JavaScript
ejs.render(template, {
creator,
subcreator: bookInfo.subCreator ?? null
});
するとテンプレートは
ejs
<% if (subcreator) { -%>
<br/>
<% } -%>
で安全に動きます。
方法③(短い書き方)
ejs
<% if (locals.subcreator) { -%>
<br/>
<% } -%>
EJSでは locals に全変数が入っています。
AozoraEpub3移植でよくある原因
Java版はVelocityなので
velocity #if($subcreator)
は 未定義でもエラーになりません。
しかし EJSは未定義変数でエラーになります。
一番Java互換に近い書き方
ejs
<% if (typeof subcreator !== "undefined" && subcreator) { -%>
💡 もし今 Velocity → EJS を全部変換しているなら
**AozoraEpub3で必ずエラーになるテンプレート箇所(約12か所)**を一覧にできます。
実は 今の subcreator もその中の1つです。
You:
クラスでsubcreatorをnull宣言するのはどう
ChatGPT:
結論から言うと クラスで subcreator = null を宣言するのは良い方法です。
むしろ EJSを使うなら一番安定します。
方法(推奨)
JSクラスで初期化します。
JavaScript
class BookInfo {
constructor() {
this.creator \= null;
this.subcreator \= null;
}
}
そしてEJSに渡すとき
JavaScript ejs.render(template, bookInfo);
するとテンプレート側は安全に書けます。
ejs
<% if (subcreator) { -%>
<br/>
<% } -%>
subcreator が
null undefined ""
どれでも false扱いになります。
この方法のメリット
EJS側をシンプルにできる
NG(エラーの原因)
ejs
<% if (typeof subcreator !== "undefined") { %>
OK
ejs
<% if (subcreator) { %>
AozoraEpub3移植的にも正しい
Java版の BookInfo は実質こういう構造です
Java String creator; String subCreator;
Javaは 未初期化でも null になるので
JSでも null初期化するのが一番Java互換です。
さらに安全な書き方(おすすめ)
JavaScript
class BookInfo {
creator \= null;
subcreator \= null;
}
最近のNode.jsなら class field が使えます。
もう一つの重要ポイント
EJSに渡すときは 必ずオブジェクトに含める
JavaScript
ejs.render(template, {
creator: bookInfo.creator,
subcreator: bookInfo.subcreator
});
または
JavaScript ejs.render(template, bookInfo);
💡 AozoraEpub3のVelocity→EJS移植では
次の変数も 全部 null 初期化しておくとエラー防げます
title creator subcreator series publisher language
もし希望があれば
AozoraEpub3用 BookInfo.js(Java完全互換フィールド付き)
を出します。
実は 未定義変数でEJSが落ちる問題を全部防ぐ版があります。
You:
見つからない場合にnullを入れるようにして let line = bookInfo.getTitleText(); if (line) this.ejsData.TITLE = converter.convertTitleLineToEpub3(line); line = bookInfo.getSubTitleText(); if (line) this.ejsData.SUBTITLE = converter.convertTitleLineToEpub3(line); line = bookInfo.getOrgTitleText(); if (line) this.ejsData.ORGTITLE = converter.convertTitleLineToEpub3(line); line = bookInfo.getSubOrgTitleText(); if (line) this.ejsData.SUBORGTITLE = converter.convertTitleLineToEpub3(line); line = bookInfo.getCreatorText(); if (line) this.ejsData.CREATOR = converter.convertTitleLineToEpub3(line); line = bookInfo.getSubCreatorText(); if (line) this.ejsData.SUBCREATOR = converter.convertTitleLineToEpub3(line); line = bookInfo.getSeriesText(); if (line) this.ejsData.SERIES = converter.convertTitleLineToEpub3(line); line = bookInfo.getPublisherText(); if (line) this.ejsData.PUBLISHER = converter.convertTitleLineToEpub3(line);
ChatGPT:
「見つからない場合に null を入れる」なら、
if を使うより 三項演算子 or || null を使うときれいに書けます。
一番シンプルな書き方:
JavaScript let line \= bookInfo.getTitleText(); this.ejsData.TITLE \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getSubTitleText(); this.ejsData.SUBTITLE \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getOrgTitleText(); this.ejsData.ORGTITLE \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getSubOrgTitleText(); this.ejsData.SUBORGTITLE \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getCreatorText(); this.ejsData.CREATOR \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getSubCreatorText(); this.ejsData.SUBCREATOR \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getSeriesText(); this.ejsData.SERIES \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getPublisherText(); this.ejsData.PUBLISHER \= line ? converter.convertTitleLineToEpub3(line) : null;
もっとJSらしく安全に書く(おすすめ)
undefined や "" もまとめて処理できます。
JavaScript let line; line \= bookInfo.getTitleText(); this.ejsData.TITLE \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getSubTitleText(); this.ejsData.SUBTITLE \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getOrgTitleText(); this.ejsData.ORGTITLE \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getSubOrgTitleText(); this.ejsData.SUBORGTITLE \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getCreatorText(); this.ejsData.CREATOR \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getSubCreatorText(); this.ejsData.SUBCREATOR \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getSeriesText(); this.ejsData.SERIES \= line ? converter.convertTitleLineToEpub3(line) : null; line \= bookInfo.getPublisherText(); this.ejsData.PUBLISHER \= line ? converter.convertTitleLineToEpub3(line) : null;
さらにAozoraEpub3移植向けにきれいに書く(おすすめ)
同じ処理なので 関数化できます。
JavaScript
const setTitle \= (key, getter) => {
const line \= getter.call(bookInfo);
this.ejsData\[key\] \= line ? converter.convertTitleLineToEpub3(line) : null;
};
setTitle("TITLE", bookInfo.getTitleText);
setTitle("SUBTITLE", bookInfo.getSubTitleText);
setTitle("ORGTITLE", bookInfo.getOrgTitleText);
setTitle("SUBORGTITLE", bookInfo.getSubOrgTitleText);
setTitle("CREATOR", bookInfo.getCreatorText);
setTitle("SUBCREATOR", bookInfo.getSubCreatorText);
setTitle("SERIES", bookInfo.getSeriesText);
setTitle("PUBLISHER", bookInfo.getPublisherText);
メリット
コード量 1/3
バグが減る
Java版の構造を維持
💡 AozoraEpub3移植でさらに重要なこと
EJSテンプレートではこう書けます
ejs
<% if (SUBCREATOR) { %>
null にしておけば 未定義エラーが絶対出ません。
もし希望があれば
**AozoraEpub3の Epub3Writer.js を Java構造そのままでJS最適化した版(バグ少ない版)**も出せます。
実は今の部分は AozoraEpub3移植でよく整理する箇所です。