A reference document for working with the opmlProjectEditor format used by Dave Winer's OPML Editor and Frontier-based tools.
opmlProjectEditor is a way of storing a multi-file software project in a single OPML file. Each top-level outline node under the project headline represents a file. The file's contents are stored as child outline nodes, one node per line, with indentation representing code structure.
<?xml version="1.0" encoding="UTF-8"?>
<opml version="2.0">
<head>
<title>project title</title>
<dateCreated>...</dateCreated>
<dateModified>...</dateModified>
<ownerName>Dave Winer</ownerName>
<ownerId>http://davewiner.com/</ownerId>
</head>
<body>
<outline text="/path/to/project/">
<outline text="worknotes.md" created="..."/>
<outline text="styles.css" created="...">
<outline text="body {">
<outline text="background-color: white;"/>
<outline text="}"/>
</outline>
</outline>
<outline text="code.js" created="...">
...
</outline>
<outline text="index.html" created="...">
...
</outline>
</outline>
</body>
</opml>- The
<body>contains one outline node whosetextattribute is the project path (e.g./scripting.com/code/testing/domplayground2/). - All files live as direct children of that project node.
- Each file is an outline node whose
textis the filename (e.g.styles.css,code.js,index.html). - File contents are stored as child outline nodes, one per line of the file.
- Indentation in the outline represents indentation in the file — each level of nesting adds one tab.
- Each line of code is stored as the
textattribute of an outline node. - Empty lines are stored as
<outline text=""/>or<outline text="" created="..."/>. - XML special characters are escaped:
<→<,>→>,"→",&→&,'→'. - For HTML/JS content stored inside
textattributes, all angle brackets and quotes must be entity-escaped.
- A node with
isComment="true"is a commented-out line. - The node AND all its descendants must be skipped when reading the file.
- This is critical — a commented-out CSS rule block means the selector line AND all its property lines are excluded.
- Most nodes have a
createdattribute with an RFC 822 date string (e.g.Wed, 18 Dec 2024 17:19:15 GMT). - The
<head>hasdateCreatedanddateModified. - Timestamps are optional on generated nodes but conventional.
- The file should be declared as UTF-8:
<?xml version="1.0" encoding="UTF-8"?>. - Use numeric XML character references for special typographic characters to ensure they survive any encoding round-trip:
—(em-dash),“(left double quote),”(right double quote),’(right single quote). - Do NOT use named HTML entities like
—insidetextattributes — the XML parser will reject them, and a code generator will double-escape them to&mdash;.
When generating an OPML file programmatically from source files (e.g. with Python), the key steps are:
- Read each source file as UTF-8.
- Split on newlines.
- For each line, determine its indent level (count leading tabs).
- Emit the line as an outline node at the appropriate nesting depth.
- XML-escape the
textattribute content (&,",<,>,'). - Close outline nodes properly when indent level decreases.
def text_to_outline_nodes(text, base_indent):
lines = text.split('\n')
result = []
indent_stack = [base_indent]
for line in lines:
if not line.strip():
tabs = '\t' * base_indent
result.append(f'{tabs}<outline text=""/>')
continue
stripped = line.lstrip('\t')
level = len(line) - len(stripped)
depth = base_indent + level
tabs = '\t' * depth
escaped = (stripped
.replace('&', '&')
.replace('"', '"')
.replace('<', '<')
.replace('>', '>'))
result.append(f'{tabs}<outline text="{escaped}"/>')
return '\n'.join(result)When reconstructing source files from OPML:
- Find the file node by its
textattribute (filename). - Walk its child outline nodes recursively.
- Skip any node where
isComment="true"— and skip all its descendants. - For each non-comment node, the
textattribute is one line of the file. - The nesting depth relative to the file node determines the indentation (one tab per level).
- Unescape XML entities back to their characters.
The worknotes.md file is a running log of development notes, written in the outline. Convention:
- Top-level entries are dated headings:
#### 3/1/26; by [author] - Sub-items are notes, decisions, architecture docs.
- Author is whoever wrote the entry — can be a person or "Claude".
- Newest entries at top.
CSS files stored in OPML follow the same line-per-node convention. Additional notes:
- No multi-line block comments in CSS (they would require complex nesting). Use inline end-of-line comments only.
- CSS rules nest naturally: the selector line is a parent node, property lines are children, closing
}is a child at the same level as the properties. - Commented-out CSS rules appear as
isComment="true"on the selector node, which excludes the entire rule including all properties.
For HTML and JS files, content stored in text attributes must have all <, >, ", & escaped. This means:
<div class="foo">becomes<div class="foo">- Template literal strings with embedded HTML are heavily escaped.
- When reading back, unescape in the correct order:
&last.
The project's top-level outline text is a URL path identifying where the project lives on the server, e.g.:
/scripting.com/code/testing/domplayground2/
This is used by the build system to know where to publish the files.