यहां तक कि आज उपलब्ध पुस्तकालयों की भीड़ के बावजूद, कभी-कभी ऐसा पुस्तकालय ढूंढना चुनौतीपूर्ण हो सकता है जो किसी विशेष कार्य के लिए आवश्यक विशिष्ट कार्यक्षमता प्रदान करता हो। संपूर्ण लाइब्रेरी की खोज में समय बर्बाद करने के बजाय, मैं अपना खुद का कार्यान्वयन तैयार करने की सलाह देता हूं; भले ही इसे विशेष रूप से एक प्रोजेक्ट के लिए तैयार किया गया हो।
एक बार, मुझे एक ऐसे पुस्तकालय की आवश्यकता महसूस हुई जो रिपोर्ट निर्माण के लिए डेटा कक्षाओं को आसानी से एक्सेल दस्तावेज़ में परिवर्तित कर सके। जब मुझे कोई उपयुक्त पुस्तकालय नहीं मिला, तो मैंने अपनी विशिष्ट आवश्यकताओं के अनुरूप कार्यक्षमता विकसित करने का निर्णय लिया।
मेरी महत्वाकांक्षा जैक्सन के समान एक लाइब्रेरी डिजाइन करने की थी, जो एनोटेशन द्वारा निर्धारित डेटा कक्षाओं की सूची को एक्सेल दस्तावेज़ में बदलने के लिए एनोटेशन का उपयोग करेगी।
मैं अपने द्वारा बनाई गई लाइब्रेरी को साझा करना चाहता हूं, यह आशा करते हुए कि इससे दूसरों को लाभ हो सकता है या उन्हें अपने अद्वितीय कार्यों के लिए अपना स्वयं का मैपर बनाने के लिए प्रेरित किया जा सकता है। आइए जानें कि इसे प्राप्त करने के लिए जावा में ऐसा डेटा मैपर कैसे विकसित किया जाए:
इस से:
void demoReport() { var excelMapper = new ExcelMapperImpl(); var fileName = "demo-out-" + LocalTime.now() + ".xlsx"; List<Demo> demos = generateDemos(); try (Workbook workbook = excelMapper.createWorkbookFromObject(demos); var fileOutputStream = new FileOutputStream(fileName)) { workbook.write(fileOutputStream); } }
आइए एक्सेल मैपिंग के लिए आवश्यक मुख्य तत्वों की पहचान करें। इसके मूल में, हमें एक एक्सेल कॉलम की आवश्यकता होती है। रिपोर्ट के इस मूलभूत घटक को प्रत्येक पंक्ति में कॉलम नाम और संबंधित मान को स्पष्ट रूप से प्रदर्शित करना चाहिए।
इसके अलावा, हमें फॉर्मूला कोशिकाओं के लिए समर्थन शामिल करना चाहिए, जिससे हम मूल्यों का उपयोग कर सकें और परिणामों को गतिशील रूप से प्रस्तुत कर सकें। कॉलम के अंत में, एक समापन सूत्र आवश्यक है, चाहे वह अंतिम-उपयोगकर्ता के लिए औसत, योग या किसी अन्य प्रासंगिक मीट्रिक का प्रतिनिधित्व करता हो।
केवल डेटा सेल से परे, हमें सेल स्टाइल को आसानी से प्रबंधित करने के लिए सुविधाओं को भी एकीकृत करना चाहिए।
आवश्यक तत्वों की पहचान करने के बाद, अगला कदम आवश्यक एनोटेशन तैयार करना है। प्रारंभिक एनोटेशन सेल स्टाइल के बारे में मेटाडेटा एम्बेड करेगा। यह एनोटेशन मूलभूत विशेषताओं को उनके डिफ़ॉल्ट मानों के साथ शामिल करेगा:
@Retention(RetentionPolicy.RUNTIME) public @interface ColumnExcelStyle { ExcelColumnDataFormat cellTypePattern() default ExcelColumnDataFormat.NONE; IndexedColors cellColor() default IndexedColors.AUTOMATIC; boolean isWrapText() default false; boolean isCentreAlignment() default false; boolean isFramed() default true; ExcelColumnFont fontName() default ExcelColumnFont.DEFAULT; short fontSize() default -1; boolean isFontBold() default false; ExcelColumnCellTextColor fontColor() default ExcelColumnCellTextColor.AUTOMATIC; }
रिपोर्ट निर्माण के लिए महत्वपूर्ण प्राथमिक शैली तत्वों को विशेषताओं के रूप में व्यक्त किया जाता है। इसके बाद, एक्सेल कॉलम एनोटेशन की शुरुआत की जा सकती है:
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ColumnExcel { String[] applyNames() default {}; int position(); ColumnExcelStyle headerStyle() default @ColumnExcelStyle( fontColor = ExcelColumnCellTextColor.BLACK, isCentreAlignment = true, isFontBold = true, fontSize = 14, isWrapText = true); ColumnExcelStyle cellStyle() default @ColumnExcelStyle; }
इस एनोटेशन में संभावित कॉलम नाम (एक्सेल से मैपिंग के लिए) और एक अनिवार्य फ़ील्ड - ' position
' शामिल है। यह कॉलम का स्थान निर्धारित करेगा और सूत्र गणना में सहायक होगा। इसके अतिरिक्त, यह हेडर और सेल दोनों की शैली का विवरण देगा।
उत्कृष्ट। अब, आइए एक्सेल फ़ार्मुलों के लिए विशिष्ट एक एनोटेशन तैयार करें। पंक्ति की स्थिति पर निर्भर एक गतिशील सूत्र का अनुमान लगाते हुए, यह एनोटेशन केवल विधियों के लिए होगा:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ColumnExcelFormula { String name() default ""; int position(); ColumnExcelStyle headerStyle() default @ColumnExcelStyle( fontColor = ExcelColumnCellTextColor.BLACK, isCentreAlignment = true, isFontBold = true, fontSize = 14, isWrapText = true); ColumnExcelStyle cellStyle() default @ColumnExcelStyle; }
अंत में, आइए अंतिम सूत्र के लिए एक एनोटेशन पेश करें, जो आम तौर पर एक्सेल में अंतिम पंक्ति पर कब्जा कर लेता है, एक कॉलम के लिए संचयी परिणाम को सारांशित या चित्रित करता है। यह देखते हुए कि सूत्र का एनोटेशन भी केवल तरीकों के लिए इसकी प्रयोज्यता को अनिवार्य बनाता है:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ColumnExcelTotalFormula { boolean useValue() default false; int position(); ColumnExcelStyle cellStyle() default @ColumnExcelStyle; }
सभी आवश्यक एनोटेशन के निर्माण के बाद, एक सेवा वर्ग तैयार करना अगला चरण बन जाता है। उपयोग की जाने वाली मुख्य लाइब्रेरी Apache POI होगी, जो .xls और .xlsx फ़ाइलों के साथ काम करने के लिए प्रभावी है। यह वर्ग एक्सेल रिपोर्ट तैयार करने के लिए एनोटेशन की विशेषताओं का उपयोग करेगा।
प्राथमिक विधि वस्तुओं की एक सूची को इनपुट के रूप में स्वीकार करती है और एक तैयार कार्यपुस्तिका लौटाती है।
अतिरिक्त लचीलेपन के लिए, रिपोर्ट निर्माण के लिए फ़ाइल नाम और शीट नाम दोनों के विनिर्देश को सक्षम करने के लिए एक अतिभारित विधि पेश की जाएगी:
<T> Workbook createWorkbookFromObject(List<T> reportObjects) { return createWorkbookFromObject(reportObjects, 0, "Report"); } <T> Workbook createWorkbookFromObject(List<T> reportObjects, int startRowNumber, String sheetName) { ... }
प्रतिबिंब का उपयोग करके वर्ग के बारे में जानकारी निकालने के लिए, सरणी से किसी भी तत्व का चयन किया जाता है। कक्षा विवरण तक पहुंचने के बाद, पहली पंक्ति स्थापित की जा सकती है। एनोटेशन से डेटा का उपयोग करने से उनके संबंधित नामों के साथ कोशिकाओं के निर्माण की अनुमति मिलती है।
यदि कोई नाम अनुपस्थित है, तो क्लास फ़ील्ड का नाम विकल्प के रूप में काम कर सकता है:
private <T> void createHeaderFromDeclaredExcelColumns(Row row, Class<T> clazz, PropertyDescriptor propertyDescriptor) { try { Field field = clazz.getDeclaredField(propertyDescriptor.getName()); ColumnExcel columnExcel = field.getDeclaredAnnotation(ColumnExcel.class); if (nonNull(columnExcel)) { String headerName = columnExcel.applyNames().length > 0 ? columnExcel.applyNames()[0] : field.getName(); createHeader(row, columnExcel.position(), headerName, columnExcel.headerStyle()); } } catch (NoSuchFieldException e) { log.debug(e.getLocalizedMessage()); } }
हेडर बनाते समय, प्रत्येक सेल के लिए एक स्टाइल निर्दिष्ट करना याद रखें। स्टाइल पैरामीटर @ColumnExcelStyle
एनोटेशन से प्राप्त किए जा सकते हैं:
private void createHeader(Row row, int position, String name, ColumnExcelStyle columnExcelStyle) { Cell cell = row.createCell(position); cell.setCellValue(name); setCellFormatting(cell, columnExcelStyle); row.getSheet().autoSizeColumn(cell.getColumnIndex()); }
फिर प्रक्रिया प्रदान की गई सरणी से डेटा के आधार पर रिपोर्ट में पंक्तियाँ उत्पन्न करने में बदल जाती है। डेटा पर पुनरावृत्ति करने से, क्रमिक पंक्तियाँ बनती हैं:
for (T report : reportObjects) { Row bodyRow = sheet.createRow(proceedRowNumber); createCellsFromDeclaredExcelColumns(bodyRow, report); proceedRowNumber++; }
संपत्ति विवरणकों को फ़ील्ड तक सीधी पहुंच प्रदान करने के बजाय गेटर्स का उपयोग करने के लिए खरीदा जाता है:
private <T> void createCellsFromDeclaredExcelColumns(Row row, T tObject) { try { PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(tObject.getClass()).getPropertyDescriptors(); for (var propertyDescriptor : propertyDescriptors) { createCellFromDeclaredExcelColumns(row, tObject, propertyDescriptor); } } catch (IntrospectionException ex) { log.debug(ex.getLocalizedMessage()); } }
प्रॉपर्टी डिस्क्रिप्टर के साथ, एक सेल बनता है:
private <T> void createCellFromDeclaredExcelColumns(Row row, T tObject, PropertyDescriptor propertyDescriptor) { try { Field field = tObject.getClass().getDeclaredField(propertyDescriptor.getName()); Method readMethod = propertyDescriptor.getReadMethod(); ColumnExcel columnExcel = field.getDeclaredAnnotation(ColumnExcel.class); if (nonNull(columnExcel)) { Class<?> returnType = readMethod.getReturnType(); Cell cell = row.createCell(columnExcel.position()); Object invokeResult = readMethod.invoke(tObject); if (nonNull(invokeResult)) { defineAndAssignCellValue(returnType, cell, invokeResult, readMethod); } setCellFormatting(cell, columnExcel.cellStyle()); } } catch (NoSuchFieldException | InvocationTargetException | IllegalAccessException e) { log.debug(e.getLocalizedMessage()); } }
इसके बाद, आइए अपना ध्यान @ColumnExcelFormula
एनोटेशन को संसाधित करने पर केंद्रित करें। किसी फ़ील्ड से मान निकालने की तुलना में फ़ॉर्मूले तैयार करना थोड़ा अधिक जटिल साबित होता है। एक विधि से एक सूत्र उत्पन्न करने की अपेक्षा की जाती है, जिसे बाद में सेल को सौंपा जाता है।
विधि को लगातार एक स्ट्रिंग लौटानी चाहिए और पंक्ति संख्या को एक तर्क के रूप में स्वीकार करना चाहिए, जिससे आसन्न कोशिकाओं से सटीक डेटा उपयोग सुनिश्चित हो सके।
इस प्रकार, निर्दिष्ट सूत्र के साथ सेल बनाने से पहले इन शर्तों को पूरा करने की पुष्टि करना हैंडलर पर निर्भर करता है:
private <T> void createCellFromDeclaredExcelFormula(Row row, T tObject, Method readMethod) throws IllegalAccessException, InvocationTargetException { ColumnExcelFormula columnExcelFormula = readMethod.getDeclaredAnnotation(ColumnExcelFormula.class); if (columnExcelFormula != null) { Class<?> returnType = readMethod.getReturnType(); Cell cell = row.createCell(columnExcelFormula.position()); if (returnType.isAssignableFrom(String.class)) { cell.setCellFormula((String) readMethod.invoke(tObject, row.getRowNum())); } else { log.debug(" Return type for the method: " + readMethod.getName() + " with @ColumnExcelFormula annotation has to be String " + "and now it's: " + returnType.getName() + " method is ignored for the reason"); } setCellFormatting(cell, columnExcelFormula.cellStyle()); } }
अंतिम चरण में अंतिम परिणाम प्रदर्शित करने के लिए एक पंक्ति बनाना शामिल है। महत्वपूर्ण बात यह है कि यह पंक्ति केवल एक बार उत्पन्न होनी चाहिए, भले ही हैंडलर को रिले की गई वस्तुओं की संख्या कुछ भी हो। इस प्रयोजन के लिए स्थैतिक पद्धति में एक एनोटेशन अपेक्षित है।
यह विधि प्रारंभिक पंक्ति और वर्तमान पंक्ति दोनों की संख्या प्राप्त करती है जहां सेल को तर्क के रूप में त्वरित किया जाएगा।
प्रारंभिक पंक्ति की संख्या का प्रावधान महत्वपूर्ण है, जिससे विधि को एक सूत्र तैयार करने में मदद मिलती है जो पूरे कॉलम के लिए समग्र परिणाम का लाभ उठाता है:
private <T> void createTotalFormula(Class<T> tClazz, Row row, int firstRowNum) { Method[] methods = tClazz.getDeclaredMethods(); for (Method method : methods) { ColumnExcelTotalFormula columnExcelTotalFormula = method.getAnnotation(ColumnExcelTotalFormula.class); if (columnExcelTotalFormula != null && method.getReturnType().isAssignableFrom(String.class) && method.getParameters().length == 2 && Modifier.isStatic(method.getModifiers()) && !Modifier.isPrivate(method.getModifiers()) ) { String cellFormula = (String) method.invoke(tClazz, firstRowNum, row.getRowNum()); Cell cell = row.createCell(columnExcelTotalFormula.position()); cell.setCellFormula(cellFormula); if (columnExcelTotalFormula.useValue()) { cell = applyFormulasValue(cell); } setCellFormatting(cell, columnExcelTotalFormula.cellStyle()); } } }
मुख्य कार्यक्षमता अब लागू हो गई है, और इसे क्रियान्वित होते देखने का समय आ गया है। आइए इसके संचालन को प्रदर्शित करने के लिए एक सरल रिपोर्ट बनाएं। इसके लिए, आइए एक ' Sales
' क्लास बनाएं और सभी आवश्यक एनोटेशन शामिल करें:
@Data @Accessors(chain = true) public class Sales { @ColumnExcel( position = 0, applyNames = {"Date"}, headerStyle = @ColumnExcelStyle( fontColor = WHITE, cellColor = DARK_BLUE, isCentreAlignment = true), cellStyle = @ColumnExcelStyle( cellColor = GREY_25_PERCENT, cellTypePattern = DATE)) private LocalDate date; @ColumnExcel( position = 1, applyNames = {"Sold"}, headerStyle = @ColumnExcelStyle( fontColor = WHITE, cellColor = DARK_BLUE, isCentreAlignment = true), cellStyle = @ColumnExcelStyle( cellColor = GREY_25_PERCENT)) private Integer sold; @ColumnExcel( position = 2, applyNames = {"Price Per Unit (USD)"}, headerStyle = @ColumnExcelStyle( fontColor = WHITE, cellColor = DARK_BLUE, isCentreAlignment = true), cellStyle = @ColumnExcelStyle( cellColor = GREY_25_PERCENT, cellTypePattern = USD)) private Double pricePerUnit; @ColumnExcelFormula( position = 3, name = "Total Sales (USD)", headerStyle = @ColumnExcelStyle( fontColor = WHITE, cellColor = DARK_BLUE, isCentreAlignment = true), cellStyle = @ColumnExcelStyle( cellColor = GREY_25_PERCENT, cellTypePattern = USD)) public String sales(int rowNum) { return new CellAddress(rowNum, 1).formatAsString() + "*" + new CellAddress(rowNum, 2).formatAsString(); } @ColumnExcelTotalFormula( position = 0, cellStyle = @ColumnExcelStyle( cellColor = LIGHT_BLUE)) public static String total(int firstRowNum, int lastRowNum) { return "CONCATENATE(\"Total\")"; } @ColumnExcelTotalFormula( position = 1, cellStyle = @ColumnExcelStyle( cellColor = LIGHT_BLUE)) public static String unitsSold(int firstRowNum, int lastRowNum) { return "SUM(" + new CellAddress(firstRowNum, 1).formatAsString() + ":" + new CellAddress(lastRowNum - 1, 1).formatAsString() + ")"; } @ColumnExcelTotalFormula( position = 3, cellStyle = @ColumnExcelStyle( isCentreAlignment = false, cellColor = LIGHT_BLUE, cellTypePattern = USD)) public static String totalSales(int firstRowNum, int lastRowNum) { return "SUM(" + new CellAddress(firstRowNum, 3).formatAsString() + ":" + new CellAddress(lastRowNum - 1, 3).formatAsString() + ")"; } }
वर्ग में तीन फ़ील्ड शामिल हैं: date
, sold
, और pricePerUnit
। इसके अतिरिक्त, इसमें एक बिक्री फॉर्मूला और कुल योग के साथ एक समापन पंक्ति है: unitsSold
और totalSales
। फ़ील्ड @ColumnExcel
एनोटेशन का उपयोग करते हैं, जो कॉलम की स्थिति और नाम को दर्शाता है।
@ColumnExcelStyle
एनोटेशन हेडर और व्यक्तिगत डेटा सेल दोनों के लिए शैली को परिभाषित करता है:
@ColumnExcel( position = 0, applyNames = {"Date"}, headerStyle = @ColumnExcelStyle( fontColor = WHITE, cellColor = DARK_BLUE, isCentreAlignment = true), cellStyle = @ColumnExcelStyle( cellColor = GREY_25_PERCENT, cellTypePattern = DATE) )
जैसा कि पहले चर्चा की गई है, सूत्र बनाते समय, विधि को पंक्ति संख्या को इंगित करने वाले पैरामीटर को स्वीकार करना होगा। यह आवश्यकता विधि के हस्ताक्षर में स्पष्ट है:
public String sales(int rowNum) { return new CellAddress(rowNum, 1).formatAsString() + "*" + new CellAddress(rowNum, 2).formatAsString(); }
पंक्ति संख्या और स्तंभ सूचकांकों को देखते हुए, कोई विशिष्ट सूत्र तैयार करना संभव हो जाता है।
कक्षा के भीतर, समापन सूत्रों के लिए इच्छित विधियाँ स्थिर हैं और दो मापदंडों की आवश्यकता होती है: प्रारंभिक पंक्ति की संख्या और अंतिम पंक्ति की संख्या:
public static String unitsSold(int firstRowNum, int lastRowNum) { return "SUM(" + new CellAddress(firstRowNum, 1).formatAsString() + ":" + new CellAddress(lastRowNum - 1, 1).formatAsString() + ")"; }
अब, आइए विधि लॉन्च करें:
void salesReport() { var excelMapper = new ExcelMapperImpl(); var fileName = "sales-out-" + LocalTime.now() + ".xlsx"; List<Sales> sales = List.of( new Sales().setDate(LocalDate.of(2023, 1, 1)) .setSold(50) .setPricePerUnit(10d), new Sales().setDate(LocalDate.of(2023, 1, 2)) .setSold(40) .setPricePerUnit(11d), new Sales().setDate(LocalDate.of(2023, 1, 3)) .setSold(55) .setPricePerUnit(9d); try (Workbook workbook = excelMapper.createWorkbookFromObject(sales); var fileOutputStream = new FileOutputStream(fileName)) { workbook.write(fileOutputStream); } }
और उत्पन्न रिपोर्ट की जांच करें:
किसी विशिष्ट कार्य के लिए विशेष पुस्तकालय लिखना सीधा-सादा साबित हुआ। तैयार की गई लाइब्रेरी आवश्यकताओं को पूरा करती है और इसमें नियोजित कार्यक्षमता शामिल है। एनोटेशन का उपयोग करने वाला दृष्टिकोण सेल शैलियों के त्वरित और सुविधाजनक अनुकूलन, सूत्रों के संशोधन और विभिन्न डेटा स्रोतों से गतिशील रिपोर्ट निर्माण की सुविधा प्रदान करता है।
इसलिए, अगली बार जब एक उपयुक्त पुस्तकालय मायावी हो, तो व्यक्तिगत पुस्तकालय के विकास पर विचार करना फायदेमंद हो सकता है।
स्वाभाविक रूप से, इस आलेख में कोड की प्रत्येक पंक्ति प्रस्तुत करना संभव नहीं था; इस प्रकार, केवल मैपर के संचालन के लिए आवश्यक प्राथमिक तरीकों पर प्रकाश डाला गया। पूरा कोड मेरे GitHub पेज पर उपलब्ध है।