package com.tyndalehouse.step.mobileapp.generator; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Downloads the home page and amends it to suit the purposes of the mobile app */ public class RipHomePage { public static final String BASE_STEP = "http://localhost:8080/"; public static final Pattern CSS_URL = Pattern.compile("url\\('?([^')]+)'?\\)"); public static final Pattern JS_URL = Pattern.compile("\"(js/.*min\")"); public static void main(String[] args) throws IOException { new RipHomePage().process(BASE_STEP, "?mobile=online", "C:\\dev\\projects\\temp\\my-test-app\\www"); } /** * Downloads the STEP home page and processes it. */ private void process(final String url, final String args, final String directory) throws IOException { Document doc = download(url + args); applyRules(doc); outputDocument(doc); List<String> relativeLinks = scanLinks(doc); writeApp(directory, doc, relativeLinks); } private void writeApp(final String directory, final Document doc, final List<String> relativeLinks) throws IOException { final File root = new File(directory); FileUtils.forceMkdir(root); //write the main app, as index.html FileUtils.write(new File(root, "index.html"), doc.html()); //download files and move to relevant directories for (final String relativeLink : relativeLinks) { File f = downloadFileToDisk(root, relativeLink); downloadCSSReferencedResources(root, f); // downloadJSReferences(root, f); } downloadInternationalFiles(root); } private void downloadJSReferencedResources(final File root, final File relativeLink) throws IOException { } private void downloadCSSReferencedResources(final File root, final File relativeLink) throws IOException { final String suffix = ".css"; final Pattern urlMatcher = CSS_URL; if(!relativeLink.getName().endsWith(suffix)) { return; } //we're dealing with a CSS file, so scan the content for url() List<String> resources = new ArrayList<>(32); final String fileContents = FileUtils.readFileToString(relativeLink); //now match the url(....) final Matcher cssURLMatcher = urlMatcher.matcher(fileContents); while(cssURLMatcher.find()) { //extract first group, to download later final String group = cssURLMatcher.group(1); resources.add(group); } //calculate real root for(String resource : resources) { if(resource.contains("#")) { continue; } File relativeRoot = resource.startsWith("../") || resource.startsWith("/") ? relativeLink.getParentFile().getParentFile() : relativeLink.getParentFile(); String finalName = resource.replace("../", ""); if(finalName.startsWith("/")) { finalName = finalName.substring(1); } downloadFileToDisk(root, (relativeRoot.getAbsolutePath() + "/" + finalName).replace(root.getAbsolutePath(), "").replace('\\', '/')); } } private void downloadInternationalFiles(final File root) throws IOException { Properties prop = new Properties(); prop.load(getClass().getClassLoader().getResourceAsStream("step.core.properties")); String languages = prop.getProperty("app.languages.available"); final String[] langs = StringUtils.split(languages, ","); for(String lang : langs) { downloadFileToDisk(root, "international/interactive.js?lang=" + lang, lang); } } private File downloadFileToDisk(final File root, final String filePath) throws IOException { return downloadFileToDisk(root, filePath, null); } private File downloadFileToDisk(final File root, final String filePath, final String infix) throws IOException { final URL website = new URL(BASE_STEP + filePath); final ReadableByteChannel rbc = Channels.newChannel(website.openStream()); final int directoryEnd = filePath.lastIndexOf('/'); final File fileDirectory; String fileName; if (directoryEnd != -1) { fileDirectory = new File(root, filePath.substring(0, directoryEnd)); FileUtils.forceMkdir(fileDirectory); fileName = filePath.substring(directoryEnd + 1); } else { fileName = filePath; fileDirectory = root; } //remove any arguments if(fileName.contains("?")) { fileName = fileName.substring(0, fileName.indexOf('?')); } if(infix != null) { //add the infix just before the file extension fileName = fileName.substring(0, fileName.indexOf('.')) + '-' + infix + fileName.substring(fileName.indexOf(".")); } final File outputFile = new File(fileDirectory, fileName); final FileOutputStream fos = new FileOutputStream(outputFile); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); return outputFile; } private List<String> scanLinks(final Document doc) { Iterator<Element> iterator = doc.select("[src], [rel]:not([rel=canonical])").iterator(); List<String> links = new ArrayList<>(); while (iterator.hasNext()) { final Element next = iterator.next(); final String src = next.attr("src"); if (StringUtils.isNotBlank(src)) { addRelativeURL(links, src); } else { addRelativeURL(links, next.attr("href")); } } return links; } private void addRelativeURL(final List<String> links, final String src) { if (!src.startsWith("international")) { links.add(src); } } private void outputDocument(final Document doc) { String html = doc.html(); System.out.println(html); } private void applyRules(final Document doc) { removeExtras(doc); } private void removeComments(Node node) { // as we are removing child nodes while iterating, we cannot use a normal foreach over children, // or will get a concurrent list modification error. int i = 0; while (i < node.childNodes().size()) { Node child = node.childNode(i); if (child.nodeName().equals("#comment")) child.remove(); else { removeComments(child); i++; } } } private void removeExtras(final Document doc) { doc.select(".passageContent").empty(); doc.select(".examplesColumn").remove(); doc.select("#raiseSupportTrigger").remove(); removeComments(doc); doc.select("#languageMenu li").removeAttr("title"); } private Document download(final String url) throws IOException { return Jsoup.connect(url).get(); } }