1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-05-31 22:41:37 +00:00

Fix HTML output

We weren't escaping '<', '>', and '&', which caused browsers to get
very confused.  Browsers seem to prefer <PRE> to <CODE> for long
blocks of text, so switch to that.

Also, added support for putting long labels on their own lines in
the HTML output.

Also, fixed some unescaped angle brackets in the manual.

Also, tweaked the edit instruction operand a bit more.
This commit is contained in:
Andy McFadden 2019-09-17 19:01:14 -07:00
parent 1ddf4bed48
commit d01c61fbc1
5 changed files with 168 additions and 69 deletions

View File

@ -199,6 +199,27 @@ namespace CommonUtil {
}
}
/// <summary>
/// Escapes a string for HTML.
/// </summary>
/// <param name="str">String to process.</param>
/// <returns>Escaped string.</returns>
public static string EscapeHTML(string str) {
StringBuilder sb = new StringBuilder(str.Length);
for (int i = 0; i < str.Length; i++) {
if (str[i] == '<') {
sb.Append("&lt;");
} else if (str[i] == '>') {
sb.Append("&gt;");
} else if (str[i] == '&') {
sb.Append("&amp;");
} else {
sb.Append(str[i]);
}
}
return sb.ToString();
}
/// <summary>
/// Serializes an integer array into a string.
/// </summary>

View File

@ -96,6 +96,11 @@ namespace SourceGen {
COUNT // number of elements, must be last
}
/// <summary>
/// If set, labels that are wider than the label column should go on their own line.
/// </summary>
private bool mLongLabelNewLine;
/// <summary>
/// Constructor.
@ -107,6 +112,10 @@ namespace SourceGen {
mFormatter = formatter;
mLeftFlags = leftFlags;
// Go ahead and latch this here.
mLongLabelNewLine =
AppSettings.Global.GetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, false);
ConfigureColumns(leftFlags, rightWidths);
}
@ -399,7 +408,8 @@ namespace SourceGen {
// With the style "code { white-space: pre; }", leading spaces and EOL markers
// are preserved.
sw.Write("<code style=\"white-space: pre;\">");
//sw.Write("<code style=\"white-space: pre;\">");
sw.Write("<pre>");
StringBuilder sb = new StringBuilder(128);
for (int lineIndex = 0; lineIndex < mCodeLineList.Count; lineIndex++) {
if (Selection != null && !Selection[lineIndex]) {
@ -407,12 +417,12 @@ namespace SourceGen {
}
if (GenerateHtmlLine(lineIndex, sb)) {
sw.WriteLine(sb.ToString());
//sw.WriteLine("<br/>");
sw.Write(sb.ToString());
}
sb.Clear();
}
sw.WriteLine("</code>\r\n");
//sw.WriteLine("</code>\r\n");
sw.WriteLine("</pre>\r\n");
sw.Write(template2);
}
@ -433,8 +443,11 @@ namespace SourceGen {
}
}
}
/// <summary>
/// Generates a line of HTML output. The line will not have EOL markers added.
/// Generates HTML output for one display line. This may result in more than one line
/// of HTML output, e.g. if the label is longer than the field. EOL markers will
/// be added.
/// </summary>
/// <remarks>
/// Currently just generating a line of pre-formatted text. We could also output
@ -452,7 +465,43 @@ namespace SourceGen {
LineListGen.Line line = mCodeLineList[index];
DisplayList.FormattedParts parts = mCodeLineList.GetFormattedParts(index);
// TODO: linkify label and operand fields
int maxLabelLen = mColEnd[(int)Col.Label] - mColEnd[(int)Col.Attr] - 1;
string anchorLabel = null;
if ((line.LineType == LineListGen.Line.Type.Code ||
line.LineType == LineListGen.Line.Type.Data ||
line.LineType == LineListGen.Line.Type.EquDirective) &&
!string.IsNullOrEmpty(parts.Label)) {
anchorLabel = "<a name=\"" + LABEL_LINK_PREFIX + parts.Label +
"\">" + parts.Label + "</a>";
}
string linkOperand = null;
if ((line.LineType == LineListGen.Line.Type.Code ||
line.LineType == LineListGen.Line.Type.Data) &&
parts.Operand.Length > 0) {
linkOperand = GetLinkOperand(index, parts.Operand);
}
int colPos = 0;
bool suppressLabel = false;
if (mLongLabelNewLine && (line.LineType == LineListGen.Line.Type.Code ||
line.LineType == LineListGen.Line.Type.Data)) {
int labelLen = string.IsNullOrEmpty(parts.Label) ? 0 : parts.Label.Length;
if (labelLen > maxLabelLen) {
// put on its own line
AddSpacedString(sb, colPos, mColEnd[(int)Col.Attr], string.Empty, 0);
if (anchorLabel != null) {
sb.Append(anchorLabel);
} else {
sb.Append(parts.Label);
}
sb.Append("\r\n");
suppressLabel = true;
Debug.Assert(colPos == 0);
}
}
switch (line.LineType) {
case LineListGen.Line.Type.Code:
@ -462,17 +511,19 @@ namespace SourceGen {
case LineListGen.Line.Type.OrgDirective:
case LineListGen.Line.Type.LocalVariableTable:
if (parts.IsLongComment) {
// This happens for long comments embedded in LV tables.
if (mColEnd[(int)Col.Attr] != 0) {
TextUtil.AppendPaddedString(sb, string.Empty, mColEnd[(int)Col.Attr]);
}
sb.Append(parts.Comment);
// This happens for long comments embedded in LV tables, e.g.
// "clear table".
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Attr],
string.Empty, 0);
sb.Append(TextUtil.EscapeHTML(parts.Comment));
break;
}
// these columns are optional
if ((mLeftFlags & ActiveColumnFlags.Offset) != 0) {
TextUtil.AppendPaddedString(sb, parts.Offset,
mColEnd[(int)Col.Offset]);
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Offset],
parts.Offset, parts.Offset.Length);
}
if ((mLeftFlags & ActiveColumnFlags.Address) != 0) {
string str;
@ -481,71 +532,72 @@ namespace SourceGen {
} else {
str = string.Empty;
}
TextUtil.AppendPaddedString(sb, str, mColEnd[(int)Col.Address]);
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Address],
str, str.Length);
}
if ((mLeftFlags & ActiveColumnFlags.Bytes) != 0) {
// Shorten the "...".
string bytesStr = parts.Bytes;
if (bytesStr != null && bytesStr.Length > bytesWidth) {
if (bytesStr == null) {
bytesStr = string.Empty;
}
if (bytesStr.Length > bytesWidth) {
bytesStr = bytesStr.Substring(0, bytesWidth) + "+";
}
TextUtil.AppendPaddedString(sb, bytesStr, mColEnd[(int)Col.Bytes]);
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Bytes],
bytesStr, bytesStr.Length);
}
if ((mLeftFlags & ActiveColumnFlags.Flags) != 0) {
TextUtil.AppendPaddedString(sb, parts.Flags, mColEnd[(int)Col.Flags]);
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Flags],
parts.Flags, parts.Flags.Length);
}
if ((mLeftFlags & ActiveColumnFlags.Attr) != 0) {
TextUtil.AppendPaddedString(sb, parts.Attr, mColEnd[(int)Col.Attr]);
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Attr],
TextUtil.EscapeHTML(parts.Attr), parts.Attr.Length);
}
int labelOffset = sb.Length;
TextUtil.AppendPaddedString(sb, parts.Label, mColEnd[(int)Col.Label]);
TextUtil.AppendPaddedString(sb, parts.Opcode, mColEnd[(int)Col.Opcode]);
int operandOffset = sb.Length;
TextUtil.AppendPaddedString(sb, parts.Operand, mColEnd[(int)Col.Operand]);
// remaining columns are mandatory, but may be empty
if (suppressLabel) {
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Label],
string.Empty, 0);
} else if (anchorLabel != null) {
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Label],
anchorLabel, parts.Label.Length);
} else if (parts.Label != null) {
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Label],
parts.Label, parts.Label.Length);
}
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Opcode],
parts.Opcode, parts.Opcode.Length);
if (linkOperand != null) {
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Operand],
linkOperand, parts.Operand.Length);
} else {
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Operand],
TextUtil.EscapeHTML(parts.Operand), parts.Operand.Length);
}
if (string.IsNullOrEmpty(parts.Comment)) {
// Trim trailing spaces off opcode or operand. Would be more efficient
// to just not generate the spaces, but this is simpler and we're not
// in a hurry.
TextUtil.TrimEnd(sb);
} else {
sb.Append(parts.Comment);
sb.Append(TextUtil.EscapeHTML(parts.Comment));
}
// Replace label with anchor label. We do it this late because we need the
// spacing to be properly set, and I don't feel like changing how all the
// AppendPaddedString code works.
if ((line.LineType == LineListGen.Line.Type.Code ||
line.LineType == LineListGen.Line.Type.Data ||
line.LineType == LineListGen.Line.Type.EquDirective) &&
!string.IsNullOrEmpty(parts.Label)) {
string linkLabel = "<a name=\"" + LABEL_LINK_PREFIX + parts.Label +
"\">" + parts.Label + "</a>";
sb.Remove(labelOffset, parts.Label.Length);
sb.Insert(labelOffset, linkLabel);
// Adjust operand position.
operandOffset += linkLabel.Length - parts.Label.Length;
}
if ((line.LineType == LineListGen.Line.Type.Code ||
line.LineType == LineListGen.Line.Type.Data) &&
parts.Operand.Length > 0) {
string linkOperand = GetLinkOperand(index, parts.Operand);
if (!string.IsNullOrEmpty(linkOperand)) {
sb.Remove(operandOffset, parts.Operand.Length);
sb.Insert(operandOffset, linkOperand);
}
}
break;
case LineListGen.Line.Type.LongComment:
case LineListGen.Line.Type.Note:
if (line.LineType == LineListGen.Line.Type.Note && !IncludeNotes) {
return false;
}
if (mColEnd[(int)Col.Attr] != 0) {
// Long comments aren't the left-most field, so pad it out.
TextUtil.AppendPaddedString(sb, string.Empty, mColEnd[(int)Col.Attr]);
}
// Long comments aren't the left-most field, so pad it out.
colPos = AddSpacedString(sb, colPos, mColEnd[(int)Col.Attr],
string.Empty, 0);
// Notes have a background color. Use this to highlight the text. We
// don't apply it to the padding on the left columns.
@ -558,10 +610,10 @@ namespace SourceGen {
}
if (rgb != 0) {
sb.AppendFormat("<span style=\"background-color: #{0:x6}\">", rgb);
sb.Append(parts.Comment);
sb.Append(TextUtil.EscapeHTML(parts.Comment));
sb.Append("</span>");
} else {
sb.Append(parts.Comment);
sb.Append(TextUtil.EscapeHTML(parts.Comment));
}
break;
case LineListGen.Line.Type.Blank:
@ -570,9 +622,33 @@ namespace SourceGen {
Debug.Assert(false);
break;
}
sb.Append("\r\n");
return true;
}
private int AddSpacedString(StringBuilder sb, int startPosn, int colEnd, string str,
int virtualLength) {
int newLen = startPosn + virtualLength;
if (startPosn + virtualLength >= colEnd) {
// Off end of column. Output string plus one space, unless the length is zero
// (because that means we're just padding to the start of a column).
sb.Append(str);
if (virtualLength != 0) {
sb.Append(' ');
}
return newLen + 1;
} else {
// Short of column end. Add spaces until we reach it.
sb.Append(str);
while (newLen < colEnd) {
sb.Append(' ');
newLen++;
}
}
return newLen;
}
/// <summary>
/// Wraps the symbolic part of the operand with HTML link notation. If the operand
/// doesn't have a linkable symbol, this return null.
@ -613,7 +689,7 @@ namespace SourceGen {
string linkified = "<a href=#" + LABEL_LINK_PREFIX + sym.Label + ">" +
sym.Label + "</a>";
return operand.Replace(sym.Label, linkified);
return TextUtil.EscapeHTML(operand).Replace(sym.Label, linkified);
}
/// <summary>

View File

@ -118,8 +118,8 @@ so any failure is an opportunity for improvement.</p>
SourceGen works around when generating code.</p>
<p>Every assembler seems to have a different way of dealing with expressions.
Most of them will let you group expressions with parenthesis, but that
doesn't always help. For example, <code>PEA label >> 8 + 1</code> is
perfectly valid, but writing <code>PEA (label >> 8) + 1</code> will cause
doesn't always help. For example, <code>PEA label &gt;&gt; 8 + 1</code> is
perfectly valid, but writing <code>PEA (label &gt;&gt; 8) + 1</code> will cause
most assemblers to assume you're trying to use an alternate (and non-existent)
form of <code>PEA</code> with indirect addressing, causing the assembler
to halt with an error message. The code generator needs
@ -236,11 +236,11 @@ code, but also needs to know how to handle the corner cases.</p>
<p>Quirks:</p>
<ul>
<li>Operator precedence is unusual. Consider <code>label >> 8 - 16</code>.
<li>Operator precedence is unusual. Consider <code>label &gt;&gt; 8 - 16</code>.
cc65 puts shift higher than subtraction, whereas languages like C
and assemblers like 64tass do it the other way around. So cc65
regards the expression as <code>(label >> 8) - 16</code>, while the
more common interpretation would be <code>label >> (8 - 16)</code>.
regards the expression as <code>(label &gt;&gt; 8) - 16</code>, while the
more common interpretation would be <code>label &gt;&gt; (8 - 16)</code>.
(This is actually somewhat convenient, since none of the expressions
SourceGen currently generates require parenthesis.)</li>
<li>Undocumented opcode <code>SBX</code> ($cb) uses the mnemonic AXS. All
@ -308,14 +308,15 @@ code, but also needs to know how to handle the corner cases.</p>
<p>The "export" function takes what you see in the code list in the app
and converts it to text or HTML. The options you've set in the app
settings, such as capitalization, text delimiters, pseudo-opcode names,
and operand expression style, are all taken into account. The file
generated is not expected to work with an actual assembler.</p>
operand expression style, and display of cycle counts are all taken into
account. The file generated is not expected to work with an actual
assembler.</p>
<p>The text output is similar to what you'd get by copying lines to the
clipboard and pasting them into a text file, except that you have greater
control over which columns are included. The HTML version is augmented
with links.</p>
<p>Use File > Export to open the export dialog. You have several options:</p>
<p>Use File &gt; Export to open the export dialog. You have several options:</p>
<ul>
<li><b>Include only selected lines</b>. This allows you to choose between
exporting all or part of a file. If no lines are selected, the entire
@ -330,9 +331,7 @@ with links.</p>
<li><b>Column widths</b>. These determine the minimum widths of the
rightmost four columns. These are not hard limits: if the contents
of the column are too wide, the next column will start farther over.
The width specified does not include the space character that is
automatically added between fields. The widths are not used at
all for CSV output.</li>
The widths are not used at all for CSV output.</li>
<li><b>Text vs. CSV</b>. For text generation, you can choose between
plain text and Comma-Separated Value format. The latter is useful
for importing source code into another application, such as a

View File

@ -118,7 +118,7 @@ limitations under the License.
IsEnabled="{Binding IsPartPanelEnabled}">
<RadioButton GroupName="Part" Content="Low"
IsChecked="{Binding FormatPartLow}"/>
<RadioButton GroupName="Part" Content="_High" Margin="10,0,0,0"
<RadioButton GroupName="Part" Content="H_igh" Margin="10,0,0,0"
IsChecked="{Binding FormatPartHigh}"/>
<RadioButton GroupName="Part" Content="Bank" Margin="10,0,0,0"
IsChecked="{Binding FormatPartBank}"/>

View File

@ -947,7 +947,7 @@ namespace SourceGen.WpfGui {
NarTargetLabel = mEditedLabel.Label;
}
// Sort nice to just hit return twice after entering a label, so move the focus
// Sort of nice to just hit return twice after entering a label, so move the focus
// to the OK button.
okButton.Focus();
}
@ -985,6 +985,9 @@ namespace SourceGen.WpfGui {
// The preview and symbol value display will use mEditedProjectSymbol if it's the
// only place the symbol exists, so we want to keep the other controls updated.
UpdateControls();
// Move the focus to the OK button.
okButton.Focus();
}
private void CopyToOperandButton_Click(object sender, RoutedEventArgs e) {