最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java基于Tabula實(shí)現(xiàn)PDF合并單元格內(nèi)容的提取

 更新時間:2025年10月22日 08:28:06   作者:Kida的躺平小屋  
這篇文章主要為大家詳細(xì)介紹了Java如何基于Tabula實(shí)現(xiàn)PDF合并單元格內(nèi)容的提取,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

坑還是要填的,但是填得是否平整就有待商榷了(狗頭保命...)。

本人技術(shù)有限,只能幫各位實(shí)現(xiàn)的這個地步了。各路大神如果還有更好的實(shí)現(xiàn)也可以發(fā)出來跟小弟共勉一下哈。

首先需要說一下的是以下提供的代碼僅作研究參考使用,各位在使用之前務(wù)必自檢,因?yàn)椴⒉皇撬?pdf 的表格格式都適合。

本次實(shí)現(xiàn)的難點(diǎn)在于 PDF 是一種視覺格式,而不是語義格式。

它只記錄了“在 (x, y) 坐標(biāo)繪制文本 'ABC'”和“從 (x1, y1) 到 (x2, y2) 繪制一條線”。它根本不“知道”什么是“表格”、“行”或“合并單元格”。而 Tabula 的 SpreadsheetExtractionAlgorithm 算法是處理這種問題的最佳起點(diǎn),但它提取的結(jié)果會是“不規(guī)則”的,即每行的單元格數(shù)量可能不同。因此本次將采用后處理的方式進(jìn)行解析,Tabula 更多的只是作內(nèi)容提取,表格組織還是在后期處理進(jìn)行的。

就像上次的文章中說到

【Java】采用 Tabula 技術(shù)對 PDF 文件內(nèi)表格進(jìn)行數(shù)據(jù)提取

本次解決問題的核心思路就是通過計算每一個單元格完整的邊界框,得到它的 top,left, bottom,right。通過收集所有單元格的 top 坐標(biāo)和 bottom 坐標(biāo),推斷出表格中所有“真實(shí)”的行邊界。同理,通過收集所有單元格的 left 坐標(biāo)和 right 坐標(biāo),可以推斷出所有“真實(shí)”的列邊界。最后基于這些邊界構(gòu)建一個完整的網(wǎng)格,然后將 Tabula 提取的文本塊“放”回這個網(wǎng)格中。

為了方便測試我使用了 Deepseek 官網(wǎng)“模型細(xì)節(jié)”章節(jié)里面的那個表格。

這個表格是比較經(jīng)典的,既有列合并單元格,也有行合并單元格。而且表格中并沒有那么多復(fù)雜的內(nèi)容。

下面是我的執(zhí)行代碼

package cn.paohe;

import java.awt.Point;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;

import org.apache.pdfbox.pdmodel.PDDocument;

import technology.tabula.ObjectExtractor;
import technology.tabula.Page;
import technology.tabula.PageIterator;
import technology.tabula.Rectangle;
import technology.tabula.RectangularTextContainer;
import technology.tabula.Table;
import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;

public class MergedCellPdfExtractor {

    // 浮點(diǎn)數(shù)比較的容差
    private static final float COORDINATE_TOLERANCE = 2.0f;

    /**
	 * 為了樣例方便,在類內(nèi)部直接封裝一個單元格,包含其文本和邊界
	 */
    static class Cell extends Rectangle {
        public String text;
        public float top;
        public float left;
        public double width;
        public double height;

        public Cell(float top, float left, double width, double height, String text) {
            this.top = top;
            this.left = left;
            this.width = width;
            this.height = height;
            this.text = text == null ? "" : text.trim();
        }

        @Override
        public float getTop() {
            return top;
        }

        @Override
        public float getLeft() {
            return left;
        }

        @Override
        public double getWidth() {
            return width;
        }

        @Override
        public double getHeight() {
            return height;
        }

        @Override
        public float getBottom() {
            return (float) (top + height);
        }

        @Override
        public float getRight() {
            return (float) (left + width);
        }

        @Override
        public double getX() {
            return left;
        }

        @Override
        public double getY() {
            return top;
        }

        @Override
        public Point[] getPoints() {
            return new Point[0];
        }

        @Override
        public String toString() {
            return String.format("Cell[t=%.2f, l=%.2f, w=%.2f, h=%.2f, text='%s']", top, left, width, height, text);
        }
    }

    /**
	 * 由于表格提取的時候會出現(xiàn)偏差,因此定義表格指紋,用于去重
	 */
    static class TableFingerprint {
        private final float top;
        private final float left;
        private final int rowCount;
        private final int colCount;
        private final String contentHash;

        public TableFingerprint(Table table) {
            this.top = roundCoordinate(table.getTop());
            this.left = roundCoordinate(table.getLeft());
            this.rowCount = table.getRowCount();
            this.colCount = table.getColCount();
            this.contentHash = generateContentHash(table);
        }

        /**
		 * 生成表格的內(nèi)容 Hash,用于快速比較兩個表格是否相同
		 * 
		 * Hash 生成規(guī)則:將每個單元格的文本內(nèi)容連接起來,使用 "|" 分隔 如果單元格的數(shù)量超過 10 個,就停止生成 Hash
		 * 
		 * @param table 需要生成 Hash 的表格
		 * @return 生成的 Hash
		 */
        private String generateContentHash(Table table) {
            StringBuilder sb = new StringBuilder();
            int cellCount = 0;
            for (List<RectangularTextContainer> row : table.getRows()) {
                for (RectangularTextContainer cell : row) {
                    sb.append(cell.getText()).append("|");
                    cellCount++;
                    if (cellCount > 10) {
                        break;
                    }
                }
                if (cellCount > 10) {
					break;
				}
			}
			return sb.toString();
		}

		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof TableFingerprint)) {
				return false;
			}

			// 將要比較的對象強(qiáng)制轉(zhuǎn)換為 TableFingerprint
			TableFingerprint other = (TableFingerprint) obj;

			// 兩個表格的 top 和 left 坐差不能超過 COORDINATE_TOLERANCE
			boolean topMatch = Math.abs(this.top - other.top) < COORDINATE_TOLERANCE;
			boolean leftMatch = Math.abs(this.left - other.left) < COORDINATE_TOLERANCE;

			// 兩個表格的行數(shù)和列數(shù)必須相同
			boolean rowMatch = this.rowCount == other.rowCount;
			boolean colMatch = this.colCount == other.colCount;

			// 兩個表格的內(nèi)容 Hash must be equal
			boolean contentMatch = this.contentHash.equals(other.contentHash);

			// 如果以上條件都滿足,則返回 true
			return topMatch && leftMatch && rowMatch && colMatch && contentMatch;
		}

		@Override
		public int hashCode() {
			return contentHash.hashCode();
		}

		/**
		 * 將坐標(biāo)四舍五入到指定精度,減少浮點(diǎn)誤差
		 * 
		 * @param coord 需要四舍五入的坐標(biāo)
		 * @return 四舍五入后的坐標(biāo)
		 */
		private static float roundCoordinate(float coord) {
			// 將坐標(biāo)乘以 10,然后將結(jié)果四舍五入,然后除以 10.0f,保留一個小數(shù)點(diǎn)
			return Math.round(coord * 10) / 10.0f;
		}
	}

	/**
	 * 解析 PDF 文件中的所有表格
	 * 
	 * 1. 使用 ObjectExtractor 將 PDF 文件中的所有表格進(jìn)行提取 2. 使用 SpreadsheetExtractionAlgorithm
	 * 基于線條檢測表格,避免重復(fù)表格 3. 規(guī)范化表格,處理合并單元格
	 * 
	 * @param pdfFile 要解析的 PDF 文件
	 * @return 規(guī)范化的表格數(shù)據(jù),每個 List<List<String>> 代表一個表格
	 * @throws IOException 文件讀取異常
	 */
	public List<List<List<String>>> parseTables(File pdfFile) throws IOException {
		List<List<List<String>>> allNormalizedTables = new ArrayList<>();
		Set<TableFingerprint> seenTables = new HashSet<>();

		InputStream bufferedStream = new BufferedInputStream(new FileInputStream(pdfFile));
		try (PDDocument pdDocument = PDDocument.load(bufferedStream)) {
			ObjectExtractor oe = new ObjectExtractor(pdDocument);
			PageIterator pi = oe.extract();

			while (pi.hasNext()) {
				Page page = pi.next();

				// 使用 SpreadsheetExtractionAlgorithm 基于線條檢測表格
				SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
				List<Table> tables = sea.extract(page);

				for (Table table : tables) {
					// 去重檢查
					TableFingerprint fingerprint = new TableFingerprint(table);
					if (seenTables.contains(fingerprint)) {
						System.out.println("跳過重復(fù)表格: top=" + fingerprint.top + ", left=" + fingerprint.left);
						continue;
					}
					seenTables.add(fingerprint);

					List<List<String>> normalized = normalizeTable(table);
					if (!normalized.isEmpty()) {
						allNormalizedTables.add(normalized);
					}
				}
			}
		}
		return allNormalizedTables;
	}

	/**
	 * 規(guī)范化表格,處理合并單元格
	 * 
	 * @param table Tabula 提取的原始表格
	 * @return 規(guī)范化的 List<List<String>>
	 */
	private List<List<String>> normalizeTable(Table table) {
		// 1. 提取所有單元格及其坐標(biāo)
		List<Cell> allCells = new ArrayList<>();
		for (List<RectangularTextContainer> row : table.getRows()) {
			for (RectangularTextContainer tc : row) {
				allCells.add(new Cell(tc.getTop(), tc.getLeft(), tc.getWidth(), tc.getHeight(), tc.getText()));
			}
		}

		if (allCells.isEmpty()) {
			return new ArrayList<>();
		}

		// 2. 收集所有唯一的行起始位置和列起始位置,并添加結(jié)束位置
		NavigableSet<Float> rowBoundaries = new TreeSet<>();
		NavigableSet<Float> colBoundaries = new TreeSet<>();

		for (Cell cell : allCells) {
			rowBoundaries.add(roundCoordinate(cell.getTop()));
			rowBoundaries.add(roundCoordinate(cell.getBottom()));
			colBoundaries.add(roundCoordinate(cell.getLeft()));
			colBoundaries.add(roundCoordinate(cell.getRight()));
		}

		// 3. 轉(zhuǎn)換為列表并去除首尾(表格外邊界)
		List<Float> rowCoords = new ArrayList<>(rowBoundaries);
		List<Float> colCoords = new ArrayList<>(colBoundaries);

		// 移除最小和最大值(表格外邊界),只保留內(nèi)部網(wǎng)格線
		if (rowCoords.size() > 2) {
			rowCoords.remove(rowCoords.size() - 1); // 移除最大值(底邊)
			rowCoords.remove(0); // 移除最小值(頂邊)
		}
		if (colCoords.size() > 2) {
			colCoords.remove(colCoords.size() - 1); // 移除最大值(右邊)
			colCoords.remove(0); // 移除最小值(左邊)
		}

		// 4. 驗(yàn)證網(wǎng)格有效性
		if (rowCoords.isEmpty() || colCoords.isEmpty()) {
			return tableToListOfListOfStrings(table);
		}

		int numRows = rowCoords.size();
		int numCols = colCoords.size();
		String[][] grid = new String[numRows][numCols];

		// 初始化所有單元格為 null
		for (int r = 0; r < numRows; r++) {
			for (int c = 0; c < numCols; c++) {
				grid[r][c] = null;
			}
		}

		// 5. 將單元格內(nèi)容填充到網(wǎng)格中
		for (Cell cell : allCells) {
			// 找到單元格在網(wǎng)格中的起始索引
			int startRow = findCellStartIndex(rowCoords, cell.getTop());
			int startCol = findCellStartIndex(colCoords, cell.getLeft());

			// 容錯處理
			if (startRow == -1 || startCol == -1) {
				continue;
			}

			// 確保索引有效
			if (startRow >= numRows || startCol >= numCols) {
				continue;
			}

			// 計算單元格跨越的行數(shù)和列數(shù)
			int endRow = findCellEndIndex(rowCoords, cell.getBottom());
			int endCol = findCellEndIndex(colCoords, cell.getRight());

			if (endRow == -1)
				endRow = numRows - 1;
			if (endCol == -1)
				endCol = numCols - 1;

			// 將文本放置在左上角單元格
			if (grid[startRow][startCol] == null) {
				grid[startRow][startCol] = cell.text;
			} else {
				// 如果已有內(nèi)容,追加(處理重疊情況)
				if (!grid[startRow][startCol].isEmpty() && !cell.text.isEmpty()) {
					grid[startRow][startCol] += " " + cell.text;
				} else if (!cell.text.isEmpty()) {
					grid[startRow][startCol] = cell.text;
				}
			}

			// 標(biāo)記被合并覆蓋的其他單元格
			for (int r = startRow; r <= endRow && r < numRows; r++) {
				for (int c = startCol; c <= endCol && c < numCols; c++) {
					if (r == startRow && c == startCol) {
						continue; // 跳過左上角已填充的單元格
					}
					if (grid[r][c] == null) {
						grid[r][c] = ""; // 標(biāo)記為空字符串(合并單元格的一部分)
					}
				}
			}
		}

		// 6. 填充空單元格:優(yōu)先從左側(cè)填充,左側(cè)為空則從上方填充
		for (int r = 0; r < numRows; r++) {
			for (int c = 0; c < numCols; c++) {
				if (grid[r][c] == null || grid[r][c].isEmpty()) {
					String fillContent = null;

					// 優(yōu)先從左側(cè)獲取內(nèi)容
					if (c > 0 && grid[r][c - 1] != null && !grid[r][c - 1].isEmpty()) {
						fillContent = grid[r][c - 1];
					}
					// 左側(cè)為空或不存在,從上方獲取內(nèi)容
					else if (r > 0 && grid[r - 1][c] != null && !grid[r - 1][c].isEmpty()) {
						fillContent = grid[r - 1][c];
					}

					if (fillContent != null) {
						grid[r][c] = fillContent;
					} else if (grid[r][c] == null) {
						grid[r][c] = "";
					}
				}
			}
		}

		// 7. 將二維數(shù)組轉(zhuǎn)換為 List<List<String>>
		List<List<String>> normalizedTable = new ArrayList<>();
		for (int r = 0; r < numRows; r++) {
			List<String> normalizedRow = new ArrayList<>();
			for (int c = 0; c < numCols; c++) {
				normalizedRow.add(grid[r][c] == null ? "" : grid[r][c]);
			}
			normalizedTable.add(normalizedRow);
		}

		return normalizedTable;
	}

	/**
	 * 將坐標(biāo)四舍五入到指定精度,減少浮點(diǎn)誤差
	 */
	private float roundCoordinate(float coord) {
		return Math.round(coord * 10) / 10.0f;
	}

	/**
	 * 查找單元格起始位置在網(wǎng)格中的索引
	 */
	private int findCellStartIndex(List<Float> coords, float value) {
		float roundedValue = roundCoordinate(value);

		for (int i = 0; i < coords.size(); i++) {
			// 單元格的起始位置應(yīng)該在某個網(wǎng)格線上或之前
			if (roundedValue <= coords.get(i) + COORDINATE_TOLERANCE) {
				return i;
			}
		}

		return coords.size() - 1;
	}

	/**
	 * 查找單元格結(jié)束位置在網(wǎng)格中的索引
	 */
	private int findCellEndIndex(List<Float> coords, float value) {
		float roundedValue = roundCoordinate(value);

		for (int i = coords.size() - 1; i >= 0; i--) {
			// 單元格的結(jié)束位置應(yīng)該在某個網(wǎng)格線上或之后
			if (roundedValue >= coords.get(i) - COORDINATE_TOLERANCE) {
				return i;
			}
		}

		return 0;
	}

	/**
	 * 將 Tabula 的 Table 對象轉(zhuǎn)換為 List<List<String>>>
	 * 
	 * @param table Tabula 的 Table 對象
	 * @return List<List<String>>>
	 */
	public List<List<String>> tableToListOfListOfStrings(Table table) {
		// 創(chuàng)建一個列表來存儲表格內(nèi)容
		List<List<String>> list = new ArrayList<>();

		// 遍代表格中的每一行
		for (List<RectangularTextContainer> row : table.getRows()) {
			// 創(chuàng)建一個列表來存儲當(dāng)前行的內(nèi)容
			List<String> rowList = new ArrayList<>();

			// 遍代當(dāng)前行中的每一個單元格
			for (RectangularTextContainer tc : row) {
				// 將當(dāng)前單元格的內(nèi)容添加到行列表中
				String cellText = tc.getText() == null ? "" : tc.getText().trim();
				rowList.add(cellText);
				rowList.add(tc.getText() == null ? "" : tc.getText().trim());
			}

			// 將行列表添加到表格列表中
			list.add(rowList);
		}
		return list;
	}

	public static void main(String[] args) {
		// 請?zhí)鎿Q為你的 PDF 文件路徑
		String pdfPath = "/Users/yuanzhenhui/Desktop/測試用合并單元格解析.pdf";

		File pdfFile = new File(pdfPath);
		if (!pdfFile.exists()) {
			System.err.println("錯誤: 測試文件未找到: " + pdfPath);
			System.err.println("請在 main 方法中替換為你本地的 PDF 文件路徑。");
			return;
		}

		MergedCellPdfExtractor extractor = new MergedCellPdfExtractor();
		try {
			System.out.println("開始解析: " + pdfPath);
			List<List<List<String>>> tables = extractor.parseTables(pdfFile);

			System.out.println("解析完成,共找到 " + tables.size() + " 個表格。");
			System.out.println("========================================");

			int tableNum = 1;
			for (List<List<String>> table : tables) {
				System.out.println("\n表格 " + (tableNum++) + ":");
				System.out.println("行數(shù): " + table.size() + ", 列數(shù): " + (table.isEmpty() ? 0 : table.get(0).size()));
				System.out.println("----------------------------------------");

				for (List<String> row : table) {
					System.out.print("|");
					for (String cell : row) {
						String cellText = cell.replace("\n", " ").replace("\r", " ");
						if (cellText.length() > 15) {
							cellText = cellText.substring(0, 12) + "...";
						}
						System.out.print(String.format(" %-15s |", cellText));
					}
					System.out.println();
				}
				System.out.println("----------------------------------------");
			}

		} catch (IOException e) {
			System.err.println("解析 PDF 時出錯: " + e.getMessage());
			e.printStackTrace();
		}
	}
}

關(guān)于代碼的解釋應(yīng)該都清楚的了,由于只是用作試驗(yàn)我就沒有很精細(xì)地封裝了,大家湊合著用吧。如果面對更加復(fù)雜的表格的話我建議還是不要用這種填充的方式了,直接上大廠的 OCR 接口吧。

哦,還有東西忘了說了,關(guān)于 Maven 依賴的引入如下:

<dependency>
  <groupId>technology.tabula</groupId>
  <artifactId>tabula</artifactId>
  <version>1.0.5</version>
</dependency>
<dependency>
  <groupId>org.apache.pdfbox</groupId>
  <artifactId>pdfbox</artifactId>
  <version>2.0.35</version>
</dependency>

好了,該填的可能填好了。

以上就是Java基于Tabula實(shí)現(xiàn)PDF合并單元格內(nèi)容的提取的詳細(xì)內(nèi)容,更多關(guān)于Java Tabula提取PDF的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 關(guān)于SpringMVC的異常處理機(jī)制詳細(xì)解讀

    關(guān)于SpringMVC的異常處理機(jī)制詳細(xì)解讀

    這篇文章主要介紹了關(guān)于SpringMVC的異常處理機(jī)制詳細(xì)解讀,SpringMVC是目前主流的Web?MVC框架之一,本文將分析SpringMVC的異常處理內(nèi)容,需要的朋友可以參考下
    2023-05-05
  • 關(guān)于Selenium的UI自動化測試屏幕截圖功能實(shí)例代碼

    關(guān)于Selenium的UI自動化測試屏幕截圖功能實(shí)例代碼

    今天小編就為大家分享一篇關(guān)于Selenium的UI自動化測試屏幕截圖功能實(shí)例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • SpringBoot整合Druid實(shí)現(xiàn)SQL監(jiān)控和數(shù)據(jù)庫密碼加密

    SpringBoot整合Druid實(shí)現(xiàn)SQL監(jiān)控和數(shù)據(jù)庫密碼加密

    Druid連接池是阿里巴巴開源的數(shù)據(jù)庫連接池項(xiàng)目,Druid連接池為監(jiān)控而生,內(nèi)置強(qiáng)大的監(jiān)控功能,監(jiān)控特性不影響性能,本文給大家介紹了SpringBoot整合Druid實(shí)現(xiàn)SQL監(jiān)控和數(shù)據(jù)庫密碼加密,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下
    2024-06-06
  • 詳解Spring Data JPA動態(tài)條件查詢的寫法

    詳解Spring Data JPA動態(tài)條件查詢的寫法

    本篇文章主要介紹了Spring Data JPA動態(tài)條件查詢的寫法 ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Java中使用Lambda表達(dá)式和函數(shù)編程示例

    Java中使用Lambda表達(dá)式和函數(shù)編程示例

    這篇文章介紹了Java中使用Lambda表達(dá)式和函數(shù)編程示例,該文章會演示多個示列,分別是變量聲明上下文中的lambda、return語句上下文中的lambda、賦值上下文中的lambda、lambda在數(shù)組初始值設(shè)定項(xiàng)上下文中的用法等等,需要的朋友可以參考一下
    2021-10-10
  • Java利用Spire.PDF實(shí)現(xiàn)將PDF文檔轉(zhuǎn)換為Word格式

    Java利用Spire.PDF實(shí)現(xiàn)將PDF文檔轉(zhuǎn)換為Word格式

    在日常工作和學(xué)習(xí)中,我們經(jīng)常會遇到PDF文檔,本文將詳細(xì)介紹如何才能高效、精準(zhǔn)地使用Java實(shí)現(xiàn)PDF到Word的自動化轉(zhuǎn)換,感興趣的小伙伴可以了解下
    2025-09-09
  • 深入剖析Java編程中的序列化

    深入剖析Java編程中的序列化

    這篇文章主要介紹了深入剖析Java編程中的序列化,文中談到了序列化時對象的繼承等各種問題,案例詳盡,強(qiáng)烈推薦!需要的朋友可以參考下
    2015-07-07
  • 在 IntelliJ IDEA 中安裝和配置 Java 17的實(shí)戰(zhàn)記錄

    在 IntelliJ IDEA 中安裝和配置 Java 17的實(shí)戰(zhàn)記錄

    文章介紹了如何在IntelliJ IDEA中安裝和配置Java 17,包括下載JDK、配置項(xiàng)目SDK、設(shè)置語言級別以及驗(yàn)證配置,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2025-10-10
  • Java讀取DWG文件的示例詳解

    Java讀取DWG文件的示例詳解

    本文將通過一個代碼示例,展示如何使用Teigha Java API來讀取DWG文件,并解釋了實(shí)現(xiàn)這一功能所需的關(guān)鍵步驟,包括添加依賴、初始化API、打開文件、處理數(shù)據(jù)和關(guān)閉文件,感興趣的朋友一起看看吧
    2025-06-06
  • java并發(fā)編程專題(十)----(JUC原子類)基本類型詳解

    java并發(fā)編程專題(十)----(JUC原子類)基本類型詳解

    這篇文章主要介紹了java JUC原子類基本類型詳解的相關(guān)資料,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07

最新評論

丹阳市| 颍上县| 南投县| 左权县| 水城县| 江华| 连城县| 康马县| 光泽县| 崇左市| 沾益县| 宜君县| 宁国市| 封开县| 镇宁| 含山县| 安徽省| 当涂县| 岳西县| 克东县| 栾城县| 宁晋县| 泰和县| 黔江区| 文昌市| 汉川市| 石城县| 宁国市| 清河县| 建水县| 甘孜县| 峡江县| 手游| 宜宾市| 昆山市| 台湾省| 夹江县| 马鞍山市| 哈尔滨市| 白沙| 新安县|