# DailyOperatingStatisticsParser.java

    package cloud.wconnect.workperformance.service.pdf;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.workperformance.pojo.dto.pdf.OperatingStatisticsPdfDTO;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.node.ArrayNode;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.pdfbox.pdmodel.PDDocument;
    import org.apache.pdfbox.text.PDFTextStripper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.io.IOException;
    import java.io.InputStream;
    import java.math.BigDecimal;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.time.format.DateTimeFormatterBuilder;
    import java.time.temporal.ChronoField;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;

    /**
     * Daily Operating Statistics PDF报告解析器
     */
    @Service
    public class DailyOperatingStatisticsParser {

        @Autowired
        private ObjectMapper objectMapper;

        /**
         * 解析PDF报告
         *
         * @param inputStream 文件输入流
         * @return java.util.List<cloud.wconnect.workperformance.pojo.dto.pdf.OperatingStatisticsPdfDTO>
         * @author lichen
         * @date 2025/10/8
         */
        public List<OperatingStatisticsPdfDTO> parse(InputStream inputStream) throws IOException {
            String json = doParse(inputStream);
            return JsonUtils.convertStringToList(objectMapper, json, OperatingStatisticsPdfDTO.class);
        }

        /**
         * 解析PDF报告
         *
         * @param inputStream 文件输入流
         * @return java.lang.String
         * @author lichen
         * @date 2025/10/8
         */
        public String doParse(InputStream inputStream) throws IOException {
            try (PDDocument document = PDDocument.load(inputStream)) {
                PDFTextStripper stripper = new PDFTextStripper();
                String text = stripper.getText(document);

                List<Map<String, Object>> hotelData = parseHotelData(text);

                ArrayNode jsonArray = objectMapper.createArrayNode();
                for (Map<String, Object> hotel : hotelData) {
                    ObjectNode hotelNode = objectMapper.createObjectNode();
                    hotelNode.put("hotel", (String) hotel.get("hotel"));

                    ArrayNode contentArray = objectMapper.createArrayNode();
                    @SuppressWarnings("unchecked")
                    List<Map<String, Object>> contentList = (List<Map<String, Object>>) hotel.get("content");

                    for (Map<String, Object> content : contentList) {
                        ObjectNode contentNode = objectMapper.createObjectNode();
                        contentNode.put("date", (String) content.get("date"));
                        contentNode.put("rooms_rented", (Integer) content.get("rooms_rented"));
                        contentNode.put("occ_percent", (String) content.get("occ_percent"));
                        contentNode.put("adr", (String) content.get("adr"));
                        contentNode.put("revPAR", (String) content.get("revPAR"));
                        contentNode.put("room_rev", (String) content.get("room_rev"));
                        contentNode.put("food_rev", (String) content.get("food_rev"));
                        contentNode.put("beverage_rev", (String) content.get("beverage_rev"));
                        contentNode.put("other_rev", (String) content.get("other_rev"));
                        contentNode.put("total_rev", (String) content.get("total_rev"));
                        contentArray.add(contentNode);
                    }

                    hotelNode.set("content", contentArray);
                    jsonArray.add(hotelNode);
                }

                return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonArray);
            }
        }

        private List<Map<String, Object>> parseHotelData(String text) {
            Map<String, List<Map<String, Object>>> hotelDataMap = new HashMap<>();
            String currentHotel = null;

            String[] lines = text.split("\n");

            for (String line : lines) {
                line = line.trim();
                if (line.isEmpty()) continue;

                // 1. 判断该行是否包含日期列
                if (!containsDate(line)) {
                    continue; // 直接忽略该行
                }

                // 2. 排除汇总行
                if (line.contains("Total")) {
                    continue;
                }

                // 3. 检查是否是每个月第一天的行（包含酒店标识）
                String hotelName = extractHotelNameFromLine(line);
                if (hotelName != null) {
                    currentHotel = hotelName; // 更新当前酒店
                }

                // 4. 解析数据行
                if (currentHotel != null) {
                    Map<String, Object> parsedData = parseDataLine(currentHotel, line);
                    if (parsedData != null) {
                        hotelDataMap.computeIfAbsent(currentHotel, k -> new ArrayList<>()).add(parsedData);
                    }
                }
            }

            // 转换为最终格式
            List<Map<String, Object>> result = new ArrayList<>();
            for (Map.Entry<String, List<Map<String, Object>>> entry : hotelDataMap.entrySet()) {
                Map<String, Object> hotelResult = new HashMap<>();
                hotelResult.put("hotel", entry.getKey());
                hotelResult.put("content", entry.getValue());
                result.add(hotelResult);
            }

            return result;
        }

        private boolean containsDate(String line) {
            // 检查行是否包含日期格式 x/x/xx
            return line.matches(".*\\d{1,2}/\\d{1,2}/\\d{2}.*");
        }

        private String extractHotelNameFromLine(String line) {
            // 只有第一天的行包含酒店名称，格式：酒店名称 + 日期 + 数据
            if (line.contains("APA HOTEL Woodbridge")) {
                return "ahw";
            } else if (line.contains("Crowne Plaza Edison") && !line.contains("Newark")) {
                return "cpe";
            } else if (line.contains("Crowne Plaza Newark")) {
                return "cpn";
            } else if (line.contains("Embassy Suites")) {
                return "esp";
            } else if (line.contains("Garden Executive")) {
                return "geh";
            } else if (line.contains("Holiday Inn South")) {
                return "hsp";
            } else if (line.contains("Holiday Inn Newark")) {
                return "hin";
            }
            return null; // 不是第一天的行，没有酒店名称
        }

        private Map<String, Object> parseDataLine(String currentHotel, String line) {
            // 使用日期作为分界点，提取日期后面的数据部分
            Pattern datePattern = Pattern.compile("(\\d{1,2}/\\d{1,2}/\\d{2})(.*)");
            Matcher dateMatcher = datePattern.matcher(line);
            if (!dateMatcher.find()) {
                return null;
            }

            String date = dateMatcher.group(1);
            String dataAfterDate = dateMatcher.group(2).trim();

            // 将数据部分按空格分割，提取各个字段
            String[] parts = dataAfterDate.split("\\s+");

            Map<String, Object> data = new HashMap<>();
            data.put("date", date);

            switch (currentHotel) {
                case "ahw":
                    if (parts.length == 10) {
                        parseLength10(parts, data);
                    }
                    break;
                case "cpe", "cpn", "esp":
                    if (parts.length == 9) {
                        parseLength9(parts, data);
                    }
                    break;
                case "geh", "hin":
                    if (parts.length == 7) {
                        parseLength7(parts, data);
                    }
                    break;
                case "hsp":
                    if (parts.length == 8) {
                        parseLength8(parts, data);
                    }
                    break;
                default:;
            }

            // 如果数据长度小于等于1，则忽略
            if(data.size() == 1){
                return null;
            }

            return data;
        }

        private void parseLength10(String[] parts, Map<String, Object> data) {
            data.put("rooms_rented", Integer.parseInt(parts[0]));
            data.put("occ_percent", parts[1]);
            data.put("adr", parts[2]);
            data.put("revPAR", parts[3]);
            data.put("room_rev", parts[4]);
            data.put("food_rev", parts[5]);
            data.put("beverage_rev", parts[7]);
            data.put("other_rev", parts[8]);
            data.put("total_rev", parts[9]);
        }

        private void parseLength9(String[] parts, Map<String, Object> data) {
            data.put("rooms_rented", Integer.parseInt(parts[0]));
            data.put("occ_percent", parts[1]);
            data.put("adr", parts[2]);
            data.put("revPAR", parts[3]);
            data.put("room_rev", parts[4]);
            data.put("food_rev", parts[5]);
            data.put("beverage_rev", parts[6]);
            data.put("other_rev", parts[7]);
            data.put("total_rev", parts[8]);
        }

        private void parseLength6(String[] parts, Map<String, Object> data) {
            data.put("rooms_rented", Integer.parseInt(parts[0]));
            data.put("occ_percent", "");
            data.put("adr", "");
            data.put("revPAR", "");
            data.put("room_rev", parts[1]);
            data.put("food_rev", parts[2]);
            data.put("beverage_rev", parts[3]);
            data.put("other_rev", parts[4]);
            data.put("total_rev", parts[5]);
        }

        private void parseLength7(String[] parts, Map<String, Object> data) {
            data.put("rooms_rented", Integer.parseInt(parts[0]));
            data.put("occ_percent", parts[1]);
            data.put("adr", parts[2]);
            data.put("revPAR", parts[3]);
            data.put("room_rev", parts[4]);
            data.put("food_rev", "");
            data.put("beverage_rev", "");
            data.put("other_rev", parts[5]);
            data.put("total_rev", parts[6]);
        }

        private void parseLength8(String[] parts, Map<String, Object> data) {
            data.put("rooms_rented", Integer.parseInt(parts[0]));
            data.put("occ_percent", parts[1]);
            data.put("adr", parts[2]);
            data.put("revPAR", parts[3]);
            data.put("room_rev", parts[4]);
            data.put("food_rev", parts[5]);
            data.put("beverage_rev", "");
            data.put("other_rev", parts[6]);
            data.put("total_rev", parts[7]);
        }

        /**
         * 格式化occ%
         *
         * @param occPercent occ%
         * @return java.math.BigDecimal
         * @author lichen
         * @date 2025/10/8
         */
        public BigDecimal formatPercent(String occPercent) {
            if (StringUtils.isBlank(occPercent)) return null;

            //去除千分位和百分号
            String replace = occPercent.replace(",", "").replace("%", "");

            return new BigDecimal(replace);
        }

        /**
         * 格式化日期
         *
         * @param date 日期，例如：9/1/23，10/1/23，11/12/23
         * @return java.lang.String
         * @author lichen
         * @date 2025/10/8
         */
        public String formatDate(String date) {
            DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                    .appendPattern("M/d/")
                    .appendValueReduced(ChronoField.YEAR, 2, 2, 2000)
                    .toFormatter();

            LocalDate localDate = LocalDate.parse(date, formatter);
            return DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);
        }

        /**
         * 格式化金额
         *
         * @param currencyStr 金额，例如：$0.00，$1,785.00，$2.00
         * @return java.math.BigDecimal
         * @author lichen
         * @date 2025/10/8
         */
        public BigDecimal formatAmount(String currencyStr) {
            if (StringUtils.isBlank(currencyStr)) return null;

            // 移除货币符号和千位分隔符
            String cleanStr = currencyStr.replaceAll("[$,]", "");

            if (StringUtils.isBlank(cleanStr)) return null;

            return new BigDecimal(cleanStr);
        }
    }