Monday, November 08, 2010 at 12:04 AM.
system.verbs.builtins.op.outlineToXml
on outlineToXml (adrOutline, ownerName=user.prefs.name, ownerEmail=user.prefs.mailAddress, adrCloud=nil, version=nil, ownerId=nil) {
<<Changes:
<<3/18/06; 3:15:10 AM by DW
<<Implement user.prefs.flGenerateOpml2.
<<If it's true, and the caller has not provided a version string, set it to 2.0.
<<Implement user.prefs.opmlOwnerId.
<<If the OPML version number is 2.0 or greater, and the caller has not provided an ownerId, and user.prefs.opmlOwnerId is not empty, use it as the ownerId.
<<3/6/06; 7:42:41 AM by DW
<<Support for OPML 2.0.
<<1. New optional param, version, if it's not nil, we replace the version that's generated by the kernel with the version that's supplied.
<<2. New optional param, ownerId, if it's not nil, we replace ownerEmail with ownerId.
<<6/12/05; 4:12:14 AM by DW
<<Set appstring to "OPML Editor" if it's the OPML editor.
<<1/28/03; 4:28:20 PM by JES
<<Encode entities in the window title. Prevents formation of mal-formed XML when the outline window contains less-than or greater-than characters.
<<5/24/02; 3:02:46 PM by JES
<<Add an XML comment to the top of the file, saying what application generated the OPML. Cleaned up change notes.
<<12/20/01; 8:50:05 AM by DB
<<Fixed target setting and set more window attributes before calling the kernel
<<12/19/01; 9:43:48 AM by DMB
<<Added adrCloud parameter
<<7/19/01; 1:34:43 PM by JES
<<Preserve the window title, by patching the xml returned by the kernelCall.
<<7/18/01; 8:33:08 PM by JES
<<Copy the outline to a temporary outline contained in a local table object, instead of to @[windowTitle]^, to prevent the possibility of stomping on objects in the odb.
<<2/18/01; 4:58:37 PM by PBS
<<Preserve size, expansion state, scroll state, and window title.
<<1/1/01; 12:58:02 PM by DW
<<Preserve the dateCreated attribute of the OPML document.
<<1/1/01; 12:25:24 PM by DW
<<In the 11/17 change the cure was worse than the disease. Now all outlines have a <title> of "localOutline". Not good. To work around this, I create a local outline with the same name as the original, then pass its address to the kernel.
<<11/17/00; 4:18:33 PM by JES
<<Convert a copy of the outline to opml instead of converting the outline itself, since if the outline is in text mode, the last-typed changes aren't added to the opml.
<<09/17/00; 4:06:33 PM by PBS
<<Kernelized. Also added ownerName and ownerEmail optional parameters.
<<9/7/00; 2:53:03 PM by JES
<<Set the target to oldTarget before returning xmltext.
<<8/25/00; 11:13:16 AM by DW
<<Version 1.0d2. Add <ownerName> and <ownerEmail> to the <head>.
<<07/27/00; 3:24:37 PM by PBS
<<Encode > characters as >, so the generated XML can be compiled later.
<<7/26/00; 6:38:36 PM by DW
<<Encode ampersands, quotes and less-thans in attributes.
<<7/19/00; 6:30:02 PM by DW
<<Created.
if date.versionLessThan (Frontier.version (), "7.0b21") { //script version
on encode (s) {
s = string.replaceall (s, "&", "&");
s = string.replaceall (s, "\"", """);
s = string.replaceall (s, "<", "<");
s = string.replaceall (s, ">", ">"); //PBS 07/27/00: encode > characters, so XML can be compiled later
return (s)};
local (scrollstate, expansionstate, windowtop, windowleft, windowheight, windowwidth, windowtitle);
bundle { //set state variables based on the original window
local (oldtarget = target.set (adroutline));
scrollstate = op.getscrollstate ();
expansionstate = op.getexpansionstate ();
window.getSize (adroutline, @windowwidth, @windowheight);
window.getPosition (adroutline, @windowleft, @windowtop);
windowtitle = window.gettitle (adroutline)};
<<target.set (oldtarget)
local (xmltext = "", indentlevel = 0);
on add (s) {
xmltext = xmltext + string.filledstring ("\t", indentlevel) + s + "\r"};
add ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
local (localoutline);
localoutline = adroutline^;
local (oldtarget = target.set (@localoutline));
add ("<outlineDocument version=\"1.0d2\">"); indentlevel++;
bundle { //add <head>
add ("<head>"); indentlevel++;
add ("<title>" + xml.entityEncode (windowtitle, true) + "</title>");
add ("<dateCreated>" + date.netstandardstring (timecreated (adroutline)) + "</dateCreated>");
add ("<dateModified>" + date.netstandardstring (timemodified (adroutline)) + "</dateModified>");
add ("<ownerName>" + ownerName + "</ownerName>"); //PBS 09/17/00: ownerName is now an optional parameter to this verb
add ("<ownerEmail>" + ownerEmail + "</ownerEmail>"); //PBS 09/17/00: ownerEmail is now an optional parameter to this verb
bundle { //add expansion state
local (expansionlist = string (expansionstate), num, s = "");
for num in expansionlist {
s = s + num + ","};
s = string.delete (s, sizeof (s), 1); //delete last comma
add ("<expansionState>" + s + "</expansionState>")};
add ("<vertScrollState>" + scrollstate + "</vertScrollState>");
bundle { //add <windowXxx> elements
add ("<windowTop>" + windowtop + "</windowTop>");
add ("<windowLeft>" + windowleft + "</windowLeft>");
add ("<windowBottom>" + (windowtop + windowheight) + "</windowBottom>");
add ("<windowRight>" + (windowleft + windowwidth) + "</windowRight>")};
add ("</head>"); indentlevel--};
bundle { //add <body>
add ("<body>"); indentlevel++;
op.fullexpand ();
op.firstsummit ();
on visitLevel () {
local (s);
loop {
s = "<outline text=\"" + encode (op.getlinetext ()) + "\"";
bundle { //add attributes from refcon, if there are any
local (data = op.getrefcon ());
if typeof (data) == binarytype { //has attributes
local (attstable);
unpack (@data, @attstable);
for adr in @attstable {
s = s + " " + nameof (adr^) + "=\"" + encode (adr^) + "\""}}}; //7/26/00 DW
if script.isComment () {
s = s + " isComment=\"true\""};
if op.go (right, 1) {
add (s + "\>"); indentlevel++;
visitLevel ();
add ("</outline>"); indentlevel--;
op.go (left, 1)}
else {
add (s + "\/>")};
if not op.go (down, 1) {
break}}};
visitLevel ();
add ("</body>"); indentlevel--};
add ("</outlineDocument>"); indentlevel--;
target.set (oldTarget); // 09/07/00 JES
return (xmltext)}
else { //kernel version
local (localoutline);
local (adrtempoutline = @localoutline);
adrtempoutline^ = adroutline^;
local (oldTarget = target.set (adroutline));
bundle { //PBS 02/18/01, dmb 12/20/01: duplicate window size, pos, title, expansion & scroll state
local (windowTitle = window.getTitle (adroutline)); //use title of window, not name of outline object
if windowTitle == "" { //if title == "", use name of object
windowTitle = nameOf (adroutline^)};
windowTitle = xml.entityEncode (windowTitle, true); //JES 1/28/03: prevent malformed XML
local (horiz, vert, hpos, vpos);
window.getSize (adroutline, @horiz, @vert);
window.getPosition (adroutline, @hpos, @vpos);
local (expansionState = op.getExpansionState ());
local (scrollState = op.getScrollState ());
target.set (adrtempoutline);
window.setTitle (adrtempoutline, windowTitle);
window.setSize (adrtempoutline, horiz, vert);
window.setPosition (adrtempoutline, hpos, vpos);
op.setExpansionState (expansionState);
op.setScrollState (scrollState)};
settimecreated (adrtempoutline, timecreated (adrOutline));
local (xmltext);
if date.versionLessThan (Frontier.version (), "7.1b43") { // no adrCloud
on kernelCall (adrOutline, ownerName, ownerEmail) {
kernel (op.outlineToXml)};
xmltext = kernelCall (adrtempoutline, ownerName, ownerEmail)}
else {
on kernelCall (adrOutline, ownerName, ownerEmail, adrCloud) {
kernel (op.outlineToXml)};
xmltext = kernelCall (adrtempoutline, ownerName, ownerEmail, adrCloud)};
try {target.set (oldTarget)};
<<bundle //patch the <title> element
<<local (ixstart = string.patternMatch ("<title>", xmltext) + 7)
<<local (ixend = string.patternMatch ("</title>", xmltext))
<<xmltext = string.delete (xmltext, ixstart, ixend - ixstart)
<<xmltext = string.insert (windowTitle, xmltext, ixstart)
bundle { //initialize and use user.prefs.flGenerateOpml2
if not defined (user.prefs.flGenerateOpml2) {
user.prefs.flGenerateOpml2 = false};
if version == nil {
if user.prefs.flGenerateOpml2 {
version = "2.0"}}};
bundle { //2.0, if version is non-nil, do a substitution
if version != nil {
local (i, sizex =sizeof (xmltext));
local (ixstart, ixend);
for i = 1 to sizex { //find the 1st return
if xmltext [i] == '\r' {
ixstart = i;
break}};
for i = ixstart+1 to sizex { //find the 2nd return
if xmltext [i] == '\r' {
ixend = i;
break}};
xmltext = string.delete (xmltext, ixstart+1, ixend-ixstart-1); //replace the <opml> element
xmltext = string.insert ("<opml version=\"" + version + "\">", xmltext, ixstart+1)}};
bundle { //initialize and use user.prefs.opmlOwnerId
if not defined (user.prefs.opmlOwnerId) {
user.prefs.opmlOwnerId = ""};
if ownerId == nil {
if user.prefs.opmlOwnerId != "" {
if not date.versionLessThan (version, "2.0") {
ownerId = user.prefs.opmlOwnerId}}}};
bundle { //2.0, if ownerId is not nil, relace ownerEmail
if ownerId != nil {
local (searchfor = "<ownerEmail>" + ownerEmail + "</ownerEmail>");
local (replacewith = "<ownerId>" + ownerId + "</ownerId>");
xmltext = string.replace (xmltext, searchfor, replacewith)}};
bundle { //add a comment saying what application generated the OPML
<<Example comment:
<<<!-- OPML generated by Radio UserLand v8.0.5 on Fri, 24 May 2002 20:43:14 GMT -->
local (appstring);
bundle { //set appstring
if system.environment.isRadio {
appstring = "Radio UserLand "}
else { //Frontier or OPML Editor
local (flopmleditor = false);
if defined (system.environment.isOpmlEditor) {
if system.environment.isOpmlEditor {
flopmleditor = true}};
if flopmleditor {
appstring = "OPML Editor "}
else {
appstring = "UserLand Frontier "}};
appstring = appstring + "v" + frontier.version ()};
local (commenttext = "<!-- OPML generated by " + appstring + " on " + date.netStandardString (clock.now ()) + " -->\r\n");
local (ix = string.patternMatch ("<opml ", xmltext));
xmltext = string.insert (commenttext, xmltext, ix)};
return (xmltext)}}
<<bundle //test code
<<local (s =op.outlinetoxml (@scratchpad.testoutline, version:"2.0", ownerId:"http://blogs.opml.org/mail/dave"))
<<wp.newtextobject (s, @scratchpad.testtext)
<<edit (@scratchpad.testtext)
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.