package org.lazydoc.printer; import com.cedarsoftware.util.io.JsonWriter; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.lazydoc.config.PrinterConfig; import org.lazydoc.model.*; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Set; public class DocBookDocumentationPrinter extends DocumentationPrinter { // TODO update DocBookDocumentationPrinter @Override public void print(PrinterConfig printerConfig) throws Exception { this.printerConfig = printerConfig; printDocBookXML(); } private void printDocBookXML() throws Exception { createApiDocXML(); createChapters(); writeFiles(FilenameUtils.normalizeNoEndSeparator(printerConfig.getOutputPath())); } private void createApiDocXML() { String docbookFilename = printerConfig.getParams().get("docbook.filename"); String docbookPreface = printerConfig.getParams().get("docbook.preface"); String docbookPostface = printerConfig.getParams().get("docbook.postface"); if(StringUtils.isBlank(docbookFilename)) { throw new RuntimeException("Please provide the docbook.filename in printer config params"); } String xml = printVersion(); xml += printStartTagWithAttributes( "book", "xmlns=\"http://docbook.org/ns/docbook\" xml:lang=\"en\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:xi=\"http://www.w3.org/2001/XInclude\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:m=\"http://www.w3.org/1998/Math/MathML\" xmlns:html=\"http://www.w3.org/1999/xhtml\" version=\"5.0\""); xml += "<?dbhtml dir=\""+docbookFilename.replaceAll(".xml", "")+"\" ?>"; xml += printShortTag("toc"); if(StringUtils.isNotBlank(docbookPreface)) { xml += printShortTagWithAttributes("xi:include", "href=\""+ docbookPreface + ".xml\""); } for (DocDomain domain : printerConfig.getDomains().values()) { xml += printShortTagWithAttributes("xi:include", "href=\"chapters/" + domain.getDomain().toLowerCase() + ".xml\""); } xml += printCommonErrorDescriptions(); if(StringUtils.isNotBlank(docbookPostface)) { xml += printShortTagWithAttributes("xi:include", "href=\""+ docbookPostface + ".xml\""); } xml += printEndTag("book"); files.put("/"+docbookFilename, prettyFormat(xml, 4)); } private void createChapters() { for (DocDomain domain : printerConfig.getDomains().values()) { String xml = printVersion(); xml += printStartTagWithAttributes( "chapter", "version=\"5.0\" xml:lang=\"en\" xmlns=\"http://docbook.org/ns/docbook\" xmlns:xi=\"http://www.w3.org/2001/XInclude\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:id=\"" + domain.getDomain() + "_sect\""); xml += printFullTag("title", domain.getDomainShortDescription()); xml += printFullTag("para", domain.getDescription()); if (domain.hasExternalDocumentation() ) { xml += printShortTagWithAttributes("xi:include", "href=\"../static/" + domain.getExternalDocumentations() + ".xml\""); } if (!domain.getSubDomains().isEmpty()) { for (DocSubDomain subDomain : domain.getSubDomains().values()) { xml += printStartTag("sect1"); xml += printFullTag("title", subDomain.getSubDomainShortDescription()); xml += printFullTag("para", subDomain.getDescription()); if (subDomain.hasExternalDocumentation()) { xml += printShortTagWithAttributes("xi:include", "href=\"../static/" + subDomain.getExternalDocumentations() + ".xml\""); } xml += printApiOperationsForDomain(subDomain); xml += printCommonErrorList(subDomain.getErrorList(), "sect2"); xml += printEndTag("sect1"); } } else { xml += printApiOperationsForDomain(domain); } xml += printCommonErrorList(domain.getErrorList(), "sect2"); if (domain.hasExternalDocumentation() ) { xml += printShortTagWithAttributes("xi:include", "href=\"../static/" + domain.getExternalDocumentations() + ".xml\""); } xml += printEndTag("chapter"); files.put("/chapters/" + domain.getDomain().toLowerCase() + ".xml", prettyFormat(xml, 4)); } } private String printCommonErrorDescriptions() { String xml = printStartTag("chapter"); xml += printFullTag("title", "List of comon error codes"); xml += printFullTag("para", "This area contains a list of all errors which can occure through processing which is not specific to a certain topic."); xml += printCommonErrorList(printerConfig.getListOfCommonErrors(), "sect1"); xml += printEndTag("chapter"); return xml; } private String printApiOperationsForDomain(DocDomain domain) { String xiIncludes = ""; String xml = printTableTop("Available REST methods", "1*,3*,3*", "HTTP method", "Context path", "Description"); for (DocOperation operation : domain.getOperations()) { xml += printStartTag("row"); xml += printFullTag("entry", operation.getHttpMethod()); xml += printStartTag("entry"); xml += printStartTagWithAttributes("link", "linkend=\"" + operation.getNickname().toLowerCase() + "\""); xml += printFullTag("uri", operation.getPath()); xml += printEndTag("link"); xml += printEndTag("entry"); xml += printFullTag("entry", operation.getDescription()); xml += printEndTag("row"); xiIncludes += printShortTagWithAttributes("xi:include", "href=\"operations/" + domain.getDomain().toLowerCase() + "/" + operation.getNickname().toLowerCase() + ".xml\""); createOperation(operation.getPath(), domain.getDomain(), operation); } xml += printTableBottom(); xml += xiIncludes; return xml; } private void createOperation(String apiPath, String domain, DocOperation operation) { String xml = printVersion(); xml += printStartTagWithAttributes("sect2", "xmlns=\"http://docbook.org/ns/docbook\" xml:lang=\"en\" xmlns:xi=\"http://www.w3.org/2001/XInclude\" version=\"5.0\" xml:id=\"" + operation.getNickname().toLowerCase() + "\""); xml += printFullTag("title", operation.getShortDescription()); xml += printFullTag("para", operation.getNotes()); if (operation.hasExternalDocumentation()) { } xml += printStartTag("simplesect"); xml += printFullTag("title", "Structure of the request"); xml += printFullTag("para", "Schematic representation of the URI with its parameters:"); xml += printFullTag("programlisting", operation.getHttpMethod() + " " + apiPath); List<DocParameter> uriParameters = new ArrayList<>(); for (DocParameter parameter : operation.getParameters()) { if (!parameter.getParamType().equals("body")) { uriParameters.add(parameter); } } if (!uriParameters.isEmpty()) { xml += printFullTag("para", "The following parameters are expected:"); xml += printTableTop("Description of the call parameters", "1*,1*,3*", "Parameter", "Type", "Description"); for (DocParameter parameter : uriParameters) { if (!parameter.getParamType().equals("body")) { xml += printStartTag("row"); xml += printFullTag("entry", parameter.getName()); xml += printFullTag("entry", parameter.getDataType()); xml += printFullTag("entry", parameter.getDescription()); xml += printEndTag("row"); } } xml += printTableBottom(); } else { xml += printFullTag("para", "There are no parameters needed in the URI"); } xml += printEndTag("simplesect"); xml += printRequestBody(operation.getParameters()); xml += printResponse(operation.getOperationResponse(), operation.getResponseStatus()); xml += printEndTag("sect2"); files.put("/chapters/operations/" + domain.toLowerCase() + "/" + operation.getNickname().toLowerCase() + ".xml", prettyFormat(xml.replaceAll("&", "&"), 4)); } private String printRequestBody(List<DocParameter> parameters) { DocDataType dataType = null; DocParameter param = null; String xml = printStartTag("simplesect"); for (DocParameter parameter : parameters) { if (parameter.getParamType().equals("body")) { dataType = printerConfig.getDataTypes().get(parameter.getDataType()); param = parameter; break; } } if (dataType != null) { xml += printTableTop("Structure of the request body", "2*,1*,3*", "Type", "Cardinality", "Description"); xml += printStartTag("row"); xml += printFullTag("entry", dataType.getName()); if (dataType.isList()) { xml += printFullTag("entry", "1 .. n"); } else { xml += printFullTag("entry", "1"); } xml += printFullTag("entry", param.getDescription()); xml += printEndTag("row"); xml += printTableBottom(); xml += printDataType(dataType, true); xml += printSample(dataType); } else { xml += printFullTag("title", "Structure of the request body"); xml += printFullTag("para", "The request body is not used for this operation."); } xml += printEndTag("simplesect"); return xml; } private String printResponse(DocOperationResponse operationResponse, String responseStatus) { DocDataType dataType = printerConfig.getDataTypes().get(operationResponse.getResponseType()); String xml = printStartTag("simplesect"); xml += printFullTag("title", "Structure of the response"); xml += printFullTag("para", "If successful, the call returns HTTP status " + responseStatus); if (dataType != null) { xml += printFullTag("para", "The response body contains the following data:"); xml += printDataType(dataType, false); } else { xml += printFullTag("para", "The response body is not used in this operation"); } xml += printEndTag("simplesect"); return xml; } private String printDataType(DocDataType dataType, boolean request) { String xml = printStartTag("para"); xml += printTableTop("Structure of the " + dataType.getName() + " element", "2*,1*,1*,3*", "Property", "Type", "Cardinality", "Description"); for (DocProperty property : dataType.getProperties()) { xml += printStartTag("row"); xml += printFullTag("entry", property.getName()); xml += printFullTag("entry", property.getType()); String cardinality = property.isRequired() ? "1" : "0"; if (property.isList()) { cardinality += " .. n"; } else if (cardinality == "0") { cardinality += " .. 1"; } xml += printFullTag("entry", cardinality); xml += printFullTag("entry", property.getDescription()); xml += printEndTag("row"); } xml += printTableBottom(); xml += printEndTag("para"); for (DocProperty property : dataType.getProperties()) { DocDataType propertyDataType = getDataType(property); if (propertyDataType != null) { xml += printDataType(propertyDataType, request); } } return xml; } private String printSample(DocDataType dataType) { String xml = printStartTag("para"); xml += printStartTag("example"); xml += printFullTag("title", "Sample request"); xml += printStartTagWithAttributes("programlisting", "language=\"json\""); xml += printJsonSample(dataType); xml += printEndTag("programlisting"); xml += printEndTag("example"); xml += printEndTag("para"); return xml; } private String printJsonSample(DocDataType dataType) { List<String> jsonList = new ArrayList<>(); for (DocProperty property : dataType.getProperties()) { if (property.hasSample()) { String json = "\"" + property.getName() + "\" : "; if (property.getSample().length == 1) { json += "\"" + property.getSample()[0] + "\""; } else { json += "[\"" + StringUtils.join(property.getSample() , "\",\"") + "\"]"; } jsonList.add(json); } DocDataType propertyDataType = getDataType(property); if (propertyDataType != null) { String sample = printJsonSample(propertyDataType); if (StringUtils.isNotBlank(sample) && StringUtils.isNotBlank(property.getName())) { if (property.getType().startsWith("List[")) { jsonList.add("\"" + property.getName() + "\" : [" + sample + "]"); } else { jsonList.add("\"" + property.getName() + "\" : " + sample); } } else { jsonList.add(sample); } } } String json = StringUtils.join(jsonList, ","); if (dataType.isList()) { json = "[" + json + "]"; } else { json = "{" + json + "}"; } try { return JsonWriter.formatJson(json); } catch (IOException e) { throw new RuntimeException(e); } } private DocDataType getDataType(DocProperty property) { return printerConfig.getDataTypes().get(property.getType().replaceAll("List\\[", "").replaceAll("\\]", "")); } private String printCommonErrorList(Set<DocError> errorList, String section) { if (errorList.isEmpty()) { return ""; } String xml = printStartTag(section); xml += printFullTag("title", "Error codes and descriptions"); xml += printFullTag("para", "The following error codes may occur during execution"); xml += printTableTop("List of possible errors", "1*,3*,2*", "Http Status", "Type", "Description"); for (DocError error : errorList) { xml += printStartTag("row"); xml += printFullTag("entry", "" + error.getStatusCode()); xml += printFullTag("entry", "" + error.getErrorCode()); xml += printFullTag("entry", "" + error.getDescription()); xml += printEndTag("row"); } xml += printTableBottom(); xml += printEndTag(section); return xml; } private String printTableTop(String tableTitle, String columnWidths, String... captions) { String xml = printStartTag("table"); xml += printFullTag("title", tableTitle); xml += "<?dbfo keep-together=\"auto\" ?>"; xml += printStartTagWithAttributes("tgroup", "cols=\"" + captions.length + "\""); String[] widths = columnWidths.split(","); for (int i = 0; i < captions.length; i++) { xml += printShortTagWithAttributes("colspec", "colname=\"" + captions[i].toLowerCase() + "\" colwidth=\"" + widths[i] + "\""); } xml += printStartTag("thead"); xml += printStartTag("row"); for (int i = 0; i < captions.length; i++) { xml += printFullTag("entry", captions[i]); } xml += printEndTag("row"); xml += printEndTag("thead"); xml += printStartTag("tbody"); return xml; } private String printTableBottom() { String xml = printEndTag("tbody"); xml += printEndTag("tgroup"); xml += printEndTag("table"); return xml; } private String printShortTagWithAttributes(String name, String attributes) { return "<" + name + " " + attributes + "/>"; } private String printShortTag(String name) { return "<" + name + "/>"; } private Integer getYearFromDate() { return new Integer(new SimpleDateFormat("yyyy").format(new Date())); } private String printStartTag(String name) { return "<" + name + ">"; } private String printStartTagWithAttributes(String name, String attributes) { return "<" + (name + " " + attributes).trim() + ">"; } private String printEndTag(String name) { return "</" + name + ">"; } private String printFullTag(String name, String value) { return "<" + name + ">" + value + "</" + name + ">"; } private String printVersion() { return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; } public static String prettyFormat(String input, int indent) { try { Source xmlInput = new StreamSource(new StringReader(input)); StringWriter stringWriter = new StringWriter(); StreamResult xmlOutput = new StreamResult(stringWriter); TransformerFactory transformerFactory = TransformerFactory.newInstance(); transformerFactory.setAttribute("indent-number", indent); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.transform(xmlInput, xmlOutput); return xmlOutput.getWriter().toString(); } catch (Exception e) { throw new RuntimeException(e); // simple exception handling, please review it } } }