blob: 5a684e1eac56cea58f97a1c3427b2cd2116f9e40 [file] [log] [blame]
/**
* 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.apache.cxf.cwiki;
import java.io.Serializable;
import java.io.StringReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.helpers.DOMUtils;
import org.ccil.cowan.tagsoup.Parser;
/**
*
*/
public class Page extends AbstractPage implements Serializable {
private static final long serialVersionUID = 1L;
private static final Map<String, String> CODE_TYPE_MAP = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
static {
CODE_TYPE_MAP.put("applescript", "shBrushAppleScript.js");
CODE_TYPE_MAP.put("actionscript3", "shBrushAS3.js");
CODE_TYPE_MAP.put("as3", "shBrushAS3.js");
CODE_TYPE_MAP.put("bash", "shBrushBash.js");
CODE_TYPE_MAP.put("shell", "shBrushBash.js");
CODE_TYPE_MAP.put("coldfusion", "shBrushColdFusion.js");
CODE_TYPE_MAP.put("cpp", "shBrushCpp.js");
CODE_TYPE_MAP.put("c", "shBrushCpp.js");
CODE_TYPE_MAP.put("c#", "shBrushCSharp.js");
CODE_TYPE_MAP.put("c-sharp", "shBrushCSharp.js");
CODE_TYPE_MAP.put("csharp", "shBrushCSharp.js");
CODE_TYPE_MAP.put("css", "shBrushCss.js");
CODE_TYPE_MAP.put("delphi", "shBrushDelphi.js");
CODE_TYPE_MAP.put("pascal", "shBrushDelphi.js");
CODE_TYPE_MAP.put("diff", "shBrushDiff.js");
CODE_TYPE_MAP.put("patch", "shBrushDiff.js");
CODE_TYPE_MAP.put("pas", "shBrushDiff.js");
CODE_TYPE_MAP.put("erl", "shBrushErlang.js");
CODE_TYPE_MAP.put("erlang", "shBrushErlang.js");
CODE_TYPE_MAP.put("groovy", "shBrushGroovy.js");
CODE_TYPE_MAP.put("java", "shBrushJava.js");
CODE_TYPE_MAP.put("jfx", "shBrushJavaFX.js");
CODE_TYPE_MAP.put("javafx", "shBrushJavaFX.js");
CODE_TYPE_MAP.put("js", "shBrushJScript.js");
CODE_TYPE_MAP.put("jscript", "shBrushJScript.js");
CODE_TYPE_MAP.put("javascript", "shBrushJScript.js");
CODE_TYPE_MAP.put("perl", "shBrushPerl.js");
CODE_TYPE_MAP.put("pl", "shBrushPerl.js");
CODE_TYPE_MAP.put("php", "shBrushPhp.js");
CODE_TYPE_MAP.put("text", "shBrushPlain.js");
CODE_TYPE_MAP.put("plain", "shBrushPlain.js");
CODE_TYPE_MAP.put("none", "shBrushPlain.js");
CODE_TYPE_MAP.put("py", "shBrushPython.js");
CODE_TYPE_MAP.put("python", "shBrushPython.js");
CODE_TYPE_MAP.put("powershell", "shBrushPowerShell.js");
CODE_TYPE_MAP.put("ps", "shBrushPowerShell.js");
CODE_TYPE_MAP.put("posh", "shBrushPowerShell.js");
CODE_TYPE_MAP.put("ruby", "shBrushRuby.js");
CODE_TYPE_MAP.put("rails", "shBrushRuby.js");
CODE_TYPE_MAP.put("ror", "shBrushRuby.js");
CODE_TYPE_MAP.put("rb", "shBrushRuby.js");
CODE_TYPE_MAP.put("sass", "shBrushSass.js");
CODE_TYPE_MAP.put("scss", "shBrushSass.js");
CODE_TYPE_MAP.put("scala", "shBrushScala.js");
CODE_TYPE_MAP.put("sql", "shBrushSql.js");
CODE_TYPE_MAP.put("vb", "shBrushVb.js");
CODE_TYPE_MAP.put("vbnet", "shBrushVb.js");
CODE_TYPE_MAP.put("xml", "shBrushXml.js");
CODE_TYPE_MAP.put("xhtml", "shBrushXml.js");
CODE_TYPE_MAP.put("xslt", "shBrushXml.js");
CODE_TYPE_MAP.put("html", "shBrushXml.js");
CODE_TYPE_MAP.put("html/xml", "shBrushXml.js");
}
final XMLGregorianCalendar modified;
final String parent;
final String spaceKey;
Map<String, String> attachments;
Set<String> includes;
Map<String, Integer> childrenOf;
boolean hasBlog;
Set<String> codeTypes;
transient String renderedContent;
transient String renderedDivContent;
transient String divIdForContent;
transient SiteExporter exporter;
public Page(Document doc, SiteExporter exp) throws Exception {
this(DOMUtils.getFirstElement(doc.getDocumentElement()), exp);
}
public Page(Element root, SiteExporter exp) throws Exception {
super(root);
exporter = exp;
//org.apache.cxf.helpers.XMLUtils.printDOM(doc.getDocumentElement());
parent = DOMUtils.getChildContent(root, "parentId");
spaceKey = DOMUtils.getChildContent(root, "space");
String mod = DOMUtils.getChildContent(root, "modified");
modified = DatatypeFactory.newInstance().newXMLGregorianCalendar(mod);
modified.setMillisecond(0);
String c = DOMUtils.getChildContent(root, "content");
if (c != null) {
if (exp.getAPIVersion() == 2) {
checkContentV2(c);
} else {
checkContentV1(c);
}
}
}
/*
* Makes a shallow copy without any content
*/
public Page(Page source) {
super(source);
this.modified = source.modified;
this.parent = source.parent;
this.spaceKey = source.spaceKey;
this.attachments = source.attachments;
this.includes = source.includes;
this.childrenOf = source.childrenOf;
this.exporter = source.exporter;
this.hasBlog = source.hasBlog;
this.codeTypes = source.codeTypes;
}
private void checkContentV2(final String c) {
try {
//if ("Content Based Router".equals(title)) {
// System.out.println(c);
//}
XMLReader reader = new Parser();
reader.setFeature(Parser.namespacesFeature, true);
reader.setFeature(Parser.namespacePrefixesFeature, true);
reader.setProperty(Parser.schemaProperty, new org.ccil.cowan.tagsoup.HTMLSchema() {
{
//problem with nested lists that the confluence {toc} macro creates
elementType("ul", M_LI, M_BLOCK | M_LI, 0);
}
});
reader.setContentHandler(new V2ContentHandler(this));
reader.parse(new InputSource(new StringReader(c)));
} catch (Exception e) {
e.printStackTrace();
}
}
private void checkContentV1(String c) {
int idx = c.indexOf("{children");
while (idx != -1) {
if (childrenOf == null) {
childrenOf = new HashMap<String, Integer>();
}
idx += 9;
if (c.charAt(idx) != '}') {
// {children:page=Foo|...}
idx++;
int idx2 = c.indexOf('}', idx);
String paramString = c.substring(idx, idx2);
String params[] = paramString.split("\\||=");
String page = null;
int depth = 1;
for (int x = 0; x < params.length; x++) {
if ("page".equals(params[x])) {
page = params[x + 1];
x++;
} else if ("depth".equals(params[x])) {
depth = Integer.parseInt(params[x + 1]);
x++;
}
}
childrenOf.put(page, depth);
} else {
childrenOf.put(title, 1);
}
idx = c.indexOf("{children", idx);
}
idx = c.indexOf("{include:");
while (idx != -1) {
int idx2 = c.indexOf("}", idx);
String inc = c.substring(idx + 9, idx2);
if (includes == null) {
includes = new CopyOnWriteArraySet<String>();
}
includes.add(inc);
idx = c.indexOf("{include:", idx2);
}
idx = c.indexOf("{blog-posts");
if (idx != -1) {
hasBlog = true;
}
handleCode(c);
}
private void handleCode(String c) {
int idx = c.indexOf("{code");
while (idx != -1) {
String type = "java";
idx += 5;
if (c.charAt(idx) != '}') {
idx++;
int idx2 = c.indexOf('}', idx);
if (idx2 != -1) {
String paramString = c.substring(idx, idx2);
String params[] = paramString.split("\\||=");
for (int x = 0; x < params.length; x++) {
if ("type".equalsIgnoreCase(params[x])) {
type = params[x + 1];
x++;
} else if (CODE_TYPE_MAP.containsKey(params[x].toLowerCase())) {
type = params[x];
}
}
}
}
if (codeTypes == null) {
codeTypes = new ConcurrentSkipListSet<String>();
}
codeTypes.add(type);
idx = c.indexOf("{code", idx + 1);
}
idx = c.indexOf("{snippet");
while (idx != -1) {
String type = "java";
idx += 8;
if (c.charAt(idx) != '}') {
idx++;
int idx2 = c.indexOf('}', idx);
if (idx2 != -1) {
String paramString = c.substring(idx, idx2);
String params[] = paramString.split("\\||=");
for (int x = 0; x < params.length; x++) {
if ("lang".equalsIgnoreCase(params[x])) {
type = params[x + 1];
x++;
} else if (CODE_TYPE_MAP.containsKey(params[x].toLowerCase())) {
type = params[x];
}
}
}
}
if (codeTypes == null) {
codeTypes = new ConcurrentSkipListSet<String>();
}
codeTypes.add(type);
idx = c.indexOf("{snippet", idx + 1);
}
}
public boolean hasChildrenOf(String t, int d) {
if (childrenOf == null) {
return false;
}
Integer i = childrenOf.get(t);
if (i == null) {
return false;
}
return d <= i;
}
public boolean includesPage(String s) {
if (includes == null) {
return false;
}
return includes.contains(s);
}
public String getParentId() {
return parent;
}
public XMLGregorianCalendar getModifiedTime() {
return modified;
}
public void setContent(String c) {
renderedContent = c;
}
public String getContent() {
return renderedContent;
}
public String getSpaceKey() {
return spaceKey;
}
public void addAttachment(String aid, String filename) {
if (attachments == null) {
attachments = new HashMap<String, String>();
}
attachments.put(aid, filename);
}
public String getAttachmentFilename(String aid) {
if (attachments == null) {
return null;
}
return attachments.get(aid);
}
public void setContentForDivId(String divId, String content) {
renderedDivContent = content;
divIdForContent = divId;
}
public String getContentForDivId(String divId) {
if (divId == null) {
return renderedContent;
}
if (divId.equals(divIdForContent)) {
return renderedDivContent;
}
return null;
}
public String getLink() {
StringBuffer buffer = new StringBuffer();
buffer.append("<a href=\"");
buffer.append(url);
buffer.append("\" title=\"");
buffer.append(title);
buffer.append("\">");
buffer.append(title);
buffer.append("</a>");
return buffer.toString();
}
public Space getSpace() {
return SiteExporter.getSpace(spaceKey);
}
public boolean hasChildren() {
return exporter.hasChildren(this);
}
public List<Page> getChildren() {
return exporter.getChildren(this);
}
protected void setExporter(SiteExporter exporter) {
this.exporter = exporter;
}
protected SiteExporter getExporter() {
return exporter;
}
public boolean hasBlog() {
return hasBlog;
}
public boolean getHasCode() {
return hasCode(new HashMap<String, Boolean>());
}
public boolean hasCode(Map<String, Boolean> done) {
if (done.containsKey(getTitle())) {
return done.get(getTitle());
}
if (codeTypes != null && !codeTypes.isEmpty()) {
done.put(this.getTitle(), true);
return true;
}
if (includes != null) {
done.put(getTitle(), false);
for (String i : includes) {
try {
Page p = exporter.findPage(i);
if (p != null && p.hasCode(done)) {
done.put(this.getTitle(), true);
return true;
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println(done);
}
}
}
return false;
}
public Set<String> getCodeScripts() throws Exception {
Set<String> scripts = new HashSet<String>();
if (codeTypes != null) {
for (String s : codeTypes) {
String sc = CODE_TYPE_MAP.get(s);
if (sc == null) {
System.out.println("WARNING: no code highlighter for " + s);
} else {
scripts.add(sc);
}
}
}
if (scripts.isEmpty()) {
scripts.add(CODE_TYPE_MAP.get("java"));
scripts.add(CODE_TYPE_MAP.get("plain"));
}
if (includes != null) {
for (String i : includes) {
try {
Page p = exporter.findPage(i);
if (p != null && p.getHasCode()) {
scripts.addAll(p.getCodeScripts());
} else if (p == null) {
System.out.println(" Did not find page " + i);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
return scripts;
}
static class V2ContentHandler implements ContentHandler {
private final Page page;
enum State {
NONE,
CHILDREN,
INCLUDE,
BLOG_POSTS,
CODE,
};
private State state = State.NONE;
private Map<String, String> params = new HashMap<String, String>();
private String paramName;
private boolean unmigrated;
V2ContentHandler(Page pg) {
page = pg;
}
public void setDocumentLocator(Locator locator) {
}
public void startDocument() throws SAXException {
}
public void endDocument() throws SAXException {
}
public void startPrefixMapping(String prefix, String uri) throws SAXException {
}
public void endPrefixMapping(String prefix) throws SAXException {
}
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
if ("macro".equals(localName) || "structured-macro".equals(localName)) {
String s = atts.getValue(uri, "name");
if ("children".equals(s)) {
state = State.CHILDREN;
} else if ("include".equals(s)) {
state = State.INCLUDE;
} else if ("blog-posts".equals(s)) {
state = State.BLOG_POSTS;
} else if ("code".equals(s)) {
state = State.CODE;
} else if ("snippet".equals(s)) {
state = State.CODE;
} else if ("unmigrated-wiki-markup".equals(s)
|| "unmigrated-inline-wiki-markup".equals(s)) {
if (!unmigrated) {
System.out.println("WARNING: Page \"" + page.title + "\" ("
+ page.spaceKey + ") has unmigrated wiki content.");
unmigrated = true;
//no idea what is in there, lets just turn on the code highlighting
if (page.codeTypes == null) {
page.codeTypes = new ConcurrentSkipListSet<String>();
}
page.codeTypes.add("java");
page.codeTypes.add("xml");
page.codeTypes.add("plain");
}
} else {
//System.out.println("Unknown macro: " + s);
}
params.clear();
paramName = null;
} else if ("parameter".equals(localName)) {
paramName = atts.getValue(uri, "name");
} else if ("default-parameter".equals(localName)) {
paramName = "default-parameter";
} else if ("page".equals(localName) && state == State.INCLUDE) {
for (int x = 0; x < atts.getLength(); x++) {
if (atts.getLocalName(x).equals("content-title")
&& paramName != null) {
params.put(paramName, atts.getValue(x));
}
}
}
}
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("macro".equals(localName) || "structured-macro".equals(localName)) {
switch (state) {
case CHILDREN: {
String pageName = params.get("page");
String depth = params.get("depth");
if (depth == null || "".equals(depth.trim())) {
depth = "1";
}
if (page.childrenOf == null) {
page.childrenOf = new HashMap<String, Integer>();
}
if (pageName == null) {
page.childrenOf.put(page.title, Integer.parseInt(depth));
} else {
page.childrenOf.put(pageName, Integer.parseInt(depth));
}
params.clear();
state = State.NONE;
break;
}
case INCLUDE: {
if (page.includes == null) {
page.includes = new CopyOnWriteArraySet<String>();
}
String inc = params.get("default-parameter");
if (inc == null) {
inc = params.get("title");
}
if (inc == null) {
inc = params.get("");
}
if (inc == null) {
System.out.println(page.title + ": Did not find an include name " + params);
} else {
page.includes.add(inc);
}
state = State.NONE;
break;
}
case BLOG_POSTS:
page.hasBlog = true;
state = State.NONE;
break;
case CODE: {
if (page.codeTypes == null) {
page.codeTypes = new ConcurrentSkipListSet<String>();
}
String lang = null;
for (Map.Entry<String, String> ent : params.entrySet()) {
if ("language".equals(ent.getKey())) {
lang = ent.getValue();
} else if (ent.getKey().contains(":")) {
String parts[] = ent.getKey().split(":");
for (String s : parts) {
if (("title".equals(s) && !params.containsKey("title"))
|| (!params.containsKey("language")
&& ("xml".equals(s) || "java".equals(s)))) {
System.out.println("WARNING Page " + page.title + " has a broken code block");
}
}
} else if ("default-parameter".equals(ent.getKey())) {
String s = ent.getValue();
if ("xml".equals(s) || "java".equals(s)) {
lang = s;
}
}
}
if (lang == null) {
lang = params.get("");
if (!StringUtils.isEmpty(lang)) {
page.codeTypes.add("java");
}
}
//System.out.println("l: " + lang + " " + params);
if (StringUtils.isEmpty(lang)) {
page.codeTypes.add("bash");
lang = "java";
}
page.codeTypes.add(lang);
state = State.NONE;
break;
}
default:
state = State.NONE;
break;
}
} else if ("parameter".equals(localName)) {
paramName = null;
} else if ("default-parameter".equals(localName)) {
paramName = null;
}
}
public void characters(char[] ch, int start, int length) throws SAXException {
if (paramName != null) {
params.put(paramName, new String(ch, start, length));
}
}
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
}
public void processingInstruction(String target, String data) throws SAXException {
}
public void skippedEntity(String name) throws SAXException {
}
}
}