| package edu.illinois.ncsa.daffodil.processors |
| |
| /* Copyright (c) 2012-2013 Tresys Technology, LLC. All rights reserved. |
| * |
| * Developed by: Tresys Technology, LLC |
| * http://www.tresys.com |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy of |
| * this software and associated documentation files (the "Software"), to deal with |
| * the Software without restriction, including without limitation the rights to |
| * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
| * of the Software, and to permit persons to whom the Software is furnished to do |
| * so, subject to the following conditions: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimers. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimers in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * 3. Neither the names of Tresys Technology, nor the names of its contributors |
| * may be used to endorse or promote products derived from this Software |
| * without specific prior written permission. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE |
| * SOFTWARE. |
| */ |
| |
| import java.math.BigInteger |
| import java.text.{ ParseException, ParsePosition } |
| import java.util.regex.Pattern |
| import java.nio.{ CharBuffer, ByteBuffer } |
| import java.nio.charset.Charset |
| import java.nio.charset.MalformedInputException |
| import scala.collection.mutable.Queue |
| import edu.illinois.ncsa.daffodil.Implicits._ |
| import edu.illinois.ncsa.daffodil.dsom._ |
| import edu.illinois.ncsa.daffodil.compiler._ |
| import edu.illinois.ncsa.daffodil.xml.XMLUtils |
| import edu.illinois.ncsa.daffodil.schema.annotation.props.gen.{ YesNo, LengthKind, ByteOrder, LengthUnits } |
| import edu.illinois.ncsa.daffodil.util.{ Debug, LogLevel, Logging, Info } |
| import edu.illinois.ncsa.daffodil.util.Misc.bytes2Hex |
| import edu.illinois.ncsa.daffodil.processors._ |
| import edu.illinois.ncsa.daffodil.exceptions.Assert |
| import edu.illinois.ncsa.daffodil.exceptions.UnsuppressableException |
| import com.ibm.icu.text.{ NumberFormat, DecimalFormat } |
| import edu.illinois.ncsa.daffodil.grammar.Terminal |
| import scala.util.parsing.input.{ Reader } |
| import java.sql.Timestamp |
| import edu.illinois.ncsa.daffodil.grammar.Gram |
| import edu.illinois.ncsa.daffodil.schema.annotation.props.gen.TextTrimKind |
| import edu.illinois.ncsa.daffodil.schema.annotation.props.gen.TextStringJustification |
| import edu.illinois.ncsa.daffodil.schema.annotation.props.gen.TextNumberJustification |
| import edu.illinois.ncsa.daffodil.schema.annotation.props.gen.TextCalendarJustification |
| import edu.illinois.ncsa.daffodil.schema.annotation.props.gen.TextBooleanJustification |
| import edu.illinois.ncsa.daffodil.processors.{ Parser => DaffodilParser } |
| import scala.util.parsing.combinator.RegexParsers |
| |
| abstract class StaticDelimiter(kindString: String, delim: String, e: Term, guard: Boolean = true) |
| extends StaticText(delim, e, kindString, guard) |
| |
| abstract class StaticText(delim: String, e: Term, kindString: String, guard: Boolean = true) |
| extends Text(e, guard) //extends DelimParserBase(e, guard) |
| with WithParseErrorThrowing with TextReader { |
| |
| val charset = e.knownEncodingCharset |
| val term = e.asInstanceOf[Term] |
| |
| val staticTexts = delim.split("\\s").toList |
| val staticTextsCooked: Queue[String] = new Queue |
| |
| staticTexts.foreach(x => staticTextsCooked.enqueue(EntityReplacer.replaceAll(x, Some(e)))) |
| |
| val delimsRaw = e.allTerminatingMarkup.map { |
| case (delimValue, elemName, elemPath) => (delimValue.constantAsString, elemName, elemPath) |
| } |
| val delimsCooked1 = delimsRaw.map { |
| case (delimValue, elemName, elemPath) => { |
| (new ListOfStringValueAsLiteral(delimValue.toString, e).cooked, elemName, elemPath) |
| } |
| } |
| val delimsCooked = delimsCooked1.map { case (delimValue, _, _) => delimValue }.flatten |
| |
| // Here we expect that remoteDelims shall be defined as those delimiters who are not |
| // also defined locally. That is to say that local should win over remote. |
| val remoteDelims = delimsCooked.toSet.diff(staticTextsCooked.toSet) |
| |
| // here we define the parsers so that they are pre-compiled/generated |
| val delimParser = new DFDLDelimParserStatic(e.knownEncodingStringBitLengthFunction) |
| val (pInputDelimiterParser, pIsLocalDelimParser, remoteDelimRegex) = |
| delimParser.generateInputDelimiterParsers(staticTextsCooked.toSet, remoteDelims) |
| |
| def parseMethod(input: Reader[Char]): DelimParseResult = { |
| val result: DelimParseResult = delimParser.parseInputDelimiter(pInputDelimiterParser, pIsLocalDelimParser, input) |
| result |
| } |
| |
| //e.schemaDefinitionWarningUnless(e.ignoreCase == YesNo.No, "Property ignoreCase='yes' not supported.") |
| Assert.invariant(delim != "") // shouldn't be here at all in this case. |
| |
| def parser: DaffodilParser = new PrimParser(this, e) { |
| |
| override def toBriefXML(depthLimit: Int = -1) = { |
| "<" + kindString + ">" + delim + " " + delimsRaw + "</" + kindString + ">" |
| } |
| |
| override def toString = kindString + "('" + delim + "')" // with terminating markup: " + term.prettyTerminatingMarkup + ")" |
| |
| // This has to stay here, moving it outside of PrimParser causes it |
| // to not be defined. Why? |
| e.schemaDefinitionWarningUnless(e.ignoreCase == YesNo.No, "Property ignoreCase='yes' not supported.") |
| |
| val eName = e.toString() |
| |
| def parse(start: PState): PState = withParseErrorThrowing(start) { |
| // withLoggingLevel(LogLevel.Debug) |
| { |
| |
| log(LogLevel.Debug, "%s - Parsing delimiter at byte position: %s", eName, (start.bitPos >> 3)) |
| log(LogLevel.Debug, "%s - Parsing delimiter at bit position: %s", eName, start.bitPos) |
| |
| log(LogLevel.Debug, "%s - Looking for local(%s) not remote (%s).", eName, staticTextsCooked.toSet, remoteDelims) |
| |
| val bytePos = (start.bitPos >> 3).toInt |
| |
| log(LogLevel.Debug, "Retrieving reader state.") |
| val reader = getReader(charset, start.bitPos, start) |
| |
| // Well they may not be delimiters, but the logic is the same as for a |
| // set of static delimiters. |
| val result = parseMethod(reader) |
| |
| log(LogLevel.Debug, "%s - %s - DelimParseResultult: %s", this.toString(), eName, result) |
| |
| result match { |
| case _: DelimParseFailure => { |
| log(LogLevel.Debug, "%s - %s: Delimiter not found!", this.toString(), eName) |
| return PE(start, "%s - %s: Delimiter not found!", this.toString(), eName) |
| } |
| case s: DelimParseSuccess if (s.delimiterLoc == DelimiterLocation.Remote) => { |
| val (remoteDelimValue, remoteElemName, remoteElemPath) = |
| getMatchedDelimiterInfo(remoteDelimRegex, s.delimiter, delimsCooked1) |
| |
| log(LogLevel.Debug, "%s - %s: Found delimiter (%s) for %s when looking for %s(%s) for %s %s", |
| this.toString(), eName, remoteDelimValue, remoteElemPath, kindString, staticTexts.mkString(" "), e.path, positionalInfo) |
| return PE(start, "%s - %s: Found delimiter (%s) for %s when looking for %s(%s) for %s %s", |
| this.toString(), eName, remoteDelimValue, remoteElemPath, kindString, staticTexts.mkString(" "), e.path, positionalInfo) |
| } |
| case s: DelimParseSuccess => |
| { |
| val numBits = e.knownEncodingStringBitLengthFunction(s.delimiter) |
| val endCharPos = if (start.charPos == -1) s.delimiter.length else start.charPos + s.delimiter.length() |
| val endBitPosDelim = numBits + start.bitPos |
| |
| log(LogLevel.Debug, "%s - Found %s", eName, s.delimiter) |
| log(LogLevel.Debug, "%s - Ended at byte position %s", eName, (endBitPosDelim >> 3)) |
| log(LogLevel.Debug, "%s - Ended at bit position %s", eName, endBitPosDelim) |
| |
| return start.withPos(endBitPosDelim, endCharPos, Some(s.next)) |
| } |
| start |
| } |
| } |
| } |
| } |
| |
| def unparser: Unparser = new Unparser(e) { |
| val t = e.asInstanceOf[Term] |
| override def toString = "StaticText('" + delim + "' with terminating markup: " + t.prettyTerminatingMarkup + ")" |
| // setLoggingLevel(LogLevel.Info) |
| e.schemaDefinitionWarningUnless(e.ignoreCase == YesNo.No, "Property ignoreCase='yes' is not supported.") |
| Assert.invariant(delim != "") //shouldn't be here at all in this case |
| |
| def unparse(start: UState): UState = { |
| val encoder = e.knownEncodingCharset.newEncoder() |
| start.outStream.setEncoder(encoder) |
| start.outStream.fillCharBuffer(unparserDelim) |
| log(LogLevel.Debug, "Unparsed: " + start.outStream.getData) |
| start |
| } |
| } |
| |
| def unparserDelim: String |
| } |
| |
| abstract class Text(e: Term, guard: Boolean) extends DelimParserBase(e, guard) { |
| lazy val oes = { |
| val oes = e.optionEscapeScheme |
| oes.foreach { es => |
| e.schemaDefinitionUnless(es.isKnownEscapeCharacter != Some(false), "Runtime expressions for escapeCharacters are not supported.") |
| e.schemaDefinitionUnless(es.isKnownEscapeEscapeCharacter != Some(false), "Runtime expressions for escapeEscapeCharacters are not supported.") |
| } |
| oes |
| } |
| lazy val esObj = EscapeScheme.getEscapeScheme(oes, e) |
| val eName = e.toString() |
| |
| val positionalInfo = { |
| if (e.isDirectChildOfSequence) { |
| e.nearestEnclosingSequence match { |
| case Some(es) => { |
| val pos = e.positionInNearestEnclosingSequence - 1 |
| if (es.hasPrefixSep) { |
| if (e.hasPriorRequiredSiblings) { |
| val prior: Term = es.groupMembers(pos - 1) |
| "after " + prior.prettyName + " and before " + eName |
| } else "before " + eName |
| } else if (es.hasInfixSep) |
| if (e.hasPriorRequiredSiblings && e.hasLaterRequiredSiblings) { |
| val prior: Term = es.groupMembers(pos - 1) |
| |
| "after " + prior.prettyName + " and before " + eName |
| } else if (e.hasPriorRequiredSiblings) { |
| val prior: Term = es.groupMembers(pos - 1) |
| "after " + prior.prettyName + " and before " + eName |
| } else if (e.hasLaterRequiredSiblings) { |
| val later: Term = es.groupMembers(pos + 1) |
| "before " + later.prettyName |
| } else { "" } |
| else if (es.hasPostfixSep) |
| if (e.hasPriorRequiredSiblings && e.hasLaterRequiredSiblings) { |
| val later: Term = es.groupMembers(pos + 1) |
| |
| "after " + eName + " and before " + later.prettyName |
| } else if (e.hasPriorRequiredSiblings) { |
| val prior: Term = es.groupMembers(pos - 1) |
| "after " + prior.prettyName + " and before " + eName |
| } else if (e.hasLaterRequiredSiblings) { |
| val later: Term = es.groupMembers(pos + 1) |
| "before " + later.prettyName |
| } else { "" } |
| else |
| "" |
| } |
| case None => "" |
| } |
| } |
| } |
| |
| def getMatchedDelimiterInfo(remoteDelimRegex: Set[(String, String)], foundDelimiter: String, |
| delimiters: List[(List[String], String, String)]) = { |
| val matchedDelim = remoteDelimRegex.find { |
| case (delimRegex, _) => { |
| foundDelimiter.matches("(?s)^(" + delimRegex + ")$") |
| } |
| } match { |
| case Some((_, theValue)) => theValue |
| case None => Assert.impossibleCase() |
| } |
| |
| val (remoteDelimValue, remoteElemName, remoteElemPath, _) = |
| { |
| val findResult = delimiters.map { |
| case (delimValueList, elemName, elemPath) => { |
| delimValueList.find(delim => delim == matchedDelim) match { |
| case Some(d) => (d, elemName, elemPath, true) |
| case None => (delimValueList.mkString(","), elemName, elemPath, false) |
| } |
| } |
| }.toSet.filter { x => x._4 == true } |
| |
| if (findResult.size == 0) Assert.impossibleCase() |
| findResult.head |
| } |
| (remoteDelimValue, remoteElemName, remoteElemPath) |
| } |
| |
| } |
| |
| abstract class DynamicText(delimExpr: CompiledExpression, e: Term, kindString: String, guard: Boolean = true) |
| extends Text(e, guard) |
| with WithParseErrorThrowing with TextReader { |
| |
| val charset = e.knownEncodingCharset |
| val term = e.asInstanceOf[Term] |
| |
| // If there are any static delimiters, pre-process them here |
| lazy val staticDelimsRaw = |
| e.allTerminatingMarkup.filter { |
| case (delimValue, _, _) => delimValue.isConstant |
| }.map { |
| case (delimValue, eName, ePath) => (delimValue.constantAsString, eName, ePath) |
| } |
| lazy val staticDelimsCooked1 = staticDelimsRaw.map { |
| case (delimValue, elemName, elemPath) => { (new ListOfStringValueAsLiteral(delimValue.toString, e).cooked, elemName, elemPath) } |
| } |
| lazy val staticDelimsCooked = staticDelimsCooked1.map { case (delimValue, _, _) => delimValue }.flatten |
| lazy val (staticDelimsParsers, staticDelimsRegex) = dp.generateDelimiter(staticDelimsCooked.toSet) |
| |
| def parseMethod(pInputDelimiterParser: dp.Parser[String], |
| pIsLocalDelimParser: dp.Parser[String], |
| input: Reader[Char]): DelimParseResult = { |
| val result: DelimParseResult = dp.parseInputDelimiter(pInputDelimiterParser, pIsLocalDelimParser, input) |
| result |
| } |
| |
| def parser: DaffodilParser = new PrimParser(this, e) { |
| |
| override def toBriefXML(depthLimit: Int = -1) = { |
| "<" + kindString + ">" + delimExpr + " " + delimExpr + "</" + kindString + ">" |
| } |
| |
| e.schemaDefinitionWarningUnless(e.ignoreCase == YesNo.No, "Property ignoreCase='yes' not supported.") |
| |
| Assert.invariant(delimExpr.toString != "") // shouldn't be here at all in this case. |
| override def toString = kindString + "('" + delimExpr + "')" // with terminating markup: " + term.prettyTerminatingMarkup + ")" |
| |
| lazy val tm = e.allTerminatingMarkup |
| val eName = e.toString() |
| |
| def parse(start: PState): PState = withParseErrorThrowing(start) { |
| // withLoggingLevel(LogLevel.Debug) |
| { |
| |
| // We must feed variable context out of one evaluation and into the next. |
| // So that the resulting variable map has the updated status of all evaluated variables. |
| var vars = start.variableMap |
| |
| val dynamicDelimsRaw = e.allTerminatingMarkup.filter { case (delimValue, elemName, elemPath) => !delimValue.isConstant }.map { |
| case (delimValue, elemName, elemPath) => |
| { |
| val R(res, newVMap) = delimValue.evaluate(start.parentElement, vars, start) |
| vars = newVMap |
| (res, elemName, elemPath) |
| } |
| } |
| // Dynamic delimiters can only be evaluated at runtime |
| val dynamicDelimsCooked1 = dynamicDelimsRaw.map { |
| case (delimValue, elemValue, elemPath) => { (new ListOfStringValueAsLiteral(delimValue.toString, e).cooked, elemValue, elemPath) } |
| } |
| val dynamicDelimsCooked = dynamicDelimsCooked1.map { case (delimValue, _, _) => delimValue }.flatten |
| val delimsCooked = dynamicDelimsCooked.union(staticDelimsCooked) |
| val (dynamicDelimsParsers, dynamicDelimsRegex) = dp.generateDelimiter(dynamicDelimsCooked.toSet) |
| |
| val localDelimsRaw = { |
| val R(res, newVMap) = delimExpr.evaluate(start.parentElement, vars, start) |
| vars = newVMap |
| res |
| } |
| val localDelimsCooked1 = new ListOfStringValueAsLiteral(localDelimsRaw.toString(), e).cooked |
| val localDelimsCooked = localDelimsCooked1 |
| val (localDelimsParser, localDelimsRegex) = dp.generateDelimiter(localDelimsCooked.toSet) |
| |
| val pIsLocalDelimParser = dp.generateIsLocalDelimParser(localDelimsRegex) |
| |
| val postEvalState = start.withVariables(vars) |
| |
| log(LogLevel.Debug, "%s - Parsing delimiter at byte position: %s", eName, (postEvalState.bitPos >> 3)) |
| log(LogLevel.Debug, "%s - Parsing delimiter at bit position: %s", eName, postEvalState.bitPos) |
| |
| log(LogLevel.Debug, "%s - Looking for local(%s) not remote (%s).", eName, localDelimsCooked.toSet, delimsCooked.toSet) |
| |
| val bytePos = (postEvalState.bitPos >> 3).toInt |
| |
| log(LogLevel.Debug, "Retrieving reader state.") |
| val reader = getReader(charset, start.bitPos, postEvalState) |
| |
| val remoteDelims: Array[dp.Parser[String]] = staticDelimsParsers.union(dynamicDelimsParsers) |
| val remoteDelimRegex = dp.getDelimsRegex(staticDelimsCooked.union(dynamicDelimsCooked).toSet) |
| val pInputDelimiterParser = dp.generateInputDelimiterParser(localDelimsParser, remoteDelims) |
| |
| val result = parseMethod(pInputDelimiterParser, pIsLocalDelimParser, reader) |
| |
| log(LogLevel.Debug, "%s - %s - DelimParseResultult: %s", this.toString(), eName, result) |
| |
| result match { |
| case _: DelimParseFailure => { |
| log(LogLevel.Debug, "%s - %s: Delimiter not found!", this.toString(), eName) |
| return PE(start, "%s - %s: Delimiter not found!", this.toString(), eName) |
| } |
| case s: DelimParseSuccess if (s.delimiterLoc == DelimiterLocation.Remote) => { |
| val (remoteDelimValue, remoteElemName, remoteElemPath) = |
| getMatchedDelimiterInfo(remoteDelimRegex, s.delimiter, dynamicDelimsCooked1) |
| |
| log(LogLevel.Debug, "%s - %s: Found delimiter (%s) for %s when looking for %s(%s) for %s", |
| this.toString(), eName, remoteDelimValue, remoteElemPath, kindString, localDelimsCooked.mkString(" "), e.path) |
| return PE(start, "%s - %s: Found delimiter (%s) for %s when looking for %s(%s) for %s", |
| this.toString(), eName, remoteDelimValue, remoteElemPath, kindString, localDelimsCooked.mkString(" "), e.path) |
| } |
| case s: DelimParseSuccess => |
| { |
| val numBits = e.knownEncodingStringBitLengthFunction(s.delimiter) |
| val endCharPos = if (postEvalState.charPos == -1) s.delimiter.length else postEvalState.charPos + s.delimiter.length() |
| val endBitPosDelim = numBits + postEvalState.bitPos |
| |
| log(LogLevel.Debug, "%s - Found %s", eName, s.delimiter) |
| log(LogLevel.Debug, "%s - Ended at byte position %s", eName, (endBitPosDelim >> 3)) |
| log(LogLevel.Debug, "%s - Ended at bit position %s", eName, endBitPosDelim) |
| |
| return postEvalState.withPos(endBitPosDelim, endCharPos, Some(s.next)) |
| } |
| postEvalState |
| } |
| } |
| } |
| } |
| |
| /* |
| def unparser: Unparser = new Unparser(e) { |
| val t = e.asInstanceOf[Term] |
| override def toString = "StaticText('" + delimExpr + "' with terminating markup: " + t.prettyTerminatingMarkup + ")" |
| // setLoggingLevel(LogLevel.Info) |
| e.schemaDefinitionWarningUnless(e.ignoreCase == YesNo.No, "Property ignoreCase='yes' is not supported.") |
| Assert.invariant(delimExpr != "") //shouldn't be here at all in this case |
| |
| def unparse(start: UState): UState = { |
| // We really want to do something similar to the below to evaluate the expression |
| // for a delimiter. |
| // val localDelimsRaw = { |
| // val R(res, newVMap) = delimExpr.evaluate(start.parentElement, vars, start) |
| // vars = newVMap |
| // res |
| // } |
| // val localDelimsCooked1 = new ListOfStringValueAsLiteral(localDelimsRaw.toString(), e).cooked |
| // val localDelimsCooked = localDelimsCooked1 |
| val encoder = e.knownEncodingCharset.newEncoder() |
| start.outStream.setEncoder(encoder) |
| |
| // TODO: This is not correct, we need to be able to evaluate delimExpr and select a |
| // delimiter to use here. |
| start.outStream.fillCharBuffer(delimExpr.toString()) //start.outStream.fillCharBuffer(unparserDelim) |
| log(LogLevel.Debug, "Unparsed: " + start.outStream.getData)) |
| start |
| } |
| } */ |
| def unparser: Unparser = DummyUnparser |
| |
| //def unparserDelim: String |
| } |
| |
| abstract class DynamicDelimiter(kindString: String, delimExpr: CompiledExpression, e: Term, guard: Boolean = true) |
| extends DynamicText(delimExpr, e, kindString, guard) |
| |
| //case class StaticInitiator(e: Term) extends StaticDelimiter(e.initiator.constantAsString, e) |
| case class StaticInitiator(e: Term) extends StaticDelimiter("Init", e.initiator.constantAsString, e) { |
| Assert.invariant(e.hasInitiator) |
| lazy val unparserDelim = e.initiator.constantAsString.split("""\s""").head |
| } |
| //case class StaticTerminator(e : Term) extends StaticDelimiter(e.terminator.constantAsString, e) |
| case class StaticTerminator(e: Term) extends StaticDelimiter("Term", e.terminator.constantAsString, e) { |
| Assert.invariant(e.hasTerminator) |
| lazy val unparserDelim = e.terminator.constantAsString.split("""\s""").head |
| } |
| case class DynamicInitiator(e: Term) extends DynamicDelimiter("Init", e.initiator, e) |
| case class DynamicTerminator(e: Term) extends DynamicDelimiter("Term", e.terminator, e) |
| |
| // Note: for a static separator, we pass s, the sequence, because that is where |
| // the charset encoding comes from. |
| case class StaticSeparator(s: Sequence, t: Term) extends StaticDelimiter("Sep", s.separator.constantAsString, s) { |
| Assert.invariant(s.hasSeparator) |
| lazy val unparserDelim = s.separator.constantAsString.split("""\s""").head |
| } |
| case class DynamicSeparator(s: Sequence, t: Term) extends DynamicDelimiter("Sep", s.separator, s) |
| |
| case class LiteralNilExplicitLengthInBytes(e: ElementBase) |
| extends LiteralNilInBytesBase(e, "LiteralNilExplicit") { |
| |
| val expr = e.length |
| val exprText = expr.prettyExpr |
| |
| final def computeLength(start: PState) = { |
| val R(nBytesAsAny, newVMap) = expr.evaluate(start.parentElement, start.variableMap, start) |
| val nBytes = nBytesAsAny.toString().toLong //nBytesAsAny.asInstanceOf[Long] |
| (nBytes, newVMap) |
| } |
| |
| } |
| |
| case class LiteralNilKnownLengthInBytes(e: ElementBase, lengthInBytes: Long) |
| extends LiteralNilInBytesBase(e, "LiteralNilKnown") { |
| |
| final def computeLength(start: PState) = { |
| (lengthInBytes, start.variableMap) |
| } |
| |
| } |
| |
| abstract class LiteralNilInBytesBase(e: ElementBase, label: String) |
| extends StaticText(e.nilValue, e, label, e.isNillable) |
| with Padded { |
| |
| protected def computeLength(start: PState): (Long, VariableMap) |
| |
| // We are to assume that we can always read nBytes |
| // a failure to read nBytes is a failure period. |
| |
| lazy val unparserDelim = Assert.notYetImplemented() |
| |
| override def parser = new PrimParser(this, e) { |
| |
| override def toBriefXML(depthLimit: Int = -1): String = { |
| "<" + name + " nilValue='" + e.nilValue + "'/>" |
| } |
| |
| val isEmptyAllowed = e.nilValue.contains("%ES;") |
| val eName = e.toString() |
| val nilValuesCooked = new ListOfStringValueAsLiteral(e.nilValue, e).cooked |
| val charsetName = charset.name() |
| |
| def parse(start: PState): PState = { |
| // withLoggingLevel(LogLevel.Debug) |
| { |
| |
| // TODO: What if someone passes in nBytes = 0 for Explicit length, is this legal? |
| |
| val (nBytes: Long, newVMap: VariableMap) = computeLength(start) |
| val postEvalState = start.withVariables(newVMap) |
| log(LogLevel.Debug, "Explicit length %s", nBytes) |
| |
| //val postEvalState = start //start.withVariables(vars) |
| |
| log(LogLevel.Debug, "%s - Looking for: %s Count: %s", eName, nilValuesCooked, nilValuesCooked.length) |
| val in = postEvalState.inStream |
| |
| val bytePos = (postEvalState.bitPos >> 3).toInt |
| log(LogLevel.Debug, "%s - Starting at bit pos: %s", eName, postEvalState.bitPos) |
| log(LogLevel.Debug, "%s - Starting at byte pos: %s", eName, bytePos) |
| |
| // some encodings aren't whole bytes |
| // if (postEvalState.bitPos % 8 != 0) { return PE(postEvalState, "LiteralNilPattern - not byte aligned.") } |
| |
| val decoder = charset.newDecoder() |
| val d = new DelimParser(e.knownEncodingStringBitLengthFunction) |
| try { |
| val reader = in.getCharReader(charset, postEvalState.bitPos) |
| val bytes = in.getBytes(postEvalState.bitPos, nBytes.toInt) |
| val cb = decoder.decode(ByteBuffer.wrap(bytes)) |
| val result = cb.toString |
| val trimmedResult = d.removePadding(result, justificationTrim, padChar) |
| val endBitPos = postEvalState.bitPos + (nBytes.toInt * 8) |
| val endCharPos = if (postEvalState.charPos == -1) result.length() else postEvalState.charPos + result.length() |
| |
| // We have a field, is it empty? |
| val isFieldEmpty = trimmedResult.length == 0 //result.length() == 0 |
| |
| if (isFieldEmpty && isEmptyAllowed) { |
| // Valid! |
| postEvalState.parentElement.makeNil() |
| return postEvalState // Empty, no need to advance |
| } else if (isFieldEmpty && !isEmptyAllowed) { |
| // Fail! |
| return PE(postEvalState, "%s - Empty field found but not allowed!", eName) |
| } else if (d.isFieldDfdlLiteral(trimmedResult, nilValuesCooked.toSet)) { |
| // Contains a nilValue, Success! |
| postEvalState.parentElement.makeNil() |
| |
| log(LogLevel.Debug, "%s - Found %s", eName, trimmedResult) |
| log(LogLevel.Debug, "%s - Ended at byte position %s", eName, (endBitPos >> 3)) |
| log(LogLevel.Debug, "%s - Ended at bit position ", eName, endBitPos) |
| |
| return postEvalState.withPos(endBitPos, endCharPos, Some(reader)) // Need to advance past found nilValue |
| } else { |
| // Fail! |
| return PE(postEvalState, "%s - Does not contain a nil literal!", eName) |
| } |
| } catch { |
| case e: IndexOutOfBoundsException => { |
| // In this case, we failed to get the bytes |
| if (isEmptyAllowed) { |
| // Valid! |
| postEvalState.parentElement.makeNil() |
| return postEvalState // Empty, no need to advance |
| } else { |
| return PE(postEvalState, "%s - Insufficient Bytes in field; required %s", name, nBytes) |
| } |
| } |
| case u: UnsuppressableException => throw u |
| case e: Exception => { return PE(postEvalState, "%s - Exception: \n%s", name, e.getMessage()) } |
| } |
| } |
| } |
| |
| } |
| |
| override def unparser: Unparser = new Unparser(e) { |
| def unparse(start: UState): UState = { |
| Assert.notYetImplemented() |
| } |
| } |
| } |
| |
| case class LiteralNilExplicitLengthInChars(e: ElementBase) |
| extends StaticText(e.nilValue, e, "LiteralNilExplicit", e.isNillable) |
| with Padded { |
| // We are to assume that we can always read nChars |
| // a failure to read nChars is a failure period. |
| |
| // TODO: LiteralNilExplicitLengthInChars really is a variation of LiteralNilPattern |
| lazy val unparserDelim = Assert.notYetImplemented() |
| |
| override def parser = new PrimParser(this, e) { |
| |
| override def toBriefXML(depthLimit: Int = -1): String = { |
| "<" + name + " nilValue='" + e.nilValue + "'/>" |
| } |
| |
| val isEmptyAllowed = e.nilValue.contains("%ES;") |
| val eName = e.toString() |
| val nilValuesCooked = new ListOfStringValueAsLiteral(e.nilValue, e).cooked |
| val charsetName = charset.name() |
| val expr = e.length |
| val exprText = expr.prettyExpr |
| |
| def parse(start: PState): PState = { |
| // withLoggingLevel(LogLevel.Info) |
| { |
| |
| //val postEvalState = start //start.withVariables(vars) |
| |
| val R(nCharsAsAny, newVMap) = expr.evaluate(start.parentElement, start.variableMap, start) |
| val nChars = nCharsAsAny.asInstanceOf[String] //nBytesAsAny.asInstanceOf[Long] |
| val postEvalState = start.withVariables(newVMap) |
| log(LogLevel.Debug, "Explicit length %s", nChars) |
| |
| val pattern = "(?s)^.{%s}".format(nChars) |
| |
| log(LogLevel.Debug, "%s - Looking for: %s Count: %s", eName, nilValuesCooked, nilValuesCooked.length) |
| |
| val bytePos = (postEvalState.bitPos >> 3).toInt |
| log(LogLevel.Debug, "%s - Starting at bit pos: %s", eName, postEvalState.bitPos) |
| log(LogLevel.Debug, "%s - Starting at byte pos: %s", eName, bytePos) |
| |
| // Don't check this here. This can vary by encoding. |
| //if (postEvalState.bitPos % 8 != 0) { return PE(start, "LiteralNilPattern - not byte aligned.") } |
| |
| log(LogLevel.Debug, "Retrieving reader state.") |
| val reader = getReader(charset, start.bitPos, start) |
| |
| if (nChars == 0 && isEmptyAllowed) { |
| log(LogLevel.Debug, "%s - explicit length of 0 and %ES; found as nilValue.", eName) |
| postEvalState.parentElement.makeNil() |
| return postEvalState // Empty, no need to advance |
| } |
| |
| val d = new DelimParser(e.knownEncodingStringBitLengthFunction) |
| |
| val result = d.parseInputPatterned(pattern, reader) |
| |
| result match { |
| case _: DelimParseFailure => |
| return PE(postEvalState, "%s - %s - Parse failed.", this.toString(), eName) |
| case s: DelimParseSuccess => { |
| // We have a field, is it empty? |
| val field = d.removePadding(s.field, justificationTrim, padChar) |
| val isFieldEmpty = field.length() == 0 |
| |
| if (isFieldEmpty && isEmptyAllowed) { |
| // Valid! |
| start.parentElement.makeNil() |
| return postEvalState // Empty, no need to advance |
| } else if (isFieldEmpty && !isEmptyAllowed) { |
| // Fail! |
| return PE(postEvalState, "%s - Empty field found but not allowed!", eName) |
| } else if (d.isFieldDfdlLiteral(field, nilValuesCooked.toSet)) { |
| // Contains a nilValue, Success! |
| start.parentElement.makeNil() |
| |
| val numBits = s.numBits //e.knownEncodingStringBitLength(result.field) |
| val endCharPos = |
| if (postEvalState.charPos == -1) s.field.length |
| else postEvalState.charPos + s.field.length |
| val endBitPos = numBits + start.bitPos |
| |
| log(LogLevel.Debug, "%s - Found %s", eName, s.field) |
| log(LogLevel.Debug, "%s - Ended at byte position %s", eName, (endBitPos >> 3)) |
| log(LogLevel.Debug, "%s - Ended at bit position ", eName, endBitPos) |
| |
| return postEvalState.withPos(endBitPos, endCharPos, Some(s.next)) // Need to advance past found nilValue |
| } else { |
| // Fail! |
| return PE(postEvalState, "%s - Does not contain a nil literal!", eName) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| override def unparser: Unparser = new Unparser(e) { |
| def unparse(start: UState): UState = { |
| Assert.notYetImplemented() |
| } |
| } |
| |
| } |
| |
| case class LiteralNilExplicit(e: ElementBase, nUnits: Long) |
| extends StaticText(e.nilValue, e, "LiteralNilExplicit", e.isNillable) |
| with Padded { |
| lazy val unparserDelim = Assert.notYetImplemented() |
| //val stParser = super.parser |
| |
| override def parser = new PrimParser(this, e) { |
| |
| override def toBriefXML(depthLimit: Int = -1): String = { |
| "<" + name + " nilValue='" + e.nilValue + "'/>" |
| } |
| |
| val pattern = e.lengthPattern |
| |
| val isEmptyAllowed = e.nilValue.contains("%ES;") |
| val eName = e.toString() |
| val nilValuesCooked = new ListOfStringValueAsLiteral(e.nilValue, e).cooked |
| val charsetName = charset.name() |
| |
| def parse(start: PState): PState = { |
| // withLoggingLevel(LogLevel.Info) |
| { |
| |
| val postEvalState = start //start.withVariables(vars) |
| |
| log(LogLevel.Debug, "%s - Looking for: %s Count: %s", eName, nilValuesCooked, nilValuesCooked.length) |
| |
| val bytePos = (postEvalState.bitPos >> 3).toInt |
| log(LogLevel.Debug, "%s - Starting at bit pos: %s", eName, postEvalState.bitPos) |
| log(LogLevel.Debug, "%s - Starting at byte pos: %s", eName, bytePos) |
| |
| if (postEvalState.bitPos % 8 != 0) { return PE(start, "LiteralNilPattern - not byte aligned.") } |
| |
| log(LogLevel.Debug, "Retrieving reader state.") |
| val reader = getReader(charset, start.bitPos, start) |
| |
| // val byteReader = in.byteReader.atPos(bytePos) |
| // val reader = byteReader.charReader(decoder.charset().name()) |
| |
| val d = new DelimParser(e.knownEncodingStringBitLengthFunction) |
| |
| val result = d.parseInputPatterned(pattern, reader) |
| |
| result match { |
| case _: DelimParseFailure => |
| return PE(postEvalState, "%s - %s - Parse failed.", this.toString(), eName) |
| case s: DelimParseSuccess => { |
| // We have a field, is it empty? |
| val field = d.removePadding(s.field, justificationTrim, padChar) |
| val isFieldEmpty = field.length() == 0 |
| |
| if (isFieldEmpty && isEmptyAllowed) { |
| // Valid! |
| start.parentElement.makeNil() |
| return postEvalState // Empty, no need to advance |
| } else if (isFieldEmpty && !isEmptyAllowed) { |
| // Fail! |
| return PE(postEvalState, "%s - Empty field found but not allowed!", eName) |
| } else if (d.isFieldDfdlLiteral(field, nilValuesCooked.toSet)) { |
| // Contains a nilValue, Success! |
| start.parentElement.makeNil() |
| |
| val numBits = s.numBits //e.knownEncodingStringBitLength(result.field) |
| //val endCharPos = start.charPos + result.field.length() |
| val endCharPos = |
| if (postEvalState.charPos == -1) s.field.length |
| else postEvalState.charPos + s.field.length |
| val endBitPos = numBits + start.bitPos |
| |
| log(LogLevel.Debug, "%s - Found %s", eName, s.field) |
| log(LogLevel.Debug, "%s - Ended at byte position %s", eName, (endBitPos >> 3)) |
| log(LogLevel.Debug, "%s - Ended at bit position ", eName, endBitPos) |
| |
| //return postEvalState.withPos(endBitPos, endCharPos) // Need to advance past found nilValue |
| return postEvalState.withPos(endBitPos, endCharPos, Some(s.next)) // Need to advance past found nilValue |
| } else { |
| // Fail! |
| return PE(postEvalState, "%s - Does not contain a nil literal!", eName) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| override def unparser: Unparser = new Unparser(e) { |
| def unparse(start: UState): UState = { |
| Assert.notYetImplemented() |
| } |
| } |
| } |
| |
| case class LiteralNilPattern(e: ElementBase) |
| extends StaticText(e.nilValue, e, "LiteralNilPattern", e.isNillable) |
| with Padded { |
| lazy val unparserDelim = Assert.notYetImplemented() |
| //val stParser = super.parser |
| |
| override def parser = new PrimParser(this, e) { |
| |
| override def toBriefXML(depthLimit: Int = -1): String = { |
| "<" + name + " nilValue='" + e.nilValue + "'/>" |
| } |
| |
| val pattern = e.lengthPattern |
| |
| val isEmptyAllowed = e.nilValue.contains("%ES;") |
| val eName = e.toString() |
| val nilValuesCooked = new ListOfStringValueAsLiteral(e.nilValue, e).cooked |
| val charsetName = charset.name() |
| |
| def parse(start: PState): PState = { |
| // withLoggingLevel(LogLevel.Info) |
| { |
| |
| val postEvalState = start //start.withVariables(vars) |
| |
| log(LogLevel.Debug, "%s - Looking for: %s Count: %s", eName, nilValuesCooked, nilValuesCooked.length) |
| |
| val bytePos = (postEvalState.bitPos >> 3).toInt |
| log(LogLevel.Debug, "%s - Starting at bit pos: %s", eName, postEvalState.bitPos) |
| log(LogLevel.Debug, "%s - Starting at byte pos: %s", eName, bytePos) |
| |
| if (postEvalState.bitPos % 8 != 0) { return PE(start, "LiteralNilPattern - not byte aligned.") } |
| |
| log(LogLevel.Debug, "Retrieving reader state.") |
| val reader = getReader(charset, start.bitPos, start) |
| |
| val d = new DelimParser(e.knownEncodingStringBitLengthFunction) |
| |
| val result = d.parseInputPatterned(pattern, reader) |
| |
| result match { |
| case _: DelimParseFailure => |
| return PE(postEvalState, "%s - %s - Parse failed.", this.toString(), eName) |
| case s: DelimParseSuccess => { |
| // We have a field, is it empty? |
| val field = d.removePadding(s.field, justificationTrim, padChar) |
| val isFieldEmpty = field.length() == 0 |
| |
| if (isFieldEmpty && isEmptyAllowed) { |
| // Valid! |
| start.parentElement.makeNil() |
| return postEvalState // Empty, no need to advance |
| } else if (isFieldEmpty && !isEmptyAllowed) { |
| // Fail! |
| return PE(postEvalState, "%s - Empty field found but not allowed!", eName) |
| } else if (d.isFieldDfdlLiteral(field, nilValuesCooked.toSet)) { |
| // Contains a nilValue, Success! |
| start.parentElement.makeNil() |
| |
| val numBits = s.numBits //e.knownEncodingStringBitLength(result.field) |
| |
| val endCharPos = |
| if (postEvalState.charPos == -1) s.field.length |
| else postEvalState.charPos + s.field.length |
| val endBitPos = numBits + start.bitPos |
| |
| log(LogLevel.Debug, "%s - Found %s", eName, s.field) |
| log(LogLevel.Debug, "%s - Ended at byte position %s", eName, (endBitPos >> 3)) |
| log(LogLevel.Debug, "%s - Ended at bit position ", eName, endBitPos) |
| |
| return postEvalState.withPos(endBitPos, endCharPos, Some(s.next)) // Need to advance past found nilValue |
| } else { |
| // Fail! |
| return PE(postEvalState, "%s - Does not contain a nil literal!", eName) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| override def unparser: Unparser = new Unparser(e) { |
| def unparse(start: UState): UState = { |
| Assert.notYetImplemented() |
| } |
| } |
| } |
| |
| abstract class LiteralNilDelimitedEndOfData(eb: ElementBase) |
| extends StringDelimited(eb) { |
| val nilValuesCooked = new ListOfStringValueAsLiteral(eb.nilValue, eb).cooked |
| val isEmptyAllowed = eb.nilValue.contains("%ES;") // TODO: move outside parser |
| |
| override def processResult(result: DelimParseResult, state: PState): PState = { |
| result match { |
| case f: DelimParseFailure => |
| return parser.PE(state, "%s - %s - Parse failed.", this.toString(), eName) |
| case s: DelimParseSuccess => { |
| // We have a field, is it empty? |
| //val field = dp.removePadding(s.field, justificationTrim, padChar) |
| val field = s.field |
| val isFieldEmpty = field.length() == 0 // Note: field has been stripped of padChars |
| |
| if (isFieldEmpty && !isEmptyAllowed) { |
| // Fail! |
| return parser.PE(state, "%s - Empty field found but not allowed!", eName) |
| } else if ((isFieldEmpty && isEmptyAllowed) || // Empty, but must advance past padChars if there were any. |
| dp.isFieldDfdlLiteral(field, nilValuesCooked.toSet)) { // Not empty, but matches. |
| // Contains a nilValue, Success! |
| state.parentElement.makeNil() |
| |
| val numBits = s.numBits |
| //val endCharPos = start.charPos + result.field.length() |
| val endCharPos = if (state.charPos == -1) s.numCharsRead else state.charPos + s.numCharsRead |
| val endBitPos = numBits + state.bitPos |
| |
| log(LogLevel.Debug, "%s - Found %s", eName, s.field) |
| log(LogLevel.Debug, "%s - Ended at byte position %s", eName, (endBitPos >> 3)) |
| log(LogLevel.Debug, "%s - Ended at bit position ", eName, endBitPos) |
| |
| //return postEvalState.withPos(endBitPos, endCharPos) // Need to advance past found nilValue |
| return state.withPos(endBitPos, endCharPos, Some(s.next)) // Need to advance past found nilValue |
| } else { |
| // Fail! |
| return parser.PE(state, "%s - Does not contain a nil literal!", eName) |
| } |
| } |
| } |
| } |
| } |
| |
| case class LiteralNilDelimitedEndOfDataStatic(eb: ElementBase) |
| extends LiteralNilDelimitedEndOfData(eb) with StaticDelim |
| case class LiteralNilDelimitedEndOfDataDynamic(eb: ElementBase) |
| extends LiteralNilDelimitedEndOfData(eb) with DynamicDelim |
| |
| case class LogicalNilValue(e: ElementBase) extends Primitive(e, e.isNillable) |
| |