Re: Wiki Markup for ECMA documents.
Atsushi Eno <[email protected]>
| Newsgroups | gmane.comp.gnome.mono.documentation |
|---|---|
| Message-ID | <[email protected]> |
Hola, > But with this code, we can start moving forward. We still need the > inverse process Wiki to ECMA XML before this can land on SVN. Yeah. So, here it is. http://monkey.workarea.jp/trans/mono/index.php/TestMonodocToMediaWiki3 However, whitespace handling in the ECMA->Wiki xsl is a blocker for practical use (it unexpectedly merges sequential <para>s), so am really going to rewrite the code in C#. Atsushi Eno _______________________________________________ Mono-docs-list maillist - [email protected] http://lists.ximian.com/mailman/listinfo/mono-docs-list
wiki2ecma.cs
(text/plain, 8.1 KB)
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
namespace Monodoc
{
public class WikiStyleDocParser
{
public static void Main (string [] args)
{
if (args.Length < 1) {
Console.Error.WriteLine ("usage: wiki2ecma sourcefile [--full]");
return;
}
bool full = args.Length > 1 && args [1] == "--full";
bool isXml = false;
using (Stream s = File.OpenRead (args [0])) {
isXml = (s.ReadByte () == '<');
}
string text;
if (isXml) {
XmlDocument doc = new XmlDocument ();
doc.Load (args [0]);
XmlNode node = doc.SelectSingleNode ("//text");
text = node.InnerText;
} else {
StreamReader sr = new StreamReader (args [0],
Encoding.UTF8);
text = sr.ReadToEnd ();
}
// Pass the input Wiki-like content as the .ctor()
// parameter.
WikiStyleDocParser p = new WikiStyleDocParser (text);
XmlNode result;
if (full)
result = p.ParseEntireDoc ();
else
result = p.ParseContent ();
XmlTextWriter xw = new XmlTextWriter (Console.Out);
xw.Formatting = Formatting.Indented;
result.WriteTo (xw);
xw.Close ();
}
string [] lines;
int lineno = 1;
XmlDocument doc;
string current_member;
public WikiStyleDocParser (string source)
{
lines = source.Split ('\n');
doc = new XmlDocument ();
doc.AppendChild (doc.CreateElement ("root"));
}
public XmlNode ParseContent ()
{
ProcessContent (doc.DocumentElement);
return doc.DocumentElement;
}
public XmlNode ParseEntireDoc ()
{
XmlElement el = doc.DocumentElement;
while (lineno < lines.Length) {
string line = lines [lineno].Trim ();
if (line.Length == 0) {
lineno++;
continue;
}
XmlNode node = null;
switch (line) {
case "=== Summary ===":
node = ProcessTaggedContent (EditTarget.Summary);
el.AppendChild (node);
break;
case "=== Remarks ===":
node = ProcessTaggedContent (EditTarget.Remarks);
el.AppendChild (node);
break;
case "=== Parameters ===":
ProcessList (el, "param", "name");
break;
case "=== Exceptions ===":
ProcessList (el, "exception", "type");
break;
default:
if (StrUtil.StartsWith (line, "==")) {
current_member = line.Substring (
3, line.Length - 6).Trim ();
el = doc.CreateElement ("Member");
el.SetAttribute ("MemberName", current_member);
doc.DocumentElement.AppendChild (el);
lineno++;
break;
}
throw MarkupError ("Unexpected line format: " + line);
}
}
return doc.DocumentElement;
}
void ProcessList (XmlNode parent, string elemName, string defAttr)
{
lineno++;
for (; lineno < lines.Length; lineno++) {
string line = lines [lineno];
if (line.Length == 0)
continue;
if (line [0] != ';')
break;
int idx = line.IndexOf (':');
XmlElement el = doc.CreateElement (elemName);
parent.AppendChild (el);
el.SetAttribute (defAttr, line.Substring (1, idx - 1));
ProcessSimpleLine (el, line, idx + 1);
}
}
XmlNode ProcessTaggedContent (string target)
{
XmlElement el = doc.CreateElement (target);
lineno++;
ProcessContent (el);
return el;
}
void ProcessContent (XmlNode container)
{
while (lineno < lines.Length) {
string line = lines [lineno];
if (line.Length == 0) {
lineno++;
continue;
}
switch (line [0]) {
case '=':
return;
case '{':
ProcessTable (container);
break;
case ':':
XmlElement el = doc.CreateElement ("block");
el.SetAttribute ("subset", "none");
el.SetAttribute ("type", "note");
container.AppendChild (el);
ProcessSimple (el, true);
break;
default:
el = doc.CreateElement ("para");
container.AppendChild (el);
ProcessSimple (el, false);
break;
}
}
}
void ProcessTable (XmlNode container)
{
lineno++;
XmlElement list = doc.CreateElement ("list");
container.AppendChild (list);
list.SetAttribute ("type", "table");
XmlElement tline = null;
for (; lineno < lines.Length; lineno++) {
string line = lines [lineno];
if (line == "|}") {
lineno++;
return;
}
if (line.Length == 0)
continue;
if (line == "|-") {
tline = doc.CreateElement ("item");
continue;
}
switch (line [0]) {
case '!':
tline = doc.CreateElement ("listheader");
int endTerm = line.IndexOf ('!', 1);
int beginDesc = endTerm < 0 ? -1 : line.IndexOf ('!', endTerm + 1);
if (beginDesc < 0)
throw MarkupError ("list table header has incorrect markup : " + line);
XmlElement term = doc.CreateElement ("term");
term.InnerText = line.Substring (1, endTerm - 1);
tline.AppendChild (term);
XmlElement desc = doc.CreateElement ("description");
desc.InnerText = line.Substring (beginDesc + 1);
tline.AppendChild (desc);
list.AppendChild (tline);
break;
case '|':
if (tline == null)
throw MarkupError ("Specify '|-' to begin new table line");
endTerm = line.IndexOf ('|', 1);
beginDesc = endTerm < 0 ? -1 : line.IndexOf ('|', endTerm + 1);
term = doc.CreateElement ("term");
term.InnerText = line.Substring (1, endTerm - 1);
tline.AppendChild (term);
desc = doc.CreateElement ("description");
ProcessSimpleLine (desc, line, beginDesc + 1);
tline.AppendChild (desc);
list.AppendChild (tline);
break;
}
tline = null;
}
// there is already "return" statement above.
throw MarkupError ("End of list table is missing");
}
void ProcessSimple (XmlNode container, bool allowColon)
{
for (;lineno < lines.Length; lineno++) {
string line = lines [lineno];
if (line.Length == 0) {
if (lineno + 1 < lines.Length &&
lines [lineno + 1] == String.Empty) {
lineno++;
return;
}
continue;
}
switch (line [0]) {
case '=':
case '{':
return;
case ':':
if (!allowColon)
return;
ProcessSimpleLine (container, line, 1);
break;
default:
ProcessSimpleLine (container, line, 0);
break;
}
}
}
void ProcessSimpleLine (XmlNode container, string line, int from)
{
int idx;
while ((idx = line.IndexOf ('[', from)) >= 0) {
from = ProcessLink (
container, line, idx, from);
}
if (from != line.Length)
container.AppendChild (doc.CreateTextNode (line.Substring (from) + '\n'));
}
int ProcessLink (XmlNode container, string line, int idx, int from)
{
int end = line.IndexOf (']', idx);
if (end < idx)
throw MarkupError (String.Format ("There is no matching ']' to close link at position {1} : {0}", line, idx));
if (idx > from) {
XmlText text = doc.CreateTextNode (
line.Substring (from, idx - from));
container.AppendChild (text);
}
int sep = line.IndexOf ('|', idx, end - idx);
if (sep > 0) {
if (line [idx + 1] != '[' ||
sep < 0 || sep > end ||
end + 1 >= line.Length ||
line [end + 1] != ']')
throw MarkupError (String.Format ("Invalid reference markup at position {1} : {0}", line, idx));
// see cref
XmlElement el = doc.CreateElement ("see");
el.SetAttribute ("cref", line.Substring (
idx + 2, sep - idx - 2).Trim ());
container.AppendChild (el);
} else {
// paramref
if (line [idx + 1] != '[' ||
end + 1 >= line.Length ||
line [end + 1] != ']')
throw MarkupError (String.Format ("Invalid reference markup at position {1} : {0}", line, idx));
XmlElement el = doc.CreateElement ("paramref");
el.SetAttribute ("name", line.Substring (
idx + 2, end - idx - 2));
container.AppendChild (el);
}
end += 2;
return end;
}
Exception MarkupError (string message)
{
throw new Exception (String.Format (
"At line {1} : {0}", message, lineno));
}
}
class EditTarget
{
public const string Summary = "summary";
public const string Remarks = "remarks";
}
class StrUtil
{
static CompareInfo ci = CultureInfo.CurrentCulture.CompareInfo;
public static bool StartsWith (string s, string target)
{
return ci.IsPrefix (s, target, CompareOptions.Ordinal);
}
}
}
monodoc2mediawiki.xsl
(text/xml, 5.5 KB)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<!--xsl:strip-space elements="*" / -->
<xsl:template match="/">
<xsl:for-each select="*">
<xsl:choose>
<xsl:when test="name()='Type'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:when test="name()='Docs'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:when test="name()='summary'">
<xsl:call-template name="content" />
</xsl:when>
<xsl:when test="name()='remarks'">
<xsl:call-template name="content" />
</xsl:when>
<xsl:otherwise>
<xsl:message>Unexpected top level element: <xsl:value-of select="name()" /></xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template match="Type">
<xsl:apply-templates select="Docs" />
<xsl:apply-templates select="Members/Member" />
</xsl:template>
<xsl:template match="Docs">
<xsl:apply-templates select="summary" />
<xsl:if test="param">
<xsl:text>=== Parameters ===
</xsl:text>
<xsl:apply-templates select="param" />
<xsl:text>

</xsl:text>
</xsl:if>
<xsl:if test="exception">
<xsl:text>=== Exceptions ===
</xsl:text>
<xsl:apply-templates select="exception" />
<xsl:text>

</xsl:text>
</xsl:if>
<xsl:apply-templates select="remarks" />
</xsl:template>
<xsl:template match="Member">
<xsl:text>== </xsl:text>
<xsl:value-of select="@MemberName" />
<xsl:if test="Parameters/*">
<xsl:text>(</xsl:text>
<xsl:for-each select="Parameters/Parameter">
<!-- equivalent to position()=1 but more efficient -->
<xsl:if test="@Name != ../Parameter[1]/@Name">
<xsl:text>, </xsl:text>
</xsl:if>
<xsl:value-of select="@Type" />
</xsl:for-each>
<xsl:text>)</xsl:text>
</xsl:if>
<xsl:text> ==
</xsl:text>
<xsl:apply-templates select="Docs" />
</xsl:template>
<xsl:template match="summary">
<xsl:text>
=== Summary ===
</xsl:text>
<xsl:call-template name="content" />
<xsl:text>

</xsl:text>
</xsl:template>
<xsl:template match="remarks">
<xsl:text>
=== Remarks ===
</xsl:text>
<xsl:call-template name="content" />
<xsl:text>

</xsl:text>
</xsl:template>
<!-- Template for editable content -->
<xsl:template name="content">
<xsl:for-each select="* | text()">
<xsl:choose>
<xsl:when test="@type='table' and name(.)='list'">
<xsl:call-template name="table" />
</xsl:when>
<xsl:when test="name(.)='para' or name(.)='block'">
<xsl:apply-templates />
</xsl:when>
<xsl:when test="*">
<xsl:message>Unexpected element '<xsl:value-of select="name()" />'</xsl:message>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(.)" />
<xsl:text>
</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<!-- inline content -->
<xsl:template match="text()">
<xsl:value-of select="normalize-space (translate(., '
', ''))" />
</xsl:template>
<xsl:template match="paramref">
<xsl:text> </xsl:text><xsl:value-of select="concat('[[', @name, ']]')" /><xsl:text> </xsl:text>
</xsl:template>
<xsl:template match="see">
<xsl:text> </xsl:text>
<xsl:choose>
<xsl:when test="@cref">
<xsl:value-of select="concat('[[', @cref, ' | ', substring (@cref, 3), ']]')" />
</xsl:when>
<xsl:when test="@langword">
<code class='langword'><xsl:value-of select="@langword" /></code>
</xsl:when>
</xsl:choose>
<xsl:text> </xsl:text>
</xsl:template>
<!-- block content -->
<xsl:template match="para">
<xsl:text>
</xsl:text>
<xsl:apply-templates />
<xsl:text>

</xsl:text>
</xsl:template>
<xsl:template match="exception">
<xsl:text>
</xsl:text>
<xsl:text>;</xsl:text>
<xsl:value-of select="substring (@cref, 3)" />
<xsl:text>:</xsl:text>
<xsl:apply-templates />
<xsl:text>

</xsl:text>
</xsl:template>
<xsl:template match="param">
<xsl:text>
</xsl:text>
<xsl:text>;</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>:</xsl:text>
<xsl:apply-templates />
<xsl:text>

</xsl:text>
</xsl:template>
<xsl:template match="block">
<xsl:choose>
<xsl:when test="@subset='none' and @type='note'">
<xsl:text>
</xsl:text>
<xsl:text>:</xsl:text>
<xsl:apply-templates />
<xsl:text>

</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:message>Unexpected block element: subset is '<xsl:value-of select="@subset" />' and type is '<xsl:value-of select="@type" />'</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="list">
<xsl:choose>
<xsl:when test="@type='table'">
<xsl:call-template name="table" />
</xsl:when>
<xsl:otherwise>
<xsl:message>Unexpected list type: <xsl:value-of select="@type" /></xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="table">
<xsl:text>
{| border="1" cellspacing="2"
</xsl:text>
<xsl:if test="listheader">
<xsl:text>! </xsl:text>
<xsl:value-of select="listheader/term" />
<xsl:text> !! </xsl:text>
<xsl:value-of select="listheader/description" />
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:for-each select="item">
<xsl:text>|-
</xsl:text>
<xsl:text>| </xsl:text>
<xsl:value-of select="normalize-space(term)" />
<xsl:text> || </xsl:text>
<xsl:apply-templates select="description" />
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>|}

</xsl:text>
</xsl:template>
</xsl:stylesheet>