package folioxml.slx;
import folioxml.core.InvalidMarkupException;
public class SlxValidator {
protected SlxContextStack stack;
public SlxValidator(SlxContextStack stackReference) {
this.stack = stackReference;
}
/* Slx compatibility tag set
infobase-meta, style-def/>, record, record-attribute/>, span, link, popupLink, end-popup-contents/>, note, namedPopup, parabreak />, object/>, table, tr, td
* paragraph-attribute/>, pagebreak />, br/>, bookmark/>, pp/>, se/>
*/
/* Transformed tag set
* new: <p>, <popup>, <link type="popup">
* infobase-meta, style-def/>, record>, span, link, popup, note, namedPopup, object/>, table, tr, td
* br/>, bookmark/>
*/
/* removed by transform: record-attribute, paragraph-attribute, pp, se, pagebreak, popupLink, end-popup-contents, parabreak*/
/*
* context tags: record, infobase-meta, popupLink, note, namedPopup, popup
* standard: p, table, tr, td, object/>, br/>, bookmark/>, style-def/>, record-attribute/>, paragraph-attribute/>
* ghost: span, link
*
* auto-repairs: insert <p> tags, close p tags
* auto-close tr, td, p
* auto-close record tags.
* auto-close ghost tags (span,link) before context end.
*/
/**
* These tags can't have content.
*/
private static String noContentTags = "br|bookmark|style-def|record-attribute|paragraph-attribute|object|pagebreak|object-def";
/**
* Called before SlxTransformer makes any modifications to ancestors.
* This tag recieves some tags that validate() doesn't, such as record-attribute and paragraph-attribute.
*
* @param t
* @throws folioxml.core.InvalidMarkupException
*/
public void preValidate(SlxToken t) throws InvalidMarkupException {
//Verify infobase-meta tags,
//Verify record-attribute tags
//verify paragraph-attribute tags
//verify style-def tags.
SlxToken topContext = stack.getTopContext();
boolean isInRoot = (topContext != null && topContext.matches("record") && "root".equalsIgnoreCase(topContext.get("level")));
if (t.isContent() && isInRoot)
throw new InvalidMarkupException("Text and entities are not allowed at the top of the file. They cannot appear in the root record.", t);
//We only proccess opening tags for normal elements, but both opening and closing for ghost elements
if (t.isTag() && (t.isOpening() || t.isGhost)) {
if (topContext == null && !t.matches("record"))
throw new InvalidMarkupException("Only record tags are allowed at the root level. All tags must be contained within a record.", t);
if (isInRoot) {
if (!t.matches("infobase-meta|style-def|object-def|namedPopup"))
throw new InvalidMarkupException("Only style definitions, named popups, and infobase information can appear in the root record", t);
} else {
if (t.matches("style-def|infobase-meta|object-def"))
throw new InvalidMarkupException("infobase-meta, object-def, and style-def are only allowed in the root record, at the top of the file.", t);
if (t.matches("record-attribute") && !stack.has("record", true))
throw new InvalidMarkupException("The record-attribute tag can only be used within a record.", t);
if (t.matches("paragraph-attribute") && !stack.has("p", true))
throw new InvalidMarkupException("The paragraph-attribute tag can only be used within a paragraph.", t);
}
}
}
/**
* Called before SlxTransformer pushes/pops a tag
*
* @param t
* @throws folioxml.core.InvalidMarkupException
*/
public void validate(SlxToken t) throws InvalidMarkupException {
SlxToken top = stack.top();
if (t.isTag()) {
if (!t.matches("infobase-meta|record|note|popup|namedPopup|link|span|p|table|tr|td|th|object|br|bookmark|style-def|object-def")) {
throw new InvalidMarkupException("Unrecognized tag.", t);
}
//Normal tags are already required to close at the same level as they open. We only need to test when they open.
if (t.isOpening()) {
if (t.matches("record")) assert (stack.size() == 0);
if (t.matches("tr")) assert (top.matches("table")); //tr tags only go in tables
if (top.matches("table")) assert (t.matches("tr")); //tables can only contain tr tags
if (t.matches("td|th")) assert (top.matches("tr")); //td tags only go in tr
if (top.matches("tr")) assert (t.matches("td|th")); //tr tags can only contain td tags
//paragraphs cannot contain tabes
if (t.matches("table")) assert (!stack.has("p"));
//tables cannot contain other tables - (Are you sure?)
if (t.matches("table")) assert (!stack.has("table"));
//notes cannot contain other notes
if (t.matches("note")) assert (!stack.has("note", true));
//records cannot contain other records
if (t.matches("record")) assert (!stack.has("record", true));
//notes must be inside a paragraph
if (t.matches("note")) assert (stack.has("p"));
//links cannot contain other links
if (t.matches("link") && stack.has("link"))
throw new InvalidMarkupException("Links cannot be nested. " + t + " was found inside " + stack.get("link"), t);
//Paragraphs should be right below the context node or table cell
if (t.matches("p")) assert (top.matches("record|note|popup|namedPopup|td|infobase-meta"));
}
if (stack.has(noContentTags)) {
assert (t.isClosing() && t.matches(noContentTags)); //These tags shouldn't be followed by anthing but their own closing tags
}
} else if (t.isContent()) {
assert (!top.matches("table|tr")); //Tables and tr can't have content directly inside them
assert (!stack.has(noContentTags));//can't have any content
}
}
/**
* Used by GhostTagSplitter.
* This prevents span tags from getting split around td and tr elements causing invalid markup.
* <p>
* Only called for span elements.
*
* @param top
* @param t The child tokne
* @return
* @throws InvalidMarkupException
*/
public static boolean isAllowedInside(SlxToken t, SlxToken parent) throws InvalidMarkupException {
assert (t.matches("span|link"));
//return !(parent.matches("table|tr|object|br|bookmark|style-def"));
///if (parent.matches("span|p|td|th|infobase-meta|record|note|popup|namedPopup|link")) return true; //Cannot be directly inside - table|tr|object|br|bookmark|style-def|object-def
//Removing record tag from allowed parent list - need to force span tags to be inside paragraph tags, not directly in the file.
if (parent.matches("span|p|td|th|infobase-meta|note|popup|namedPopup|link"))
return true; //Cannot be directly inside - table|tr|object|br|bookmark|style-def|object-def
return false;
}
}