package Breccia.Web.imager; import Breccia.parser.plain.Language; import Java.WhitespaceCollapser; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.w3c.dom.Element; import org.w3c.dom.Node; import static Breccia.Web.imager.ImageNodes.head; import static Breccia.Web.imager.ImageNodes.ownerFractum; import static Breccia.Web.imager.ImageNodes.sourceText; import static Breccia.Web.imager.ImageNodes.successorTitlingLabel; import static Java.Nodes.hasName; import static Java.Nodes.successor; import static Java.Nodes.successorAfter; import static Java.Nodes.successorElement; import static Java.Nodes.textChildFlat; import static Java.Patterns.quote; // Yields more readable patterns than does `Pattern.quote`. import static Java.StringBuilding.clear; import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.MULTILINE; import static java.util.regex.Pattern.UNICODE_CASE; /** Compiler of patterns from the object clause of an afterlinker. * Before each call to `compile`, ensure that `mSubject` is correctly set. * * @see #mSubject */ final class ObjectClausePatternCompiler extends PatternCompiler { ObjectClausePatternCompiler( ImageMould mould ) { super( mould ); } /** Returns the Java compilation of a default pattern matcher to be used where an object clause * is absent or comprises a fractal context locant. * * @param cR The image of a referential command from an afterlinker. */ final Pattern compileDefaultPattern( final Element cR ) throws FailedInterpolation { final StringBuilder bP = clear( stringBuilder ); bP.append( anchoredPrefix_either ); appendVariable_same( cR, 0, bP, /*toExpandWhitespace*/true ); return Pattern.compile( bP.toString(), CASE_INSENSITIVE | UNICODE_CASE | MULTILINE/*[MLM]*/ ); } /** The Java pattern matcher of the subject clause successfully matched to the first subject. * * @see * Breccia language definition, match procedure for a subject clause */ Matcher mSubject; //// P r i v a t e //////////////////////////////////////////////////////////////////////////////////// /** @see FailedInterpolation#interpolator * @see FailedInterpolation#index *//* * @paramImplied #stringBuilder2 * @paramImplied #stringBuilder3 */ private void appendVariable_same( final Element interpolator, final int index, final StringBuilder bP, final boolean toExpandWhitespace ) throws FailedInterpolation { // Subject clause, in the presence of // ─────────────── if( mSubject != null ) { final StringBuilder b = clear( stringBuilder2 ); final int gN = mSubject.groupCount(); // captures of its matcher // ┈┈┈┈┈┈┈┈ if( gN > 0 ) { for( int g = 1;; ++g ) { final String capture = mSubject.group( g ); assert capture != null && capture.length() != 0; // Implied by `mSubject` API. quote( capture, b ); if( g == gN ) break; b.append( ' ' ); } append( b, bP, toExpandWhitespace ); return; } // whole match // ┈┈┈┈┈┈┈┈┈┈┈ b.append( mSubject.group() ); assert b.length() != 0; // Implied by `mSubject` API. BreccianCollapser.collapseWhitespace( b ); // This may empty `b`, wherefore: if( b.length() > 0 ) { final StringBuilder c = clear( stringBuilder3 ); quote( b, c ); append( c, bP, toExpandWhitespace ); } else append( " ", bP, toExpandWhitespace ); return; } Node n = ownerFractum( interpolator ); assert hasName( "Afterlinker", n ); n = n.getParentNode(); // Parent of the present afterlinker. Node head = head( n ); // Wherein lies the subject. if( head == null ) { throw new FailedInterpolation( interpolator, index, "Misplaced back reference, no parent head to refer to" ); } final StringBuilder b = clear( stringBuilder2 ); final StringBuilder c = clear( stringBuilder3 ); if( hasName( "Division", n )) { final Element firstTitlingLabel = successorTitlingLabel( head, successorAfter(head) ); // Titled division, the parent of the present afterlinker is // ─────────────── if( firstTitlingLabel != null ) { b.append( textChildFlat( firstTitlingLabel )); BreccianCollapser.collapseWhitespace( b ); quote( b, c ); append( c, bP, toExpandWhitespace ); return; }} // File fractum or point, the parent of the present afterlinker is // ───────────────────── head = head.cloneNode( /*deeply*/true ); /* So both preserving the original parent head, and keeping the nodal scan that follows within the bounds of the isolated clone. */ strip: for( n = successorElement(head); n != null; n = successorElement(n) ) { final String name = n.getLocalName(); if( "CommentAppender".equals( name ) || "CommentBlock" .equals( name ) || "IndentBlind" .equals( name )) { for( ;; ) { // Remove it and all successors. final Node s = successorAfter( n ); n.getParentNode().removeChild( n ); if( s == null ) break strip; n = s; }}} b.append( sourceText( head )); BreccianCollapser.collapseWhitespace( b ); quote( b, c ); append( c, bP, toExpandWhitespace ); } private static final WhitespaceCollapser BreccianCollapser = new WhitespaceCollapser() { public @Override boolean isWhitespace( char ch ) { return Language.isWhitespace( ch ); }}; private final StringBuilder stringBuilder = new StringBuilder( /*initial capacity*/0x800 ); // = 2048 private final StringBuilder stringBuilder2 = new StringBuilder( /*initial capacity*/0x800 ); private final StringBuilder stringBuilder3 = new StringBuilder( /*initial capacity*/0x800 ); // ━━━ P a t t e r n C o m p i l e r ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /* @paramImplied #stringBuilder2 * @paramImplied #stringBuilder3 */ protected @Override void append( final Element variable, final StringBuilder bP, final boolean toExpandWhitespace ) throws FailedInterpolation { final String tF = textChildFlat( variable ); // ${same} // ─────── if( "${same}".equals( tF )) { appendVariable_same( variable, variableName, bP, toExpandWhitespace ); return; } // ${1}, ${2}, ${3}, … ${9} // ───── if( tF.length() == /*e.g.*/"${1}".length() ) { final int g = tF.charAt(variableName) - '0'; if( 1 <= g && g <= 9 ) { if( mSubject == null ) { throw new FailedInterpolation( variable, 0, "Back reference to a subject-clause capture, but no subject clause" ); } final int gN = mSubject.groupCount(); if( g > gN ) { throw new FailedInterpolation( variable, variableName, "No such capture group (" + g + ") in the subject clause" ); } final String capture = mSubject.group( g ); assert capture != null && capture.length() != 0; // Implied by `mSubject` API. final StringBuilder b = clear( stringBuilder2 ); quote( capture, b ); append( b, bP, toExpandWhitespace ); return; }} super.append( variable, bP, toExpandWhitespace ); }} // NOTE // ───── // MLM Multi-line mode operation of Breccian pattern matchers. // http://reluk.ca/project/Breccia/language_definition.brec.xht#consistent,perl-s,multi-line // http://reluk.ca/project/Breccia/language_definition.brec.xht#consistent,perl-s,multi-line:2 // Copyright © 2022-2024 Michael Allan. Licence MIT.