画像情報が空だった場合に止まるのを修正する
[ERROR] (65) エラーが発生しました : Cannot invoke "String.replaceFirst(String, String)" because "srcImageFileName" is null [ERROR] 書籍の情報が取得できませんでした
画像のalt属性を追加したときの処理の問題。
You:
[ERROR] (65)
エラーが発生しました : Cannot invoke "String.replaceFirst(String, String)" because "srcImageFileName" is null
[ERROR] 書籍の情報が取得できませんでした
<img src=""/>
画像情報がからだった場合にnullが返される
public String correctExt(String srcImageFileName) {
if (this.hasImage(srcImageFileName)) return srcImageFileName;
//拡張子修正
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".png");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".jpg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".jpeg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".gif");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".webp");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".PNG");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".JPG");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".JPEG");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".GIF");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".WEBP");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Png");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Jpg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Jpeg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Gif");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Webp");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
return null;
}
public void addImageAlt(String fileName, String altText) {
imageAltMap.put(correctExt(fileName), altText);
}
if (lowerChukiTag.startsWith("<img")) {
//src=の値抽出
String imageFileName = this.getTagAttr(chukiTag, "src");
if (imageFileName != null) {
imageInfoReader.addImageFileName(imageFileName);//画像がなければそのまま追加
if (bookInfo.firstImageLineNum == -1) {
//小さい画像は無視
ImageInfo imageInfo = imageInfoReader.getImageInfo(imageInfoReader.correctExt(imageFileName));
if (imageInfo != null && imageInfo.getWidth() > 64 && imageInfo.getHeight() > 64) {
bookInfo.firstImageLineNum = lineNum;
bookInfo.firstImageIdx = imageInfoReader.countImageFileNames()-1;
}
}
}
String altText = this.getTagAttr(chukiTag, "alt");
if (altText == null || altText.isEmpty()) {
altText = ""; // ファイル名を代替
}
imageInfoReader.addImageAlt(imageFileName, altText);
}
ChatGPT:
原因はほぼ明確です。
<img src=""/> の場合、
Java String imageFileName \= this.getTagAttr(chukiTag, "src");
が "" を返し、その後
Java imageInfoReader.addImageAlt(imageFileName, altText);
が実行されます。
addImageAlt() 内で、
Java imageAltMap.put(correctExt(fileName), altText);
→ correctExt("")
となります。
ただし、今回の例外メッセージは
Cannot invoke "String.replaceFirst(...)" because "srcImageFileName" is null
なので、getTagAttr() が src="" に対して 空文字ではなく null を返している可能性が高いです。
そして correctExt() の最初に、
Java if (this.hasImage(srcImageFileName))
がありますが、hasImage(null) が false を返した後、
Java srcImageFileName.replaceFirst(...)
に到達して NPE になっています。
一番安全な修正
correctExt() 自体を null / 空文字に対応させるのがおすすめです。
Java
public String correctExt(String srcImageFileName) {
// null または空文字なら画像なし
if (srcImageFileName \== null || srcImageFileName.isEmpty()) {
return null;
}
if (this.hasImage(srcImageFileName)) {
return srcImageFileName;
}
// 拡張子修正
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".png");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".jpg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".jpeg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".gif");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".webp");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".PNG");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".JPG");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".JPEG");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".GIF");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".WEBP");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".Png");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".Jpg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".Jpeg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".Gif");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".Webp");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
return null;
}
ただし、これだけでは不十分です。
addImageAlt() も修正したほうがいいです。
Java
public void addImageAlt(String fileName, String altText) {
String correctFileName \= correctExt(fileName);
if (correctFileName \== null) {
return;
}
imageAltMap.put(correctFileName, altText);
}
さらに、今回の処理なら src がない画像を addImageFileName() に入れる必要もありません。
現在は、
Java
if (lowerChukiTag.startsWith("<img")) {
String imageFileName \= this.getTagAttr(chukiTag, "src");
if (imageFileName != null) {
imageInfoReader.addImageFileName(imageFileName);
...
}
String altText \= this.getTagAttr(chukiTag, "alt");
if (altText \== null || altText.isEmpty()) {
altText \= "";
}
imageInfoReader.addImageAlt(imageFileName, altText);
}
となっていますが、ここを 画像ファイル名が有効な場合だけ alt も登録するようにすると綺麗です。
Java
if (lowerChukiTag.startsWith("<img")) {
String imageFileName \= this.getTagAttr(chukiTag, "src");
if (imageFileName != null && !imageFileName.isEmpty()) {
imageInfoReader.addImageFileName(imageFileName);
if (bookInfo.firstImageLineNum \== \-1) {
// 小さい画像は無視
ImageInfo imageInfo \=
imageInfoReader.getImageInfo(
imageInfoReader.correctExt(imageFileName)
);
if (imageInfo != null
&& imageInfo.getWidth() \> 64
&& imageInfo.getHeight() \> 64) {
bookInfo.firstImageLineNum \= lineNum;
bookInfo.firstImageIdx \=
imageInfoReader.countImageFileNames() \- 1;
}
}
String altText \= this.getTagAttr(chukiTag, "alt");
if (altText \== null || altText.isEmpty()) {
altText \= "";
}
imageInfoReader.addImageAlt(imageFileName, altText);
}
}
さらに重要な点
今回の
HTML <img src\=""/>
は、そもそも画像として扱う必要がありません。
したがって私は、
correctExt()はnull/ 空文字を受けても落ちないようにする<img>のsrcが空なら画像登録処理そのものをスキップする
という二重防御をおすすめします。
特に correctExt() は他の場所からも呼ばれている可能性があるので、
Java
if (srcImageFileName \== null || srcImageFileName.isEmpty()) {
return null;
}
を入れておく価値があります。
なお、もう一つ気になるのが correctExt() の拡張子置換です。現在は .png → .jpg → .jpeg ……と**同じ文字列に対して何度も replaceFirst()**しています。これは意図した動作にはなっていますが、拡張子を一度抽出して候補を順番に試す実装にしたほうがかなり安全です。必要なら、ここを PNG/JPG/JPEG/GIF/WEBPの大文字小文字を全部まとめて処理する形に整理できます。
You:
[SwingWorker-pool-2-thread-1] ERROR com.github.hmdev.image.ImageInfoReader - e:
java.io.FileNotFoundException: C:\Users\Owner\Documents\GitHub\AozoraEpub3\test_data (�A�N�Z�X�����ۂ���܂����B)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:185)
at java.base/java.io.FileInputStream.
/** ファイルから画像情報を生成 */
static public ImageInfo getImageInfo(File imageFile) throws IOException
{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(imageFile));
ImageInfo imageInfo = ImageInfo.getImageInfo(bis);
bis.close();
return imageInfo;
}
ChatGPT:
これは先ほどの src="" とは少し別の問題です。
スタックトレースを見る限り、
java.io.FileNotFoundException: C:\\Users\\Owner\\Documents\\GitHub\\AozoraEpub3\\test\_data
を ImageInfo.getImageInfo() に渡しています。
つまり、ImageInfo.getImageInfo(File imageFile) 自体が悪いというより、getImageInfo() に渡される File が「画像ファイル」ではなく test_data になっているのが問題です。
まず確認したい箇所
ここです。
Epub3Writer.getImageFilePath(Epub3Writer.java:1074)
↓
ImageInfoReader.getImageInfo(ImageInfoReader.java:137)
↓
ImageInfoReader.\_getImageInfo(ImageInfoReader.java:217)
↓
ImageInfo.getImageInfo(ImageInfo.java:55)
特に、
Java Epub3Writer.java:1074
の getImageInfo() に何を渡しているかを確認してください。
おそらく最終的に、
Java
new File("C:\\\\Users\\\\Owner\\\\Documents\\\\GitHub\\\\AozoraEpub3\\\\test\_data")
のような File が生成されています。
ImageInfo.getImageInfo() に防御処理を入れる
現在、
Java
static public ImageInfo getImageInfo(File imageFile) throws IOException
{
BufferedInputStream bis \=
new BufferedInputStream(new FileInputStream(imageFile));
ImageInfo imageInfo \= ImageInfo.getImageInfo(bis);
bis.close();
return imageInfo;
}
ですが、ここは少なくともファイルかどうかを確認したほうがいいです。
Java
/\*\* ファイルから画像情報を生成 \*/
static public ImageInfo getImageInfo(File imageFile) throws IOException
{
if (imageFile \== null) {
return null;
}
if (!imageFile.isFile()) {
return null;
}
try (BufferedInputStream bis \=
new BufferedInputStream(new FileInputStream(imageFile))) {
return ImageInfo.getImageInfo(bis);
}
}
これなら、
test\_data
がディレクトリだった場合も FileNotFoundException を発生させずに null を返せます。
try-with-resources にしたのも重要です。
ただし、根本原因はこちら
今回、
C:\\Users\\Owner\\Documents\\GitHub\\AozoraEpub3\\test\_data
というパスが出ているのがかなり重要です。
正常なら、
C:\\Users\\Owner\\Documents\\GitHub\\AozoraEpub3\\test\_data\\image.jpg
のように画像ファイルまで含まれている必要があります。
つまり、
test\_data
が画像の親ディレクトリとして扱われてしまっています。
例えば本来、
Java File imageFile \= new File(imageDir, imageFileName);
となるべきところで、
Java File imageFile \= imageDir;
のようになっている可能性があります。
今回の流れで特に怪しいところ
先ほどの <img src=""> 対応を入れたのであれば、私は ImageInfoReader のこの辺を確認します。
Java \_getImageInfo(...) getImageInfo(...)
特に、
Java File imageFile \= ...
を作っているところです。
そして一時的に、
Java
System.out.println("imageFile = " + imageFile);
System.out.println("exists = " + imageFile.exists());
System.out.println("isFile = " + imageFile.isFile());
System.out.println("isDirectory= " + imageFile.isDirectory());
を入れると原因がすぐ分かります。
今回なら、おそらく、
imageFile = C:\\Users\\Owner\\Documents\\GitHub\\AozoraEpub3\\test\_data exists = true isFile = false isDirectory= true
となるはずです。
さらに重要:correctExt() が null を返すケース
先ほどの修正後に、
Java
imageInfoReader.getImageInfo(
imageInfoReader.correctExt(imageFileName)
);
としている場合は注意してください。
例えば、
HTML <img src\=""/>
なら、
Java
correctExt("")
→ null
になります。
そして getImageInfo(null) がどう処理されるかによって、別の問題が発生します。
なのでここも、
Java
String correctFileName \=
imageInfoReader.correctExt(imageFileName);
if (correctFileName != null) {
ImageInfo imageInfo \=
imageInfoReader.getImageInfo(correctFileName);
if (imageInfo != null
&& imageInfo.getWidth() \> 64
&& imageInfo.getHeight() \> 64) {
bookInfo.firstImageLineNum \= lineNum;
bookInfo.firstImageIdx \=
imageInfoReader.countImageFileNames() \- 1;
}
}
としておくのが安全です。
結論
今回のエラーは、
FileInputStream("...\\\\test\_data")
になっていることが直接の原因です。
優先順位としては、
Epub3Writer.java:1074でgetImageInfo()に何を渡しているか確認ImageInfo.getImageInfo(File)にisFile()の防御を追加correctExt()がnullを返した場合はgetImageInfo()を呼ばない<img src="">は画像登録自体をスキップ
という順番で直すのがいいです。
特に Epub3Writer.java:1074 の前後20~30行を見れば、「なぜ test_data が画像ファイルとして渡っているのか」までかなり正確に追えます。
You:
public String getImageFilePath(String srcImageFileName, int lineNum) throws IOException
{
boolean isCover = false;
ImageInfo imageInfo = this.imageInfoReader.getImageInfo(srcImageFileName);
//拡張子修正
if (imageInfo == null) {
//画像があるかチェック
String altImageFileName = this.imageInfoReader.correctExt(srcImageFileName);
imageInfo = this.imageInfoReader.getImageInfo(altImageFileName);
if (imageInfo != null) {
LogAppender.warn(lineNum, "画像拡張子変更", srcImageFileName);
srcImageFileName = altImageFileName;
}
}
this.imageIndex++; //0001から開始 (本文内の順番に合せるため、ファイルが無くてもカウント)
if (imageInfo != null) {
String imageId = imageInfo.getId();
//画像は未だ出力されていない
if (imageId == null) {
imageId = decimalFormat.format(this.imageIndex);
this.imageInfos.add(imageInfo);
this.outImageFileNames.add(srcImageFileName);
if (this.imageIndex-1 == this.bookInfo.coverImageIndex) {
//imageInfo.setIsCover(true);
isCover = true;
}
}
String outImageFileName = imageId+"."+imageInfo.getExt().replaceFirst("jpeg", "jpg");
imageInfo.setId(imageId);
imageInfo.setOutFileName(outImageFileName);
//先頭に表紙ページ移動の場合でカバーページならnullを返して本文中から削除
if (bookInfo.insertCoverPage && isCover) return null;
return "../"+IMAGES_PATH+outImageFileName;
} else {
LogAppender.warn(lineNum, "画像ファイルなし", srcImageFileName);
}
return null;
}
ChatGPT:
はい、このコードを見ると原因がかなり絞れます。
問題はこの部分です。
Java ImageInfo imageInfo \= this.imageInfoReader.getImageInfo(srcImageFileName);
そして、
Java
if (imageInfo \== null) {
String altImageFileName \=
this.imageInfoReader.correctExt(srcImageFileName);
imageInfo \= this.imageInfoReader.getImageInfo(altImageFileName);
です。
srcImageFileName が null または空文字なのに、そのまま getImageInfo() に渡しています。
さらに今回のログでは、
C:\\Users\\Owner\\Documents\\GitHub\\AozoraEpub3\\test\_data
が FileInputStream に渡されています。
つまり ImageInfoReader.getImageInfo() が、画像ファイル名として渡された文字列をパスとして解決した結果、test_data ディレクトリそのものを開こうとしている状態です。
getImageFilePath() で最初に弾くのが一番いい
このメソッドは「画像をEPUBに出力するためのパスを取得する」メソッドなので、null / 空文字を受け取ったらここで終了させるのが自然です。
Java
public String getImageFilePath(String srcImageFileName, int lineNum) throws IOException
{
boolean isCover \= false;
// srcがない画像は無視
if (srcImageFileName \== null || srcImageFileName.isBlank()) {
return null;
}
ImageInfo imageInfo \=
this.imageInfoReader.getImageInfo(srcImageFileName);
// 拡張子修正
if (imageInfo \== null) {
String altImageFileName \=
this.imageInfoReader.correctExt(srcImageFileName);
if (altImageFileName != null) {
imageInfo \=
this.imageInfoReader.getImageInfo(altImageFileName);
if (imageInfo != null) {
LogAppender.warn(
lineNum,
"画像拡張子変更",
srcImageFileName
);
srcImageFileName \= altImageFileName;
}
}
}
this.imageIndex++; // 0001から開始(本文内の順番に合わせるため、ファイルがなくてもカウント)
if (imageInfo != null) {
String imageId \= imageInfo.getId();
// 画像は未だ出力されていない
if (imageId \== null) {
imageId \= decimalFormat.format(this.imageIndex);
this.imageInfos.add(imageInfo);
this.outImageFileNames.add(srcImageFileName);
if (this.imageIndex \- 1 \== this.bookInfo.coverImageIndex) {
isCover \= true;
}
}
String outImageFileName \=
imageId + "." +
imageInfo.getExt().replaceFirst("jpeg", "jpg");
imageInfo.setId(imageId);
imageInfo.setOutFileName(outImageFileName);
// 先頭に表紙ページ移動の場合でカバーページなら本文中から削除
if (bookInfo.insertCoverPage && isCover) {
return null;
}
return "../" + IMAGES\_PATH + outImageFileName;
} else {
LogAppender.warn(
これで、
HTML <img src\=""\>
や
HTML <img\>
などから getImageFilePath() が呼ばれても、画像ファイル検索まで進みません。
ただし、今回の test_data 問題はもう一段確認したい
src="" が原因なら、今回のスタックトレースは通常、
FileNotFoundException: ...\\test\_data\\
のような形になる可能性があります。
ところが実際には、
FileNotFoundException: C:\\Users\\Owner\\Documents\\GitHub\\AozoraEpub3\\test\_data
となっています。
これは ImageInfoReader 側で画像ファイル名を解決するときに、
test\_data + srcImageFileName
ではなく、
test\_data
だけになっている可能性があります。
なので、次に見るべきなのは ImageInfoReader.getImageInfo() です。
おそらく、
Java public ImageInfo getImageInfo(String fileName)
の中で最終的に、
Java File imageFile \= new File(...);
を作っていると思います。
そして correctExt() も少し整理したほうがいい
現在のコードでは、
Java
String altImageFileName \=
this.imageInfoReader.correctExt(srcImageFileName);
imageInfo \=
this.imageInfoReader.getImageInfo(altImageFileName);
となっています。
ここで correctExt() が画像を見つけられなかった場合、
Java return null;
します。
そのため、
Java getImageInfo(null)
を呼ばないようにする必要があります。
今回のように、
Java
if (altImageFileName != null) {
imageInfo \=
this.imageInfoReader.getImageInfo(altImageFileName);
}
としておけば安全です。
さらに ImageInfo.getImageInfo(File) も防御する
最終防衛線として、ここも変更しておくのがおすすめです。
Java
/\*\* ファイルから画像情報を生成 \*/
static public ImageInfo getImageInfo(File imageFile) throws IOException
{
if (imageFile \== null || !imageFile.isFile()) {
return null;
}
try (BufferedInputStream bis \=
new BufferedInputStream(new FileInputStream(imageFile))) {
return ImageInfo.getImageInfo(bis);
}
}
これなら、
null
や
ディレクトリ
が来ても FileInputStream で例外になりません。
今回の修正方針
私は 3箇所で防御するのがいいと思います。
HTML ↓ <img src=""> ↓ getImageFilePath() ↓ ① null / 空文字なら return null ↓ ImageInfoReader ↓ ② correctExt() の null を処理 ↓ ImageInfo.getImageInfo(File) ↓ ③ isFile() を確認
これなら、HTML編集時にTinyMCEが生成するような
HTML <img src\=""\>
だけでなく、
HTML <img\> <img src\="存在しない画像.png"\> <img src\="画像.jpg"\>
なども安全に処理できます。
特に今回のAozoraEpub3のような変換処理では、「画像がない」こと自体を例外にしない設計にしておくのが適しています。
なお、getImageFilePath() の imageIndex++ は現在「画像ファイルがなくてもカウントする」という意図なので、src が空の場合にそれまで実行するかどうかは別途判断が必要です。元のコメント通り本文中の <img> の出現順を維持したいなら、今の早期 return ではなく imageIndex++ を先に残す設計もできます。
You:
correctExt() の拡張子置換のリファクタリング
public String correctExt(String srcImageFileName) {
// null または空文字なら画像なし
if (srcImageFileName == null || srcImageFileName.isEmpty()) {
return null;
}
if (this.hasImage(srcImageFileName)) return srcImageFileName;
//拡張子修正
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".png");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".jpg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".jpeg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".gif");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".webp");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".PNG");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".JPG");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".JPEG");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".GIF");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".WEBP");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Png");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Jpg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Jpeg");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Gif");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
srcImageFileName = srcImageFileName.replaceFirst("\\.\\w+$", ".Webp");
if (this.hasImage(srcImageFileName)) return srcImageFileName;
return null;
}
ChatGPT:
はい。ここはかなりきれいにできます。
現在の実装の問題は、拡張子の候補ごとに replaceFirst() を繰り返していることです。
例えば、
Java
srcImageFileName \= srcImageFileName.replaceFirst("\\\\.\\\\w+$", ".png");
をしたあとに .jpg に置換しているため、結果としては動きますが、かなり冗長です。
おすすめのリファクタリング
拡張子候補を配列にしてループします。
Java
public String correctExt(String srcImageFileName) {
// null または空文字なら画像なし
if (srcImageFileName \== null || srcImageFileName.isEmpty()) {
return null;
}
// 元のファイル名が存在する場合
if (this.hasImage(srcImageFileName)) {
return srcImageFileName;
}
// 拡張子を取得
int extPos \= srcImageFileName.lastIndexOf('.');
// 拡張子がない場合は修正できない
if (extPos < 0) {
return null;
}
// 拡張子以外の部分
String baseName \= srcImageFileName.substring(0, extPos);
// 対応する拡張子
String\[\] extensions \= {
".png",
".jpg",
".jpeg",
".gif",
".webp",
".PNG",
".JPG",
".JPEG",
".GIF",
".WEBP",
".Png",
".Jpg",
".Jpeg",
".Gif",
".Webp"
};
for (String extension : extensions) {
String imageFileName \= baseName + extension;
if (this.hasImage(imageFileName)) {
return imageFileName;
}
}
return null;
}
これだけで現在の処理とほぼ同じ動作になります。
さらにおすすめ:大文字小文字を全部列挙しない
ただ、Javaであれば、さらに整理できます。
現在は、
.png .PNG .Png
など15パターンを列挙しています。
対応したいのが「拡張子の大文字小文字の違い」だけなら、実際には画像ファイル名のベース部分を取り出して、
Java .png .jpg .jpeg .gif .webp
だけを試せば十分です。
ただし、hasImage() がWindows上で実際にファイルシステムを見ているのであれば、Windowsは通常ファイル名の大文字小文字を区別しません。
つまり、
image.png image.PNG image.Png
はWindowsでは同じファイルとして扱われます。
そのため、Windowsで使うAozoraEpub3なら、基本的には次で十分です。
Java
public String correctExt(String srcImageFileName) {
// null または空文字なら画像なし
if (srcImageFileName \== null || srcImageFileName.isEmpty()) {
return null;
}
// 元のファイル名が存在する場合
if (this.hasImage(srcImageFileName)) {
return srcImageFileName;
}
// 拡張子を取得
int extPos \= srcImageFileName.lastIndexOf('.');
// 拡張子がない場合
if (extPos < 0) {
return null;
}
String baseName \= srcImageFileName.substring(0, extPos);
String\[\] extensions \= {
".png",
".jpg",
".jpeg",
".gif",
".webp"
};
for (String extension : extensions) {
String imageFileName \= baseName + extension;
if (this.hasImage(imageFileName)) {
return imageFileName;
}
}
return null;
}
ただし注意
ここで重要なのは、hasImage() の実装です。
もし hasImage() が、
Java new File(...).exists()
のようにWindowsのファイルシステムを使っているなら、上記で問題ありません。
一方、AozoraEpub3が将来的にLinux/macOSでも動かすことを考えるなら、大文字小文字は区別されるため、
image.png image.PNG image.Png
を区別する必要があります。
その場合は、現在の15パターンをループにまとめる方法が適しています。
私ならこうする
AozoraEpub3のコードとしては、拡張子候補を定数化しておくのが一番読みやすいと思います。
Java
private static final String\[\] IMAGE\_EXTENSIONS \= {
".png",
".jpg",
".jpeg",
".gif",
".webp",
".PNG",
".JPG",
".JPEG",
".GIF",
".WEBP",
".Png",
".Jpg",
".Jpeg",
".Gif",
".Webp"
};
public String correctExt(String srcImageFileName) {
// null または空文字なら画像なし
if (srcImageFileName \== null || srcImageFileName.isEmpty()) {
return null;
}
// 元のファイル名が存在する場合
if (this.hasImage(srcImageFileName)) {
return srcImageFileName;
}
// 拡張子がない場合は修正できない
int extPos \= srcImageFileName.lastIndexOf('.');
if (extPos < 0) {
return null;
}
String baseName \= srcImageFileName.substring(0, extPos);
for (String extension : IMAGE\_EXTENSIONS) {
String imageFileName \= baseName + extension;
if (this.hasImage(imageFileName)) {
return imageFileName;
}
}
return null;
}
これなら今後、
Java ".avif"
などを追加したくなった場合も、
Java
private static final String\[\] IMAGE\_EXTENSIONS \= {
".png",
".jpg",
".jpeg",
".gif",
".webp",
".avif"
};
とするだけです。
もう一つ改善するなら
実はこの部分、
Java
int extPos \= srcImageFileName.lastIndexOf('.');
にも少し問題があります。
例えば、
images/book.v1/image
や、
images/.hidden
のようなパスを考えると、単純な lastIndexOf('.') よりも、最後の / または \ より後にある . だけを拡張子として扱うほうが堅牢です。
AozoraEpub3は画像パスを扱うので、私は最終的には Path/File ベースではなく、青空文庫の画像パス形式を壊さない文字列ベースの拡張子判定としてそこまで考慮した実装にするのをおすすめします。

















































































































![ユースケース実践ガイド[復刻版]【リフロー型】 効果的なユースケースの書き方 ユースケース実践ガイド[復刻版]【リフロー型】 効果的なユースケースの書き方](https://m.media-amazon.com/images/I/51MGWiKWUlL._SL500_.jpg)

