I have input strings in nearly standard ISO 8601, but the minute-of-hour part is omitted (don't ask why, funky data feed out of my control).
2018-01-23T12
How do I parse that while letting the minute-of-hour default to zero?
2018-01-23T12:00
DateTimeFormatter.ISO_LOCAL_DATE_TIME
The default parser for LocalDateTime is DateTimeFormatter.ISO_LOCAL_DATE_TIME. This formatter tolerates optional second-of-minute. The fractional second and the second-of-minute are both set to zero.
LocalDateTime ldt = LocalDateTime.parse( "2018-01-23T12:00" ) ; // Works.
But omitting the minute-of-hour fails, with exception thrown.
LocalDateTime ldt = LocalDateTime.parse( "2018-01-23T12" ) ; // Fails.
DateTimeFormatterBuilder
I am aware of the DateTimeFormatterBuilder class and its ability to tolerate optional parts while setting default values.
But I have been able to use it properly. I tried the pattern "uuuu-MM-dd HH" while setting both minute-of-hour and second-of-minute to default to zero.
String input = "2018-01-23T12";
DateTimeFormatterBuilder b = new DateTimeFormatterBuilder().parseDefaulting( ChronoField.MINUTE_OF_HOUR , 0 ).parseDefaulting( ChronoField.SECOND_OF_MINUTE , 0 ).appendPattern( "uuuu-MM-dd HH" );
DateTimeFormatter f = b.toFormatter( Locale.US );
LocalDateTime ldt = LocalDateTime.parse( input , f );
System.out.println( ldt );
Exception thrown:
Exception in thread "main" java.time.format.DateTimeParseException: Text '2018-01-23T12' could not be parsed at index 10
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1988)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1890)
at java.base/java.time.LocalDateTime.parse(LocalDateTime.java:492)
at com.basilbourque.example.App.doIt(App.java:31)
at com.basilbourque.example.App.main(App.java:22)
If you look carefully at the error message, you will see that it says: Text '2018-01-23T12' could not be parsed at index 10
This means that the problem is the T in the format.
If we then go back to the DateTimeFormatterBuilder, the pattern specified is: "uuuu-MM-dd HH". Here is the problem, this specifies a space when there is in fact a T. The solution is to replace this pattern with: "uuuu-MM-dd'T'HH"
String input = "2018-01-23T12";
DateTimeFormatterBuilder b = new DateTimeFormatterBuilder().parseDefaulting( ChronoField.MINUTE_OF_HOUR , 0 ).parseDefaulting( ChronoField.SECOND_OF_MINUTE , 0 ).appendPattern( "uuuu-MM-dd'T'HH" );
DateTimeFormatter f = b.toFormatter( Locale.US );
LocalDateTime ldt = LocalDateTime.parse( input , f );
System.out.println( ldt );
2018-01-23T12:00
Indeed, you do not even need the builder at all. Specify a DateTimeFormatter with that same pattern. The minute-of-hour does default to zero.
String input = "2018-01-23T12";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH" ) ;
LocalDateTime ldt = LocalDateTime.parse( input , f );
2018-01-23T12:00
Related
On trying to assign values to Nested Object Properties,Dart treats the Nested Object(class OperandRange) as null.
Default values have been assigned to the Nested Object Properties but the issue exists.
In the case below Nested Object Class OperandRange should be assigned minimum and maximum values but dart considers it to the Null.
How to resolve this?
Code
import 'dart:io';
//Nested Object Class
class OperandRange{
double _minValue = 0;
double _maxValue = 10;
OperandRange(this._minValue , this._maxValue);
double get minValue => _minValue;
double get maxValue => _maxValue;
set minValue(double _val){
_minValue = (_val) ;
}
set maxValue(double _val){
_maxValue = (_val) ;
}
}
class OperationData{
List<OperandRange> operandList = [];//Nested Object
List<String> operatorList = [] ;
OperationData({this.operandList, this.operatorList});
}
void main(){
int _operationCount = 2;
OperationData _operation = OperationData();
for(int _index = 0 ; _index < _operationCount ; _index++) {
stdout.write(" Operation $_index - Name(string): ");
_operation.operatorList[_index] = stdin.readLineSync();
//Null Object
stdout.write(" Operand $_index - Minimum Value (double) : ");
_operation.operandList[_index]._minValue =
double.parse(stdin.readLineSync());
stdout.write(" Operand $_index - Maximum Value (double): ");
_operation.operandList[_index]._maxValue =
double.parse(stdin.readLineSync());
}
}
Error
Operation 0 - Name(string): Add
Unhandled exception:
NoSuchMethodError: The method '[]=' was called on null.
Receiver: null
Tried calling: []=(0, "Add")
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1 main (1.dart:41:28)
#2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:283:19)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
Process finished with exit code 255
Here is what's happening.
You initialize operandList with a nested list. But this never has any effect because you also initialize it in OperationData constructor. Once you mention it in constructor arguments, it will either be set to a value you pass to constructor, or set to null if you do not pass this argument to constructor.
For your purpose you may remove constructor altogether as you never pass anything to it. Then your [] defaults will stand.
Otherwise, if in some cases you need to initialize it with a custom list, you may do it like this:
class OperationData{
List<OperandRange> operandList;
List<String> operatorList;
OperationData({
List<OperandRange> operandList,
List<String>operatorList,
}) :
this.operandList = operandList ?? <OperandList>[],
this.operatorList = operatorList ?? <String>[]
;
}
The same goes for your OperandRange class. 0 and 10 defaults will never be used as the constructor requires explicit values. By the way, I do not see OperandRange creation at all. The list stays empty. You will catch a next error when trying to access an index out of bounds when you fix the first error.
Also you should upgrade to Dart 2.12 if possible. It introduced null-safety that would show you this error at compile time.
I having an issue when trying to update a SF stateful service after updating a child object of the service state. The upgrade fails to pass the 1st upgrade domain with a 'package activation' error. Digging around in the event viewer on the offending node gives the below exception:
Errormsg=TStore.OnApplyAddAsync: Unexpected exception System.NullReferenceException: Object reference not set to an instance of an object.
at xxx.DataChildObject.GetHashCode(IEqualityComparer comp)
at xxx.Data.GetHashCode(IEqualityComparer comp)
at xxx.Data.GetHashCode() in C:\Users\xxx\Source\Repos\xxx\xxx\xxx.Core\Domain.fs:line 17
at System.Fabric.Store.TStore`5.OnApplyAdd(TransactionBase txn, MetadataOperationData metadataOperationData, RedoUndoOperationData operationRedoUndo, Boolean isIdempotent, String applyType) Assert=System.Exception: at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
The change to the 'DataChildObject' was to add a new field that is a tuple of 2 doubles.
I understand that F# is automatically generating equality methods and these must be triggered during SF state and that due to datacontact serialisation these are null during the GetHashCode check.
I don't understand exactly when this check is being performed or why though?
As a test i tried overriding the GetHasCode method on my DataChildObject, but this didn't change the error i get when trying to upgrade my service.
[<DataContract>]
type DataChildObject =
class
[<DataMember(IsRequired=false,Name="Value1")>] val mutable Value1 : float * float
[<DataMember(IsRequired=false,Name="Value2")>] val mutable Value2 : float * float
[<DataMember(IsRequired=false,Name="NewValue")>] val mutable NewValue : float * float
new (v1,v2) = {Value1=v1;Value2=v2;NewValue=1.0,1.0}
override this.GetHashCode() =
let value1 = if (box this.Value1 = null) then 1 else this.Value1.GetHashCode()
let value2 = if (box this.Value2 = null) then 1 else this.Value2.GetHashCode()
let newValue = if (box this.NewValue = null) then 1 else (this.NewValue).GetHashCode()
value1+value2+newValue
end
I expect these domain objects to change more in the near future so any help in understanding exact how to get past or avoid this issue is helpful.
Thanks
I think I'm very close to getting this to print. However it still isn't. There is no exception thrown and it does seem to be hitting the zebra printer, but nothing. Its a long shot as I think most people are in the same position I am and know little about it. Any help anyone can give no matter how small will be welcomed, I'm losing the will to live
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
using (var stream = new MemoryStream())
{
if (responseStream == null)
{
return;
}
responseStream.CopyTo(stream);
stream.Position = 0;
using (var zipout = ZipFile.Read(stream))
{
using (var ms = new MemoryStream())
{
foreach (var e in zipout.Where(e => e.FileName.Contains(".png")))
{
e.Extract(ms);
}
if (ms.Length <= 0)
{
return;
}
var binaryData = ms.ToArray();
byte[] compressedFileData;
// Compress the data using the LZ77 algorithm.
using (var outStream = new MemoryStream())
{
using (var compress = new DeflateStream(outStream, CompressionMode.Compress, true))
{
compress.Write(binaryData, 0, binaryData.Length);
compress.Flush();
compress.Close();
}
compressedFileData = outStream.ToArray();
}
// Encode the compressed data using the MIME Base64 algorithm.
var base64 = Convert.ToBase64String(compressedFileData);
// Calculate a CRC across the encoded data.
var crc = Calc(Convert.FromBase64String(base64));
// Add a unique header to differentiate the new format from the existing ASCII hexadecimal encoding.
var finalData = string.Format(":Z64:{0}:{1}", base64, crc);
var zplToSend = "~DYR:LOGO,P,P," + finalData.Length + ",," + finalData;
const string PrintImage = "^XA^FO0,0^IMR:LOGO.PNG^FS^XZ";
try
{
var client = new System.Net.Sockets.TcpClient();
client.Connect(IpAddress, Port);
var writer = new StreamWriter(client.GetStream(), Encoding.UTF8);
writer.Write(zplToSend);
writer.Flush();
writer.Write(PrintImage);
writer.Close();
client.Close();
}
catch (Exception ex)
{
// Catch Exception
}
}
}
}
}
}
private static ushort Calc(byte[] data)
{
ushort wCrc = 0;
for (var i = 0; i < data.Length; i++)
{
wCrc ^= (ushort)(data[i] << 8);
for (var j = 0; j < 8; j++)
{
if ((wCrc & 0x8000) != 0)
{
wCrc = (ushort)((wCrc << 1) ^ 0x1021);
}
else
{
wCrc <<= 1;
}
}
}
return wCrc;
}
The following code is working for me. The issue was the commands, these are very very important! Overview of the command I have used below, more can be found here
PrintImage
^XA
Start Format Description The ^XA command is used at the beginning of ZPL II code. It is the opening bracket and indicates the start of a new label format. This command is substituted with a single ASCII control character STX (control-B, hexadecimal 02). Format ^XA Comments Valid ZPL II format requires that label formats should start with the ^XA command and end with the ^XZ command.
^FO
Field Origin Description The ^FO command sets a field origin, relative to the label home (^LH) position. ^FO sets the upper-left corner of the field area by defining points along the x-axis and y-axis independent of the rotation. Format ^FOx,y,z
x = x-axis location (in dots) Accepted Values: 0 to 32000 Default
Value: 0
y = y-axis location (in dots) Accepted Values: 0 to 32000
Default Value: 0
z = justification The z parameter is only
supported in firmware versions V60.14.x, V50.14.x, or later. Accepted
Values: 0 = left justification 1 = right justification 2 = auto
justification (script dependent) Default Value: last accepted ^FW
value or ^FW default
^IM
Image Move Description The ^IM command performs a direct move of an image from storage area into the bitmap. The command is identical to the ^XG command (Recall Graphic), except there are no sizing parameters. Format ^IMd:o.x
d = location of stored object Accepted Values: R:, E:, B:, and A: Default Value: search priority
o = object name Accepted Values: 1 to 8 alphanumeric characters Default Value: if a name is not specified, UNKNOWN is used
x = extension Fixed Value: .GRF, .PNG
^FS
Field Separator Description The ^FS command denotes the end of the field definition. Alternatively, ^FS command can also be issued as a single ASCII control code SI (Control-O, hexadecimal 0F). Format ^FS
^XZ
End Format Description The ^XZ command is the ending (closing) bracket. It indicates the end of a label format. When this command is received, a label prints. This command can also be issued as a single ASCII control character ETX (Control-C, hexadecimal 03). Format ^XZ Comments Label formats must start with the ^XA command and end with the ^XZ command to be in valid ZPL II format.
zplToSend
^MN
Media Tracking Description This command specifies the media type being used and the black mark offset in dots. This bulleted list shows the types of media associated with this command:
Continuous Media – this media has no physical characteristic (such as a web, notch, perforation, black mark) to separate labels. Label length is determined by the ^LL command.
Continuous Media, variable length – same as Continuous Media, but if portions of the printed label fall outside of the defined label length, the label size will automatically be extended to contain them. This label length extension applies only to the current label. Note that ^MNV still requires the use of the ^LL command to define the initial desired label length.
Non-continuous Media – this media has some type of physical characteristic (such as web, notch, perforation, black mark) to separate the labels.
Format ^MNa,b
a = media being used Accepted Values: N = continuous media Y = non-continuous media web sensing d, e W = non-continuous media web sensing d, e M = non-continuous media mark sensing A = auto-detects the type of media during calibration d, f V = continuous media, variable length g Default Value: a value must be entered or the command is ignored
b = black mark offset in dots This sets the expected location of the media mark relative to the point of separation between documents. If set to 0, the media mark is expected to be found at the point of separation. (i.e., the perforation, cut point, etc.) All values are listed in dots. This parameter is ignored unless the a parameter is set to M. If this parameter is missing, the default value is used. Accepted Values: -80 to 283 for direct-thermal only printers -240 to 566 for 600 dpi printers -75 to 283 for KR403 printers -120 to 283 for all other printers Default Value: 0
~DY
Download Objects Description The ~DY command downloads to the printer graphic objects or fonts in any supported format. This command can be used in place of ~DG for more saving and loading options. ~DY is the preferred command to download TrueType fonts on printers with firmware later than X.13. It is faster than ~DU. The ~DY command also supports downloading wireless certificate files. Format ~DYd:f,b,x,t,w,data
Note
When using certificate files, your printer supports:
- Using Privacy Enhanced Mail (PEM) formatted certificate files.
- Using the client certificate and private key as two files, each downloaded separately.
- Using exportable PAC files for EAP-FAST.
- Zebra recommends using Linear sty
d = file location .NRD and .PAC files reside on E: in firmware versions V60.15.x, V50.15.x, or later. Accepted Values: R:, E:, B:, and A: Default Value: R:
f = file name Accepted Values: 1 to 8 alphanumeric characters Default Value: if a name is not specified, UNKNOWN is used
b = format downloaded in data field .TTE and .TTF are only supported in firmware versions V60.14.x, V50.14.x, or later. Accepted Values: A = uncompressed (ZB64, ASCII) B = uncompressed (.TTE, .TTF, binary) C = AR-compressed (used only by Zebra’s BAR-ONE® v5) P = portable network graphic (.PNG) - ZB64 encoded Default Value: a value must be specified
clearDownLabel
^ID
Description The ^ID command deletes objects, graphics, fonts, and stored formats from storage areas. Objects can be deleted selectively or in groups. This command can be used within a printing format to delete objects before saving new ones, or in a stand-alone format to delete objects.
The image name and extension support the use of the asterisk (*) as a wild card. This allows you to easily delete a selected groups of objects. Format ^IDd:o.x
d = location of stored object Accepted Values: R:, E:, B:, and A: Default Value: R:
o = object name Accepted Values: any 1 to 8 character name Default Value: if a name is not specified, UNKNOWN is used
x = extension Accepted Values: any extension conforming to Zebra conventions
Default Value: .GRF
const string PrintImage = "^XA^FO0,0,0^IME:LOGO.PNG^FS^XZ";
var zplImageData = string.Empty;
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
using (var stream = new MemoryStream())
{
if (responseStream == null)
{
return;
}
responseStream.CopyTo(stream);
stream.Position = 0;
using (var zipout = ZipFile.Read(stream))
{
using (var ms = new MemoryStream())
{
foreach (var e in zipout.Where(e => e.FileName.Contains(".png")))
{
e.Extract(ms);
}
if (ms.Length <= 0)
{
return;
}
var binaryData = ms.ToArray();
foreach (var b in binaryData)
{
var hexRep = string.Format("{0:X}", b);
if (hexRep.Length == 1)
{
hexRep = "0" + hexRep;
}
zplImageData += hexRep;
}
var zplToSend = "^XA" + "^FO0,0,0" + "^MNN" + "~DYE:LOGO,P,P," + binaryData.Length + ",," + zplImageData + "^XZ";
var label = GenerateStreamFromString(zplToSend);
var client = new System.Net.Sockets.TcpClient();
client.Connect(IpAddress, Port);
label.CopyTo(client.GetStream());
label.Flush();
client.Close();
var cmd = GenerateStreamFromString(PrintImage);
var client2 = new System.Net.Sockets.TcpClient();
client2.Connect(IpAddress, Port);
cmd.CopyTo(client2.GetStream());
cmd.Flush();
client2.Close();var clearDownLabel = GenerateStreamFromString("^XA^IDR:LOGO.PNG^FS^XZ");
var client3 = new System.Net.Sockets.TcpClient();
client3.Connect(IpAddress, Port);
clearDownLabel.CopyTo(client3.GetStream());
clearDownLabel.Flush();
client3.Close();
}
}
}
}
}
}
Easy once you know how.
Zebra ZPL logo example in base64
Python3
import crcmod
import base64
crc16 = crcmod.predefined.mkCrcFun('xmodem')
s = hex(crc16(ZPL_LOGO.encode()))[2:]
print (f"crc16: {s}")
Poorly documented may I say the least
I would like to "beautify" the output of one of my Dart scripts, like so:
-----------------------------------------
OpenPGP signing notes from key `CD42FF00`
-----------------------------------------
<Paragraph>
And I wonder if there is a particularly simple and/or optimized way of printing the same character x times in Dart. In Python, print "-" * x would print the "-" character x times.
Learning from this answer, for the purpose of this question, I wrote the following minimal code, which makes use of the core Iterable class:
main() {
// Obtained with '-'.codeUnitAt(0)
const int FILLER_CHAR = 45;
String headerTxt;
Iterable headerBox;
headerTxt = 'OpenPGP signing notes from key `CD42FF00`';
headerBox = new Iterable.generate(headerTxt.length, (e) => FILLER_CHAR);
print(new String.fromCharCodes(headerBox));
print(headerTxt);
print(new String.fromCharCodes(headerBox));
// ...
}
This gives the expected output, but is there a better way in Dart to print a character (or string) x times? In my example, I want to print the "-" character headerTxt.length times.
The original answer is from 2014, so there must have been some updates to the Dart language: a simple string multiplied by an int works.
main() {
String title = 'Dart: Strings can be "multiplied"';
String line = '-' * title.length
print(line);
print(title);
print(line);
}
And this will be printed as:
---------------------------------
Dart: Strings can be "multiplied"
---------------------------------
See Dart String's multiply * operator docs:
Creates a new string by concatenating this string with itself a number of times.
The result of str * n is equivalent to str + str + ...(n times)... + str.
Returns an empty string if times is zero or negative.
I use this way.
void main() {
print(new List.filled(40, "-").join());
}
So, your case.
main() {
const String FILLER = "-";
String headerTxt;
String headerBox;
headerTxt = 'OpenPGP signing notes from key `CD42FF00`';
headerBox = new List.filled(headerTxt.length, FILLER).join();
print(headerBox);
print(headerTxt);
print(headerBox);
// ...
}
Output:
-----------------------------------------
OpenPGP signing notes from key `CD42FF00`
-----------------------------------------
I plan to include text metadata (like bold, font-size, etc.) in the process of parsing to achieve better recognition.
For instance, I have a given structure, where a word on its own line word/r/n which is bold and sized 24px, is the title for some article. In order to get better recognition results, I want to take the characters as well as the metadata in account. In terms of ANTRL I'm not sure how this could be done best. I'd like to do something like:
Wrap each character of the original text into a custom object with fields for the metadata and pass that to ANTLR.
Preprocess the text and insert at specific places annotations for the metadata which is considered by the grammer.
I really like to take option 1. but I'm not sure which part from ANTLR I need to subclass etc. Do I have to start at the ANTLRInputStream-Object, in order to get a proper stream for a subclassed Lexer to get custom Tokens for a subclassed Parser etc. Is there a more elegant way, especially in querying the tokens while parsing with actions in a {} block ?
If anyone has some hints and/or experiences this would be great!
EDIT:
Here is a more specific simple example: I have a file wich includes the encoding of metadata which I parse forehand. the actual text including newline look like the following:
entryOne
Here is some content one.
entryTwo
Here is some content two.
Where the titlesentryOneand entryTwo are originally font-size of 24px and the content is font-size of 12px (as exemplary given values). Char by char I create a new instance of a custom object encapsulating the character as String and the font-size.
I initialize respective objects for each of the characters with fields of the font-size, e.g for the first letter of entryOne like
MyChar aTitelChar = new MyChar("e", 24);
For the content, like the second line Here is some content one. I create instances of MyChar like:
MyChar aContentChar= new MyChar("H", 12);
All characters of the texts are wrapped in instances of the below MyChar-Class and added to a List<MyChar> in order to produce a new input for ANTLR.
below is the Java Class for the characters:
public class MyChar {
private int fontSizePx;
private String text;
public MyChar(String text, int fontSizePx) {
this.text = text;
this.fontSizePx = fontSizePx;
}
public int getFontSizePx() {
return fontSizePx;
}
public String getText() {
return text;
}
}
I want that my grammar matches the above two entries (or more formatted this way) which in turn consist each of a title and a content which is terminated with a fullstop. This grammar could look like this:
rule: entry+ NEWLINE
;
entry:
title
content
;
title:
letters NEWLINE
;
content:
(letters)+ '.' NEWLINE
;
letters:
LETTERS
;
LETTERS:
('a'..'z' | 'A'..'Z')+
;
WS:
(' ' | '\t' | 'f' ) + {$channel = HIDDEN;};
NEWLINE:'\r'? '\n';
Now, for instance, what I want to do is to find out if it's really a title of an entry by checking the font-size of all letters encompassing the title-token before titel-rule returns. In case the input conforms to the grammar but is actually some kind of mistake (the original metadata-encoded file starts with something that conforms to the title-rule but its actually the content) the author of the grammar could sort that out if he knows that the original font-size for titles is 24 and check this. If one of the letter-tokens doesn't equal to font-size 24 throw an exception/don't return/do smthg. appropriate.
The thing I'm pondering on is where to plug in the List<MyChar> to provide this functionality (to query kinds of metadata while parsing in context of ANTLR). I'm experimenting with ANTLR's Classes but as I'm new to ANTLR I thought probably some of the experienced users can point me in the right direction, like where would be a good insertion points for custom objects? should I start by implenting CharStream and override some methods? Probably there is something which ANTLR provides which I haven't found yet?
Here's one way to accomplish what I think you're going for, using the parser to manage matching input to metadata. Note that I made whitespace significant because it's part of the content and can't be skipped. I also made periods part of content to simplify the example, rather than using them as a marker.
SysEx.g
grammar SysEx;
#header {
import java.util.List;
}
#parser::members {
private List<MyChar> metadata;
private int curpos;
private boolean isTitleInput(String input) {
return isFontSizeInput(input, 24);
}
private boolean isContentInput(String input){
return isFontSizeInput(input, 12);
}
private boolean isFontSizeInput(String input, int fontSize){
List<MyChar> sublist = metadata.subList(curpos, curpos + input.length());
System.out.println(String.format("Testing metadata for input=\%s, font-size=\%d", input, fontSize));
int start = curpos;
//move our metadata pointer forward.
skipInput(input);
for (int i = 0, count = input.length(); i < count; ++i){
MyChar chardata = sublist.get(i);
char c = input.charAt(i);
if (chardata.getText().charAt(0) != c){
//This character doesn't match the metadata (ERROR!)
System.out.println(String.format("Content mismatch at metadata position \%d: metadata=(\%s,\%d); input=\%c", start + i, chardata.getText(), chardata.getFontSizePx(), c));
return false;
} else if (chardata.getFontSizePx() != fontSize){
//The font is wrong.
System.out.println(String.format("Format mismatch at metadata position \%d: metadata=(\%s,\%d); input=\%c", start + i, chardata.getText(), chardata.getFontSizePx(), c));
return false;
}
}
//All characters check out.
return true;
}
private void skipInput(String str){
curpos += str.length();
System.out.println("\t\tMoving metadata pointer ahead by " + str.length() + " to " + curpos);
}
}
rule[List<MyChar> metadata]
#init {
this.metadata = metadata;
}
: entry+ EOF
;
entry
: title content
{System.out.println("Finished reading entry.");}
;
title
: line {isTitleInput($line.text)}? newline {System.out.println("Finished reading title " + $line.text);}
;
content
: line {isContentInput($line.text)}? newline {System.out.println("Finished reading content " + $line.text);}
;
newline
: (NEWLINE{skipInput($NEWLINE.text);})+
;
line returns [String text]
#init {
StringBuilder builder = new StringBuilder();
}
#after {
$text = builder.toString();
}
: (ANY{builder.append($ANY.text);})+
;
NEWLINE:'\r'? '\n';
ANY: .; //whitespace can't be skipped because it's content.
A title is a line that matches the title metadata (size 24 font) followed by one or more newline characters.
A content is a line that matches the content metadata (size 12 font) followed by one or more newline characters. As mentioned above, I removed the check for a period for simplification.
A line is a sequence of characters that does not include newline characters.
A validating semantic predicate (the {...}? after line) is used to validate that the line matches the metadata.
Here is the code I used to test the grammar (minus imports, for brevity):
SysExGrammar.java
public class SysExGrammar {
public static void main(String[] args) throws Exception {
//Create some metadata that matches our input.
List<MyChar> matchingMetadata = new ArrayList<MyChar>();
appendMetadata(matchingMetadata, "entryOne\r\n", 24);
appendMetadata(matchingMetadata, "Here is some content one.\r\n", 12);
appendMetadata(matchingMetadata, "entryTwo\r\n", 24);
appendMetadata(matchingMetadata, "Here is some content two.\r\n", 12);
parseInput(matchingMetadata);
System.out.println("Finished example #1");
//Create some metadata that doesn't match our input (negative test).
List<MyChar> mismatchingMetadata = new ArrayList<MyChar>();
appendMetadata(mismatchingMetadata, "entryOne\r\n", 24);
appendMetadata(mismatchingMetadata, "Here is some content one.\r\n", 12);
appendMetadata(mismatchingMetadata, "entryTwo\r\n", 12); //content font size!
appendMetadata(mismatchingMetadata, "Here is some content two.\r\n", 12);
parseInput(mismatchingMetadata);
System.out.println("Finished example #2");
}
private static void parseInput(List<MyChar> metadata) throws Exception {
//Test setup
InputStream resource = SysExGrammar.class.getResourceAsStream("SysExTest.txt");
CharStream input = new ANTLRInputStream(resource);
resource.close();
SysExLexer lexer = new SysExLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
SysExParser parser = new SysExParser(tokens);
parser.rule(metadata);
System.out.println("Parsing encountered " + parser.getNumberOfSyntaxErrors() + " syntax errors");
}
private static void appendMetadata(List<MyChar> metadata, String string,
int fontSize) {
for (int i = 0, count = string.length(); i < count; ++i){
metadata.add(new MyChar(string.charAt(i) + "", fontSize));
}
}
}
SysExTest.txt (note this uses Windows newlines (\r\n)
entryOne
Here is some content one.
entryTwo
Here is some content two.
Test output (trimmed; the second example has deliberately-mismatched metadata):
Parsing encountered 0 syntax errors
Finished example #1
Parsing encountered 2 syntax errors
Finished example #2
This solution requires that each MyChar corresponds to a character in the input (including newline characters, although you can remove that limitation if you like -- I would remove it if I didn't already have this answer written up ;) ).
As you can see, it's possible to tie the metadata to the parser and everything works as expected. I hope this helps.