You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by vi...@apache.org on 2019/05/18 14:35:12 UTC

[netbeans-tools] branch master updated: Tutorials

This is an automated email from the ASF dual-hosted git repository.

vieiro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans-tools.git


The following commit(s) were added to refs/heads/master by this push:
     new 3049f26  Tutorials
3049f26 is described below

commit 3049f26ce5bcd7adb5f59c54f2c55c4a585b8ca0
Author: vieiro <vi...@apache.org>
AuthorDate: Sat May 18 16:32:47 2019 +0200

    Tutorials
---
 .gitignore                                         |   3 +
 tutorials-convert/README.md                        |  18 +
 tutorials-convert/nbactions.xml                    |  66 +++
 tutorials-convert/pom.xml                          |  95 ++++
 .../tools/tutorials/AsciidocPostProcessor.java     | 240 +++++++++
 .../tutorials/CustomAsciiDocDocumentBuilder.java   | 544 +++++++++++++++++++++
 ...CustomAsciiDocDocumentBuilderWithoutTables.java |  47 ++
 .../netbeans/tools/tutorials/ExternalLinksMap.java |  81 +++
 .../netbeans/tools/tutorials/HTMLConverter.java    | 268 ++++++++++
 .../org/netbeans/tools/tutorials/Language.java     |  66 +++
 .../tools/tutorials/LocalizedTutorialSection.java  |  91 ++++
 .../tools/tutorials/TutorialsBundle.properties     |  46 ++
 .../tutorials/TutorialsBundle_es_CA.properties     |  31 ++
 .../tools/tutorials/TutorialsBundle_ja.properties  |  31 ++
 .../tutorials/TutorialsBundle_pt_BR.properties     |  31 ++
 .../tools/tutorials/TutorialsBundle_ru.properties  |  31 ++
 .../tutorials/TutorialsBundle_zh_CN.properties     |  31 ++
 .../tools/tutorials/index-template.mustache        |  33 ++
 .../tools/tutorials/section-template.mustache      |  27 +
 19 files changed, 1780 insertions(+)

diff --git a/.gitignore b/.gitignore
index 08e92ac..f7eea08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,6 @@
 /html-convert/target/**
 /html-convert/tutorials-asciidoc/**
 /html-convert/external-links.txt
+/tutorials-convert/tutorials-asciidoc/**
+/tutorials-convert/target**
+/tutorials-convert/external-links.txt
diff --git a/tutorials-convert/README.md b/tutorials-convert/README.md
new file mode 100644
index 0000000..80bba1b
--- /dev/null
+++ b/tutorials-convert/README.md
@@ -0,0 +1,18 @@
+# tutorials-convert
+
+This tool reads the NetBeans tutorials in HTML format and converts them to AsciiDoc format.
+
+The NetBeans platform tutorials can be found in https://svn.netbeans.org/svn/platform~platform-content/trunk/tutorials/
+
+The NetBeans platform tutorial images can be found in https://svn.netbeans.org/svn/platform~platform-content/trunk/images/
+
+
+## Getting started
+
+1. Check out the tutorials from SVN above in a directory "X".
+2. Check out the images from SVN above in directory "Y".
+2. Run `mvn package exec:java X Y`, where "X" is the directory in the previous step.
+4. Open the `tutorials-asciidoc` directory to see the results.
+5. See the generated "external-links.txt" file to see referenced external links.
+
+NOTE: This tool is expected to be run once, after that manual revision of generated files should be done.
diff --git a/tutorials-convert/nbactions.xml b/tutorials-convert/nbactions.xml
new file mode 100644
index 0000000..24261f4
--- /dev/null
+++ b/tutorials-convert/nbactions.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<actions>
+        <action>
+            <actionName>run</actionName>
+            <packagings>
+                <packaging>jar</packaging>
+            </packagings>
+            <goals>
+                <goal>process-classes</goal>
+                <goal>exec:java</goal>
+            </goals>
+            <properties>
+                <exec.args>C:\Users\avieiro\Downloads\NETBEANS\</exec.args>
+                <exec.executable>java</exec.executable>
+            </properties>
+        </action>
+        <action>
+            <actionName>debug</actionName>
+            <packagings>
+                <packaging>jar</packaging>
+            </packagings>
+            <goals>
+                <goal>process-classes</goal>
+                <goal>org.codehaus.mojo:exec-maven-plugin:1.5.0:exec</goal>
+            </goals>
+            <properties>
+                <exec.args>-agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} -classpath %classpath org.netbeans.tools.tutorials.HTMLConverter C:\Users\avieiro\Downloads\NETBEANS</exec.args>
+                <exec.executable>java</exec.executable>
+                <jpda.listen>true</jpda.listen>
+            </properties>
+        </action>
+        <action>
+            <actionName>profile</actionName>
+            <packagings>
+                <packaging>jar</packaging>
+            </packagings>
+            <goals>
+                <goal>process-classes</goal>
+                <goal>org.codehaus.mojo:exec-maven-plugin:1.5.0:exec</goal>
+            </goals>
+            <properties>
+                <exec.args>-classpath %classpath org.netbeans.tools.tutorials.HTMLConverter C:\Users\avieiro\Downloads\NETBEANS</exec.args>
+                <exec.executable>java</exec.executable>
+            </properties>
+        </action>
+    </actions>
diff --git a/tutorials-convert/pom.xml b/tutorials-convert/pom.xml
new file mode 100644
index 0000000..671d209
--- /dev/null
+++ b/tutorials-convert/pom.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.netbeans.tools</groupId>
+    <artifactId>tutorials-convert</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.mylyn.docs</groupId>
+            <artifactId>org.eclipse.mylyn.wikitext</artifactId>
+            <version>3.0.20</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.mylyn.docs</groupId>
+            <artifactId>org.eclipse.mylyn.wikitext.mediawiki</artifactId>
+            <version>3.0.20</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.mylyn.docs</groupId>
+            <artifactId>org.eclipse.mylyn.wikitext.asciidoc</artifactId>
+            <version>3.0.20</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.mylyn.docs</groupId>
+            <artifactId>org.eclipse.mylyn.wikitext.html</artifactId>
+            <version>3.0.20</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.mylyn.docs</groupId>
+            <artifactId>org.eclipse.mylyn.wikitext.markdown</artifactId>
+            <version>3.0.20</version>
+        </dependency>
+        <dependency>
+            <groupId>org.asciidoctor</groupId>
+            <artifactId>asciidoctorj</artifactId>
+            <version>1.5.6</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.spullara.mustache.java</groupId>
+            <artifactId>compiler</artifactId>
+            <version>0.9.5</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <version>1.5.0</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>java</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <mainClass>org.netbeans.tools.tutorials.HTMLConverter</mainClass>
+                    <arguments>
+                      <argument>/home/antonio/REPOS/netbeans-tutorials</argument>
+                      <argument>/home/antonio/REPOS/images/</argument>
+                    </arguments>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/AsciidocPostProcessor.java b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/AsciidocPostProcessor.java
new file mode 100644
index 0000000..aa0abc4
--- /dev/null
+++ b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/AsciidocPostProcessor.java
@@ -0,0 +1,240 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+ */
+package org.netbeans.tools.tutorials;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+import com.github.mustachejava.functions.BundleFunctions;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class AsciidocPostProcessor {
+
+    private static final Logger LOG = Logger.getLogger(AsciidocPostProcessor.class.getName());
+
+    private static enum ContentSectionState {
+        BEFORE_CONTENT_SECTION,
+        INSIDE_CONTENT_SECTION,
+        AFTER_CONTENT_SECTION
+    };
+
+    private static Pattern TITLE_PATTERN = Pattern.compile("^= (.*)$");
+
+    private static Pattern EN_CONTENTS_PATTERN = Pattern.compile("^.*Contents.*");
+
+    private static boolean isContentsHeader(String line) {
+        return line.contains("*Content")
+                || line.contains("目录")
+                || line.contains("目次")
+                || line.contains("Conteúdo")
+                || line.contains("Содержание");
+    }
+
+    /**
+     * Scans a generated AsciiDoc file and remove the "*Contents*" section with
+     * all links in it. Because the contents section is being replaced by a
+     * table of contents 'toc'.
+     *
+     * @param file
+     * @param titles
+     * @throws IOException
+     */
+    private static void cleanUpAndGetTitle(File file, HashMap<File, String> titles) throws IOException {
+        File temporaryFile = new File(file.getParentFile(), file.getName() + ".tmp");
+        ContentSectionState state = ContentSectionState.BEFORE_CONTENT_SECTION;
+        String title = null;
+
+        try (BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
+                PrintWriter output = new PrintWriter(new OutputStreamWriter(new FileOutputStream(temporaryFile), StandardCharsets.UTF_8))) {
+
+            do {
+                String line = input.readLine();
+                if (line == null) {
+                    break;
+                }
+                if (title == null) {
+                    Matcher m = TITLE_PATTERN.matcher(line);
+                    if (m.matches()) {
+                        title = m.group(1);
+                    }
+                }
+                switch (state) {
+                    case BEFORE_CONTENT_SECTION:
+                        if (isContentsHeader(line)) {
+                            state = ContentSectionState.INSIDE_CONTENT_SECTION;
+                            break;
+                        }
+                        output.println(line);
+                        break;
+                    case INSIDE_CONTENT_SECTION:
+                        if (line.startsWith("* <")
+                                || line.startsWith("* link:")) {
+                            // Ignore bullet lists with cross references or external links
+                        } else {
+                            output.println(line);
+                        }
+                        if (line.startsWith("=")) {
+                            state = ContentSectionState.AFTER_CONTENT_SECTION;
+                        }
+                        break;
+                    case AFTER_CONTENT_SECTION:
+                        output.println(line);
+                        break;
+                }
+            } while (true);
+
+        }
+
+        Files.move(temporaryFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
+
+        if (title != null) {
+            titles.put(file, title);
+        }
+    }
+
+    static Map<File, String> removeContentSetcion(File dest) throws Exception {
+        LOG.log(Level.INFO, "Removing \"Content\" section of files...");
+
+        // Retrieve the list of asciidoc files
+        List<File> asciidocFiles = Files.find(dest.toPath(), 999,
+                (p, bfa) -> bfa.isRegularFile()).map(Path::toFile).filter((f) -> f.getName().endsWith(".asciidoc")).collect(Collectors.toList());
+
+        // Remove the 'Contents' section and fetch titles
+        HashMap<File, String> titles = new HashMap<>();
+        for (File file : asciidocFiles) {
+            cleanUpAndGetTitle(file, titles);
+        }
+
+        return titles;
+    }
+
+    /**
+     * Generates index.asciidoc, index_ja.asciidoc, index_pt_BR.asciidoc, etc.,
+     * in the nested directories, each index file has a list of links to
+     * asciidoc documents in the directory. Asciidoc documents are sorted by
+     * title, attending to the proper Locale collation rules.
+     *
+     * @param dest The destination directory.
+     * @param titles
+     * @throws IOException
+     */
+    static void generateIndexes(File dest, Map<File, String> titles) throws IOException {
+        LOG.info("Generating index.asciidoc (and translations) on all directories...");
+        /*
+        Compute the list of directories under 'dest'
+         */
+        List<File> directories = Files.find(dest.toPath(), 999,
+                (p, bfa) -> bfa.isDirectory()
+        ).map((p) -> p.toFile()).collect(Collectors.toList());
+
+        /*
+        A filter that selects documents in english (i.e., without _ja, _pt_BR, etc. suffixes).
+         */
+        FileFilter englishTutorialsFilter = (f) -> f.isFile() && Language.getLanguage(f) == Language.DEFAULT;
+
+        MustacheFactory factory = new DefaultMustacheFactory();
+        Mustache indexMustache = factory.compile("org/netbeans/tools/tutorials/index-template.mustache");
+        Mustache sectionMustache = factory.compile("org/netbeans/tools/tutorials/section-template.mustache");
+
+        /*
+        Iterate over all nexted directories...
+         */
+        for (File directory : directories) {
+            if ("images".equals(directory.getName())) {
+                continue;
+            }
+
+            HashMap<Language, List<File>> filesByLanguage = new HashMap<>();
+            /*
+            Compute the files in english
+             */
+            File[] tutorialsEnglish = directory.listFiles(englishTutorialsFilter);
+            for (File english : tutorialsEnglish) {
+
+                List<File> englishFiles = filesByLanguage.get(Language.DEFAULT);
+                if (englishFiles == null) {
+                    englishFiles = new ArrayList<File>();
+                    filesByLanguage.put(Language.DEFAULT, englishFiles);
+                }
+                englishFiles.add(english);
+                /*
+                And retrieve the list of translations of the english file.
+                 */
+                HashMap<Language, File> translations = Language.getTranslations(english);
+                for (Map.Entry<Language, File> translation : translations.entrySet()) {
+                    List<File> languageFiles = filesByLanguage.get(translation.getKey());
+                    if (languageFiles == null) {
+                        languageFiles = new ArrayList<>();
+                        filesByLanguage.put(translation.getKey(), languageFiles);
+                    }
+                    languageFiles.add(translation.getValue());
+                }
+            }
+
+            for (Map.Entry<Language, List<File>> entry : filesByLanguage.entrySet()) {
+                Language language = entry.getKey();
+                if (language == Language.UNKNOWN) {
+                    continue;
+                }
+                ResourceBundle bundle = ResourceBundle.getBundle("org.netbeans.tools.tutorials.TutorialsBundle", language.locale);
+                String directoryTitle = bundle.getString( directory.getName() + ".title");
+                if (directoryTitle == null) {
+                    throw new IllegalArgumentException("Please add a title for directory '" + directory.getName() + "' in locale " + language.locale);
+                }
+                LocalizedTutorialSection section = new LocalizedTutorialSection(language, directoryTitle);
+                section.addAll(entry.getValue());
+                section.sort(titles);
+
+                // Generate the index
+                String name = "index" + language.extension;
+                File output = new File(directory, name);
+                try (Writer indexOutput = new OutputStreamWriter(new FileOutputStream(output), StandardCharsets.UTF_8)) {
+                    indexMustache.execute(indexOutput, section);
+                }
+                
+                // Also generate section.asciidoc (section_ja.asciidoc, etc.)
+                // This will be in a sidebar for all tutorials in this section
+                String sectionSidebarName = "section" + language.extension;
+                File sectionSidebarFile = new File(directory, sectionSidebarName);
+                try (Writer indexOutput = new OutputStreamWriter(new FileOutputStream(sectionSidebarFile), StandardCharsets.UTF_8)) {
+                    sectionMustache.execute(indexOutput, section);
+                }
+                
+            }
+
+
+        }
+    }
+
+}
diff --git a/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/CustomAsciiDocDocumentBuilder.java b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/CustomAsciiDocDocumentBuilder.java
new file mode 100644
index 0000000..f49f291
--- /dev/null
+++ b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/CustomAsciiDocDocumentBuilder.java
@@ -0,0 +1,544 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+ */
+package org.netbeans.tools.tutorials;
+
+import com.google.common.base.Joiner;
+import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.base.Strings;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+import org.eclipse.mylyn.wikitext.asciidoc.internal.AsciiDocDocumentBuilder;
+import org.eclipse.mylyn.wikitext.parser.Attributes;
+import org.eclipse.mylyn.wikitext.parser.DocumentBuilder;
+import org.eclipse.mylyn.wikitext.parser.ImageAttributes;
+import org.eclipse.mylyn.wikitext.parser.LinkAttributes;
+import org.eclipse.mylyn.wikitext.parser.builder.AbstractMarkupDocumentBuilder;
+
+/**
+ * An AsciiDocDocumentBuilder.
+ *
+ * @author Antonio Vieiro <vi...@apache.org>
+ */
+public class CustomAsciiDocDocumentBuilder extends AsciiDocDocumentBuilder {
+
+    /**
+     * Base class for asciidoc delimited blocks.
+     */
+    class AsciiDocContentBlock extends AbstractMarkupDocumentBuilder.NewlineDelimitedBlock {
+
+        protected String prefix;
+        protected String suffix;
+
+        AsciiDocContentBlock(DocumentBuilder.BlockType blockType, String prefix, String suffix) {
+            this(blockType, prefix, suffix, 1, 1);
+        }
+
+        AsciiDocContentBlock(DocumentBuilder.BlockType blockType, String prefix, String suffix, int leadingNewlines, int trailingNewlines) {
+            super(blockType, leadingNewlines, trailingNewlines);
+            this.prefix = prefix;
+            this.suffix = suffix;
+        }
+
+        AsciiDocContentBlock(String prefix, String suffix, int leadingNewlines, int trailingNewlines) {
+            this(null, prefix, suffix, leadingNewlines, trailingNewlines);
+        }
+
+        @Override
+        public void write(int c) throws IOException {
+            CustomAsciiDocDocumentBuilder.this.emitContent(c);
+        }
+
+        @Override
+        public void write(String s) throws IOException {
+            CustomAsciiDocDocumentBuilder.this.emitContent(s);
+        }
+
+        @Override
+        public void open() throws IOException {
+            super.open();
+            pushWriter(new StringWriter());
+        }
+
+        @Override
+        public void close() throws IOException {
+            Writer thisContent = popWriter();
+            String content = thisContent.toString();
+            if (content.length() > 0) {
+                emitContent(content);
+            }
+            super.close();
+        }
+
+        protected void emitContent(final String content) throws IOException {
+            CustomAsciiDocDocumentBuilder.this.emitContent(prefix);
+            String trimmedContent = content.replaceAll("\\*Note:[ ]\\*\\*", "NOTE: ");
+            // String trimmedContent = content.replaceAll("\\*[Nn][Oo][Tt][Ee]:[ ]*\\*", "NOTE: ");
+            CustomAsciiDocDocumentBuilder.this.emitContent(trimmedContent);
+            CustomAsciiDocDocumentBuilder.this.emitContent(suffix);
+        }
+
+    }
+
+    /**
+     * Handles links. Links with images are handled properly, copying images
+     * from the image directory close to the document.
+     */
+    private class LinkBlock extends AsciiDocContentBlock {
+
+        final LinkAttributes attributes;
+
+        LinkBlock(LinkAttributes attributes) {
+            super("", "", 0, 0);
+            this.attributes = attributes;
+        }
+
+        @Override
+        protected void emitContent(String content) throws IOException {
+            String href = attributes.getHref();
+            if (href == null) {
+                if (attributes.getId() != null) {
+                    super.emitContent("[[" + attributes.getId() + "]]\n");
+                    return;
+                }
+                LOG.log(Level.WARNING, "Empty href: {0}", href);
+            }
+            href = href == null ? "" : href;
+            href = copyImageIfRequired(href, false);
+
+            if (href.startsWith("http")) {
+                externalLinks.addExternalLink(href, CustomAsciiDocDocumentBuilder.this.relativePathToTutorialFile);
+            }
+
+            if (content.contains("image:")) {
+                // Hande links with images properly, using a image with a "link" attribute
+                // content is something like "image:images/whatever-small.png[]" (small image)
+                // href is something like "images/whatever.png" (larger image)
+                // This must be transformed (https://stackoverflow.com/questions/34299474/using-an-image-as-a-link-in-asciidoc)
+                // to
+                // image:whatever-small.png[link="whatever.png"]
+                String smallPart = content.substring(6);
+                int i = smallPart.indexOf('[');
+                smallPart = i == -1 ? smallPart : smallPart.substring(0, i);
+                StringBuilder sb = new StringBuilder();
+                sb.append("\n[.feature]\n");
+                sb.append("--\n");
+                sb.append("image");
+                sb.append(smallPart);
+                sb.append("[role=\"left\", link=\"").append(href).append("\"]\n");
+                sb.append("--\n");
+                CustomAsciiDocDocumentBuilder.this.emitContent(sb.toString());
+            } else {
+                // link::http://url.com[label]
+                CustomAsciiDocDocumentBuilder.this.emitContent("link:"); //$NON-NLS-1$
+                CustomAsciiDocDocumentBuilder.this.emitContent(href);
+                CustomAsciiDocDocumentBuilder.this.emitContent("[+");
+                if (content != null) {
+                    CustomAsciiDocDocumentBuilder.this.emitContent(content);
+                }
+                CustomAsciiDocDocumentBuilder.this.emitContent("+]");
+            }
+        }
+
+    }
+
+    /**
+     * A header-1 block.
+     */
+    class AsciiDocMainHeaderBlock extends AsciiDocContentBlock {
+
+        public AsciiDocMainHeaderBlock() {
+            super("", "", 2, 2);
+        }
+
+        @Override
+        protected void emitContent(String content) throws IOException {
+            String trimmedContent = content.replaceAll("\n", " ");
+            super.emitContent("= " + trimmedContent + "\n");
+            super.emitContent(":jbake-type: platform-tutorial\n");
+            super.emitContent(":jbake-tags: tutorials \n");
+            super.emitContent(":jbake-status: published\n");
+            super.emitContent(":syntax: true\n");
+            super.emitContent(":source-highlighter: pygments\n");
+            super.emitContent(":toc: left\n");
+            super.emitContent(":toc-title:\n");
+            super.emitContent(":icons: font\n");
+            super.emitContent(":experimental:\n");
+            super.emitContent(":description: " + trimmedContent + " - Apache NetBeans\n");
+            super.emitContent(":keywords: Apache NetBeans Platform, Platform Tutorials, " + trimmedContent + "");
+            super.emitContent("\n");
+        }
+
+    }
+
+    private static final String headerPrefix(int level) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < level; i++) {
+            sb.append("=");
+        }
+        sb.append(" ");
+        return sb.toString();
+    }
+
+    class NumberedListItemBlock extends AsciiDocContentBlock {
+
+        private int count = 0;
+        
+        private NumberedListItemBlock(String prefix) {
+            super(BlockType.LIST_ITEM, prefix, "", 1, 1);
+        }
+
+        @Override
+        public void open() throws IOException {
+            super.open();
+            if (getPreviousBlock() instanceof AsciiDocListBlock) {
+                AsciiDocListBlock list = (AsciiDocListBlock) getPreviousBlock();
+                list.addListItem(this);
+                count = list.getCount();
+            }
+        }
+
+        @Override
+        protected void emitContent(String content) throws IOException {
+            if (getPreviousBlock().getBlockType() == BlockType.NUMERIC_LIST) {
+                prefix = String.format("%n[start=%d]%n%d. ", count, count);
+            }
+            super.emitContent(content);
+        }
+
+    }
+
+    class AsciiDocListBlock extends AsciiDocContentBlock {
+
+        private int count = 0;
+
+        private AsciiDocListBlock(BlockType type, int leadingNewLines) {
+            super(type, "", "", leadingNewLines, 1);
+        }
+
+        @Override
+        protected void emitContent(String content) throws IOException {
+            super.emitContent(prefix);
+            super.emitContent(content);
+            if (!content.endsWith("\n\n")) {
+                super.emitContent(suffix);
+            }
+        }
+
+        protected void addListItem(NumberedListItemBlock item) {
+            count++;
+        }
+
+        protected int getCount() {
+            return count;
+        }
+    }
+
+    class AsciiDocHeaderBlock extends AsciiDocContentBlock {
+
+        private Attributes attributes;
+        private int level;
+
+        public AsciiDocHeaderBlock(int level, Attributes attributes) {
+            super("", "", 2, 2);
+            this.attributes = attributes;
+            this.level = level;
+        }
+
+        @Override
+        protected void emitContent(String content) throws IOException {
+            super.emitContent("\n");
+            if (attributes != null && attributes.getId() != null) {
+                super.emitContent("[[" + attributes.getId() + "]]\n");
+            }
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < level; i++) {
+                sb.append("=");
+            }
+            sb.append(" ");
+            sb.append(content.replaceAll("\n", " "));
+            sb.append("\n\n");
+            super.emitContent(sb.toString());
+        }
+    }
+
+    /**
+     * An inline code block.
+     */
+    class AsciiDocInlinePreformatted extends AsciiDocContentBlock {
+
+        String language;
+
+        AsciiDocInlinePreformatted(String language, CustomAsciiDocDocumentBuilder documentBuilder) {
+            super(BlockType.CODE, " ``", "`` ", 0, 0);
+            this.language = language;
+        }
+
+    }
+
+    private static final Pattern RUBY_PATTERN = Pattern.compile("^require '.*", Pattern.MULTILINE);
+    private static final Pattern C_PATTERN = Pattern.compile("^#include.*", Pattern.MULTILINE);
+    private static final Pattern SHELL_PATTERN = Pattern.compile("^\\$ ", Pattern.MULTILINE);
+
+    /**
+     * Generates a
+     * <pre> block.
+     */
+    class AsciiDocPreformatted extends AsciiDocContentBlock {
+
+        String language;
+
+        AsciiDocPreformatted(String language) {
+            super(BlockType.PREFORMATTED, "", "", 1, 1);
+            this.language = language;
+        }
+
+        @Override
+        protected void emitContent(String content) throws IOException {
+            if (language == null) {
+                // Use "java" as a default language for tutorials
+                language = "java";
+                if (content.contains("<?xml") || content.contains("</")) {
+                    language = "xml";
+                }
+                if (content.contains("<div") || content.contains("<p>")) {
+                    language = "html";
+                }
+                if (C_PATTERN.matcher(content).find()) {
+                    language = "c";
+                }
+                if (RUBY_PATTERN.matcher(content).find()) {
+                    language = "ruby";
+                }
+                if (SHELL_PATTERN.matcher(content).find()) {
+                    language = "shell";
+                }
+                if (content.contains("<?php")) {
+                    language = "php";
+                }
+                if (content.contains("$.") || content.contains("function (")) {
+                    language = "javascript";
+                }
+            }
+            // [label](http://url.com) or
+            // [label](http://url.com "title")
+            CustomAsciiDocDocumentBuilder.this.emitContent("\n[source," + language + "]\n");
+            CustomAsciiDocDocumentBuilder.this.emitContent("----\n");
+            CustomAsciiDocDocumentBuilder.this.emitContent(content.startsWith("\n") ? content : "\n" + content);
+            CustomAsciiDocDocumentBuilder.this.emitContent("\n----\n\n");
+        }
+    }
+
+    private static Logger LOG = Logger.getLogger(CustomAsciiDocDocumentBuilder.class.getName());
+
+    private File topDirectory;
+    private File imageDirectory;
+    private File outputDirectory;
+    private File imageDestDirectory;
+    private ExternalLinksMap externalLinks;
+    private Language language;
+    private String relativePathToTutorialsRoot;
+    private File outputFile;
+    private String relativePathToTutorialFile;
+
+    public CustomAsciiDocDocumentBuilder(File topDirectory, File imageDirectory, File outputFile, BufferedWriter output, ExternalLinksMap externalLinks) {
+        super(output);
+        this.topDirectory = topDirectory;
+        this.imageDirectory = imageDirectory;
+        this.outputFile = outputFile;
+        this.outputDirectory = outputFile.getParentFile();
+        this.language = Language.getLanguage(outputFile);
+        imageDestDirectory = new File(outputDirectory, "images");
+        imageDestDirectory.mkdirs();
+        this.externalLinks = externalLinks;
+        relativePathToTutorialsRoot = outputDirectory.toURI().relativize(topDirectory.toURI()).getPath();
+        relativePathToTutorialFile = topDirectory.toURI().relativize(outputFile.toURI()).getPath();
+
+    }
+
+    /**
+     * Responsible for handling headers.
+     *
+     * @param level
+     * @param attributes
+     * @return
+     */
+    @Override
+    protected Block computeHeading(int level, Attributes attributes) {
+        if (level == 1) {
+            return new AsciiDocMainHeaderBlock();
+        }
+        //return super.computeHeading(level, attributes);
+        return new AsciiDocHeaderBlock(level, attributes);
+    }
+
+    @Override
+    protected Block computeSpan(SpanType type, Attributes attributes) {
+        switch (type) {
+            case MARK:
+                throw new IllegalStateException("Mark");
+            case MONOSPACE:
+                return new AsciiDocInlinePreformatted(null, this);
+            case LINK:
+                if (attributes instanceof LinkAttributes) {
+                    LinkAttributes linkAttributes = (LinkAttributes) attributes;
+                    if (linkAttributes.getHref() != null) {
+                        if (linkAttributes.getHref().startsWith("#")) {
+                            return new AsciiDocContentBlock("<<" + linkAttributes.getHref().substring(1) + ",", ">>", 0, 0);
+                            /* This is an internal link */
+                        }
+                        return new LinkBlock((LinkAttributes) attributes);
+                    } else if (linkAttributes.getId() != null) {
+                        return new AsciiDocContentBlock("[[", "]]", 0, 1);
+                    }
+                }
+                return new AsciiDocContentBlock("", "", 0, 0);
+            case DELETED:
+                return new AsciiDocContentBlock("[.line-through]#", "#", 0, 0);
+            case UNDERLINED:
+                return new AsciiDocContentBlock("[.underline]#", "#", 0, 0);
+            default:
+                return super.computeSpan(type, attributes);
+        }
+    }
+
+    @Override
+    public void link(Attributes attributes, String hrefOrHashName, String text) {
+        super.link(attributes, hrefOrHashName, text);
+    }
+
+    @Override
+    public void entityReference(String entity) {
+        super.entityReference(entity);
+    }
+
+    @Override
+    protected Block
+            computeBlock(BlockType type, Attributes attributes) {
+        switch (type) {
+            case CODE:
+            case PREFORMATTED:
+                String language = null;
+
+                if (attributes.getCssClass() != null) {
+                    if (attributes.getCssClass().equals("source-java")) {
+                        language = "java";
+
+                    } else if (attributes.getCssClass().equals("source-xml")) {
+                        language = "xml";
+
+                    } else if (attributes.getCssClass().equals("source-properties")) {
+                        language = "yaml";
+
+                    }
+                }
+                return new AsciiDocPreformatted(language);
+            case NUMERIC_LIST:
+                if (currentBlock != null) {
+                    BlockType currentBlockType = currentBlock.getBlockType();
+                    if (currentBlockType == BlockType.LIST_ITEM || currentBlockType == BlockType.DEFINITION_ITEM
+                            || currentBlockType == BlockType.DEFINITION_TERM) {
+                        return new AsciiDocListBlock(type, 1);
+                    }
+                }
+                return new AsciiDocListBlock(type, 2);
+            case LIST_ITEM:
+                if (computeCurrentListType() == BlockType.NUMERIC_LIST) {
+                    return new NumberedListItemBlock(""); //$NON-NLS-1$
+                }
+                return super.computeBlock(type, attributes);
+            default:
+                return super.computeBlock(type, attributes);
+        }
+    }
+
+    @Override
+    public void image(Attributes attributes, String url) {
+        url = copyImageIfRequired(url, true);
+
+        assertOpenBlock();
+        try {
+            currentBlock.write(computeImage(attributes, url, false));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private String computeImage(Attributes attributes, String url, boolean inline) {
+        // image:/path/to/img.jpg[] or
+        // image:path/to/img.jpg[alt text]
+        String altText = null;
+        String title = null;
+        if (attributes instanceof ImageAttributes) {
+            ImageAttributes imageAttr = (ImageAttributes) attributes;
+            altText = imageAttr.getAlt();
+        }
+        if (!Strings.isNullOrEmpty(attributes.getTitle())) {
+            title = "title=\"" + attributes.getTitle().replaceAll("\n", " ").replaceAll("\r", " ") + '"'; //$NON-NLS-1$
+        }
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(inline ? "image:" : "image::"); //$NON-NLS-1$
+        sb.append(Strings.nullToEmpty(url));
+        sb.append("["); //$NON-NLS-1$
+        sb.append(Joiner.on(", ").skipNulls().join(altText, title)); //$NON-NLS-1$
+        sb.append("]"); //$NON-NLS-1$
+        return sb.toString();
+    }
+
+    private String copyImageIfRequired(String url, boolean warnMissingImages) {
+        if (url.startsWith("http://") || url.startsWith("https://")) {
+            // System.err.format("Image url '%s'%n", url);
+            return url;
+        }
+        if (url.startsWith("../../images/")) {
+            url = url.replace("../../images/", "/");
+        }
+        if (url.startsWith("../images/")) {
+            url = url.replace("../images/", "/");
+        }
+        File imageFile = new File(imageDirectory, url);
+        File copiedImageFile = new File(imageDestDirectory, imageFile.getName());
+        if (imageFile.exists()) {
+            if (!copiedImageFile.exists()) {
+                try {
+                    Files.copy(imageFile.toPath(), copiedImageFile.toPath());
+                    url = imageDestDirectory.getName() + "/" + imageFile.getName();
+                } catch (IOException ex) {
+                    Logger.getLogger(CustomAsciiDocDocumentBuilder.class.getName()).log(Level.SEVERE, null, ex);
+                }
+            } else {
+                url = imageDestDirectory.getName() + "/" + imageFile.getName();
+            }
+        } else if (warnMissingImages) {
+            LOG.log(Level.WARNING, "Image not found: {0}\n  in file {1}\n  for file {2}", new Object[]{url,
+                imageFile.getAbsolutePath(),
+                CustomAsciiDocDocumentBuilder.this.outputFile.getAbsolutePath()});
+        }
+        return url;
+    }
+}
diff --git a/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/CustomAsciiDocDocumentBuilderWithoutTables.java b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/CustomAsciiDocDocumentBuilderWithoutTables.java
new file mode 100644
index 0000000..bc701c2
--- /dev/null
+++ b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/CustomAsciiDocDocumentBuilderWithoutTables.java
@@ -0,0 +1,47 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+ */
+package org.netbeans.tools.tutorials;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import org.eclipse.mylyn.wikitext.parser.Attributes;
+
+/**
+ *
+ */
+public class CustomAsciiDocDocumentBuilderWithoutTables extends CustomAsciiDocDocumentBuilder {
+
+    public CustomAsciiDocDocumentBuilderWithoutTables(File topDirectory, File imageDirectory, File outputFile, BufferedWriter output, ExternalLinksMap externalLinks) {
+        super(topDirectory, imageDirectory, outputFile, output, externalLinks);
+    }
+
+    @Override
+    protected Block computeBlock(BlockType type, Attributes attributes) {
+        switch(type) {
+            case TABLE:
+            case TABLE_CELL_HEADER:
+            case TABLE_CELL_NORMAL:
+            case TABLE_ROW:
+                return new AsciiDocContentBlock("", "", 0, 0);
+        }
+        return super.computeBlock(type, attributes); //To change body of generated methods, choose Tools | Templates.
+    }
+
+
+}
diff --git a/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/ExternalLinksMap.java b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/ExternalLinksMap.java
new file mode 100644
index 0000000..3e19585
--- /dev/null
+++ b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/ExternalLinksMap.java
@@ -0,0 +1,81 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+ */
+package org.netbeans.tools.tutorials;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Maps external links (String, href) to File's referencing it.
+ */
+public class ExternalLinksMap {
+    
+    /** Maps "href" to tutorials. */
+    private TreeMap<String, TreeSet<String>> hrefToFile;
+    /** Maps domain names ("bits.netbeans.org", for instance) to hrefs */
+    private TreeMap<String, TreeSet<String>> domainToHref;
+    
+    public ExternalLinksMap() {
+        hrefToFile = new TreeMap<>();
+        domainToHref = new TreeMap<>();
+    }
+    
+    public void addExternalLink(String href, String tutorial) {
+        try {
+            URL url = new URL(href);
+            TreeSet<String> hrefs = domainToHref.get(url.getHost());
+            if (hrefs == null) {
+                hrefs = new TreeSet<>();
+                domainToHref.put(url.getHost(), hrefs);
+            }
+            hrefs.add(href);
+            
+            TreeSet<String> tutorials = hrefToFile.get(href);
+            if (tutorials == null) {
+                tutorials = new TreeSet<>();
+                hrefToFile.put(href, tutorials);
+            }
+            tutorials.add(tutorial);
+        } catch (MalformedURLException ex) {
+            Logger.getLogger(ExternalLinksMap.class.getName()).log(Level.SEVERE, null, ex);
+        }
+    }
+    
+    public Set<String> getDomains() {
+        return domainToHref.keySet();
+    }
+    
+    public Set<String> getHrefs(String domain) {
+        return domainToHref.get(domain);
+    }
+    
+    public Set<String> getTutorials(String href) {
+        return hrefToFile.get(href);
+    }
+    
+    @Override
+    public String toString() {
+        return domainToHref.toString() + " " + hrefToFile.toString();
+    }
+}
diff --git a/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/HTMLConverter.java b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/HTMLConverter.java
new file mode 100644
index 0000000..9954675
--- /dev/null
+++ b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/HTMLConverter.java
@@ -0,0 +1,268 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+ */
+package org.netbeans.tools.tutorials;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+import java.io.*;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.eclipse.mylyn.wikitext.parser.HtmlParser;
+import org.xml.sax.InputSource;
+
+/**
+ * Converts HTML files in a source directory to ASCIIDOC files in a destination
+ * directory.
+ *
+ * @author Antonio Vieiro <vi...@apache.org>
+ */
+public class HTMLConverter {
+
+    private static final String APACHE_LICENSE_HEADER = ""
+            + "\n"
+            + "    Licensed to the Apache Software Foundation (ASF) under one\n"
+            + "    or more contributor license agreements.  See the NOTICE file\n"
+            + "    distributed with this work for additional information\n"
+            + "    regarding copyright ownership.  The ASF licenses this file\n"
+            + "    to you under the Apache License, Version 2.0 (the\n"
+            + "    \"License\"); you may not use this file except in compliance\n"
+            + "    with the License.  You may obtain a copy of the License at\n"
+            + "\n"
+            + "      http://www.apache.org/licenses/LICENSE-2.0\n"
+            + "\n"
+            + "    Unless required by applicable law or agreed to in writing,\n"
+            + "    software distributed under the License is distributed on an\n"
+            + "    \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n"
+            + "    KIND, either express or implied.  See the License for the\n"
+            + "    specific language governing permissions and limitations\n"
+            + "    under the License.\n\n";
+
+    private static final Logger LOG = Logger.getLogger(HTMLConverter.class.getName());
+    private static Transformer transformer;
+    private static DocumentBuilderFactory documentBuilderFactory;
+
+    private static void convert(File docsTutorialsDocs, File docsTutorialsImages, File dest, ExternalLinksMap externalLinks) throws Exception {
+        LOG.log(Level.INFO, "Converting tutorials from {0} to {1}", new Object[]{docsTutorialsDocs.getAbsolutePath(), dest.getAbsolutePath()});
+
+        List<File> html_files = Files.find(docsTutorialsDocs.toPath(), 999,
+                (p, bfa) -> bfa.isRegularFile()).map(Path::toFile).filter((f) -> f.getName().endsWith(".html")).collect(Collectors.toList());
+
+        URI baseDirectory = docsTutorialsDocs.toURI();
+        int fileCount = 0;
+        boolean debug=false;
+        for (File htmlFile : html_files) {
+            if (debug) {
+                if (! htmlFile.getName().equals("annotations.html")) {
+                    continue;
+                }
+            }
+            String relativePath = baseDirectory.relativize(htmlFile.toURI()).getPath().replaceAll("\\.html", ".asciidoc");
+            File asciidoc = new File(dest, relativePath);
+            convertHTMLToAsciiDoc(dest, htmlFile, docsTutorialsImages, asciidoc, externalLinks);
+            fileCount++;
+        }
+        LOG.log(Level.INFO, "Converted {0} tutorials.", fileCount);
+    }
+    
+    private static void convertTrails(File docsTutorialsTrailsDirectory, File docsTutorialsImages, File dest, ExternalLinksMap externalLinks) throws Exception {
+         LOG.log(Level.INFO, "Converting trails {0} to {1}", new Object[]{docsTutorialsTrailsDirectory.getAbsolutePath(), dest.getAbsolutePath()});
+
+        List<File> html_files = Files.find(docsTutorialsTrailsDirectory.toPath(), 999,
+                (p, bfa) -> bfa.isRegularFile()).map(Path::toFile).filter((f) -> f.getName().endsWith(".html")).collect(Collectors.toList());
+
+        URI baseDirectory = docsTutorialsTrailsDirectory.toURI();
+        int fileCount = 0;
+        boolean debug=false;
+        for (File htmlFile : html_files) {
+            if (debug) {
+                if (! htmlFile.getName().equals("annotations.html")) {
+                    continue;
+                }
+            }
+            String relativePath = baseDirectory.relativize(htmlFile.toURI()).getPath().replaceAll("\\.html", ".asciidoc");
+            File asciidoc = new File(dest, relativePath);
+            convertHTMLToAsciiDocWithoutTables(dest, htmlFile, docsTutorialsImages, asciidoc, externalLinks);
+            fileCount++;
+        }
+        LOG.log(Level.INFO, "Converted {0} trails.", fileCount);       
+    }
+
+    private static String asciidocHeader = null;
+
+    private static String getAsciidocHeader() {
+        if (asciidocHeader == null) {
+            StringBuilder sb = new StringBuilder();
+            String[] lines = APACHE_LICENSE_HEADER.split("\n");
+            for (String line : lines) {
+                sb.append("// ").append(line).append("\n");
+            }
+            sb.append("//\n\n");
+            asciidocHeader = sb.toString();
+        }
+        return asciidocHeader;
+    }
+
+    private static ThreadLocal<SimpleDateFormat> sdf = null;
+
+    private static final synchronized SimpleDateFormat getSimpleDateFormat() {
+        if (sdf == null) {
+            SimpleDateFormat d = new SimpleDateFormat("yyyy-MM-dd");
+            sdf = new ThreadLocal<>();
+            sdf.set(d);
+        }
+        return sdf.get();
+    }
+
+    /**
+     * Converts the given HTML file to AsciiDoc format.
+     * @param inputHTMLFile The input file.
+     * @param imageDirectory The directory where images are to be found.
+     * @param outputAsciidocFile The output asciidoc file.
+     * @param externalLinks A map used to store external links detected in the HTML file.
+     * @throws Exception on error.
+     */
+    private static void convertHTMLToAsciiDoc(File topDirectory, File inputHTMLFile, File imageDirectory, File outputAsciidocFile, ExternalLinksMap externalLinks) throws Exception {
+        if (! outputAsciidocFile.getParentFile().exists()) {
+            if (! outputAsciidocFile.getParentFile().mkdirs()) {
+                throw new IOException(String.format("Cannot create directory '%s'", outputAsciidocFile.getParent()));
+            }
+        }
+        System.out.format("Converting '%s'%n", inputHTMLFile.getAbsolutePath());
+        try (BufferedWriter output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputAsciidocFile), "utf-8"), 16 * 1024);
+                BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputHTMLFile), "utf-8"))) {
+            output.write(getAsciidocHeader());
+
+            CustomAsciiDocDocumentBuilder asciidocBuilder = new CustomAsciiDocDocumentBuilder(topDirectory, imageDirectory, outputAsciidocFile, output, externalLinks);
+            InputSource source = new InputSource(reader);
+            HtmlParser.instance().parse(source, asciidocBuilder);
+            // HtmlParser.instanceWithHtmlCleanupRules().parse(source, asciidocBuilder);
+        }
+    }
+    
+    /**
+     * Converts the given HTML file to AsciiDoc format, ignoring tables completely.
+     * @param inputHTMLFile The input file.
+     * @param imageDirectory The directory where images are to be found.
+     * @param outputAsciidocFile The output asciidoc file.
+     * @param externalLinks A map used to store external links detected in the HTML file.
+     * @throws Exception on error.
+     */
+    private static void convertHTMLToAsciiDocWithoutTables(File topDirectory, File inputHTMLFile, File imageDirectory, File outputAsciidocFile, ExternalLinksMap externalLinks) throws Exception {
+        if (! outputAsciidocFile.getParentFile().exists()) {
+            if (! outputAsciidocFile.getParentFile().mkdirs()) {
+                throw new IOException(String.format("Cannot create directory '%s'", outputAsciidocFile.getParent()));
+            }
+        }
+        try (BufferedWriter output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputAsciidocFile), "utf-8"), 16 * 1024);
+                BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputHTMLFile), "utf-8"))) {
+            output.write(getAsciidocHeader());
+
+            CustomAsciiDocDocumentBuilderWithoutTables asciidocBuilder = new CustomAsciiDocDocumentBuilderWithoutTables(topDirectory, imageDirectory, outputAsciidocFile, output, externalLinks);
+            InputSource source = new InputSource(reader);
+            HtmlParser.instance().parse(source, asciidocBuilder);
+        }
+    }
+    
+    private static void checkDirectoryExists(String message, File dir) throws Exception {
+        String error = null;
+        if (!dir.exists()) {
+            error = String.format("%s '%s' does not exist.", message, dir.getAbsolutePath());
+        }
+        if (error == null && !dir.isDirectory()) {
+            error = String.format("%s '%s' is not a directory.", message, dir.getAbsolutePath());
+        }
+        if (error == null && !dir.canRead()) {
+            error = String.format("%s '%s' is not readable.", message, dir.getAbsolutePath());
+        }
+        if (error != null) {
+            throw new IllegalArgumentException(error);
+        }
+    }
+
+    private static void usage() {
+
+        System.err.println("Use: java " + HTMLConverter.class
+                .getName() + " tutorials-directory images-directory");
+        System.err.println("   See README.md for instructions on how to prepare those directories.");
+        System.exit(1);
+    }
+
+    public static void main(String[] args) throws Exception {
+        if (args.length != 2) {
+            usage();
+        }
+        
+        File tutorialsDirectory = new File(args[0]);
+        checkDirectoryExists("Incorrect tutorials directory ", tutorialsDirectory);
+        
+        File docsTutorialsImagesDirectory = new File(args[1]);
+        checkDirectoryExists("Incorrect images directory ", docsTutorialsImagesDirectory);
+
+        File currentDirectory = new File(System.getProperty("user.dir"));
+        File dest = new File(currentDirectory, "tutorials-asciidoc");
+
+        if (!dest.exists()) {
+            if (!dest.mkdirs()) {
+                throw new IllegalStateException("Cannot create directory " + dest.getAbsolutePath());
+            }
+        }
+
+        checkDirectoryExists("Output directory", dest);
+        if (!dest.canWrite()) {
+            throw new IllegalStateException("Cannot write to " + dest.getAbsolutePath());
+        }
+
+        ExternalLinksMap externalLinks = new ExternalLinksMap();
+
+        convert(tutorialsDirectory, docsTutorialsImagesDirectory, dest, externalLinks);
+        
+        convertTrails(tutorialsDirectory, docsTutorialsImagesDirectory, dest, externalLinks);
+
+        LOG.info("Generating 'external-links.txt' with list of external links...");
+        
+        try ( PrintWriter ef = new PrintWriter(new FileWriter("external-links.yml"))) {
+            for (String domain : externalLinks.getDomains()) {
+                ef.format("- domain: \"%s\"%n", domain);
+                ef.format("  links:%n");
+                for (String href : externalLinks.getHrefs(domain)) {
+                    ef.format("    link: \"%s\"%n", href);
+                    ef.format("    used-at:%n");
+                    for (String tutorial : externalLinks.getTutorials(href)) {
+                        ef.format("      - \"%s\"%n", tutorial);
+                    }
+                }
+            }
+        }
+
+        /* Remove a hand-made "content" section, that is not replaced by the asciidoc 'toc' stuff */
+        Map<File, String> titles = AsciidocPostProcessor.removeContentSetcion(dest);
+        
+        /* Generate some "index.asciidoc" files with the list of tutorials on each directory. */
+        AsciidocPostProcessor.generateIndexes(dest, titles);
+        
+    }
+
+}
diff --git a/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/Language.java b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/Language.java
new file mode 100644
index 0000000..bf4421b
--- /dev/null
+++ b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/Language.java
@@ -0,0 +1,66 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+ */
+package org.netbeans.tools.tutorials;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ *
+ * @author avieiro
+ */
+public enum Language {
+    UNKNOWN("", Locale.getDefault(), "English"), DEFAULT(".asciidoc", Locale.ENGLISH, "English"), PORTUGUESE("_pt_BR.asciidoc", new Locale("pt_BR"), "Português brasileiro"), CHINESE("_zh_CN.asciidoc", new Locale("zh_CN"), "中文"), JAPANESE("_ja.asciidoc", new Locale("ja"), "日本人"), RUSSIAN("_ru.asciidoc", new Locale("ru"), "русский"), CATALAN("_ca.asciidoc", new Locale("ca_ES"), "Català");
+    public final String extension;
+    public final Locale locale;
+    public final String title;
+    /* Array of non-default languages.*/
+    public static final Language[] FOREIGN_LANGUAGES = {RUSSIAN, JAPANESE, CHINESE, PORTUGUESE, CATALAN};
+
+    Language(String extension, Locale locale, String title) {
+        this.extension = extension;
+        this.locale = locale;
+        this.title = title;
+    }
+
+    public static Language getLanguage(File file) {
+        String name = file.getName().toLowerCase();
+        for (Language language : FOREIGN_LANGUAGES) {
+            if (name.endsWith(language.extension.toLowerCase())) {
+                return language;
+            }
+        }
+        return name.endsWith(DEFAULT.extension.toLowerCase()) ? DEFAULT : UNKNOWN;
+    }
+
+    public static HashMap<Language, File> getTranslations(File file) {
+        File parentDirectory = file.getParentFile();
+        String prefix = file.getName().replace(".asciidoc", "");
+        HashMap<Language, File> translations = new HashMap<>();
+        for (Language l : FOREIGN_LANGUAGES) {
+            File translationFile = new File(parentDirectory, prefix + l.extension);
+            if (translationFile.exists()) {
+                translations.put(l, translationFile);
+            }
+        }
+        return translations;
+    }
+    
+}
diff --git a/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/LocalizedTutorialSection.java b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/LocalizedTutorialSection.java
new file mode 100644
index 0000000..c58124f
--- /dev/null
+++ b/tutorials-convert/src/main/java/org/netbeans/tools/tutorials/LocalizedTutorialSection.java
@@ -0,0 +1,91 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+ */
+package org.netbeans.tools.tutorials;
+
+import java.io.File;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author avieiro
+ */
+public class LocalizedTutorialSection {
+    
+    public static final String URL_KEY = "url";
+    public static final String TITLE_KEY = "title";
+    private Language language;
+    private ArrayList<File> files;
+    private ArrayList<HashMap<String, String>> details;
+    private String title;
+    
+    public LocalizedTutorialSection(Language language, String title) {
+        this.language = language;
+        this.files = new ArrayList<>();
+        this.details = new ArrayList<>();
+        this.title = title;
+    }
+
+    public void add(File file) {
+        this.files.add(file);
+    }
+
+    public void addAll(List<File> files) {
+        this.files.addAll(files);
+    }
+
+    public void sort(Map<File, String> fileTitles) {
+        ArrayList<File> sortedFiles = new ArrayList<>(this.files);
+        Collator collator = Collator.getInstance(language.locale);
+        sortedFiles.sort((file1, file2) -> {
+            String title1 = fileTitles.get(file1);
+            String title2 = fileTitles.get(file2);
+            title1 = title1 == null ? file1.getName() : title1;
+            title2 = title2 == null ? file2.getName() : title2;
+            return collator.compare(title1, title2);
+        });
+        this.files = sortedFiles;
+        details.clear();
+        for (File file : files) {
+            HashMap<String, String> detail = new HashMap<String, String>();
+            details.add(detail);
+            detail.put(URL_KEY, file.getName().replaceAll(".asciidoc", ".html"));
+            detail.put(TITLE_KEY, fileTitles.get(file));
+        }
+    }
+
+    public Language getLanguage() {
+        return language;
+    }
+
+    public ArrayList<File> getFiles() {
+        return files;
+    }
+
+    public ArrayList<HashMap<String, String>> getDetails() {
+        return details;
+    }
+    
+    public String getTitle() {
+        return title;
+    }
+}
diff --git a/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle.properties b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle.properties
new file mode 100644
index 0000000..ec76b64
--- /dev/null
+++ b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle.properties
@@ -0,0 +1,46 @@
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements.  See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership.  The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied.  See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+tutorials-asciidoc.title=NetBeans Tutorials
+tutorials.title=NetBeans Platform Tutorials
+60.title=NetBeans 6.0 Platform Tutorials
+61.title=NetBeans 6.1 Platform Tutorials
+67.title=NetBeans 6.7 Platform Tutorials
+68.title=NetBeans 6.8 Platform Tutorials
+69.title=NetBeans 6.9 Platform Tutorials
+691.title=NetBeans 6.9.1 Platform Tutorials
+70.title=NetBeans 7.0 Platform Tutorials
+71.title=NetBeans 7.1 Platform Tutorials
+72.title=NetBeans 7.2 Platform Tutorials
+73.title=NetBeans 7.3 Platform Tutorials
+74.title=NetBeans 7.4 Platform Tutorials
+80.title=NetBeans 8.0 Platform Tutorials
+
+
+cnd.title=C and C++ Tutorials
+ide.title=NetBeans IDE Tutorials
+java.title=Java Tutorials
+javaee.title=JavaEE Tutorials
+ecommerce.title=e-Commerce Tutorials
+javame.title=JavaME Tutorials
+php.title=PHP Tutorials
+web.title=Web Technologies Tutorials
+webclient.title=HTML5 Tutorials
+websvc.title=Web Service Tutorials
+
+
+
+
diff --git a/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_es_CA.properties b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_es_CA.properties
new file mode 100644
index 0000000..912e3ac
--- /dev/null
+++ b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_es_CA.properties
@@ -0,0 +1,31 @@
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements.  See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership.  The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied.  See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+tutorials-asciidoc.title=NetBeans Tutorials
+cnd.title=C and C++ Tutorials
+ide.title=NetBeans IDE Tutorials
+java.title=Java Tutorials
+javaee.title=JavaEE Tutorials
+ecommerce.title=e-Commerce Tutorials
+javame.title=JavaME Tutorials
+php.title=PHP Tutorials
+web.title=Web Technologies Tutorials
+webclient.title=HTML5 Tutorials
+websvc.title=Web Service Tutorials
+
+
+
+
diff --git a/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_ja.properties b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_ja.properties
new file mode 100644
index 0000000..912e3ac
--- /dev/null
+++ b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_ja.properties
@@ -0,0 +1,31 @@
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements.  See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership.  The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied.  See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+tutorials-asciidoc.title=NetBeans Tutorials
+cnd.title=C and C++ Tutorials
+ide.title=NetBeans IDE Tutorials
+java.title=Java Tutorials
+javaee.title=JavaEE Tutorials
+ecommerce.title=e-Commerce Tutorials
+javame.title=JavaME Tutorials
+php.title=PHP Tutorials
+web.title=Web Technologies Tutorials
+webclient.title=HTML5 Tutorials
+websvc.title=Web Service Tutorials
+
+
+
+
diff --git a/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_pt_BR.properties b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_pt_BR.properties
new file mode 100644
index 0000000..912e3ac
--- /dev/null
+++ b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_pt_BR.properties
@@ -0,0 +1,31 @@
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements.  See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership.  The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied.  See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+tutorials-asciidoc.title=NetBeans Tutorials
+cnd.title=C and C++ Tutorials
+ide.title=NetBeans IDE Tutorials
+java.title=Java Tutorials
+javaee.title=JavaEE Tutorials
+ecommerce.title=e-Commerce Tutorials
+javame.title=JavaME Tutorials
+php.title=PHP Tutorials
+web.title=Web Technologies Tutorials
+webclient.title=HTML5 Tutorials
+websvc.title=Web Service Tutorials
+
+
+
+
diff --git a/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_ru.properties b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_ru.properties
new file mode 100644
index 0000000..912e3ac
--- /dev/null
+++ b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_ru.properties
@@ -0,0 +1,31 @@
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements.  See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership.  The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied.  See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+tutorials-asciidoc.title=NetBeans Tutorials
+cnd.title=C and C++ Tutorials
+ide.title=NetBeans IDE Tutorials
+java.title=Java Tutorials
+javaee.title=JavaEE Tutorials
+ecommerce.title=e-Commerce Tutorials
+javame.title=JavaME Tutorials
+php.title=PHP Tutorials
+web.title=Web Technologies Tutorials
+webclient.title=HTML5 Tutorials
+websvc.title=Web Service Tutorials
+
+
+
+
diff --git a/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_zh_CN.properties b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_zh_CN.properties
new file mode 100644
index 0000000..912e3ac
--- /dev/null
+++ b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/TutorialsBundle_zh_CN.properties
@@ -0,0 +1,31 @@
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements.  See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership.  The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied.  See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+tutorials-asciidoc.title=NetBeans Tutorials
+cnd.title=C and C++ Tutorials
+ide.title=NetBeans IDE Tutorials
+java.title=Java Tutorials
+javaee.title=JavaEE Tutorials
+ecommerce.title=e-Commerce Tutorials
+javame.title=JavaME Tutorials
+php.title=PHP Tutorials
+web.title=Web Technologies Tutorials
+webclient.title=HTML5 Tutorials
+websvc.title=Web Service Tutorials
+
+
+
+
diff --git a/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/index-template.mustache b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/index-template.mustache
new file mode 100644
index 0000000..649f96e
--- /dev/null
+++ b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/index-template.mustache
@@ -0,0 +1,33 @@
+// 
+//     Licensed to the Apache Software Foundation (ASF) under one
+//     or more contributor license agreements.  See the NOTICE file
+//     distributed with this work for additional information
+//     regarding copyright ownership.  The ASF licenses this file
+//     to you under the Apache License, Version 2.0 (the
+//     "License"); you may not use this file except in compliance
+//     with the License.  You may obtain a copy of the License at
+// 
+//       http://www.apache.org/licenses/LICENSE-2.0
+// 
+//     Unless required by applicable law or agreed to in writing,
+//     software distributed under the License is distributed on an
+//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+//     KIND, either express or implied.  See the License for the
+//     specific language governing permissions and limitations
+//     under the License.
+//
+
+= {{title}}
+:jbake-type: tutorial
+:jbake-tags: tutorials
+:jbake-status: published
+:toc: left
+:toc-title:
+:description: {{title}}
+
+{{#details}}
+- link:{{url}}[{{title}}]
+{{/details}}
+
+
+
diff --git a/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/section-template.mustache b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/section-template.mustache
new file mode 100644
index 0000000..5ea7713
--- /dev/null
+++ b/tutorials-convert/src/main/resources/org/netbeans/tools/tutorials/section-template.mustache
@@ -0,0 +1,27 @@
+// 
+//     Licensed to the Apache Software Foundation (ASF) under one
+//     or more contributor license agreements.  See the NOTICE file
+//     distributed with this work for additional information
+//     regarding copyright ownership.  The ASF licenses this file
+//     to you under the Apache License, Version 2.0 (the
+//     "License"); you may not use this file except in compliance
+//     with the License.  You may obtain a copy of the License at
+// 
+//       http://www.apache.org/licenses/LICENSE-2.0
+// 
+//     Unless required by applicable law or agreed to in writing,
+//     software distributed under the License is distributed on an
+//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+//     KIND, either express or implied.  See the License for the
+//     specific language governing permissions and limitations
+//     under the License.
+//
+
+.{{title}}
+************************************************
+{{#details}}
+- link:{{url}}[{{title}}]
+{{/details}}
+************************************************
+
+


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists