Added PC11 support for loading tapes remotely

This commit is contained in:
Jeff Parsons 2016-10-17 18:22:42 -07:00 committed by Jeff Parsons
commit 961dce8f78
15 changed files with 993 additions and 441 deletions

View file

@ -1094,7 +1094,6 @@ Disk.prototype.load = function(sDiskName, sDiskPath, file, fnNotify, controller)
};
/**
*
* build(buffer, fModified)
*
* Builds a disk image from an ArrayBuffer (eg, from a FileReader object), rather than from JSON-encoded data.
@ -1217,24 +1216,6 @@ Disk.prototype.doneLoad = function(sURL, sDiskData, nErrorCode)
}
/*
* The most likely source of any exception will be here, where we're parsing the disk data.
*
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
*
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
* the following RegExp replacements take care of those requirements.
*
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
*
* ["unrecognized disk path: test.img"]
*/
var aDiskData;
if (sDiskData.substr(0, 1) == "<") { // if the "data" begins with a "<"...
@ -1249,6 +1230,25 @@ Disk.prototype.doneLoad = function(sURL, sDiskData, nErrorCode)
*/
aDiskData = ["Missing disk image: " + this.sDiskName];
} else {
/*
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
*
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
* the following RegExp replacements take care of those requirements.
*
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
*
* ["unrecognized disk path: test.img"]
*/
if (sDiskData.indexOf("0x") < 0 && sDiskData.substr(0, 2) != "[\"") {
aDiskData = JSON.parse(sDiskData.replace(/([a-z]+):/gm, "\"$1\":").replace(/\/\/[^\n]*/gm, ""));
} else {

View file

@ -659,10 +659,7 @@ FDC.prototype.initBus = function(cmp, bus, cpu, dbg)
bus.addPortOutputTable(this, FDC.aPortOutput);
this.addDiskette("None", "", true);
if (this.fLocalDisks) {
this.addDiskette("Local Disk", "?");
}
if (this.fLocalDisks) this.addDiskette("Local Disk", "?");
this.addDiskette("Remote Disk", "??");
if (!this.autoMount()) this.setReady();
@ -1535,6 +1532,7 @@ FDC.prototype.doneLoadDiskette = function onFDCLoadNotify(drive, disk, sDiskette
/**
* addDiskette(sName, sPath, fTop)
*
* @this {FDC}
* @param {string} sName
* @param {string} sPath
* @param {boolean} [fTop] (default is bottom)
@ -1563,6 +1561,7 @@ FDC.prototype.addDiskette = function(sName, sPath, fTop)
* This is used to deal with mount requests (eg, autoMount) that supply a path without a name;
* if we can find the path in the "listDisks" control, then we return the associated disk name.
*
* @this {FDC}
* @param {string} sPath
* @return {string|null}
*/

View file

@ -31,6 +31,7 @@
if (NODE) {
var str = require("../../shared/lib/strlib");
var web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
@ -48,14 +49,30 @@ function PC11(parms)
{
Component.call(this, "PC11", parms, PC11);
/*
* We record any 'autoMount' object now, but we no longer parse it until initBus(), because the Computer's
* getMachineParm() service may have an override for us.
*/
this.configMount = parms['autoMount'] || null;
/*
* TODO: Technically, the PC11 should have a timer that "clocks" data from the abReader buffer into the
* PRB register at the appropriate rate (300 CPS for the high-speed version, 10 CPS for the low-speed version).
*/
this.prs = 0; // PRS register
this.prb = 0; // PRB register
this.iReader = 0; // buffer index
this.abReader = []; // buffer for the PRB register
this.prs = 0; // PRS register
this.prb = 0; // PRB register
this.iReader = 0; // buffer index
this.abReader = []; // buffer for the PRB register
this.flags.local = false;
this.sTapeName = this.sTapePath = this.sTapeFile = "";
/*
* Support for local tape images is currently limited to desktop browsers with FileReader support;
* when this flag is set, setBinding() allows local tape bindings and informs initBus() to update the
* "listTapes" binding accordingly.
*/
this.fLocalTapes = (!web.isMobile() && window && 'FileReader' in window);
}
Component.subclass(PC11);
@ -64,14 +81,103 @@ Component.subclass(PC11);
* setBinding(sType, sBinding, control, sValue)
*
* @this {PC11}
* @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc)
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "buffer")
* @param {string|null} sType is the type of the HTML control (eg, "button", "list", "text", etc)
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listTapes")
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
* @param {string} [sValue] optional data value
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
PC11.prototype.setBinding = function(sType, sBinding, control, sValue)
{
var pc11 = this;
switch (sBinding) {
case "listTapes":
this.bindings[sBinding] = control;
control.onchange = function onChangeListTapes(event) {
var controlDesc = pc11.bindings["descTape"];
var controlOption = control.options[control.selectedIndex];
if (controlDesc && controlOption) {
var dataValue = {};
var sValue = controlOption.getAttribute("data-value");
if (sValue) {
try {
dataValue = eval("(" + sValue + ")");
} catch (e) {
Component.error("PC11 option error: " + e.message);
}
}
var sHTML = dataValue['desc'];
if (sHTML === undefined) sHTML = "";
var sHRef = dataValue['href'];
if (sHRef !== undefined) sHTML = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sHTML + "</a>";
controlDesc.innerHTML = sHTML;
}
};
return true;
case "descTape":
this.bindings[sBinding] = control;
return true;
case "loadTape":
this.bindings[sBinding] = control;
control.onclick = function onClickLoadTape(event) {
var controlTapes = pc11.bindings["listTapes"];
if (controlTapes) {
var sTapeName = controlTapes.options[controlTapes.selectedIndex].text;
var sTapePath = controlTapes.value;
pc11.loadSelectedTape(sTapeName, sTapePath);
}
};
return true;
case "mountTape":
if (!this.fLocalTapes) {
if (DEBUG) this.log("Local tape support not available");
/*
* We could also simply hide the control; eg:
*
* control.style.display = "none";
*
* but removing the control altogether seems better.
*/
control.parentNode.removeChild(/** @type {Node} */ (control));
return false;
}
this.bindings[sBinding] = control;
/*
* Enable "Mount" button only if a file is actually selected
*/
control.addEventListener('change', function() {
var fieldset = control.children[0];
var files = fieldset.children[0].files;
var submit = fieldset.children[1];
submit.disabled = !files.length;
});
control.onsubmit = function(event) {
var file = event.currentTarget[1].files[0];
if (file) {
var sTapePath = file.name;
var sTapeName = str.getBaseName(sTapePath, true);
pc11.loadSelectedTape(sTapeName, sTapePath, file);
}
/*
* Prevent reloading of web page after form submission
*/
return false;
};
return true;
default:
break;
}
return false;
};
@ -91,9 +197,30 @@ PC11.prototype.initBus = function(cmp, bus, cpu, dbg)
this.cpu = cpu;
this.dbg = dbg;
this.configMount = this.cmp.getMachineParm('autoMount') || this.configMount;
if (this.configMount) {
if (typeof this.configMount == "string") {
try {
/*
* The most likely source of any exception will be right here, where we're parsing
* this JSON-encoded data.
*/
this.configMount = eval("(" + this.configMount + ")");
} catch (e) {
Component.error("PC11 auto-mount error: " + e.message + " (" + this.configMount + ")");
this.configMount = null;
}
}
}
bus.addIOTable(this, PC11.UNIBUS_IOTABLE);
this.setReady();
this.addTape("None", "", true);
if (this.fLocalTapes) this.addTape("Local Tape", "?");
this.addTape("Remote Tape", "??");
if (!this.autoMount()) this.setReady();
};
/**
@ -142,6 +269,318 @@ PC11.prototype.reset = function()
this.abReader = [];
};
/**
* autoMount(fRemount)
*
* @this {PC11}
* @param {boolean} [fRemount] is true if we're remounting all auto-mounted tapes
* @return {boolean} true if one or more tape images are being auto-mounted, false if none
*/
PC11.prototype.autoMount = function(fRemount)
{
if (!fRemount) this.cAutoMount = 0;
if (this.configMount) {
var sTapePath = this.configMount['path'];
var sTapeName = this.configMount['name'] || this.findTape(sTapePath);
if (sTapePath && sTapeName) {
if (!this.loadTape(sTapeName, sTapePath, true) && fRemount) {
this.setReady(false);
}
} else {
this.notice("Incorrect auto-mount settings for PC11 (" + JSON.stringify(this.configMount) + ")");
}
}
return !!this.cAutoMount;
};
/**
* loadSelectedTape(sTapeName, sTapePath, file)
*
* @this {PC11}
* @param {string} sTapeName
* @param {string} sTapePath
* @param {File} [file] is set if there's an associated File object
*/
PC11.prototype.loadSelectedTape = function(sTapeName, sTapePath, file)
{
if (!sTapePath) {
this.unloadTape();
return;
}
if (sTapePath == "?") {
this.notice('Use "Choose File" and "Mount" to select and load a local tape.');
return;
}
/*
* If the special path of "??" is selected, then we want to prompt the user for a URL. Oh, and
* make sure we pass an empty string as the 2nd parameter to prompt(), so that IE won't display
* "undefined" -- because after all, undefined and "undefined" are EXACTLY the same thing, right?
*
* TODO: This is literally all I've done to support remote tape images. There's probably more
* I should do, like dynamically updating "listTapes" to include new entries, and adding new entries
* to the save/restore data.
*/
if (sTapePath == "??") {
sTapePath = window.prompt("Enter the URL of a remote tape image.", "") || "";
if (!sTapePath) return;
sTapeName = str.getBaseName(sTapePath);
if (DEBUG) this.println("Attempting to load " + sTapePath + " as \"" + sTapeName + "\"");
}
this.loadTape(sTapeName, sTapePath, false, file);
};
/**
* loadTape(sTapeName, sTapePath, fAutoMount, file)
*
* NOTE: If sTapePath is already loaded, nothing needs to be done.
*
* @this {PC11}
* @param {string} sTapeName
* @param {string} sTapePath
* @param {boolean} [fAutoMount]
* @param {File} [file] is set if there's an associated File object
* @return {number} 1 if tape loaded, 0 if queued up (or busy), -1 if already loaded
*/
PC11.prototype.loadTape = function(sTapeName, sTapePath, fAutoMount, file)
{
if (sTapePath) {
if (this.sTapePath.toLowerCase() != sTapePath.toLowerCase()) {
this.unloadTape(fAutoMount, true);
if (this.flags.busy) {
this.notice("PC11 busy");
return 0;
}
// if (DEBUG) this.println("tape queued: " + sTapeName);
this.flags.busy = true;
if (fAutoMount) {
this.fAutoMount = true;
this.cAutoMount++;
if (this.messageEnabled()) this.printMessage("auto-loading tape: " + sTapeName);
}
this.flags.local = !!file;
if (!this.load(sTapeName, sTapePath, file)) {
return 0;
}
return 1;
}
if (DEBUG) this.println("tape loaded");
}
return -1;
};
/**
* load(sTapeName, sTapePath, file)
*
* @this {PC11}
* @param {string} sTapeName
* @param {string} sTapePath
* @param {File} [file] is set if there's an associated File object
* @return {boolean} true if load completed (successfully or not), false if queued
*/
PC11.prototype.load = function(sTapeName, sTapePath, file)
{
var sTapeURL = sTapePath;
if (DEBUG) {
var sMessage = 'load("' + sTapeName + '","' + sTapePath + '")';
this.printMessage(sMessage);
}
var sTapeFile = str.getBaseName(sTapePath);
var pc11 = this;
if (file) {
var reader = new FileReader();
reader.onload = function() {
pc11.build(reader.result);
};
reader.readAsArrayBuffer(file);
return true;
}
/*
* If there's an occurrence of API_ENDPOINT anywhere in the path, we assume we can use it as-is;
* ie, that the user has already formed a URL of the type we use ourselves for unconverted tape images.
*/
if (sTapePath.indexOf(DumpAPI.ENDPOINT) < 0) {
/*
* If the selected tape image has a "json" extension, then we assume it's a pre-converted
* JSON-encoded tape image, so we load it as-is; otherwise, we ask our server-side tape image
* converter to return the corresponding JSON-encoded data.
*/
var sTapeExt = str.getExtension(sTapePath);
if (sTapeExt == DumpAPI.FORMAT.JSON || sTapeExt == DumpAPI.FORMAT.JSON_GZ) {
sTapeURL = encodeURI(sTapePath);
} else {
var sTapeParm = DumpAPI.QUERY.PATH;
sTapeURL = web.getHost() + DumpAPI.ENDPOINT + '?' + sTapeParm + '=' + encodeURIComponent(sTapePath) + "&" + DumpAPI.QUERY.FORMAT + "=" + DumpAPI.FORMAT.JSON;
}
}
return !!web.getResource(sTapeURL, null, true, function(sURL, sResponse, nErrorCode) {
pc11.doneLoad(sURL, sTapeName, sTapePath, sResponse, nErrorCode);
});
};
/**
* build(buffer)
*
* Builds a disk image from an ArrayBuffer (eg, from a FileReader object), rather than from JSON-encoded data.
*
* @this {PC11}
* @param {?} buffer (we KNOW this is an ArrayBuffer, but we can't seem to convince the Closure Compiler)
*/
PC11.prototype.build = function(buffer)
{
if (buffer) {
this.aTapeData = new Uint8Array(buffer, 0, buffer.byteLength);
}
};
/**
* doneLoad(sURL, sTapeName, sTapePath, sTapeData, nErrorCode)
*
* @this {PC11}
* @param {string} sURL
* @param {string} sTapeName
* @param {string} sTapePath
* @param {string} sTapeData
* @param {number} nErrorCode (response from server if anything other than 200)
*/
PC11.prototype.doneLoad = function(sURL, sTapeName, sTapePath, sTapeData, nErrorCode)
{
this.flags.busy = false;
var fPrintOnly = (nErrorCode < 0 && this.cmp && !this.cmp.flags.powered);
if (nErrorCode) {
/*
* This can happen for innocuous reasons, such as the user switching away too quickly, forcing
* the request to be cancelled. And unfortunately, the browser cancels XMLHttpRequest requests
* BEFORE it notifies any page event handlers, so if the Computer's being powered down, we won't know
* that yet. For now, we rely on the lack of a specific error (nErrorCode < 0), and suppress the
* notify() alert if there's no specific error AND the computer is not powered up yet.
*/
this.notice("Unable to load tape \"" + sTapeName + "\" (error " + nErrorCode + ": " + sURL + ")", fPrintOnly);
}
else {
if (DEBUG && this.messageEnabled()) {
this.printMessage('doneLoad("' + sTapePath + '")');
}
Component.addMachineResource(this.idMachine, sURL, sTapeData);
var resource = web.parseMemoryResource(sURL, sTapeData);
if (resource) {
this.sTapeName = sTapeName;
this.sTapePath = sTapePath;
this.aTapeData = resource.aBytes;
if (DEBUG) this.println("tape loaded: " + sTapeName);
}
}
if (this.fAutoMount) {
this.fAutoMount = false;
if (!--this.cAutoMount) this.setReady();
}
this.displayTape();
};
/**
* addTape(sName, sPath, fTop)
*
* @this {PC11}
* @param {string} sName
* @param {string} sPath
* @param {boolean} [fTop] (default is bottom)
*/
PC11.prototype.addTape = function(sName, sPath, fTop)
{
var controlTapes = this.bindings["listTapes"];
if (controlTapes && controlTapes.options) {
for (var i = 0; i < controlTapes.options.length; i++) {
if (controlTapes.options[i].value == sPath) return;
}
var controlOption = document.createElement("option");
controlOption.text = sName;
controlOption.value = sPath;
if (fTop && controlTapes.childNodes[0]) {
controlTapes.insertBefore(controlOption, controlTapes.childNodes[0]);
} else {
controlTapes.appendChild(controlOption);
}
}
};
/**
* findTape(sPath)
*
* This is used to deal with mount requests (eg, autoMount) that supply a path without a name;
* if we can find the path in the "listTapes" control, then we return the associated tape name.
*
* @this {PC11}
* @param {string} sPath
* @return {string|null}
*/
PC11.prototype.findTape = function(sPath)
{
var controlTapes = this.bindings["listTapes"];
if (controlTapes && controlTapes.options) {
for (var i = 0; i < controlTapes.options.length; i++) {
var control = controlTapes.options[i];
if (control.value == sPath) return control.text;
}
}
return str.getBaseName(sPath, true);
};
/**
* displayTape()
*
* @this {PC11}
*/
PC11.prototype.displayTape = function()
{
var controlTapes = this.bindings["listTapes"];
if (controlTapes && controlTapes.options) {
var sTargetPath = (this.flags.local? "?" : this.sTapePath);
for (var i = 0; i < controlTapes.options.length; i++) {
if (controlTapes.options[i].value == sTargetPath) {
if (controlTapes.selectedIndex != i) {
controlTapes.selectedIndex = i;
}
break;
}
}
if (i == controlTapes.options.length) controlTapes.selectedIndex = 0;
}
};
/**
* unloadTape(fAutoUnload, fQuiet)
*
* @this {PC11}
* @param {boolean} [fAutoUnload] is true if this unload is being forced as part of an automount and/or restored mount
* @param {boolean} [fQuiet]
*/
PC11.prototype.unloadTape = function(fAutoUnload, fQuiet)
{
this.sTapeName = "";
this.sTapePath = "";
this.flags.local = false;
};
/**
* save()
*

View file

@ -298,10 +298,45 @@ web.parseMemoryResource = function(sURL, sData)
nLoad: null,
nExec: null
};
if (sData.charAt(0) == "[" || sData.charAt(0) == "{") {
try {
var a, ib;
var data = eval("(" + sData + ")");
var a, ib, data;
if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"...
/*
* Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid,
* but more recent server configs now display a somewhat friendlier HTML error page. The downside,
* however, is that the original error has been buried, and we've received "data" that isn't actually
* tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message.
*/
throw new Error(sData);
}
/*
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
*
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
* the following RegExp replacements take care of those requirements.
*
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
*
* ["unrecognized disk path: test.img"]
*/
if (sData.indexOf("0x") < 0 && sData.substr(0, 2) != "[\"") {
data = JSON.parse(sData.replace(/([a-z]+):/gm, "\"$1\":").replace(/\/\/[^\n]*/gm, ""));
} else {
data = eval("(" + sData + ")");
}
resource.nLoad = data['load'];
resource.nExec = data['exec'];
@ -347,22 +382,26 @@ web.parseMemoryResource = function(sURL, sData)
resource = null;
}
} catch (e) {
Component.error("Resource data error: " + e.message);
Component.error("Resource data error (" + sURL + "): " + e.message);
resource = null;
}
}
else {
/*
* Parse the data manually; we'll assume it's in "simplified" hex form
* (a series of hex byte-values separated by whitespace).
* Parse the data manually; we assume it's a series of hex byte-values separated by whitespace.
*/
var ab = [];
var sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, "");
var asHexData = sHexData.split(" ");
resource.aBytes = new Array(asHexData.length);
for (i = 0; i < asHexData.length; i++) {
resource.aBytes[i] = parseInt(asHexData[i], 16);
Component.assert(!isNaN(resource.aBytes[i]));
var n = parseInt(asHexData[i], 16);
if (isNaN(n)) {
Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")");
break;
}
ab.push(n & 0xff);
}
if (i == asHexData.length) resource.aBytes = ab;
}
return resource;
};

View file

@ -740,21 +740,32 @@
<xsl:template match="device[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/device"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
<xsl:apply-templates select="document($componentFile)/device">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="mount" select="@automount"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="device[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:param name="mount" select="''"/>
<xsl:variable name="type">
<xsl:choose>
<xsl:when test="@type"><xsl:value-of select="@type"/></xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="autoMount">
<xsl:choose>
<xsl:when test="$mount != ''"><xsl:value-of select="$mount"/></xsl:when>
<xsl:when test="@automount"><xsl:value-of select="@automount"/></xsl:when>
<xsl:otherwise><xsl:value-of select="@autoMount"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">device</xsl:with-param>
<xsl:with-param name="parms">,type:'<xsl:value-of select="$type"/>'</xsl:with-param>
<xsl:with-param name="parms">,type:'<xsl:value-of select="$type"/>',autoMount:'<xsl:value-of select="$autoMount"/>'</xsl:with-param>
</xsl:call-template>
</xsl:template>
@ -880,10 +891,11 @@
<xsl:template match="fdc[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:param name="mount" select="''"/>
<xsl:variable name="automount">
<xsl:variable name="autoMount">
<xsl:choose>
<xsl:when test="$mount != ''"><xsl:value-of select="$mount"/></xsl:when>
<xsl:otherwise><xsl:value-of select="@automount"/></xsl:otherwise>
<xsl:when test="@automount"><xsl:value-of select="@automount"/></xsl:when>
<xsl:otherwise><xsl:value-of select="@autoMount"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="sortBy">
@ -895,7 +907,7 @@
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">fdc</xsl:with-param>
<xsl:with-param name="parms">,autoMount:'<xsl:value-of select="$automount"/>',sortBy:'<xsl:value-of select="$sortBy"/>'</xsl:with-param>
<xsl:with-param name="parms">,autoMount:'<xsl:value-of select="$autoMount"/>',sortBy:'<xsl:value-of select="$sortBy"/>'</xsl:with-param>
</xsl:call-template>
</xsl:template>