Skip to content

Commit 1cc8d7b

Browse files
Refactoring while testing a bug with UnionExpr.
1 parent 386a869 commit 1cc8d7b

File tree

8 files changed

+98
-56
lines changed

8 files changed

+98
-56
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
This document is no longer maintained in favor of GitHub's release system, and just kept here for historical reasons.
1+
This document is no longer maintained in favor of [GitHub's release system](https://github.com/DesignLiquido/xslt-processor/releases), and just kept here for historical reasons.
22

33
2018-03-20 Johannes Wilm <[email protected]>
44

TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ XSLT-processor TODO
55
* XSLT validation, besides the version number;
66
* XSL:number
77
* `attribute-set`, `decimal-format`, etc. (check `src/xslt.ts`)
8+
* `/html/body//ul/li|html/body//ol/li` has `/html/body//ul/li` evaluated by this XPath implementation as "absolute", and `/html/body//ol/li` as "relative". Both should be evaluated as "absolute".
89

910
Help is much appreciated. It seems to currently work for most of our purposes, but fixes and additions are always welcome!

src/dom/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function mapExec(array: any[], func: Function) {
1818

1919
// Returns an array that contains the return value of the given
2020
// function applied to every element of the input array.
21-
export function mapExpr(array, func) {
21+
export function mapExpr(array: any[], func: Function) {
2222
const ret = [];
2323
for (let i = 0; i < array.length; ++i) {
2424
ret.push(func(array[i]));

src/xpath/node-tests/node-test-pi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export class NodeTestPI implements NodeTest {
1010
this.target = target;
1111
}
1212

13-
evaluate(ctx: ExprContext) {
14-
const node = ctx.nodeList[ctx.position];
13+
evaluate(context: ExprContext) {
14+
const node = context.nodeList[context.position];
1515
return new BooleanValue(
1616
node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE && (!this.target || node.nodeName == this.target)
1717
);

src/xpath/tokens.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,13 @@ export const xPathTokenRules: XPathTokenRule[] = [
328328
];
329329

330330
// Quantifiers that are used in the productions of the grammar.
331-
export const Q_01 = {
331+
export const Q_ZERO_OR_ONE = {
332332
label: '?'
333333
};
334-
export const Q_MM = {
334+
export const Q_ZERO_OR_MULTIPLE = {
335335
label: '*'
336336
};
337-
export const Q_1M = {
337+
export const Q_ONE_OR_MULTIPLE = {
338338
label: '+'
339339
};
340340

src/xpath/xpath.ts

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ import {
5656
import { Expression } from './expressions/expression';
5757

5858
import {
59-
Q_MM,
60-
Q_01,
61-
Q_1M,
59+
Q_ZERO_OR_MULTIPLE,
60+
Q_ZERO_OR_ONE,
61+
Q_ONE_OR_MULTIPLE,
6262
xPathTokenRules,
6363
TOK_DIV,
6464
TOK_MOD,
@@ -135,7 +135,7 @@ export class XPath {
135135

136136
// The productions of the grammar. Columns of the table:
137137
//
138-
// - target nonterminal,
138+
// - target non-terminal,
139139
// - pattern,
140140
// - precedence,
141141
// - semantic value factory
@@ -148,14 +148,14 @@ export class XPath {
148148
// and thus evaluates XPath expressions.
149149
//
150150
// The precedence is used to decide between reducing and shifting by
151-
// comparing the precendence of the rule that is candidate for
151+
// comparing the precedence of the rule that is candidate for
152152
// reducing with the precedence of the look ahead token. Precedence of
153153
// -1 means that the precedence of the tokens in the pattern is used
154154
// instead. TODO: It shouldn't be necessary to explicitly assign
155155
// precedences to rules.
156156

157157
// DGF As it stands, these precedences are purely empirical; we're
158-
// not sure they can be made to be consistent at all.
158+
// not sure if they can be made to be consistent at all.
159159
xPathGrammarRules = [
160160
[XPathLocationPath, [XPathRelativeLocationPath], 18, this.passExpr],
161161
[XPathLocationPath, [XPathAbsoluteLocationPath], 18, this.passExpr],
@@ -194,7 +194,7 @@ export class XPath {
194194
[XPathFunctionCall, [TOK_QNAME, TOK_PARENO, TOK_PARENC], -1, this.makeFunctionCallExpr1],
195195
[
196196
XPathFunctionCall,
197-
[TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_MM, TOK_PARENC],
197+
[TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_ZERO_OR_MULTIPLE, TOK_PARENC],
198198
-1,
199199
this.makeFunctionCallExpr2
200200
],
@@ -208,7 +208,7 @@ export class XPath {
208208
[XPathPathExpr, [XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath], 19, this.makePathExpr1],
209209
[XPathPathExpr, [XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath], 19, this.makePathExpr2],
210210

211-
[XPathFilterExpr, [XPathPrimaryExpr, XPathPredicate, Q_MM], 31, this.makeFilterExpr],
211+
[XPathFilterExpr, [XPathPrimaryExpr, XPathPredicate, Q_ZERO_OR_MULTIPLE], 31, this.makeFilterExpr],
212212

213213
[XPathExpr, [XPathPrimaryExpr], 16, this.passExpr],
214214
[XPathExpr, [XPathUnionExpr], 16, this.passExpr],
@@ -356,7 +356,7 @@ export class XPath {
356356
return new NodeTestNC(ncname.value);
357357
}
358358

359-
makeNodeTestExpr3(qname: any) {
359+
makeNodeTestExpr3(qname: TokenExpr) {
360360
return new NodeTestName(qname.value);
361361
}
362362

@@ -385,12 +385,12 @@ export class XPath {
385385
return new NodeTestPI(target.value);
386386
}
387387

388-
makePredicateExpr(pareno: any, expr: any) {
389-
return new PredicateExpr(expr);
388+
makePredicateExpr(pareno: any, expression: any) {
389+
return new PredicateExpr(expression);
390390
}
391391

392-
makePrimaryExpr(pareno: any, expr: any) {
393-
return expr;
392+
makePrimaryExpr(pareno: any, expression: any) {
393+
return expression;
394394
}
395395

396396
makeFunctionCallExpr1(name: any) {
@@ -406,11 +406,11 @@ export class XPath {
406406
return ret;
407407
}
408408

409-
makeArgumentExpr(comma: any, expr: any) {
410-
return expr;
409+
makeArgumentExpr(comma: any, expression: any) {
410+
return expression;
411411
}
412412

413-
makeUnionExpr(expr1: any, pipe: any, expr2: any) {
413+
makeUnionExpr(expr1: Expression, pipe: TokenExpr, expr2: Expression) {
414414
return new UnionExpr(expr1, expr2);
415415
}
416416

@@ -567,56 +567,56 @@ export class XPath {
567567
}
568568
}
569569

570-
xPathMatchStack(stack: any, pattern: any) {
570+
xPathMatchStack(stack: any[], pattern: any[]) {
571571
// NOTE(mesch): The stack matches for variable cardinality are
572572
// greedy but don't do backtracking. This would be an issue only
573573
// with rules of the form A* A, i.e. with an element with variable
574574
// cardinality followed by the same element. Since that doesn't
575575
// occur in the grammar at hand, all matches on the stack are
576576
// unambiguous.
577577

578-
const S = stack.length;
579-
const P = pattern.length;
578+
const stackLength = stack.length;
579+
const patternLength = pattern.length;
580580
let p;
581581
let s;
582582
const match: any = [];
583-
match.matchlength = 0;
583+
match.matchLength = 0;
584584
let ds = 0;
585-
for (p = P - 1, s = S - 1; p >= 0 && s >= 0; --p, s -= ds) {
585+
for (p = patternLength - 1, s = stackLength - 1; p >= 0 && s >= 0; --p, s -= ds) {
586586
ds = 0;
587587
const qmatch: any = [];
588-
if (pattern[p] == Q_MM) {
588+
if (pattern[p] == Q_ZERO_OR_MULTIPLE) {
589589
p -= 1;
590590
match.push(qmatch);
591591
while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
592592
qmatch.push(stack[s - ds]);
593593
ds += 1;
594-
match.matchlength += 1;
594+
match.matchLength += 1;
595595
}
596-
} else if (pattern[p] == Q_01) {
596+
} else if (pattern[p] == Q_ZERO_OR_ONE) {
597597
p -= 1;
598598
match.push(qmatch);
599599
while (s - ds >= 0 && ds < 2 && stack[s - ds].tag == pattern[p]) {
600600
qmatch.push(stack[s - ds]);
601601
ds += 1;
602-
match.matchlength += 1;
602+
match.matchLength += 1;
603603
}
604-
} else if (pattern[p] == Q_1M) {
604+
} else if (pattern[p] == Q_ONE_OR_MULTIPLE) {
605605
p -= 1;
606606
match.push(qmatch);
607607
if (stack[s].tag == pattern[p]) {
608608
while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
609609
qmatch.push(stack[s - ds]);
610610
ds += 1;
611-
match.matchlength += 1;
611+
match.matchLength += 1;
612612
}
613613
} else {
614614
return [];
615615
}
616616
} else if (stack[s].tag == pattern[p]) {
617617
match.push(stack[s]);
618618
ds += 1;
619-
match.matchlength += 1;
619+
match.matchLength += 1;
620620
} else {
621621
return [];
622622
}
@@ -764,10 +764,10 @@ export class XPath {
764764
const pattern: any = rule[1];
765765

766766
for (let j = pattern.length - 1; j >= 0; --j) {
767-
if (pattern[j] == Q_1M) {
767+
if (pattern[j] == Q_ONE_OR_MULTIPLE) {
768768
push_(this.xPathRules, pattern[j - 1].key, rule);
769769
break;
770-
} else if (pattern[j] == Q_MM || pattern[j] == Q_01) {
770+
} else if (pattern[j] == Q_ZERO_OR_MULTIPLE || pattern[j] == Q_ZERO_OR_ONE) {
771771
push_(this.xPathRules, pattern[j - 1].key, rule);
772772
--j;
773773
} else {
@@ -924,8 +924,8 @@ export class XPath {
924924
* grammatical rules to them, "reducing" them to higher-level
925925
* tokens. Ultimately, any valid XPath should reduce to exactly one
926926
* "Expr" token.
927-
928-
* Reduce too early or too late and you'll have two tokens that can't reduce
927+
*
928+
* Reduce too early or too late, and you'll have two tokens that can't reduce
929929
* to single Expr. For example, you may hastily reduce a qname that
930930
* should name a function, incorrectly treating it as a tag name.
931931
* Or you may reduce too late, accidentally reducing the last part of the
@@ -940,11 +940,15 @@ export class XPath {
940940
*
941941
* Some tokens have left associativity, in which case we shift when they
942942
* have LOWER precedence than the candidate.
943+
* @param stack The actual grammar rule stack.
944+
* @param ahead The grammar rule ahead.
945+
* @return `true` if a grammar rule candidate was applied. `false` otherwise.
946+
* @private
943947
*/
944948
private xPathReduce(
945949
stack: GrammarRuleCandidate[],
946950
ahead: GrammarRuleCandidate
947-
) {
951+
): boolean {
948952
let candidate: GrammarRuleCandidate = null;
949953

950954
if (stack.length > 0) {
@@ -956,9 +960,8 @@ export class XPath {
956960
}
957961
}
958962

959-
let ret;
960963
if (candidate && (!ahead || candidate.prec > ahead.prec || (ahead.tag.left && candidate.prec >= ahead.prec))) {
961-
for (let i = 0; i < candidate.match.matchlength; ++i) {
964+
for (let i = 0; i < candidate.match.matchLength; ++i) {
962965
stack.pop();
963966
}
964967

@@ -968,12 +971,12 @@ export class XPath {
968971
}`
969972
);
970973

971-
const matchExpression = mapExpr(candidate.match, (m) => m.expr);
974+
const matchExpression = mapExpr(candidate.match, (m: GrammarRuleCandidate) => m.expr);
972975
this.xPathLog(`going to apply ${candidate.rule[3]}`);
973976
candidate.expr = candidate.rule[3].apply(this, matchExpression);
974977

975978
stack.push(candidate);
976-
ret = true;
979+
return true;
977980
} else {
978981
if (ahead) {
979982
this.xPathLog(
@@ -983,9 +986,9 @@ export class XPath {
983986
);
984987
stack.push(ahead);
985988
}
986-
ret = false;
989+
990+
return false;
987991
}
988-
return ret;
989992
}
990993

991994
/**

tests/lmht/html-to-lmht.test.tsx

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ describe('HTML to LMHT', () => {
162162
<xsl:apply-templates select="@*|node()" />
163163
</cabeça>
164164
</xsl:template>
165-
<xsl:template match="/html/base">
165+
<xsl:template match="/html/head/base">
166166
<base-ligações>
167167
<xsl:for-each select="@*">
168168
<xsl:choose>
@@ -184,7 +184,7 @@ describe('HTML to LMHT', () => {
184184
</base-ligações>
185185
</xsl:template>
186186
<!-- Tag \`style\` não é traduzida. -->
187-
<xsl:template match="/html/style">
187+
<xsl:template match="/html/head/style">
188188
<style>
189189
<xsl:apply-templates select="@*|node()" />
190190
</style>
@@ -1061,7 +1061,7 @@ describe('HTML to LMHT', () => {
10611061
<xsl:apply-templates select="@*|node()" />
10621062
</lista-simples>
10631063
</xsl:template>
1064-
<xsl:template match="/html/body//ol/li|html/body//ul/li">
1064+
<xsl:template match="/html/body//ul/li|html/body//ol/li">
10651065
<item-lista>
10661066
<xsl:apply-templates select="node()" />
10671067
</item-lista>
@@ -1571,6 +1571,44 @@ describe('HTML to LMHT', () => {
15711571
assert.equal(outXmlString, expectedOutString);
15721572
});
15731573

1574+
it('https://github.com/DesignLiquido/lmht-js/issues/2', async () => {
1575+
const xmlString = `<!DOCTYPE html>
1576+
<html lang="pt-BR">
1577+
<body>
1578+
<h1><a href="#">Delégua Blog</a></h1>
1579+
<nav>
1580+
<ul>
1581+
<li><a href="#">Início</a></li>
1582+
<li><a href="#">Sobre</a></li>
1583+
<li><a href="#">Contato</a></li>
1584+
</ul>
1585+
</nav>
1586+
</body>
1587+
</html>
1588+
`;
1589+
1590+
const expectedOutString = `<lmht>`+
1591+
`<corpo>`+
1592+
`<título1><ligação destino="#">Delégua Blog</ligação></título1>`+
1593+
`<navegação>`+
1594+
`<lista-simples>`+
1595+
`<item-lista><ligação destino="#">Início</ligação></item-lista>`+
1596+
`<item-lista><ligação destino="#">Sobre</ligação></item-lista>`+
1597+
`<item-lista><ligação destino="#">Contato</ligação></item-lista>`+
1598+
`</lista-simples>`+
1599+
`</navegação>`+
1600+
`</corpo>`+
1601+
`</lmht>`;
1602+
1603+
const xsltClass = new Xslt({ selfClosingTags: true });
1604+
const xmlParser = new XmlParser();
1605+
const xml = xmlParser.xmlParse(xmlString);
1606+
const xslt = xmlParser.xmlParse(xsltString);
1607+
const outXmlString = await xsltClass.xsltProcess(xml, xslt);
1608+
1609+
assert.equal(outXmlString, expectedOutString);
1610+
});
1611+
15741612
it('https://github.com/DesignLiquido/lmht-js/issues/3', async () => {
15751613
const xmlString = `<!DOCTYPE html>
15761614
<html lang="pt-BR">
@@ -1625,5 +1663,5 @@ describe('HTML to LMHT', () => {
16251663
const outXmlString = await xsltClass.xsltProcess(xml, xslt);
16261664

16271665
assert.equal(outXmlString, expectedOutString);
1628-
})
1666+
});
16291667
});

tests/xpath/xpath.test.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -728,17 +728,17 @@ describe('xpath', () => {
728728
];
729729

730730
const parsedXML = xmlParser.xmlParse(xml);
731-
const ctx = new ExprContext([parsedXML], []);
731+
const context = new ExprContext([parsedXML], []);
732732

733733
for (const test of tests) {
734-
const expr = xPath.xPathParse(test[0] as any);
734+
const expression = xPath.xPathParse(test[0] as any);
735735

736-
ctx.setReturnOnFirstMatch(false);
737-
const normalResults = expr.evaluate(ctx);
736+
context.setReturnOnFirstMatch(false);
737+
const normalResults = expression.evaluate(context);
738738
assert.equal(normalResults.value.length, test[1], `normal results count: ${test[0]}`);
739739

740-
ctx.setReturnOnFirstMatch(true);
741-
const firstMatchResults = expr.evaluate(ctx);
740+
context.setReturnOnFirstMatch(true);
741+
const firstMatchResults = expression.evaluate(context);
742742
assert.equal(firstMatchResults.value.length, 1, `first match results count: ${test[0]}`);
743743

744744
assert.equal(

0 commit comments

Comments
 (0)