package com.doumee.core.haikang.isapi;
|
|
import com.doumee.core.haikang.isapi.model.DeviceInfoDTO;
|
import com.doumee.core.haikang.isapi.model.MediaItemDTO;
|
import com.doumee.core.haikang.isapi.model.RecordTrackDTO;
|
import com.doumee.core.haikang.isapi.model.SearchPageResult;
|
import com.doumee.core.haikang.isapi.model.StorageInfoDTO;
|
import org.apache.commons.lang3.StringUtils;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.w3c.dom.Document;
|
import org.w3c.dom.Element;
|
import org.w3c.dom.Node;
|
import org.w3c.dom.NodeList;
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
import java.io.ByteArrayInputStream;
|
import java.net.URLDecoder;
|
import java.nio.charset.StandardCharsets;
|
import java.text.SimpleDateFormat;
|
import java.util.ArrayList;
|
import java.util.Date;
|
import java.util.List;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
|
/**
|
* ISAPI XML 响应解析
|
*/
|
public class IsapiXmlParser {
|
|
private static final Logger log = LoggerFactory.getLogger(IsapiXmlParser.class);
|
|
private IsapiXmlParser() {
|
}
|
|
public static DeviceInfoDTO parseDeviceInfo(String xml) {
|
if (StringUtils.isBlank(xml)) {
|
return null;
|
}
|
DeviceInfoDTO dto = new DeviceInfoDTO();
|
dto.setDeviceName(extractTag(xml, "deviceName"));
|
dto.setDeviceId(extractTag(xml, "deviceID"));
|
dto.setModel(extractTag(xml, "model"));
|
dto.setSerialNumber(extractTag(xml, "serialNumber"));
|
dto.setFirmwareVersion(extractTag(xml, "firmwareVersion"));
|
dto.setDeviceType(extractTag(xml, "deviceType"));
|
return dto;
|
}
|
|
public static StorageInfoDTO parseStorage(String xml) {
|
if (StringUtils.isBlank(xml)) {
|
return null;
|
}
|
StorageInfoDTO dto = new StorageInfoDTO();
|
long total = 0;
|
long free = 0;
|
try {
|
Document doc = parseDocument(xml);
|
NodeList hddNodes = doc.getElementsByTagName("hdd");
|
for (int i = 0; i < hddNodes.getLength(); i++) {
|
Element hdd = (Element) hddNodes.item(i);
|
total += parseLong(getChildText(hdd, "capacity"));
|
free += parseLong(getChildText(hdd, "freeSpace"));
|
}
|
} catch (Exception e) {
|
total = parseLong(extractTag(xml, "capacity"));
|
free = parseLong(extractTag(xml, "freeSpace"));
|
}
|
if (total > 0) {
|
dto.setTotalSpaceGb(total);
|
dto.setFreeSpaceGb(free);
|
}
|
return dto;
|
}
|
|
public static List<RecordTrackDTO> parseRecordTracks(String xml) {
|
List<RecordTrackDTO> list = new ArrayList<>();
|
if (StringUtils.isBlank(xml)) {
|
return list;
|
}
|
try {
|
Document doc = parseDocument(xml);
|
NodeList trackNodes = doc.getElementsByTagName("Track");
|
if (trackNodes.getLength() == 0) {
|
trackNodes = doc.getElementsByTagName("track");
|
}
|
for (int i = 0; i < trackNodes.getLength(); i++) {
|
Element track = (Element) trackNodes.item(i);
|
String id = getChildText(track, "id");
|
if (StringUtils.isBlank(id)) {
|
id = getChildText(track, "trackID");
|
}
|
if (StringUtils.isBlank(id)) {
|
continue;
|
}
|
RecordTrackDTO dto = new RecordTrackDTO();
|
dto.setId(id.trim());
|
dto.setChannel(getChildText(track, "channel"));
|
dto.setStreamType(resolveStreamType(id));
|
list.add(dto);
|
}
|
} catch (Exception ignored) {
|
}
|
if (list.isEmpty()) {
|
Matcher m = Pattern.compile("<id>(\\d+)</id>").matcher(xml);
|
while (m.find()) {
|
RecordTrackDTO dto = new RecordTrackDTO();
|
dto.setId(m.group(1));
|
dto.setStreamType(resolveStreamType(dto.getId()));
|
list.add(dto);
|
}
|
}
|
return list;
|
}
|
|
public static SearchPageResult parseSearchPage(String xml) {
|
SearchPageResult page = new SearchPageResult();
|
if (StringUtils.isBlank(xml)) {
|
return page;
|
}
|
String normalized = normalizeXml(xml);
|
if (normalized.contains("statusCode") && normalized.contains("<statusCode>")) {
|
String statusCode = firstNonBlank(extractTagIgnoreCase(normalized, "statusCode"), extractTag(normalized, "statusCode"));
|
if (StringUtils.isNotBlank(statusCode) && !"1".equals(statusCode.trim()) && !"0".equals(statusCode.trim())) {
|
log.warn("ISAPI search 返回错误 statusCode={} statusString={} subStatusCode={}",
|
statusCode,
|
firstNonBlank(extractTagIgnoreCase(normalized, "statusString"), extractTag(normalized, "statusString")),
|
firstNonBlank(extractTagIgnoreCase(normalized, "subStatusCode"), extractTag(normalized, "subStatusCode")));
|
}
|
}
|
page.setResponseStatusStrg(firstNonBlank(
|
extractTagIgnoreCase(normalized, "responseStatusStrg"),
|
extractTag(normalized, "responseStatusStrg")));
|
page.setNumOfMatches((int) parseLong(firstNonBlank(
|
extractTagIgnoreCase(normalized, "numOfMatches"),
|
extractTag(normalized, "numOfMatches"),
|
extractTagIgnoreCase(normalized, "totalMatches"),
|
extractTag(normalized, "totalMatches"))));
|
page.setHasMore(IsapiConstants.SEARCH_STATUS_MORE.equalsIgnoreCase(page.getResponseStatusStrg()));
|
page.setItems(parseSearchResult(normalized));
|
if (page.getItems().isEmpty() && page.getNumOfMatches() > 0) {
|
log.warn("ISAPI search numOfMatches={} 但解析 items 为空", page.getNumOfMatches());
|
}
|
return page;
|
}
|
|
public static List<MediaItemDTO> parseSearchResult(String xml) {
|
List<MediaItemDTO> list = new ArrayList<>();
|
if (StringUtils.isBlank(xml)) {
|
return list;
|
}
|
String normalized = normalizeXml(xml);
|
if (normalized.startsWith("{")) {
|
return parseSearchResultJson(normalized);
|
}
|
try {
|
Document doc = parseDocument(normalized);
|
collectSearchMatchItems(doc.getDocumentElement(), list);
|
} catch (Exception e) {
|
log.warn("ISAPI search DOM解析失败,尝试正则: {}", e.getMessage());
|
}
|
if (list.isEmpty()) {
|
collectSearchMatchItemsByRegex(normalized, list);
|
}
|
if (list.isEmpty() && (normalized.toLowerCase().contains("searchmatchitem")
|
|| normalized.toLowerCase().contains("matchelement"))) {
|
log.warn("ISAPI search 含匹配项但解析为空,response={}",
|
StringUtils.abbreviate(normalized, 500));
|
}
|
return list;
|
}
|
|
private static final String[] MATCH_ITEM_TAGS = {"searchMatchItem", "matchElement", "matchItem"};
|
|
private static void collectSearchMatchItems(Element root, List<MediaItemDTO> list) {
|
if (root == null) {
|
return;
|
}
|
for (String tag : MATCH_ITEM_TAGS) {
|
NodeList nodes = getElementsByLocalName(root, tag);
|
for (int i = 0; i < nodes.getLength(); i++) {
|
Node node = nodes.item(i);
|
if (node.getNodeType() != Node.ELEMENT_NODE) {
|
continue;
|
}
|
addParsedMedia(list, parseMatchItem((Element) node));
|
}
|
if (!list.isEmpty()) {
|
return;
|
}
|
}
|
}
|
|
private static void collectSearchMatchItemsByRegex(String xml, List<MediaItemDTO> list) {
|
for (String tag : MATCH_ITEM_TAGS) {
|
Matcher matcher = Pattern.compile(
|
"(?is)<(?:\\w+:)?" + tag + "\\b[^>]*>(.*?)</(?:\\w+:)?" + tag + "\\s*>").matcher(xml);
|
while (matcher.find()) {
|
addParsedMedia(list, parseMatchItemFromBlock(matcher.group(1)));
|
}
|
if (!list.isEmpty()) {
|
return;
|
}
|
}
|
}
|
|
private static void addParsedMedia(List<MediaItemDTO> list, MediaItemDTO media) {
|
if (media == null) {
|
return;
|
}
|
if (StringUtils.isBlank(media.getFileIndex())) {
|
String fallback = buildFileIndexFallback(media);
|
if (StringUtils.isNotBlank(fallback)) {
|
media.setFileIndex(fallback);
|
if (StringUtils.isBlank(media.getFileName())) {
|
media.setFileName(fallback);
|
}
|
}
|
}
|
if (StringUtils.isNotBlank(media.getFileIndex()) || StringUtils.isNotBlank(media.getPlaybackUri())) {
|
list.add(media);
|
}
|
}
|
|
private static List<MediaItemDTO> parseSearchResultJson(String json) {
|
List<MediaItemDTO> list = new ArrayList<>();
|
try {
|
com.alibaba.fastjson.JSONObject root = com.alibaba.fastjson.JSONObject.parseObject(json);
|
com.alibaba.fastjson.JSONObject searchResult = root.getJSONObject("CMSearchResult");
|
if (searchResult != null) {
|
root = searchResult;
|
}
|
com.alibaba.fastjson.JSONArray matchList = extractJsonMatchList(root);
|
if (matchList == null || matchList.isEmpty()) {
|
return list;
|
}
|
for (int i = 0; i < matchList.size(); i++) {
|
com.alibaba.fastjson.JSONObject item = matchList.getJSONObject(i);
|
if (item == null) {
|
continue;
|
}
|
com.alibaba.fastjson.JSONObject desc = item.getJSONObject("MediaSegmentDescriptor");
|
if (desc == null) {
|
desc = item.getJSONObject("mediaSegmentDescriptor");
|
}
|
if (desc == null) {
|
desc = item.getJSONObject("recordSegmentDescriptor");
|
}
|
if (desc == null) {
|
desc = item;
|
}
|
MediaItemDTO dto = new MediaItemDTO();
|
dto.setTrackId(firstNonBlank(item.getString("trackID"), item.getString("trackId")));
|
com.alibaba.fastjson.JSONObject timeSpan = item.getJSONObject("timeSpan");
|
dto.setStartTime(parseIsapiTime(firstNonBlank(item.getString("startTime"),
|
timeSpan != null ? timeSpan.getString("startTime") : null)));
|
dto.setEndTime(parseIsapiTime(firstNonBlank(item.getString("endTime"),
|
timeSpan != null ? timeSpan.getString("endTime") : null)));
|
dto.setPlaybackUri(firstNonBlank(desc.getString("playbackURI"), desc.getString("playbackUri"),
|
desc.getString("downloadURI"), desc.getString("fileUrl")));
|
dto.setContentType(firstNonBlank(desc.getString("contentType"), desc.getString("contenType")));
|
dto.setFileSize(desc.getLong("size"));
|
dto.setFileName(firstNonBlank(desc.getString("name"), desc.getString("fileName"),
|
desc.getString("mediaID"), item.getString("sourceID")));
|
dto.setUserName(firstNonBlank(desc.getString("policeName"), item.getString("policeName")));
|
dto.setRecorderSn(firstNonBlank(desc.getString("bodyCameraShortSN"),
|
desc.getString("recorderCode"), desc.getString("bodyCameraShortSN")));
|
if (StringUtils.isBlank(dto.getFileName())) {
|
dto.setFileName(extractNameFromUri(dto.getPlaybackUri()));
|
}
|
applyFileIdentity(dto, desc.getString("mediaID"));
|
dto.setMediaType(resolveMediaType(dto.getContentType(), dto.getTrackId(), dto.getFileName()));
|
addParsedMedia(list, dto);
|
}
|
} catch (Exception e) {
|
log.warn("ISAPI search JSON解析失败: {}", e.getMessage());
|
}
|
return list;
|
}
|
|
private static com.alibaba.fastjson.JSONArray extractJsonMatchList(com.alibaba.fastjson.JSONObject root) {
|
com.alibaba.fastjson.JSONArray matchList = root.getJSONArray("MatchList");
|
if (matchList == null) {
|
matchList = root.getJSONArray("matchList");
|
}
|
if (matchList != null) {
|
return matchList;
|
}
|
com.alibaba.fastjson.JSONObject matchObj = root.getJSONObject("MatchList");
|
if (matchObj == null) {
|
matchObj = root.getJSONObject("matchList");
|
}
|
if (matchObj == null) {
|
return null;
|
}
|
com.alibaba.fastjson.JSONArray items = matchObj.getJSONArray("SearchMatchItem");
|
if (items == null) {
|
items = matchObj.getJSONArray("searchMatchItem");
|
}
|
if (items == null) {
|
items = matchObj.getJSONArray("matchElement");
|
}
|
if (items == null) {
|
items = matchObj.getJSONArray("MatchElement");
|
}
|
if (items != null) {
|
return items;
|
}
|
com.alibaba.fastjson.JSONObject single = matchObj.getJSONObject("SearchMatchItem");
|
if (single == null) {
|
single = matchObj.getJSONObject("searchMatchItem");
|
}
|
if (single == null) {
|
single = matchObj.getJSONObject("matchElement");
|
}
|
if (single == null) {
|
single = matchObj.getJSONObject("MatchElement");
|
}
|
if (single != null) {
|
com.alibaba.fastjson.JSONArray arr = new com.alibaba.fastjson.JSONArray();
|
arr.add(single);
|
return arr;
|
}
|
return null;
|
}
|
|
private static MediaItemDTO parseMatchItemFromBlock(String block) {
|
MediaItemDTO dto = new MediaItemDTO();
|
dto.setTrackId(extractTagIgnoreCase(block, "trackID"));
|
dto.setStartTime(parseIsapiTime(firstNonBlank(
|
extractTagIgnoreCase(block, "startTime"),
|
extractNestedTagIgnoreCase(block, "timeSpan", "startTime"))));
|
dto.setEndTime(parseIsapiTime(firstNonBlank(
|
extractTagIgnoreCase(block, "endTime"),
|
extractNestedTagIgnoreCase(block, "timeSpan", "endTime"))));
|
String descBlock = firstNonBlank(
|
extractBlockIgnoreCase(block, "mediaSegmentDescriptor"),
|
extractBlockIgnoreCase(block, "recordSegmentDescriptor"),
|
block);
|
dto.setPlaybackUri(firstNonBlank(
|
extractTagIgnoreCase(descBlock, "playbackURI"),
|
extractTagIgnoreCase(descBlock, "playbackUri"),
|
extractTagIgnoreCase(descBlock, "downloadURI"),
|
extractTagIgnoreCase(descBlock, "fileUrl")));
|
dto.setContentType(firstNonBlank(
|
extractTagIgnoreCase(descBlock, "contentType"),
|
extractTagIgnoreCase(descBlock, "contenType")));
|
dto.setFileSize(parseLong(firstNonBlank(
|
extractTagIgnoreCase(descBlock, "size"),
|
extractSizeFromUri(dto.getPlaybackUri()))));
|
String mediaId = extractTagIgnoreCase(descBlock, "mediaID");
|
dto.setFileName(firstNonBlank(
|
extractTagIgnoreCase(descBlock, "name"),
|
extractTagIgnoreCase(descBlock, "fileName"),
|
mediaId,
|
extractTagIgnoreCase(block, "sourceID")));
|
dto.setUserName(firstNonBlank(
|
extractTagIgnoreCase(descBlock, "policeName"),
|
extractTagIgnoreCase(block, "policeName")));
|
dto.setRecorderSn(firstNonBlank(
|
extractTagIgnoreCase(descBlock, "bodyCameraShortSN"),
|
extractTagIgnoreCase(descBlock, "recorderCode"),
|
extractTagIgnoreCase(block, "bodyCameraShortSN"),
|
extractTagIgnoreCase(block, "recorderCode"),
|
extractTagIgnoreCase(descBlock, "shortSerialNumber")));
|
if (StringUtils.isBlank(dto.getFileName())) {
|
dto.setFileName(extractNameFromUri(dto.getPlaybackUri()));
|
}
|
applyFileIdentity(dto, mediaId);
|
dto.setMediaType(resolveMediaType(dto.getContentType(), dto.getTrackId(), dto.getFileName()));
|
return dto;
|
}
|
|
private static void applyFileIdentity(MediaItemDTO dto, String mediaId) {
|
if (StringUtils.isBlank(dto.getFileIndex())) {
|
dto.setFileIndex(firstNonBlank(
|
mediaId,
|
StringUtils.isNoneBlank(dto.getTrackId(), dto.getFileName())
|
? dto.getTrackId() + "_" + dto.getFileName() : null,
|
dto.getFileName()));
|
}
|
}
|
|
private static String resolveContentType(Element desc) {
|
return firstNonBlank(getChildText(desc, "contentType"), getChildText(desc, "contenType"));
|
}
|
|
public static String parseSecurityToken(String json) {
|
if (StringUtils.isBlank(json)) {
|
return null;
|
}
|
Matcher m = Pattern.compile("\"value\"\\s*:\\s*\"([^\"]+)\"").matcher(json);
|
if (m.find()) {
|
return m.group(1);
|
}
|
return null;
|
}
|
|
private static MediaItemDTO parseMatchItem(Element item) {
|
MediaItemDTO dto = new MediaItemDTO();
|
dto.setTrackId(firstNonBlank(
|
getChildText(item, "trackID"),
|
getChildText(item, "trackId"),
|
getChildText(item, "trackid")));
|
|
Element timeSpan = findFirstChildElement(item, "timeSpan");
|
if (timeSpan != null) {
|
dto.setStartTime(parseIsapiTime(getChildText(timeSpan, "startTime")));
|
dto.setEndTime(parseIsapiTime(getChildText(timeSpan, "endTime")));
|
} else {
|
dto.setStartTime(parseIsapiTime(getChildText(item, "startTime")));
|
dto.setEndTime(parseIsapiTime(getChildText(item, "endTime")));
|
}
|
|
Element desc = findDescriptorElement(item);
|
|
dto.setPlaybackUri(firstNonBlank(
|
getChildText(desc, "playbackURI"),
|
getChildText(desc, "playbackUri"),
|
getChildText(desc, "downloadURI"),
|
getChildText(desc, "fileUrl"),
|
getChildText(item, "playbackURI"),
|
getChildText(item, "downloadURI")));
|
dto.setContentType(resolveContentType(desc));
|
dto.setFileSize(parseLong(firstNonBlank(
|
getChildText(desc, "size"),
|
getChildText(desc, "fileSize"),
|
extractSizeFromUri(dto.getPlaybackUri()))));
|
String mediaId = getChildText(desc, "mediaID");
|
String name = firstNonBlank(
|
getChildText(desc, "name"),
|
getChildText(desc, "fileName"),
|
getChildText(item, "name"),
|
getChildText(item, "fileName"),
|
mediaId,
|
getChildText(item, "sourceID"));
|
if (StringUtils.isBlank(name)) {
|
name = extractNameFromUri(dto.getPlaybackUri());
|
}
|
dto.setFileName(name);
|
applyFileIdentity(dto, mediaId);
|
dto.setUserName(firstNonBlank(
|
getChildText(desc, "policeName"),
|
getChildText(item, "policeName"),
|
getChildText(desc, "userName")));
|
dto.setRecorderSn(firstNonBlank(
|
getChildText(desc, "bodyCameraShortSN"),
|
getChildText(desc, "recorderCode"),
|
getChildText(item, "bodyCameraShortSN"),
|
getChildText(item, "recorderCode"),
|
getChildText(desc, "shortSerialNumber"),
|
getChildText(item, "shortSerialNumber")));
|
dto.setMediaType(resolveMediaType(dto.getContentType(), dto.getTrackId(), dto.getFileName()));
|
return dto;
|
}
|
|
private static Element findDescriptorElement(Element item) {
|
NodeList descriptors = getElementsByLocalName(item, "mediaSegmentDescriptor");
|
if (descriptors.getLength() == 0) {
|
descriptors = getElementsByLocalName(item, "recordSegmentDescriptor");
|
}
|
if (descriptors.getLength() == 0) {
|
descriptors = getElementsByLocalName(item, "segmentDescriptor");
|
}
|
return descriptors.getLength() > 0 ? (Element) descriptors.item(0) : item;
|
}
|
|
private static Element findFirstChildElement(Element parent, String localName) {
|
if (parent == null) {
|
return null;
|
}
|
NodeList nodes = parent.getChildNodes();
|
for (int i = 0; i < nodes.getLength(); i++) {
|
Node node = nodes.item(i);
|
if (node.getNodeType() != Node.ELEMENT_NODE) {
|
continue;
|
}
|
Element el = (Element) node;
|
String name = el.getLocalName() != null ? el.getLocalName() : el.getNodeName();
|
if (localName.equalsIgnoreCase(name)) {
|
return el;
|
}
|
}
|
return null;
|
}
|
|
private static int resolveStreamType(String trackId) {
|
if (StringUtils.isBlank(trackId) || trackId.length() < 3) {
|
return 0;
|
}
|
char last = trackId.charAt(trackId.length() - 1);
|
if (last == '3') {
|
return 2;
|
}
|
if (last == '2') {
|
return 1;
|
}
|
return 0;
|
}
|
|
private static int resolveMediaType(String contentType, String trackId, String fileName) {
|
if (StringUtils.isNotBlank(contentType)) {
|
String lower = contentType.toLowerCase();
|
if (lower.contains("picture") || lower.contains("image") || lower.equals("metadata")) {
|
return 1;
|
}
|
if (lower.contains("audio")) {
|
return 2;
|
}
|
if (lower.contains("video")) {
|
return 0;
|
}
|
}
|
if (StringUtils.isNotBlank(fileName)) {
|
String lower = fileName.toLowerCase();
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".png")
|
|| lower.endsWith(".bmp")) {
|
return 1;
|
}
|
if (lower.endsWith(".mp3") || lower.endsWith(".wav") || lower.endsWith(".aac")) {
|
return 2;
|
}
|
if (lower.endsWith(".mp4") || lower.endsWith(".avi") || lower.endsWith(".mov")
|
|| lower.endsWith(".mkv")) {
|
return 0;
|
}
|
}
|
if (StringUtils.isNotBlank(trackId) && trackId.endsWith("3")) {
|
return 1;
|
}
|
return 0;
|
}
|
|
private static int resolveMediaType(String contentType, String trackId) {
|
return resolveMediaType(contentType, trackId, null);
|
}
|
|
private static String extractNameFromUri(String uri) {
|
if (StringUtils.isBlank(uri)) {
|
return null;
|
}
|
String normalized = uri.replace("&", "&");
|
Matcher m = Pattern.compile("[?&](?:name|filename|fileName)=([^&\\s]+)", Pattern.CASE_INSENSITIVE).matcher(normalized);
|
if (m.find()) {
|
try {
|
return URLDecoder.decode(m.group(1), StandardCharsets.UTF_8.name());
|
} catch (Exception e) {
|
return m.group(1);
|
}
|
}
|
int slash = normalized.lastIndexOf('/');
|
if (slash >= 0 && slash < normalized.length() - 1) {
|
String tail = normalized.substring(slash + 1);
|
int q = tail.indexOf('?');
|
if (q > 0) {
|
tail = tail.substring(0, q);
|
}
|
if (StringUtils.isNotBlank(tail) && !tail.equals("tracks")) {
|
return tail;
|
}
|
}
|
return null;
|
}
|
|
private static String buildFileIndexFallback(MediaItemDTO dto) {
|
if (StringUtils.isNotBlank(dto.getPlaybackUri())) {
|
return String.valueOf(Math.abs(dto.getPlaybackUri().hashCode()));
|
}
|
if (dto.getStartTime() != null) {
|
return StringUtils.defaultString(dto.getTrackId(), "track") + "_" + dto.getStartTime().getTime();
|
}
|
return null;
|
}
|
|
private static String getDirectChildText(Element parent, String tag) {
|
if (parent == null) {
|
return null;
|
}
|
NodeList nodes = parent.getChildNodes();
|
for (int i = 0; i < nodes.getLength(); i++) {
|
Node node = nodes.item(i);
|
if (node.getNodeType() == Node.ELEMENT_NODE && tag.equalsIgnoreCase(node.getNodeName())) {
|
return node.getTextContent();
|
}
|
}
|
return null;
|
}
|
|
private static NodeList getElementsByLocalName(Element root, String localName) {
|
NodeList nodes = root.getElementsByTagNameNS("*", localName);
|
if (nodes.getLength() > 0) {
|
return nodes;
|
}
|
nodes = root.getElementsByTagName(localName);
|
if (nodes.getLength() > 0) {
|
return nodes;
|
}
|
return findElementsByLocalNameIgnoreCase(root, localName);
|
}
|
|
private static NodeList findElementsByLocalNameIgnoreCase(Element root, String localName) {
|
java.util.List<Element> matched = new ArrayList<>();
|
collectElementsByLocalNameIgnoreCase(root, localName.toLowerCase(), matched);
|
return new NodeListWrapper(matched);
|
}
|
|
private static void collectElementsByLocalNameIgnoreCase(Element element, String localNameLower,
|
java.util.List<Element> matched) {
|
if (localNameLower.equals(element.getLocalName() != null
|
? element.getLocalName().toLowerCase()
|
: element.getNodeName().toLowerCase())) {
|
matched.add(element);
|
}
|
NodeList children = element.getChildNodes();
|
for (int i = 0; i < children.getLength(); i++) {
|
Node child = children.item(i);
|
if (child.getNodeType() == Node.ELEMENT_NODE) {
|
collectElementsByLocalNameIgnoreCase((Element) child, localNameLower, matched);
|
}
|
}
|
}
|
|
private static String normalizeXml(String xml) {
|
String text = stripBom(StringUtils.defaultString(xml).trim());
|
if (text.startsWith("\"") && text.endsWith("\"")) {
|
text = text.substring(1, text.length() - 1);
|
}
|
if (text.startsWith("<") || text.startsWith("<")) {
|
text = text.replace("<", "<").replace(">", ">").replace(""", "\"");
|
}
|
return text;
|
}
|
|
private static String extractSizeFromUri(String uri) {
|
if (StringUtils.isBlank(uri)) {
|
return null;
|
}
|
Matcher matcher = Pattern.compile("[?&;]size=([0-9]+)", Pattern.CASE_INSENSITIVE)
|
.matcher(uri.replace("&", "&"));
|
return matcher.find() ? matcher.group(1) : null;
|
}
|
|
private static String stripBom(String text) {
|
if (text != null && text.startsWith("\uFEFF")) {
|
return text.substring(1);
|
}
|
return text;
|
}
|
|
private static String extractTagIgnoreCase(String xml, String tag) {
|
Pattern pattern = Pattern.compile(
|
"(?is)<(?:\\w+:)?" + Pattern.quote(tag) + "\\b[^>]*>([^<]*)</(?:\\w+:)?" + Pattern.quote(tag) + "\\s*>");
|
Matcher matcher = pattern.matcher(xml);
|
return matcher.find() ? matcher.group(1).trim() : null;
|
}
|
|
private static String extractNestedTagIgnoreCase(String xml, String parentTag, String childTag) {
|
String parentBlock = extractBlockIgnoreCase(xml, parentTag);
|
if (StringUtils.isBlank(parentBlock)) {
|
return null;
|
}
|
return extractTagIgnoreCase(parentBlock, childTag);
|
}
|
|
private static String extractBlockIgnoreCase(String xml, String tag) {
|
Pattern pattern = Pattern.compile(
|
"(?is)<(?:\\w+:)?" + Pattern.quote(tag) + "\\b[^>]*>(.*?)</(?:\\w+:)?" + Pattern.quote(tag) + "\\s*>");
|
Matcher matcher = pattern.matcher(xml);
|
return matcher.find() ? matcher.group(1) : null;
|
}
|
|
/** 兼容命名空间 XML 的 NodeList */
|
private static class NodeListWrapper implements NodeList {
|
private final java.util.List<Element> elements;
|
|
NodeListWrapper(java.util.List<Element> elements) {
|
this.elements = elements;
|
}
|
|
@Override
|
public Node item(int index) {
|
return elements.get(index);
|
}
|
|
@Override
|
public int getLength() {
|
return elements.size();
|
}
|
}
|
|
private static Document parseDocument(String xml) throws Exception {
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
factory.setNamespaceAware(true);
|
return factory.newDocumentBuilder()
|
.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
|
}
|
|
private static String getChildText(Element parent, String tag) {
|
if (parent == null) {
|
return null;
|
}
|
NodeList nodes = getElementsByLocalName(parent, tag);
|
if (nodes.getLength() == 0) {
|
return null;
|
}
|
return nodes.item(0).getTextContent();
|
}
|
|
private static String extractTag(String xml, String tag) {
|
Matcher m = Pattern.compile("<" + tag + ">([^<]*)</" + tag + ">").matcher(xml);
|
if (m.find()) {
|
return m.group(1).trim();
|
}
|
return null;
|
}
|
|
private static long parseLong(String val) {
|
if (StringUtils.isBlank(val)) {
|
return 0;
|
}
|
try {
|
return Long.parseLong(val.trim());
|
} catch (NumberFormatException e) {
|
return 0;
|
}
|
}
|
|
private static Date parseIsapiTime(String timeStr) {
|
if (StringUtils.isBlank(timeStr)) {
|
return null;
|
}
|
String[] patterns = {
|
"yyyy-MM-dd'T'HH:mm:ss'Z'",
|
"yyyy-MM-dd'T'HH:mm:ssXXX",
|
"yyyy-MM-dd'T'HH:mm:ss",
|
"yyyyMMdd'T'HHmmss'Z'",
|
"yyyyMMdd'T'HHmmss"
|
};
|
for (String pattern : patterns) {
|
try {
|
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
|
if (pattern.endsWith("'Z'")) {
|
sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
|
}
|
return sdf.parse(timeStr.trim());
|
} catch (Exception ignored) {
|
}
|
}
|
return null;
|
}
|
|
private static String firstNonBlank(String... values) {
|
for (String v : values) {
|
if (StringUtils.isNotBlank(v)) {
|
return v;
|
}
|
}
|
return null;
|
}
|
}
|