mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2024-11-16 12:20:12 +01:00
update
This commit is contained in:
parent
b07437dbfa
commit
1e35556034
@ -0,0 +1,139 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.parser.Parser;
|
||||||
|
import org.commonmark.renderer.html.HtmlRenderer;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.FileToPdf;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
public class ConvertEpubToPdf {
|
||||||
|
//TODO
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/epub-to-single-pdf")
|
||||||
|
@Hidden
|
||||||
|
@Operation(
|
||||||
|
summary = "Convert an EPUB file to a single PDF",
|
||||||
|
description = "This endpoint takes an EPUB file input and converts it to a single PDF."
|
||||||
|
)
|
||||||
|
public ResponseEntity<byte[]> epubToSinglePdf(
|
||||||
|
@RequestPart(required = true, value = "fileInput") MultipartFile fileInput)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
if (fileInput == null) {
|
||||||
|
throw new IllegalArgumentException("Please provide an EPUB file for conversion.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String originalFilename = fileInput.getOriginalFilename();
|
||||||
|
if (originalFilename == null || !originalFilename.endsWith(".epub")) {
|
||||||
|
throw new IllegalArgumentException("File must be in .epub format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, byte[]> epubContents = extractEpubContent(fileInput);
|
||||||
|
List<String> htmlFilesOrder = getHtmlFilesOrderFromOpf(epubContents);
|
||||||
|
|
||||||
|
List<byte[]> individualPdfs = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String htmlFile : htmlFilesOrder) {
|
||||||
|
byte[] htmlContent = epubContents.get(htmlFile);
|
||||||
|
byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent, htmlFile.replace(".html", ".pdf"));
|
||||||
|
individualPdfs.add(pdfBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pseudo-code to merge individual PDFs into one.
|
||||||
|
byte[] mergedPdfBytes = mergeMultiplePdfsIntoOne(individualPdfs);
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(mergedPdfBytes, originalFilename.replace(".epub", ".pdf"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming a pseudo-code function that merges multiple PDFs into one.
|
||||||
|
private byte[] mergeMultiplePdfsIntoOne(List<byte[]> individualPdfs) {
|
||||||
|
// You can use a library such as iText or PDFBox to perform the merging here.
|
||||||
|
// Return the byte[] of the merged PDF.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, byte[]> extractEpubContent(MultipartFile fileInput) throws IOException {
|
||||||
|
Map<String, byte[]> contentMap = new HashMap<>();
|
||||||
|
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(fileInput.getInputStream())) {
|
||||||
|
ZipEntry zipEntry = zis.getNextEntry();
|
||||||
|
while (zipEntry != null) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int read = 0;
|
||||||
|
while ((read = zis.read(buffer)) != -1) {
|
||||||
|
baos.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
contentMap.put(zipEntry.getName(), baos.toByteArray());
|
||||||
|
zipEntry = zis.getNextEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getHtmlFilesOrderFromOpf(Map<String, byte[]> epubContents) throws Exception {
|
||||||
|
String opfContent = new String(epubContents.get("OEBPS/content.opf")); // Adjusting for given path
|
||||||
|
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||||
|
InputSource is = new InputSource(new StringReader(opfContent));
|
||||||
|
Document doc = dBuilder.parse(is);
|
||||||
|
|
||||||
|
NodeList itemRefs = doc.getElementsByTagName("itemref");
|
||||||
|
List<String> htmlFilesOrder = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < itemRefs.getLength(); i++) {
|
||||||
|
Element itemRef = (Element) itemRefs.item(i);
|
||||||
|
String idref = itemRef.getAttribute("idref");
|
||||||
|
|
||||||
|
NodeList items = doc.getElementsByTagName("item");
|
||||||
|
for (int j = 0; j < items.getLength(); j++) {
|
||||||
|
Element item = (Element) items.item(j);
|
||||||
|
if (idref.equals(item.getAttribute("id"))) {
|
||||||
|
htmlFilesOrder.add(item.getAttribute("href")); // Fetching the actual href
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlFilesOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -260,6 +260,37 @@ PdfToSinglePage.tags=single page
|
|||||||
# WEB PAGES #
|
# WEB PAGES #
|
||||||
# #
|
# #
|
||||||
###########################
|
###########################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToSinglePage
|
||||||
|
pdfToSinglePage.title=PDF To Single Page
|
||||||
|
pdfToSinglePage.header=PDF To Single Page
|
||||||
|
pdfToSinglePage.submit=Convert To Single Page
|
||||||
|
|
||||||
|
|
||||||
|
#pageExtracter
|
||||||
|
pageExtracter.title=Extract Pages
|
||||||
|
pageExtracter.header=Extract Pages
|
||||||
|
pageExtracter.submit=Extract
|
||||||
|
|
||||||
|
|
||||||
|
#getPdfInfo
|
||||||
|
getPdfInfo.title=Get Info on PDF
|
||||||
|
getPdfInfo.header=Get Info on PDF
|
||||||
|
getPdfInfo.submit=Get Info
|
||||||
|
getPdfInfo.downloadJson=Download JSON
|
||||||
|
|
||||||
|
|
||||||
|
#markdown-to-pdf
|
||||||
|
MarkdownToPDF.title=Markdown To PDF
|
||||||
|
MarkdownToPDF.header=Markdown To PDF
|
||||||
|
MarkdownToPDF.submit=Convert
|
||||||
|
MarkdownToPDF.help=Work in progress
|
||||||
|
MarkdownToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#url-to-pdf
|
#url-to-pdf
|
||||||
URLToPDF.title=URL To PDF
|
URLToPDF.title=URL To PDF
|
||||||
URLToPDF.header=URL To PDF
|
URLToPDF.header=URL To PDF
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||||
<input type="hidden" id="customMode" name="customMode" value="">
|
<input type="hidden" id="customMode" name="customMode" value="">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="pageOrder" th:text="#{pageSelection}"></label>
|
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||||
<input type="text" class="form-control" id="pageOrder" name="pageOrder" placeholder="(e.g. 1,2,8 or 4,7,12-16 or 2n-1)" required>
|
<input type="text" class="form-control" id="pageOrder" name="pageOrder" placeholder="(e.g. 1,2,8 or 4,7,12-16 or 2n-1)" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -57,6 +57,8 @@
|
|||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'auto-split-pdf', 'images/layout-split.svg', 'home.autoSplitPDF.title', 'home.autoSplitPDF.desc', 'autoSplitPDF.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'auto-split-pdf', 'images/layout-split.svg', 'home.autoSplitPDF.title', 'home.autoSplitPDF.desc', 'autoSplitPDF.tags')}"></div>
|
||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('adjust-contrast', 'images/adjust-contrast.svg', 'home.adjust-contrast.title', 'home.adjust-contrast.desc', 'adjust-contrast.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('adjust-contrast', 'images/adjust-contrast.svg', 'home.adjust-contrast.title', 'home.adjust-contrast.desc', 'adjust-contrast.tags')}"></div>
|
||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('crop', 'images/crop.svg', 'home.crop.title', 'home.crop.desc', 'crop.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('crop', 'images/crop.svg', 'home.crop.title', 'home.crop.desc', 'crop.tags')}"></div>
|
||||||
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-page', 'images/extract.svg', 'home.extractPage.title', 'home.extractPage.desc', 'extractPage.tags')}"></div>
|
||||||
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-single-page', 'images/single-page.svg', 'home.PdfToSinglePage.title', 'home.PdfToSinglePage.desc', 'PdfToSinglePage.tags')}"></div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -73,7 +75,7 @@
|
|||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('file-to-pdf', 'images/file.svg', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('file-to-pdf', 'images/file.svg', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags')}"></div>
|
||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('html-to-pdf', 'images/html.svg', 'home.HTMLToPDF.title', 'home.HTMLToPDF.desc', 'HTMLToPDF.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('html-to-pdf', 'images/html.svg', 'home.HTMLToPDF.title', 'home.HTMLToPDF.desc', 'HTMLToPDF.tags')}"></div>
|
||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('url-to-pdf', 'images/url.svg', 'home.URLToPDF.title', 'home.URLToPDF.desc', 'URLToPDF.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('url-to-pdf', 'images/url.svg', 'home.URLToPDF.title', 'home.URLToPDF.desc', 'URLToPDF.tags')}"></div>
|
||||||
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('markdown-to-pdf', 'images/markdown.svg', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags')}"></div>
|
||||||
<hr class="dropdown-divider">
|
<hr class="dropdown-divider">
|
||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-img', 'images/image.svg', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-img', 'images/image.svg', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags')}"></div>
|
||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-word', 'images/file-earmark-word.svg', 'home.PDFToWord.title', 'home.PDFToWord.desc', 'PDFToWord.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-word', 'images/file-earmark-word.svg', 'home.PDFToWord.title', 'home.PDFToWord.desc', 'PDFToWord.tags')}"></div>
|
||||||
@ -102,6 +104,7 @@
|
|||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags')}"></div>
|
||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags')}"></div>
|
||||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags')}"></div>
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags')}"></div>
|
||||||
|
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('get-info-on-pdf', 'images/info.svg', 'home.getPdfInfo.title', 'home.getPdfInfo.desc', 'getPdfInfo.tags')}"></div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{pdfToSinglePage.header}"></h2>
|
<h2 th:text="#{pdfToSinglePage.header}"></h2>
|
||||||
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-single-page}">
|
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-single-page}">
|
||||||
<p th:text="#{pdfToSinglePage.formPrompt}"></p>
|
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToSinglePage.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToSinglePage.submit}"></button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,35 +1,39 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
<html th:lang="${#locale.toString()}"
|
||||||
|
th:lang-direction="#{language.direction}"
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{getPdfInfo.title})}"></th:block>
|
xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<th:block
|
||||||
|
th:insert="~{fragments/common :: head(title=#{getPdfInfo.title})}"></th:block>
|
||||||
<body>
|
<body>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
<br> <br>
|
<br> <br>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{getPdfInfo.header}"></h2>
|
<h2 th:text="#{getPdfInfo.header}"></h2>
|
||||||
<p th:text="#{processTimeWarning}">
|
<form id="pdfInfoForm" method="post" enctype="multipart/form-data"
|
||||||
<form id="pdfInfoForm" method="post" enctype="multipart/form-data" th:action="@{get-info-on-pdf}">
|
th:action="@{get-info-on-pdf}">
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, remoteCall='false')}"></div>
|
<div
|
||||||
<br>
|
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, remoteCall='false')}"></div>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{getPdfInfo.submit}"></button>
|
<br>
|
||||||
|
<button type="submit" id="submitBtn" class="btn btn-primary"
|
||||||
|
th:text="#{getPdfInfo.submit}"></button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<!-- Iterate over each main section in the JSON -->
|
<!-- Iterate over each main section in the JSON -->
|
||||||
<div id="json-content">
|
<div id="json-content">
|
||||||
<!-- JavaScript will populate this section -->
|
<!-- JavaScript will populate this section -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Button to download the JSON -->
|
<!-- Button to download the JSON -->
|
||||||
<a href="#" id="downloadJson" class="btn btn-primary mt-3">Download JSON</a>
|
<a href="#" id="downloadJson" class="btn btn-primary mt-3"
|
||||||
|
style="display: none;" th:text="#{getPdfInfo.downloadJson}">Download
|
||||||
|
JSON</a>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
|
||||||
// Prevent the form from submitting the traditional way
|
// Prevent the form from submitting the traditional way
|
||||||
@ -47,6 +51,7 @@
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
displayJsonData(data); // Display the data
|
displayJsonData(data); // Display the data
|
||||||
setDownloadLink(data); // Set download link
|
setDownloadLink(data); // Set download link
|
||||||
|
document.getElementById("downloadJson").style.display = "block";
|
||||||
})
|
})
|
||||||
.catch(error => console.error('Error:', error));
|
.catch(error => console.error('Error:', error));
|
||||||
});
|
});
|
||||||
@ -67,8 +72,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function renderJsonSection(key, value, depth = 0) {
|
function renderJsonSection(key, value, depth = 0) {
|
||||||
// Replace spaces and other non-alphanumeric characters with underscores for valid IDs
|
// Replace spaces and other non-alphanumeric characters with underscores for valid IDs
|
||||||
let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key;
|
let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key;
|
||||||
@ -77,36 +80,51 @@
|
|||||||
<div class="card-header" id="${safeKey}-heading-${depth}">
|
<div class="card-header" id="${safeKey}-heading-${depth}">
|
||||||
<h5 class="mb-0">`;
|
<h5 class="mb-0">`;
|
||||||
|
|
||||||
// Check if the value is an object and has children
|
// Check if the value is an object and has children
|
||||||
if (value && typeof value === 'object' && (Object.keys(value).length || Array.isArray(value))) {
|
if (value && typeof value === 'object') {
|
||||||
output += `
|
// For arrays and non-array objects
|
||||||
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#${safeKey}-content-${depth}" aria-expanded="true" aria-controls="${safeKey}-content-${depth}">
|
if (Array.isArray(value) && value.length === 0) {
|
||||||
${key}
|
output += `${key}: Empty array`;
|
||||||
</button>`;
|
} else if (!Array.isArray(value) && Object.keys(value).length === 0) {
|
||||||
} else {
|
output += `${key}: Empty object`;
|
||||||
// Display both key and value for simple entries
|
} else {
|
||||||
output += `${key}: ${value}`;
|
output += `
|
||||||
}
|
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#${safeKey}-content-${depth}" aria-expanded="true" aria-controls="${safeKey}-content-${depth}">
|
||||||
|
${key}
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For simple key-value pairs
|
||||||
|
output += `${key}: ${value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
output += `
|
output += `
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div id="${safeKey}-content-${depth}" class="collapse" aria-labelledby="${safeKey}-heading-${depth}">`;
|
<div id="${safeKey}-content-${depth}" class="collapse" aria-labelledby="${safeKey}-heading-${depth}">`;
|
||||||
|
|
||||||
// Check if the value is a nested object
|
// Check if the value is a nested object
|
||||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||||
output += '<div class="card-body">';
|
output += '<div class="card-body">';
|
||||||
for (const subKey in value) {
|
if (Object.keys(value).length) {
|
||||||
output += renderJsonSection(subKey, value[subKey], depth + 1);
|
for (const subKey in value) {
|
||||||
|
output += renderJsonSection(subKey, value[subKey], depth + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output += '<p class="text-muted">Empty object</p>';
|
||||||
}
|
}
|
||||||
output += '</div>';
|
output += '</div>';
|
||||||
} else if (typeof value === 'object' && Array.isArray(value) && value.length) { // Array values
|
} else if (value && typeof value === 'object' && Array.isArray(value)) {
|
||||||
output += '<div class="card-body">';
|
output += '<div class="card-body">';
|
||||||
value.forEach((val, index) => {
|
if (value.length) {
|
||||||
// For arrays, we're going to make the displayed key more descriptive.
|
value.forEach((val, index) => {
|
||||||
const arrayKey = `${key}[${index}]`;
|
const arrayKey = `${key}[${index}]`;
|
||||||
output += renderJsonSection(arrayKey, val, depth + 1);
|
output += renderJsonSection(arrayKey, val, depth + 1);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
output += '<p class="text-muted">Empty array</p>';
|
||||||
|
}
|
||||||
output += '</div>';
|
output += '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,24 +132,13 @@
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue
Block a user