diff --git a/html2xdoc/src/main/org/apache/maven/html2xdoc/Html2XdocBean.java b/html2xdoc/src/main/org/apache/maven/html2xdoc/Html2XdocBean.java
index 89d8a4f6..fa1e92de 100644
--- a/html2xdoc/src/main/org/apache/maven/html2xdoc/Html2XdocBean.java
+++ b/html2xdoc/src/main/org/apache/maven/html2xdoc/Html2XdocBean.java
@@ -62,7 +62,6 @@
package org.apache.maven.html2xdoc;
-import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -70,140 +69,289 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.CharacterData;
+import org.dom4j.Comment;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.Node;
/**
- * A simple bean for converting a HTML document into an XDoc compliant XML document.
+ * A simple bean for converting a HTML document into an XDoc compliant XML
+ * document.
* This could be done via XSLT but is a little more complex than it might first
* appear so its done via Java code instead.
*
* @author James Strachan
*/
public class Html2XdocBean {
-
+
/** The Log to which logging calls will be made. */
private static final Log log = LogFactory.getLog(Html2XdocBean.class);
-
- private DocumentFactory factory = new DocumentFactory();
- /**
- * Converts the given HTML document into the corresponding XDoc format
- * of XML
- *
- * @param html
- * @return Document
- */
- public Document convert(Document html) {
- Document doc = factory.createDocument();
- Element root = doc.addElement("document");
- Element properties = root.addElement("properties");
- Element title = properties.addElement("title");
- title.setText(html.valueOf("/html/head/title"));
-
- Element body = root.addElement("body");
-
- Element htmlContent = (Element) html.selectSingleNode("/html/body");
+ /**
+ * Used to create the output document
+ */
+ private DocumentFactory factory = new DocumentFactory();
+
+ /**
+ * The current node to attach the sub-nodes.
+ */
+ private Element currentNode = null;
+
+ /**
+ * The current 'root' section node. This is used to keep
+ * track of the root section so that when a subsection is
+ * found it can be associated correctly.
+ */
+ private Element currentSectionNode = null;
+
+ /**
+ * The current section heading level. If a subsequent level
+ * lower or equal, then create a new section.
+ */
+ private int currentSectionHeadingLevel = Integer.MIN_VALUE;
+
+ /**
+ * The current paragraph node. This is used to associate text
+ * and formatting nodes to the current paragraph node.
+ */
+ private Element currentParaNode = null;
+
+ /**
+ * Converts the given HTML document into the corresponding XDoc format
+ * of XML
+ *
+ * @param html the input html document
+ * @return Document
+ */
+ public Document convert(Document html) {
+ Document doc = factory.createDocument();
+ Element root = doc.addElement("document");
+ Element properties = root.addElement("properties");
+ Element title = properties.addElement("title");
+ title.setText(html.valueOf("/html/head/title"));
+
+ Element body = root.addElement("body");
+
+ Element htmlContent = (Element) html.selectSingleNode("/html/body");
if (htmlContent == null) {
- log.info("No body element found for HTML document: " + html.asXML());
- }
- else {
+ log.info("No body element found for HTML document: "
+ + html.asXML());
+ } else {
addSections(body, htmlContent);
}
- return doc;
- }
-
- /**
- * Iterates thorugh the given body looking for h1, h2, h3 nodes and
- * creating the associated section elements. Any text nodes
- * contained inside the body are wrapped in a <p> element
- *
- * @param output the output destination
- * @param body the block of HTML markup to convert
- */
- protected void addSections(Element output, Element body) {
- List content = getBodyContent(body.content());
- Element section = null;
- Element p = null;
-
- for (Iterator iter = content.iterator(); iter.hasNext(); ) {
- Node node = (Node) iter.next();
+ return doc;
+ }
- String name = node.getName();
- if (name != null && name.startsWith("h")) {
- /** @todo we should handle child headings as a nested section */
- section = output.addElement("section");
- section.addAttribute("name", node.getText());
- p = null;
- }
- else {
- if (section == null ) {
- // we have a section with no name
- // should we default it to be the same as the document title?
- section = output.addElement("section");
- }
-
- if (node instanceof CharacterData) {
- // lets add a
- if (p == null) {
- p = section.addElement("p");
- }
- p.addText( node.getText() );
- }
- else {
- section.add(cloneNode(node));
- p = null;
- }
+ /**
+ * Iterates thorugh the given body looking for h1, h2, h3 nodes and
+ * creating the associated section elements. Any text nodes
+ * contained inside the body are wrapped in a <p> element
+ *
+ * @param output the output destination
+ * @param body the block of HTML markup to convert
+ */
+ protected void addSections(Element output, Element body) {
+ List content = getBodyContent(body.content());
+
+ for (Iterator iter = content.iterator(); iter.hasNext();) {
+ Node node = (Node) iter.next();
+ if (isHeading(node)) {
+ makeSection(output, node);
+ } else {
+ guaranteeHasSection(output);
+ processNode(node);
}
- }
- }
-
- /**
- * @param node
- * @return true if the given node is a heading element (h1, h2, h3 etc)
- */
- protected boolean isHeading(Node node) {
- String name = node.getName();
- return name != null && name.startsWith("h");
- }
-
- /**
- * Returns a copy of the body content, removing any whitespace from the beginning and end
- * @param body
- * @return List
- */
+ }
+
+ }
+
+ /**
+ * main algorithm which represents the iteration contract.
+ * Use the protected methods to change the behavior.
+ *
+ * @param node the node to process
+ */
+ private void processNode(Node node) {
+ if (isCharacterData(node)) {
+ addTextNode(node);
+ } else if (isTextFormatting(node)) {
+ addFormattingNode(node);
+ } else {
+ addNode(node);
+ }
+ }
+
+ /**
+ * Specifies whether the node is a text modifying construct that should be
+ * passed as is to the resultant html. Such as an anchor '<a>'.
+ *
+ * @param node the node to check
+ * @return true if the node is used to modify the formatting of the
+ * text; otherwise, false
+ */
+ protected boolean isTextFormatting(Node node) {
+ // Ultimately this needs bold, italic, and so on
+ return node.getName() != null && node.getName().equals("a");
+ }
+
+ /**
+ * Specifies whether the node is character data and should be passed as
+ * straight text to the resultant html.
+ *
+ * @param node the node to check
+ * @return true if the node is a text node; otherwise, false.
+ */
+ protected boolean isCharacterData(Node node) {
+ return node instanceof CharacterData
+ && (node instanceof Comment) == false;
+ }
+
+ /**
+ * Specifies whether the node is a heading node.
+ *
+ * @param node the node to check
+ * @return true if the given node is a heading element
+ * (h1, h2, h3 etc); otherwise, false
+ */
+ protected boolean isHeading(Node node) {
+ String name = node.getName();
+ return name != null && name.startsWith("h");
+ }
+
+ /**
+ * Determines the heading level of the node.
+ *
+ * @param node the node to check
+ * @return the integer level of the heading
+ */
+ protected int determineHeadingLevel(Node node) {
+ try {
+ String name = node.getName().substring(1);
+ return Integer.parseInt(name);
+ } catch (NumberFormatException nfe) {
+ return 1;
+ }
+ }
+
+ /**
+ * Creates a section or subsection as necessary based on the node
+ * for the output document.
+ *
+ * @param output the output document to attach the section
+ * @param node the node to base making a section on
+ */
+ protected void makeSection(Element output, Node node) {
+ int level = determineHeadingLevel(node);
+ if (needsNewSection(node)) {
+ currentNode = output.addElement("section");
+ currentSectionHeadingLevel = level;
+ currentSectionNode = currentNode;
+ } else {
+ currentNode = currentSectionNode.addElement("subsection");
+ }
+ currentNode.addAttribute("name", node.getText());
+ currentParaNode = null;
+ }
+
+ /**
+ * Determines if a new section is needed which is based on whether
+ * the node's a heading level and equal to or less than the current
+ * section's heading level.
+ *
+ * @param node the node to check
+ * @return true if the current node's information means for a new
+ * section; otherwise, false
+ */
+ protected boolean needsNewSection(Node node) {
+ int level = determineHeadingLevel(node);
+ return level <= currentSectionHeadingLevel
+ || currentSectionNode == null;
+ }
+
+ /**
+ * Determines if a paragraph node is needed.
+ */
+ private void guaranteeHasParaNode() {
+ if (currentParaNode == null) {
+ currentParaNode = currentNode.addElement("p");
+ }
+ }
+
+ /**
+ * Makes sure the current node is section, if necessary.
+ * @param output the output element to add the section to
+ */
+ private void guaranteeHasSection(Element output) {
+ if (currentNode == null) {
+ // we have a section with no name
+ // should we default it to be the same as the document title?
+ currentNode = output.addElement("section");
+ }
+ }
+
+ /**
+ * Add the node to the current node.
+ * @param node the node to add
+ */
+ private void addNode(Node node) {
+ currentNode.add(cloneNode(node));
+ currentParaNode = null;
+ }
+
+ /**
+ * Adds the text of the node to the current paragraph.
+ * @param node the node to add
+ */
+ private void addTextNode(Node node) {
+ guaranteeHasParaNode();
+ currentParaNode.addText(node.getText());
+ }
+
+ /**
+ * Adds the node to the current paragraph.
+ * @param node the node to add
+ */
+ private void addFormattingNode(Node node) {
+ guaranteeHasParaNode();
+ currentParaNode.add(cloneNode(node));
+ }
+
+ /**
+ * Returns a copy of the body content, removing any whitespace from
+ * the beginning and end.
+ *
+ * @param content the content node list to obtain body content from
+ * @return List
+ */
protected List getBodyContent(List content) {
// lets turn
into and concatenate consective entries
Element lastPre = null;
- LinkedList list = new LinkedList();
+ LinkedList list = new LinkedList();
boolean lastWasElement = true;
- for (Iterator iter = content.iterator(); iter.hasNext(); ) {
+ for (Iterator iter = content.iterator(); iter.hasNext();) {
Node node = (Node) iter.next();
-
+
if (isPre(node)) {
if (lastPre == null) {
lastPre = factory.createElement("source");
list.add(lastPre);
}
lastPre.addText(node.getText());
- }
- else {
+ } else {
if (isWhitespace(node) && lastWasElement) {
if (lastPre != null) {
lastPre.addText(node.getText());
}
- }
- else {
+ } else {
lastWasElement = node instanceof Element;
if (lastWasElement) {
- lastPre = null;
- }
- list.add(node);
+ lastPre = null;
+ }
+ list.add(node);
}
}
- }
+ }
// now lets remove any whitespace text nodes at the beginning and end
while (true) {
@@ -224,7 +372,11 @@ public class Html2XdocBean {
}
return list;
}
-
+
+ /**
+ * @param node the node to check
+ * @return true if the node is a pre tag; otherwise false.
+ */
protected boolean isPre(Node node) {
if (node instanceof Element) {
Element element = (Element) node;
@@ -234,6 +386,7 @@ public class Html2XdocBean {
}
/**
+ * @param node the node to check
* @return true if the given node is a whitespace text node
*/
protected boolean isWhitespace(Node node) {
@@ -242,30 +395,31 @@ public class Html2XdocBean {
return text.trim().length() <= 0;
}
if (node instanceof Element) {
- String name = node.getName();
- if (name.equals("p")) {
+ String name = node.getName();
+ if (name.equals("p")) {
String text = node.getText();
return text.trim().length() <= 0;
- }
- if (name.equals("br")) {
- return true;
- }
+ }
+ if (name.equals("br")) {
+ return true;
+ }
}
return false;
}
- /**
- * Normalizes the whitespace of any Elements
- * @param node
- * @return Node
- */
+ /**
+ * Normalizes the whitespace of any Elements
+ *
+ * @param node the node to clone
+ * @return Node the cloned node
+ */
protected Node cloneNode(Node node) {
- Node answer = (Node) node.clone();
+ Node answer = (Node) node.clone();
if (answer instanceof Element) {
Element element = (Element) answer;
element.normalize();
}
return answer;
}
-
+
}
diff --git a/html2xdoc/src/test/org/apache/maven/html2xdoc/TestHtml2Xdoc.java b/html2xdoc/src/test/org/apache/maven/html2xdoc/TestHtml2Xdoc.java
index 53387fcd..b516b484 100644
--- a/html2xdoc/src/test/org/apache/maven/html2xdoc/TestHtml2Xdoc.java
+++ b/html2xdoc/src/test/org/apache/maven/html2xdoc/TestHtml2Xdoc.java
@@ -108,6 +108,10 @@ public class TestHtml2Xdoc extends TestCase {
//-------------------------------------------------------------------------
public void testOne() throws Exception {
assertConversion("input1.html", "output1.xml");
+ assertConversion("h1h2.html", "h1h2.xml");
+ assertConversion("h3h4.html", "h1h2.xml");
+ assertConversion("link.html", "link.xml");
+ assertConversion("comment.html", "comment.xml");
}
// Implementation methods
diff --git a/html2xdoc/src/test/org/apache/maven/html2xdoc/comment.html b/html2xdoc/src/test/org/apache/maven/html2xdoc/comment.html
new file mode 100644
index 00000000..439d1b8e
--- /dev/null
+++ b/html2xdoc/src/test/org/apache/maven/html2xdoc/comment.html
@@ -0,0 +1,13 @@
+
+
+
+
+ A title
+
+
+
+
A section title
+
+Some text with a link.
+
+
diff --git a/html2xdoc/src/test/org/apache/maven/html2xdoc/comment.xml b/html2xdoc/src/test/org/apache/maven/html2xdoc/comment.xml
new file mode 100644
index 00000000..c7ad5d6f
--- /dev/null
+++ b/html2xdoc/src/test/org/apache/maven/html2xdoc/comment.xml
@@ -0,0 +1,12 @@
+
+
+ A title
+
+
+
+
+
+
+
+
+
diff --git a/html2xdoc/src/test/org/apache/maven/html2xdoc/h1h2.html b/html2xdoc/src/test/org/apache/maven/html2xdoc/h1h2.html
new file mode 100644
index 00000000..4166a1da
--- /dev/null
+++ b/html2xdoc/src/test/org/apache/maven/html2xdoc/h1h2.html
@@ -0,0 +1,20 @@
+
+
+
+
+ A title
+
+
+
+
A section title
+
+Some text
+
+More text
+
+
a subsection
+
+This is a subsection. It only has this.
+
+
+
diff --git a/html2xdoc/src/test/org/apache/maven/html2xdoc/h1h2.xml b/html2xdoc/src/test/org/apache/maven/html2xdoc/h1h2.xml
new file mode 100644
index 00000000..7e7748d7
--- /dev/null
+++ b/html2xdoc/src/test/org/apache/maven/html2xdoc/h1h2.xml
@@ -0,0 +1,18 @@
+
+
+ A title
+
+
+
+
Some text
+
+
+
More text
+
+
+
This is a subsection. It only has this.
+
+
+
+
+
diff --git a/html2xdoc/src/test/org/apache/maven/html2xdoc/h3h4.html b/html2xdoc/src/test/org/apache/maven/html2xdoc/h3h4.html
new file mode 100644
index 00000000..4dab3923
--- /dev/null
+++ b/html2xdoc/src/test/org/apache/maven/html2xdoc/h3h4.html
@@ -0,0 +1,20 @@
+
+
+
+
+ A title
+
+
+
+
A section title
+
+Some text
+
+More text
+
+
a subsection
+
+This is a subsection. It only has this.
+
+
+
diff --git a/html2xdoc/src/test/org/apache/maven/html2xdoc/link.html b/html2xdoc/src/test/org/apache/maven/html2xdoc/link.html
new file mode 100644
index 00000000..72e07699
--- /dev/null
+++ b/html2xdoc/src/test/org/apache/maven/html2xdoc/link.html
@@ -0,0 +1,13 @@
+
+
+
+
+ A title
+
+
+
+
A section title
+
+Some text with a link.
+
+
diff --git a/html2xdoc/src/test/org/apache/maven/html2xdoc/link.xml b/html2xdoc/src/test/org/apache/maven/html2xdoc/link.xml
new file mode 100644
index 00000000..972a8f0e
--- /dev/null
+++ b/html2xdoc/src/test/org/apache/maven/html2xdoc/link.xml
@@ -0,0 +1,10 @@
+
+
+ A title
+
+
+
+