Fix gallery resolution parsing for split archive paragraphs
This commit is contained in:
@@ -11,6 +11,10 @@ import java.util.regex.Pattern;
|
|||||||
public final class GalleryPageParser {
|
public final class GalleryPageParser {
|
||||||
private GalleryPageParser() { }
|
private GalleryPageParser() { }
|
||||||
|
|
||||||
|
/** 归档页 #db 单元格开头:分辨率标签 + 正数体积 + 受支持单位;后缀费用文本(Free、50GP)忽略。 */
|
||||||
|
private static final Pattern RESOLUTION_ENTRY =
|
||||||
|
Pattern.compile("^(Original|(\\d+)x)\\s+(\\d+(?:\\.\\d+)?)\\s*([A-Za-z]+)(?=\\s|$)", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
public static Gallery metadata(Document document, String link, int gid) throws IOException {
|
public static Gallery metadata(Document document, String link, int gid) throws IOException {
|
||||||
String title = document.select("#gn").text();
|
String title = document.select("#gn").text();
|
||||||
if (title.isBlank()) title = document.select("#gj").text();
|
if (title.isBlank()) title = document.select("#gj").text();
|
||||||
@@ -52,16 +56,37 @@ public final class GalleryPageParser {
|
|||||||
throw new IOException("上游页面缺少归档入口");
|
throw new IOException("上游页面缺少归档入口");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析归档页 #db 的可选分辨率。每个单元格合并后的文本形如 {@code Original 36.31 MiB Free}、
|
||||||
|
* {@code 800x 5.65 MiB Free}、{@code 2560x N/A N/A},单段布局形如 {@code 1280x 12.5 MiB},
|
||||||
|
* 都由同一条锚定规则覆盖;只看开头(分辨率 + 正数体积 + 单位),故 N/A 与费用文本不匹配,
|
||||||
|
* 配对也不会跨 {@code <td>} 借用相邻格。体积须能被 {@link CustomUtil#stringToFileSize(String)}
|
||||||
|
* 解析为正数,宽度须为正且不溢出。结果按宽度升序、{@code Original} 最后;无可用项抛 IOException。
|
||||||
|
*/
|
||||||
public static Map<String, String> resolutions(Document document) throws IOException {
|
public static Map<String, String> resolutions(Document document) throws IOException {
|
||||||
var expression = Pattern.compile("^(\\d+x|Original)\\s+([\\d.]+)\\s*([A-Za-z]+)$", Pattern.CASE_INSENSITIVE);
|
|
||||||
TreeMap<Integer, String> sizes = new TreeMap<>();
|
TreeMap<Integer, String> sizes = new TreeMap<>();
|
||||||
String original = null;
|
String original = null;
|
||||||
for (Element paragraph : document.select("#db td p")) {
|
for (Element cell : document.select("#db td")) {
|
||||||
var matcher = expression.matcher(paragraph.text().strip());
|
var matcher = RESOLUTION_ENTRY.matcher(cell.text().replace('\u00a0', ' ').strip());
|
||||||
if (!matcher.matches()) continue;
|
if (!matcher.find()) continue;
|
||||||
String size = matcher.group(2) + matcher.group(3);
|
String size = matcher.group(3) + matcher.group(4);
|
||||||
if (matcher.group(1).equalsIgnoreCase("Original")) original = size;
|
try {
|
||||||
else sizes.put(Integer.parseInt(matcher.group(1).replace("x", "")), size);
|
if (CustomUtil.stringToFileSize(size) <= 0) continue;
|
||||||
|
} catch (RuntimeException unsupported) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (matcher.group(1).equalsIgnoreCase("Original")) {
|
||||||
|
original = size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int width;
|
||||||
|
try {
|
||||||
|
width = Integer.parseInt(matcher.group(2));
|
||||||
|
} catch (NumberFormatException outOfRange) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (width <= 0) continue;
|
||||||
|
sizes.put(width, size);
|
||||||
}
|
}
|
||||||
Map<String, String> result = new LinkedHashMap<>();
|
Map<String, String> result = new LinkedHashMap<>();
|
||||||
sizes.forEach((width, size) -> result.put(width + "x", size));
|
sizes.forEach((width, size) -> result.put(width + "x", size));
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package com.lion.lionwebsite.Util;
|
|||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
class UpstreamParserVariantsTest {
|
class UpstreamParserVariantsTest {
|
||||||
@@ -33,4 +36,119 @@ class UpstreamParserVariantsTest {
|
|||||||
assertEquals("value", JavaScriptAssignments.string("let mpvkey = 'value';", "mpvkey"));
|
assertEquals("value", JavaScriptAssignments.string("let mpvkey = 'value';", "mpvkey"));
|
||||||
assertThrows(IOException.class, () -> JavaScriptAssignments.array("imagelist = [{\"k\":\"x\"}", "imagelist"));
|
assertThrows(IOException.class, () -> JavaScriptAssignments.array("imagelist = [{\"k\":\"x\"}", "imagelist"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 线上真实结构:每个单元格拆成三个 <p>(分辨率/体积/费用)。旧实现逐段匹配导致全部漏掉。 */
|
||||||
|
@Test void resolutionsReadSplitParagraphCellsFromLiveLayout() throws Exception {
|
||||||
|
var document = Jsoup.parse("<div id=db><table><tbody><tr>"
|
||||||
|
+ "<td><p>Original</p><p>36.31 MiB</p><p>Free</p></td>"
|
||||||
|
+ "<td><p>800x</p><p>5.65 MiB</p><p>Free</p></td>"
|
||||||
|
+ "<td><p>1280x</p><p>12.5 MiB</p><p>Free</p></td>"
|
||||||
|
+ "<td><p>1920x</p><p>30.2 MiB</p><p>Free</p></td>"
|
||||||
|
+ "<td><p>2560x</p><p>N/A</p><p>N/A</p></td>"
|
||||||
|
+ "</tr></tbody></table></div>");
|
||||||
|
|
||||||
|
var resolutions = GalleryPageParser.resolutions(document);
|
||||||
|
|
||||||
|
assertEquals(List.of("800x", "1280x", "1920x", "Original"), new ArrayList<>(resolutions.keySet()));
|
||||||
|
assertEquals("5.65MiB", resolutions.get("800x"));
|
||||||
|
assertEquals("12.5MiB", resolutions.get("1280x"));
|
||||||
|
assertEquals("30.2MiB", resolutions.get("1920x"));
|
||||||
|
assertEquals("36.31MiB", resolutions.get("Original"), "Original 应排在最后");
|
||||||
|
assertFalse(resolutions.containsKey("2560x"), "N/A 的体积不得进结果");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 历史/单段布局:分辨率与体积在同一段 <p> 也必须继续兼容。 */
|
||||||
|
@Test void resolutionsKeepMergedParagraphLayoutCompatible() throws Exception {
|
||||||
|
var document = Jsoup.parse("<div id=db><table><tbody>"
|
||||||
|
+ "<tr><td><p>1280x 12.5 MiB</p></td></tr>"
|
||||||
|
+ "<tr><td><p>Original 30.2 MiB</p></td></tr>"
|
||||||
|
+ "</tbody></table></div>");
|
||||||
|
|
||||||
|
var resolutions = GalleryPageParser.resolutions(document);
|
||||||
|
|
||||||
|
assertEquals(List.of("1280x", "Original"), new ArrayList<>(resolutions.keySet()));
|
||||||
|
assertEquals("12.5MiB", resolutions.get("1280x"));
|
||||||
|
assertEquals("30.2MiB", resolutions.get("Original"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** N/A 与体积分处两格时不得跨 td 拼接。 */
|
||||||
|
@Test void resolutionsNeverStitchAcrossCells() throws Exception {
|
||||||
|
var document = Jsoup.parse("<div id=db><table><tbody><tr>"
|
||||||
|
+ "<td><p>2560x</p><p>N/A</p><p>N/A</p></td>"
|
||||||
|
+ "<td><p>1920x</p><p>30.2 MiB</p></td>"
|
||||||
|
+ "<td><p>800x</p><p>N/A</p></td>"
|
||||||
|
+ "</tr></tbody></table></div>");
|
||||||
|
|
||||||
|
var resolutions = GalleryPageParser.resolutions(document);
|
||||||
|
|
||||||
|
assertEquals(Set.of("1920x"), resolutions.keySet());
|
||||||
|
assertEquals("30.2MiB", resolutions.get("1920x"));
|
||||||
|
assertTrue(resolutions.values().stream().noneMatch(size -> size.toUpperCase().contains("N/A")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 单元格开头不是“分辨率标签 + 体积”时不得凭空造出条目。 */
|
||||||
|
@Test void resolutionsRequireResolutionLabelWithinSameCell() throws Exception {
|
||||||
|
var document = Jsoup.parse("<div id=db><table><tbody><tr>"
|
||||||
|
+ "<td><p>800x</p><p>5.65 MiB</p></td>"
|
||||||
|
+ "<td><p>12.5 MiB</p></td>"
|
||||||
|
+ "<td><p>1920x</p><p>Costs 50 GP</p></td>"
|
||||||
|
+ "</tr></tbody></table></div>");
|
||||||
|
|
||||||
|
var resolutions = GalleryPageParser.resolutions(document);
|
||||||
|
|
||||||
|
assertEquals(List.of("800x"), new ArrayList<>(resolutions.keySet()));
|
||||||
|
assertEquals("5.65MiB", resolutions.get("800x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 嵌套标签(strong/span/div)与 由 text() 归并后仍应正确取到分辨率与体积。 */
|
||||||
|
@Test void resolutionsReadNestedTagsInsideCells() throws Exception {
|
||||||
|
var document = Jsoup.parse("<div id=db><table><tbody><tr>"
|
||||||
|
+ "<td><div><p><strong>1280x</strong></p><div><p><span>12.5</span> <em>MiB</em></p></div></div></td>"
|
||||||
|
+ "<td><p>Original</p><p>30.2 MiB</p></td>"
|
||||||
|
+ "</tr></tbody></table></div>");
|
||||||
|
|
||||||
|
var resolutions = GalleryPageParser.resolutions(document);
|
||||||
|
|
||||||
|
assertEquals(List.of("1280x", "Original"), new ArrayList<>(resolutions.keySet()));
|
||||||
|
assertEquals("12.5MiB", resolutions.get("1280x"));
|
||||||
|
assertEquals("30.2MiB", resolutions.get("Original"), " 分隔的体积也要认");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 未知单位、坏数字和费用文本都不得当作体积,只保留可解析的项。 */
|
||||||
|
@Test void resolutionsRejectUnknownUnitAndCostText() throws Exception {
|
||||||
|
var document = Jsoup.parse("<div id=db><table><tbody><tr>"
|
||||||
|
+ "<td><p>1280x</p><p>12.5 ZiB</p></td>"
|
||||||
|
+ "<td><p>1920x</p><p>Costs 50 GP</p></td>"
|
||||||
|
+ "<td><p>2560x</p><p>.5 MiB</p></td>"
|
||||||
|
+ "<td><p>800x</p><p>5.65 MiB</p></td>"
|
||||||
|
+ "</tr></tbody></table></div>");
|
||||||
|
|
||||||
|
var resolutions = GalleryPageParser.resolutions(document);
|
||||||
|
|
||||||
|
assertEquals(List.of("800x"), new ArrayList<>(resolutions.keySet()));
|
||||||
|
assertEquals("5.65MiB", resolutions.get("800x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 完全取不到可用分辨率(空文档或只有 N/A)必须抛 IOException,而不是返回空表。 */
|
||||||
|
@Test void resolutionsThrowWhenNothingUsable() throws Exception {
|
||||||
|
assertThrows(IOException.class, () -> GalleryPageParser.resolutions(Jsoup.parse("<html/>")));
|
||||||
|
assertThrows(IOException.class, () -> GalleryPageParser.resolutions(Jsoup.parse(
|
||||||
|
"<div id=db><table><tbody><tr><td><p>2560x</p><p>N/A</p><p>N/A</p></td>"
|
||||||
|
+ "<td><p>N/A</p></td></tr></tbody></table></div>")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 宽度为 0 或溢出、以及单位后粘连字符的项都要跳过,不能崩到调用方。 */
|
||||||
|
@Test void resolutionsSkipInvalidWidthAndGluedUnit() throws Exception {
|
||||||
|
var document = Jsoup.parse("<div id=db><table><tbody><tr>"
|
||||||
|
+ "<td><p>0x</p><p>5.65 MiB</p><p>Free</p></td>"
|
||||||
|
+ "<td><p>99999999999x</p><p>12.5 MiB</p><p>Free</p></td>"
|
||||||
|
+ "<td><p>800x</p><p>5.65 MiB2</p><p>Free</p></td>"
|
||||||
|
+ "<td><p>1280x</p><p>12.5 MiB</p><p>Free</p></td>"
|
||||||
|
+ "</tr></tbody></table></div>");
|
||||||
|
|
||||||
|
var resolutions = GalleryPageParser.resolutions(document);
|
||||||
|
|
||||||
|
assertEquals(List.of("1280x"), new ArrayList<>(resolutions.keySet()));
|
||||||
|
assertEquals("12.5MiB", resolutions.get("1280x"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user