You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
9.7 KiB
323 lines
9.7 KiB
var _ = require("underscore");
|
|
var lop = require("lop");
|
|
|
|
var documentMatchers = require("./styles/document-matchers");
|
|
var htmlPaths = require("./styles/html-paths");
|
|
var tokenise = require("./styles/parser/tokeniser").tokenise;
|
|
var results = require("./results");
|
|
|
|
exports.readHtmlPath = readHtmlPath;
|
|
exports.readDocumentMatcher = readDocumentMatcher;
|
|
exports.readStyle = readStyle;
|
|
|
|
|
|
function readStyle(string) {
|
|
return parseString(styleRule, string);
|
|
}
|
|
|
|
function createStyleRule() {
|
|
return lop.rules.sequence(
|
|
lop.rules.sequence.capture(documentMatcherRule()),
|
|
lop.rules.tokenOfType("whitespace"),
|
|
lop.rules.tokenOfType("arrow"),
|
|
lop.rules.sequence.capture(lop.rules.optional(lop.rules.sequence(
|
|
lop.rules.tokenOfType("whitespace"),
|
|
lop.rules.sequence.capture(htmlPathRule())
|
|
).head())),
|
|
lop.rules.tokenOfType("end")
|
|
).map(function(documentMatcher, htmlPath) {
|
|
return {
|
|
from: documentMatcher,
|
|
to: htmlPath.valueOrElse(htmlPaths.empty)
|
|
};
|
|
});
|
|
}
|
|
|
|
function readDocumentMatcher(string) {
|
|
return parseString(documentMatcherRule(), string);
|
|
}
|
|
|
|
function documentMatcherRule() {
|
|
var sequence = lop.rules.sequence;
|
|
|
|
var identifierToConstant = function(identifier, constant) {
|
|
return lop.rules.then(
|
|
lop.rules.token("identifier", identifier),
|
|
function() {
|
|
return constant;
|
|
}
|
|
);
|
|
};
|
|
|
|
var paragraphRule = identifierToConstant("p", documentMatchers.paragraph);
|
|
var runRule = identifierToConstant("r", documentMatchers.run);
|
|
|
|
var elementTypeRule = lop.rules.firstOf("p or r or table",
|
|
paragraphRule,
|
|
runRule
|
|
);
|
|
|
|
var styleIdRule = lop.rules.then(
|
|
classRule,
|
|
function(styleId) {
|
|
return {styleId: styleId};
|
|
}
|
|
);
|
|
|
|
var styleNameMatcherRule = lop.rules.firstOf("style name matcher",
|
|
lop.rules.then(
|
|
lop.rules.sequence(
|
|
lop.rules.tokenOfType("equals"),
|
|
lop.rules.sequence.cut(),
|
|
lop.rules.sequence.capture(stringRule)
|
|
).head(),
|
|
function(styleName) {
|
|
return {styleName: documentMatchers.equalTo(styleName)};
|
|
}
|
|
),
|
|
lop.rules.then(
|
|
lop.rules.sequence(
|
|
lop.rules.tokenOfType("startsWith"),
|
|
lop.rules.sequence.cut(),
|
|
lop.rules.sequence.capture(stringRule)
|
|
).head(),
|
|
function(styleName) {
|
|
return {styleName: documentMatchers.startsWith(styleName)};
|
|
}
|
|
)
|
|
);
|
|
|
|
var styleNameRule = lop.rules.sequence(
|
|
lop.rules.tokenOfType("open-square-bracket"),
|
|
lop.rules.sequence.cut(),
|
|
lop.rules.token("identifier", "style-name"),
|
|
lop.rules.sequence.capture(styleNameMatcherRule),
|
|
lop.rules.tokenOfType("close-square-bracket")
|
|
).head();
|
|
|
|
|
|
var listTypeRule = lop.rules.firstOf("list type",
|
|
identifierToConstant("ordered-list", {isOrdered: true}),
|
|
identifierToConstant("unordered-list", {isOrdered: false})
|
|
);
|
|
var listRule = sequence(
|
|
lop.rules.tokenOfType("colon"),
|
|
sequence.capture(listTypeRule),
|
|
sequence.cut(),
|
|
lop.rules.tokenOfType("open-paren"),
|
|
sequence.capture(integerRule),
|
|
lop.rules.tokenOfType("close-paren")
|
|
).map(function(listType, levelNumber) {
|
|
return {
|
|
list: {
|
|
isOrdered: listType.isOrdered,
|
|
levelIndex: levelNumber - 1
|
|
}
|
|
};
|
|
});
|
|
|
|
function createMatcherSuffixesRule(rules) {
|
|
var matcherSuffix = lop.rules.firstOf.apply(
|
|
lop.rules.firstOf,
|
|
["matcher suffix"].concat(rules)
|
|
);
|
|
var matcherSuffixes = lop.rules.zeroOrMore(matcherSuffix);
|
|
return lop.rules.then(matcherSuffixes, function(suffixes) {
|
|
var matcherOptions = {};
|
|
suffixes.forEach(function(suffix) {
|
|
_.extend(matcherOptions, suffix);
|
|
});
|
|
return matcherOptions;
|
|
});
|
|
}
|
|
|
|
var paragraphOrRun = sequence(
|
|
sequence.capture(elementTypeRule),
|
|
sequence.capture(createMatcherSuffixesRule([
|
|
styleIdRule,
|
|
styleNameRule,
|
|
listRule
|
|
]))
|
|
).map(function(createMatcher, matcherOptions) {
|
|
return createMatcher(matcherOptions);
|
|
});
|
|
|
|
var table = sequence(
|
|
lop.rules.token("identifier", "table"),
|
|
sequence.capture(createMatcherSuffixesRule([
|
|
styleIdRule,
|
|
styleNameRule
|
|
]))
|
|
).map(function(options) {
|
|
return documentMatchers.table(options);
|
|
});
|
|
|
|
var bold = identifierToConstant("b", documentMatchers.bold);
|
|
var italic = identifierToConstant("i", documentMatchers.italic);
|
|
var underline = identifierToConstant("u", documentMatchers.underline);
|
|
var strikethrough = identifierToConstant("strike", documentMatchers.strikethrough);
|
|
var allCaps = identifierToConstant("all-caps", documentMatchers.allCaps);
|
|
var smallCaps = identifierToConstant("small-caps", documentMatchers.smallCaps);
|
|
var commentReference = identifierToConstant("comment-reference", documentMatchers.commentReference);
|
|
|
|
var breakMatcher = sequence(
|
|
lop.rules.token("identifier", "br"),
|
|
sequence.cut(),
|
|
lop.rules.tokenOfType("open-square-bracket"),
|
|
lop.rules.token("identifier", "type"),
|
|
lop.rules.tokenOfType("equals"),
|
|
sequence.capture(stringRule),
|
|
lop.rules.tokenOfType("close-square-bracket")
|
|
).map(function(breakType) {
|
|
switch (breakType) {
|
|
case "line":
|
|
return documentMatchers.lineBreak;
|
|
case "page":
|
|
return documentMatchers.pageBreak;
|
|
case "column":
|
|
return documentMatchers.columnBreak;
|
|
default:
|
|
// TODO: handle unknown document matchers
|
|
}
|
|
});
|
|
|
|
return lop.rules.firstOf("element type",
|
|
paragraphOrRun,
|
|
table,
|
|
bold,
|
|
italic,
|
|
underline,
|
|
strikethrough,
|
|
allCaps,
|
|
smallCaps,
|
|
commentReference,
|
|
breakMatcher
|
|
);
|
|
}
|
|
|
|
function readHtmlPath(string) {
|
|
return parseString(htmlPathRule(), string);
|
|
}
|
|
|
|
function htmlPathRule() {
|
|
var capture = lop.rules.sequence.capture;
|
|
var whitespaceRule = lop.rules.tokenOfType("whitespace");
|
|
var freshRule = lop.rules.then(
|
|
lop.rules.optional(lop.rules.sequence(
|
|
lop.rules.tokenOfType("colon"),
|
|
lop.rules.token("identifier", "fresh")
|
|
)),
|
|
function(option) {
|
|
return option.map(function() {
|
|
return true;
|
|
}).valueOrElse(false);
|
|
}
|
|
);
|
|
|
|
var separatorRule = lop.rules.then(
|
|
lop.rules.optional(lop.rules.sequence(
|
|
lop.rules.tokenOfType("colon"),
|
|
lop.rules.token("identifier", "separator"),
|
|
lop.rules.tokenOfType("open-paren"),
|
|
capture(stringRule),
|
|
lop.rules.tokenOfType("close-paren")
|
|
).head()),
|
|
function(option) {
|
|
return option.valueOrElse("");
|
|
}
|
|
);
|
|
|
|
var tagNamesRule = lop.rules.oneOrMoreWithSeparator(
|
|
identifierRule,
|
|
lop.rules.tokenOfType("choice")
|
|
);
|
|
|
|
var styleElementRule = lop.rules.sequence(
|
|
capture(tagNamesRule),
|
|
capture(lop.rules.zeroOrMore(classRule)),
|
|
capture(freshRule),
|
|
capture(separatorRule)
|
|
).map(function(tagName, classNames, fresh, separator) {
|
|
var attributes = {};
|
|
var options = {};
|
|
if (classNames.length > 0) {
|
|
attributes["class"] = classNames.join(" ");
|
|
}
|
|
if (fresh) {
|
|
options.fresh = true;
|
|
}
|
|
if (separator) {
|
|
options.separator = separator;
|
|
}
|
|
return htmlPaths.element(tagName, attributes, options);
|
|
});
|
|
|
|
return lop.rules.firstOf("html path",
|
|
lop.rules.then(lop.rules.tokenOfType("bang"), function() {
|
|
return htmlPaths.ignore;
|
|
}),
|
|
lop.rules.then(
|
|
lop.rules.zeroOrMoreWithSeparator(
|
|
styleElementRule,
|
|
lop.rules.sequence(
|
|
whitespaceRule,
|
|
lop.rules.tokenOfType("gt"),
|
|
whitespaceRule
|
|
)
|
|
),
|
|
htmlPaths.elements
|
|
)
|
|
);
|
|
}
|
|
|
|
var identifierRule = lop.rules.then(
|
|
lop.rules.tokenOfType("identifier"),
|
|
decodeEscapeSequences
|
|
);
|
|
var integerRule = lop.rules.tokenOfType("integer");
|
|
|
|
var stringRule = lop.rules.then(
|
|
lop.rules.tokenOfType("string"),
|
|
decodeEscapeSequences
|
|
);
|
|
|
|
var escapeSequences = {
|
|
"n": "\n",
|
|
"r": "\r",
|
|
"t": "\t"
|
|
};
|
|
|
|
function decodeEscapeSequences(value) {
|
|
return value.replace(/\\(.)/g, function(match, code) {
|
|
return escapeSequences[code] || code;
|
|
});
|
|
}
|
|
|
|
var classRule = lop.rules.sequence(
|
|
lop.rules.tokenOfType("dot"),
|
|
lop.rules.sequence.cut(),
|
|
lop.rules.sequence.capture(identifierRule)
|
|
).head();
|
|
|
|
function parseString(rule, string) {
|
|
var tokens = tokenise(string);
|
|
var parser = lop.Parser();
|
|
var parseResult = parser.parseTokens(rule, tokens);
|
|
if (parseResult.isSuccess()) {
|
|
return results.success(parseResult.value());
|
|
} else {
|
|
return new results.Result(null, [results.warning(describeFailure(string, parseResult))]);
|
|
}
|
|
}
|
|
|
|
function describeFailure(input, parseResult) {
|
|
return "Did not understand this style mapping, so ignored it: " + input + "\n" +
|
|
parseResult.errors().map(describeError).join("\n");
|
|
}
|
|
|
|
function describeError(error) {
|
|
return "Error was at character number " + error.characterNumber() + ": " +
|
|
"Expected " + error.expected + " but got " + error.actual;
|
|
}
|
|
|
|
var styleRule = createStyleRule();
|
|
|