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 ===&#xA;</xsl:text>
			<xsl:apply-templates select="param" />
			<xsl:text>&#xA;&#xA;</xsl:text>
		</xsl:if>

		<xsl:if test="exception">
			<xsl:text>=== Exceptions ===&#xA;</xsl:text>
			<xsl:apply-templates select="exception" />
			<xsl:text>&#xA;&#xA;</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> ==&#xA;</xsl:text>
		<xsl:apply-templates select="Docs" />
	</xsl:template>

	<xsl:template match="summary">
		<xsl:text>&#xA;=== Summary ===&#xA;</xsl:text>
		<xsl:call-template name="content" />
		<xsl:text>&#xA;&#xA;</xsl:text>
	</xsl:template>

	<xsl:template match="remarks">
		<xsl:text>&#xA;=== Remarks ===&#xA;</xsl:text>
		<xsl:call-template name="content" />
		<xsl:text>&#xA;&#xA;</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>&#xA;</xsl:text>
				</xsl:otherwise>
			</xsl:choose>
		</xsl:for-each>
	</xsl:template>

	<!-- inline content -->

	<xsl:template match="text()">
		<xsl:value-of select="normalize-space (translate(., '&#xA;', ''))" />
	</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>&#xA;</xsl:text>
		<xsl:apply-templates />
		<xsl:text>&#xA;&#xA;</xsl:text>
	</xsl:template>

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

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

	<xsl:template match="block">
		<xsl:choose>
			<xsl:when test="@subset='none' and @type='note'">
				<xsl:text>&#xA;</xsl:text>
				<xsl:text>:</xsl:text>
				<xsl:apply-templates />
				<xsl:text>&#xA;&#xA;</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>&#xA;{| border="1" cellspacing="2"&#xA;</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>&#xA;</xsl:text>
		</xsl:if>
		<xsl:for-each select="item">
			<xsl:text>|-&#xA;</xsl:text>
			<xsl:text>| </xsl:text>
			<xsl:value-of select="normalize-space(term)" />
			<xsl:text> || </xsl:text>
			<xsl:apply-templates select="description" />
			<xsl:text>&#xA;</xsl:text>
		</xsl:for-each>
		<xsl:text>|}&#xA;&#xA;</xsl:text>
	</xsl:template>

</xsl:stylesheet>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.