TextReplaceImpl:
CEDAR
PROGRAM
IMPORTS Rope, TextFind
EXPORTS TextReplace = {
OPEN TextReplace;
SyntaxError: PUBLIC ERROR = CODE;
NoMapping: PUBLIC SIGNAL [rm: RopeMap, from: ROPE] RETURNS [to: ROPE] = CODE;
Apply:
PUBLIC
PROC [s: RopeMap, r:
ROPE]
RETURNS [mapped:
ROPE] =
{mapped ← s.Map[s.data, r]};
id: PUBLIC RopeMap ← NEW [RopeMapRep ← [Id, NIL]];
Id:
PROC [data:
REF
ANY, name:
ROPE]
RETURNS [value:
ROPE] =
{value ← name};
addBrackets: PUBLIC RopeMap ← NEW [RopeMapRep ← [AddBrackets, NIL]];
AddBrackets:
PROC [data:
REF
ANY, name:
ROPE]
RETURNS [value:
ROPE] =
{value ← Rope.Cat["<", name, ">"]};
Constant:
PUBLIC
PROC [result:
ROPE]
RETURNS [rm: RopeMap] = {
rm ← NEW [RopeMapRep ← [MapToConstant, result]];
};
MapToConstant:
PROC [data:
REF
ANY, arg:
ROPE]
RETURNS [result:
ROPE] = {
constant: ROPE = NARROW[data];
result ← constant;
};
Catted: TYPE = REF CattedRep;
CattedRep: TYPE = RECORD [first, second: RopeMap];
Cat:
PUBLIC
PROC [first, second: RopeMap]
RETURNS [catted: RopeMap] = {
catted ←
NEW [RopeMapRep ← [
MapCatted,
NEW [CattedRep ← [first, second]] ]];
};
MapCatted:
PROC [data:
REF
ANY, name:
ROPE]
RETURNS [value:
ROPE] = {
c: Catted ← NARROW[data];
mid: ROPE ← c.first.Map[c.first.data, name];
value ← c.second.Map[c.second.data, mid];
};
Layered: TYPE = REF LayeredRep;
LayeredRep: TYPE = RECORD [first, later: RopeMap];
Layer:
PUBLIC
PROC [first, later: RopeMap]
RETURNS [layered: RopeMap] = {
layered ←
NEW [RopeMapRep ← [
MapLayered,
NEW [LayeredRep ← [first, later]] ]];
};
MapLayered:
PROC [data:
REF
ANY, name:
ROPE]
RETURNS [value:
ROPE] = {
l: Layered ← NARROW[data];
value ← l.first.Map[l.first.data, name];
IF value = NIL THEN value ← l.later.Map[l.later.data, name];
};
ProcessedPairList: TYPE = LIST OF ProcessedPair;
ProcessedPair: TYPE = RECORD [finder: TextFind.Finder, replace: ROPE, literal: BOOL];
RopeMapFromPairs:
PUBLIC
PROC [pl: PairList]
RETURNS [s: RopeMap] = {
ppl: ProcessedPairList ← Process[pl];
s ← NEW [RopeMapRep ← [MapPairs, ppl]];
};
Process:
PROC [pl: PairList]
RETURNS [ppl: ProcessedPairList] = {
IF pl = NIL THEN RETURN [NIL];
ppl ←
CONS[
[
finder: TextFind.CreateFromRope[
pattern: pl.first.match,
literal: pl.first.literal,
addBounds: pl.first.addBounds],
replace: pl.first.replace, literal: pl.first.literal],
Process[pl.rest]];
};
MapPairs:
PROC [data:
REF
ANY, name:
ROPE]
RETURNS [value:
ROPE] = {
ppl: ProcessedPairList ← NARROW[data];
pp: ProcessedPair;
MapFinder:
PROC [data:
REF
ANY, name:
ROPE]
RETURNS [value:
ROPE] = {
start, after: INT;
[at: start, atEnd: after] ← TextFind.NameLoc[pp.finder, name];
value ← value.Substr[start: start, len: after - start];
};
s: RopeMap;
TRUSTED {s ← NEW [RopeMapRep ← [MapFinder, NIL]]};
s ← Nest[s];
value ← name;
FOR ppl ← ppl, ppl.rest
WHILE ppl #
NIL
DO
start: INT ← 0;
pp ← ppl.first;
WHILE start < value.Length[]
DO
at, atEnd, before, after: INT;
found: BOOL;
[found, at, atEnd, before, after] ← TextFind.SearchRope[finder: pp.finder, rope: value, start: start];
IF found
THEN {
newPiece:
ROPE ←
IF pp.literal
THEN pp.replace
ELSE Apply[s, pp.replace];
value ← value.Substr[start: 0, len: at].Cat[newPiece, value.Substr[start: atEnd]];
start ← at + newPiece.Length[];
}
ELSE EXIT;
ENDLOOP;
ENDLOOP;
};
FinderMapping: TYPE = REF FinderMappingRep;
FinderMappingRep:
TYPE =
RECORD [
finder: TextFind.Finder,
lastMatchedRope: ROPE];
MapNamedSubfieldToMatch:
PUBLIC
PROC [finder: TextFind.Finder, lastMatchedRope:
ROPE]
RETURNS [rm: RopeMap] = {
rm ←
NEW [RopeMapRep ← [
MapNamedSubfield,
NEW [FinderMappingRep ← [finder, lastMatchedRope]] ]];
};
MapNamedSubfield:
PROC [data:
REF
ANY, name:
ROPE]
RETURNS [value:
ROPE] = {
fm: FinderMapping = NARROW[data];
start, after: INT;
[at: start, atEnd: after] ← TextFind.NameLoc[fm.finder, name];
IF start = 0 AND after = 0 THEN RETURN [NIL];
value ← fm.lastMatchedRope.Substr[start: start, len: after - start];
};
MatchToTemplate: TYPE = REF MatchToTemplatePrivate;
MatchToTemplatePrivate:
TYPE =
RECORD [
finder: TextFind.Finder,
template: ROPE];
MapByMatchToTemplate:
PUBLIC
PROC [finder: TextFind.Finder, template:
ROPE]
RETURNS [rm: RopeMap] = {
mtt: MatchToTemplate = NEW [MatchToTemplatePrivate ← [finder, template]];
rm ← NEW [RopeMapRep ← [MapMatchToTemplate, mtt]];
};
MapMatchToTemplate:
PROC [data:
REF
ANY, arg:
ROPE]
RETURNS [result:
ROPE] = {
mtt: MatchToTemplate = NARROW[data];
found: BOOL = TextFind.SearchRope[mtt.finder, arg].found;
IF NOT found THEN RETURN [NIL];
result ← Apply[Nest[MapNamedSubfieldToMatch[mtt.finder, arg]], mtt.template];
};
Nest:
PUBLIC
PROC [rm: RopeMap]
RETURNS [nested: RopeMap] =
{nested ← NEW [RopeMapRep ← [MapNested, rm]]};
MapNested:
PROC [data:
REF
ANY, old:
ROPE]
RETURNS [new:
ROPE] = {
rm: RopeMap ← NARROW[data];
i: INT ← 0;
oldLen: INT ← old.Length[];
new ← NIL;
WHILE i < oldLen
DO
c: CHAR ← old.Fetch[i];
SELECT c
FROM
'' => {
IF i+1 = oldLen THEN SyntaxError[];
new ← new.Concat[Rope.FromChar[old.Fetch[i+1]]];
i ← i + 2};
'< => {j:
INT ← i+1;
name, value: ROPE;
DO
IF j >= oldLen THEN SyntaxError[];
SELECT old.Fetch[j]
FROM
'> => EXIT;
'<, '' => SyntaxError[]; --No metachars in field name
ENDCASE;
j ← j + 1;
ENDLOOP;
name ← old.Substr[start: i+1, len: j - i - 1];
value ← rm.Map[rm.data, name];
IF value = NIL THEN value ← NoMapping[rm, name];
new ← new.Concat[value];
i ← j + 1};
ENDCASE => {
new ← new.Concat[Rope.FromChar[c]];
i ← i + 1};
ENDLOOP;
};
MatchAndSubstitute:
PUBLIC
PROC [pattern, against, template:
ROPE]
RETURNS [new:
ROPE] = {
f: TextFind.Finder = TextFind.CreateFromRope[pattern];
found: BOOL = TextFind.SearchRope[f, against].found;
IF NOT found THEN RETURN [template];
new ← Apply[Nest[MapNamedSubfieldToMatch[f, against]], template];
};
}.