diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpString.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpString.java
index 329c45164..2618fb19c 100644
--- a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpString.java
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpString.java
@@ -153,4 +153,9 @@ public class XophpString {
public static byte[] str_replace(byte[] find, byte[] repl, byte[] src) {
return Bry_.Replace(src, find, repl);
}
+ public static byte[] strstr(byte[] src, byte[] find) {
+ int pos = Bry_find_.Find_fwd(src, find);
+ return pos == Bry_find_.Not_found ? null : Bry_.Mid(src, pos, src.length);
+ }
+ public static int strlen(byte[] src) {return src.length;}
}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/XomwMessage.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/XomwMessage.java
index 6be2e6b46..6e103c060 100644
--- a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/XomwMessage.java
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/XomwMessage.java
@@ -14,9 +14,10 @@ GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*;
+import gplx.xowa.mediawiki.languages.*;
/**
* The Message cls provides methods which fulfil two basic services:
-* - fetching interface messages
+* - fetching interfaceIsUserLang messages
* - processing messages into a variety of formats
*
* First implemented with MediaWiki 1.17, the Message cls is intended to
@@ -45,7 +46,7 @@ package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import
* a method. Some of them return a Message instance too so you can chain them.
* You will find below several examples of wfMessage() usage.
*
-* Fetching a message text for interface message:
+* Fetching a message text for interfaceIsUserLang message:
*
* @code
* $button = Xml::button(
@@ -105,7 +106,7 @@ package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import
* ->plain();
* @endcode
*
-* @note You can parse the text only in the content or interface languages
+* @note You can parse the text only in the content or interfaceIsUserLang languages
*
* @section message_compare_old Comparison with old wfMsg* functions:
*
@@ -151,112 +152,123 @@ package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import
*/
public class XomwMessage {
// /** Use message text as-is */
-// static final FORMAT_PLAIN = 'plain';
-// /** Use normal wikitext -> HTML parsing (the result will be wrapped in a block-level HTML tag) */
-// static final FORMAT_BLOCK_PARSE = 'block-parse';
-// /** Use normal wikitext -> HTML parsing but strip the block-level wrapper */
-// static final FORMAT_PARSE = 'parse';
-// /** Transform {{..}} constructs but don't transform to HTML */
-// static final FORMAT_TEXT = 'text';
-// /** Transform {{..}} constructs, HTML-escape the result */
-// static final FORMAT_ESCAPED = 'escaped';
-//
-// /**
-// * Mapping from Message::listParam() types to Language methods.
-// * @var array
-// */
-// protected static $listTypeMap = [
-// 'comma' => 'commaList',
-// 'semicolon' => 'semicolonList',
-// 'pipe' => 'pipeList',
-// 'text' => 'listToText',
-// ];
-//
-// /**
-// * In which language to get this message. True, which is the default,
-// * means the current user language, false content language.
-// *
-// * @var boolean
-// */
-// protected $interface = true;
-//
-// /**
-// * In which language to get this message. Overrides the $interface setting.
-// *
-// * @var Language|boolean Explicit language Object, or false for user language
-// */
-// protected $language = false;
-//
-// /**
-// * @var String The message key. If $keysToTry has more than one element,
-// * this may change to one of the keys to try when fetching the message text.
-// */
-// protected $key;
-//
-// /**
-// * @var String... List of keys to try when fetching the message.
-// */
-// protected $keysToTry;
-//
-// /**
-// * @var array List of parameters which will be substituted into the message.
-// */
-// protected $parameters = [];
-//
-// /**
-// * @var String
-// * @deprecated
-// */
-// protected $format = 'parse';
-//
-// /**
-// * @var boolean Whether database can be used.
-// */
-// protected $useDatabase = true;
-//
-// /**
-// * @var Title Title Object to use as context.
-// */
-// protected $title = null;
-//
-// /**
-// * @var Content Content Object representing the message.
-// */
+ private static final int FORMAT_PLAIN = 0; // 'plain';
+ /** Use normal wikitext -> HTML parsing (the result will be wrapped in a block-level HTML tag) */
+ private static final int FORMAT_BLOCK_PARSE = 1; // 'block-parse';
+ /** Use normal wikitext -> HTML parsing but strip the block-level wrapper */
+ private static final int FORMAT_PARSE = 2; // 'parse';
+ /** Transform {{..}} constructs but don't transform to HTML */
+ private static final int FORMAT_TEXT = 3; // 'text';
+ /** Transform {{..}} constructs, HTML-escape the result */
+ private static final int FORMAT_ESCAPED = 4; // 'escaped';
+
+ /**
+ * Mapping from Message::listParam() types to Language methods.
+ * @var array
+ */
+ private static final Hash_adp listTypeMap = Hash_adp_.New()
+ .Add_and_more("comma", "commaList")
+ .Add_and_more("semicolon", "semicolonList")
+ .Add_and_more("pipe", "pipeList")
+ .Add_and_more("text", "listToText")
+ ;
+
+ /**
+ * In which language to get this message. True, which is the default,
+ * means the current user language, false content language.
+ *
+ * @var boolean
+ */
+ private boolean interfaceIsUserLang = true;
+
+ /**
+ * In which language to get this message. Overrides the $interfaceIsUserLang setting.
+ *
+ * @var Language|boolean Explicit language Object, or false for user language
+ */
+ private XomwLanguage language;
+
+ /**
+ * @var String The message key. If $keysToTry has more than one element,
+ * this may change to one of the keys to try when fetching the message text.
+ */
+ private String key;
+
+ /**
+ * @var String... List of keys to try when fetching the message.
+ */
+ private String[] keysToTry;
+
+ /**
+ * @var array List of parameters which will be substituted into the message.
+ */
+ private List_adp parameters = List_adp_.New();
+
+ /**
+ * @var String
+ * @deprecated
+ */
+ private int format = FORMAT_PARSE;
+
+ /**
+ * @var boolean Whether database can be used.
+ */
+ private boolean useDatabase = true;
+
+ /**
+ * @var Title Title Object to use as context.
+ */
+ private XomwTitle title = null;
+
+ /**
+ * @var Content Content Object representing the message.
+ */
// protected $content = null;
-//
-// /**
-// * @var String
-// */
-// protected $message;
-//
+
+ /**
+ * @var String
+ */
+ private byte[] message;
+
+ public void CompilerAppeasement() {
+ this.language = null; this.key = null; this.keysToTry = null; this.message = null;
+ Tfds.Write(interfaceIsUserLang, language, key, keysToTry, format, useDatabase, title, message, listTypeMap, parameters);
+ Tfds.Write(FORMAT_BLOCK_PARSE, FORMAT_ESCAPED, FORMAT_PARSE, FORMAT_PLAIN, FORMAT_TEXT);
+ }
+ public XomwMessage(byte[] textBry) {
+ this.textBry = textBry;
+ }
+ public byte[] text() {return textBry;} private byte[] textBry;
+ public byte[] escaped() {throw Err_.new_unimplemented();}
+
// /**
// * @since 1.17
-// * @param String|String[]|MessageSpecifier $key Message key, or array of
+// * @param String|String[]|MessageSpecifier key Message key, or array of
// * message keys to try and use the first non-empty message for, or a
// * MessageSpecifier to copy from.
// * @param array $params Message parameters.
// * @param Language $language [optional] Language to use (defaults to current user language).
// * @throws InvalidArgumentException
// */
-// public function __construct( $key, $params = [], Language $language = null ) {
-// if ( $key instanceof MessageSpecifier ) {
+// public function __construct( key, $params = [], Language $language = null ) {
+// if ( key instanceof MessageSpecifier ) {
// if ( $params ) {
// throw new InvalidArgumentException(
-// '$params must be empty if $key is a MessageSpecifier'
+// '$params must be empty if key is a MessageSpecifier'
// );
// }
-// $params = $key->getParams();
-// $key = $key->getKey();
+// $params = key->getParams();
+// key = key->getKey();
// }
//
-// if ( !is_string( $key ) && !is_array( $key ) ) {
-// throw new InvalidArgumentException( '$key must be a String or an array' );
+// if ( !is_string( key ) && !is_array( key ) ) {
+// throw new InvalidArgumentException( 'key must be a String or an array' );
// }
//
-// $this->keysToTry = (array)$key;
+// $this->keysToTry = (array)key;
//
// if ( empty( $this->keysToTry ) ) {
-// throw new InvalidArgumentException( '$key must not be an empty list' );
+// throw new InvalidArgumentException( 'key must not be an empty list' );
// }
//
// $this->key = reset( $this->keysToTry );
@@ -274,7 +286,7 @@ public class XomwMessage {
// */
// public function serialize() {
// return serialize( [
-// 'interface' => $this->interface,
+// 'interfaceIsUserLang' => $this->interfaceIsUserLang,
// 'language' => $this->language ? $this->language->getCode() : false,
// 'key' => $this->key,
// 'keysToTry' => $this->keysToTry,
@@ -292,7 +304,7 @@ public class XomwMessage {
// */
// public function unserialize( $serialized ) {
// $data = unserialize( $serialized );
-// $this->interface = $data['interface'];
+// $this->interfaceIsUserLang = $data['interfaceIsUserLang'];
// $this->key = $data['key'];
// $this->keysToTry = $data['keysToTry'];
// $this->parameters = $data['parameters'];
@@ -380,15 +392,15 @@ public class XomwMessage {
// *
// * @since 1.17
// *
-// * @param String|String[]|MessageSpecifier $key
+// * @param String|String[]|MessageSpecifier key
// * @param mixed $param,... Parameters as strings.
// *
// * @return Message
// */
-// public static function newFromKey( $key /*...*/ ) {
+// public static function newFromKey( key /*...*/ ) {
// $params = func_get_args();
// array_shift( $params );
-// return new self( $key, $params );
+// return new self( key, $params );
// }
//
// /**
@@ -464,16 +476,16 @@ public class XomwMessage {
// public function getTitle() {
// global $wgContLang, $wgForceUIMsgAsContentMsg;
//
-// $title = $this->key;
+// title = $this->key;
// if (
// !$this->language->equals( $wgContLang )
// && in_array( $this->key, (array)$wgForceUIMsgAsContentMsg )
// ) {
// $code = $this->language->getCode();
-// $title .= '/' . $code;
+// title .= '/' . $code;
// }
//
-// return Title::makeTitle( NS_MEDIAWIKI, $wgContLang->ucfirst( strtr( $title, ' ', '_' ) ) );
+// return Title::makeTitle( NS_MEDIAWIKI, $wgContLang->ucfirst( strtr( title, ' ', '_' ) ) );
// }
//
// /**
@@ -497,8 +509,8 @@ public class XomwMessage {
// if ( $args[0] === [] ) {
// $args = [];
// } else {
-// foreach ( $args[0] as $key => $value ) {
-// if ( is_int( $key ) ) {
+// foreach ( $args[0] as key => $value ) {
+// if ( is_int( key ) ) {
// $args = $args[0];
// break;
// }
@@ -702,7 +714,7 @@ public class XomwMessage {
// public function setContext( IContextSource $context ) {
// $this->inLanguage( $context->getLanguage() );
// $this->title( $context->getTitle() );
-// $this->interface = true;
+// $this->interfaceIsUserLang = true;
//
// return $this;
// }
@@ -710,7 +722,7 @@ public class XomwMessage {
// /**
// * Request the message in any language that is supported.
// *
-// * As a side effect interface message status is unconditionally
+// * As a side effect interfaceIsUserLang message status is unconditionally
// * turned off.
// *
// * @since 1.17
@@ -734,7 +746,7 @@ public class XomwMessage {
// );
// }
// $this->message = null;
-// $this->interface = false;
+// $this->interfaceIsUserLang = false;
// return $this;
// }
@@ -760,17 +772,17 @@ public class XomwMessage {
}
// /**
-// * Allows manipulating the interface message flag directly.
+// * Allows manipulating the interfaceIsUserLang message flag directly.
// * Can be used to restore the flag after setting a language.
// *
// * @since 1.20
// *
-// * @param boolean $interface
+// * @param boolean $interfaceIsUserLang
// *
// * @return Message $this
// */
-// public function setInterfaceMessageFlag( $interface ) {
-// $this->interface = (boolean)$interface;
+// public function setInterfaceMessageFlag( $interfaceIsUserLang ) {
+// $this->interfaceIsUserLang = (boolean)$interfaceIsUserLang;
// return $this;
// }
//
@@ -794,12 +806,12 @@ public class XomwMessage {
// *
// * @since 1.18
// *
-// * @param Title $title
+// * @param Title title
// *
// * @return Message $this
// */
-// public function title( $title ) {
-// $this->title = $title;
+// public function title( title ) {
+// $this->title = title;
// return $this;
// }
//
@@ -879,7 +891,7 @@ public class XomwMessage {
//
// /**
// * Magic method implementation of the above (for PHP >= 5.2.0), so we can do, eg:
-// * $foo = new Message( $key );
+// * $foo = new Message( key );
// * $String = "$foo";
// *
// * @since 1.18
@@ -1176,7 +1188,7 @@ public class XomwMessage {
// // Cache depends on these parameters
// $msg->message = null;
// }
-// $msg->interface = $this->interface;
+// $msg->interfaceIsUserLang = $this->interfaceIsUserLang;
// $msg->language = $this->language;
// $msg->useDatabase = $this->useDatabase;
// $msg->title = $this->title;
@@ -1210,7 +1222,7 @@ public class XomwMessage {
// $String,
// $this->title,
// /*linestart*/true,
-// $this->interface,
+// $this->interfaceIsUserLang,
// $this->getLanguage()
// );
//
@@ -1229,7 +1241,7 @@ public class XomwMessage {
// protected function transformText( $String ) {
// return MessageCache::singleton()->transform(
// $String,
-// $this->interface,
+// $this->interfaceIsUserLang,
// $this->getLanguage(),
// $this->title
// );
@@ -1247,16 +1259,16 @@ public class XomwMessage {
// if ( $this->message === null ) {
// $cache = MessageCache::singleton();
//
-// foreach ( $this->keysToTry as $key ) {
-// $message = $cache->get( $key, $this->useDatabase, $this->getLanguage() );
+// foreach ( $this->keysToTry as key ) {
+// $message = $cache->get( key, $this->useDatabase, $this->getLanguage() );
// if ( $message !== false && $message !== '' ) {
// break;
// }
// }
//
// // NOTE: The constructor makes sure keysToTry isn't empty,
-// // so we know that $key and $message are initialized.
-// $this->key = $key;
+// // so we know that key and $message are initialized.
+// $this->key = key;
// $this->message = $message;
// }
// return $this->message;
@@ -1337,9 +1349,4 @@ public class XomwMessage {
// $vars = $this->getLanguage()->$func( $vars );
// return $this->extractParam( new RawMessage( $vars, $params ), $format );
// }
- public XomwMessage(byte[] textBry) {
- this.textBry = textBry;
- }
- public byte[] text() {return textBry;} private byte[] textBry;
- public byte[] escaped() {throw Err_.new_unimplemented();}
}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/XomwAbstractContent.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/XomwAbstractContent.java
new file mode 100644
index 000000000..180dffc30
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/XomwAbstractContent.java
@@ -0,0 +1,526 @@
+/*
+XOWA: the XOWA Offline Wiki Application
+Copyright (C) 2012-2017 gnosygnu@gmail.com
+
+XOWA is licensed under the terms of the General Public License (GPL) Version 3,
+or alternatively under the terms of the Apache License Version 2.0.
+
+You may use XOWA according to either of these licenses as is most appropriate
+for your project on a case-by-case basis.
+
+The terms of each license can be found in the source code repository:
+
+GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
+Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
+*/
+package gplx.xowa.mediawiki.includes.content; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*;
+// model_id = $modelId;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @see Content::getModel
+// */
+// public function getModel() {
+// return $this->model_id;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @param String $modelId The model to check
+// *
+// * @throws MWException If the provided ID is not the ID of the content model supported by this
+// * Content Object.
+// */
+// protected function checkModelID( $modelId ) {
+// if ( $modelId !== $this->model_id ) {
+// throw new MWException(
+// "Bad content model: " .
+// "expected {$this->model_id} " .
+// "but got $modelId."
+// );
+// }
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @see Content::getContentHandler
+// */
+// public function getContentHandler() {
+// return ContentHandler::getForContent( $this );
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @see Content::getDefaultFormat
+// */
+// public function getDefaultFormat() {
+// return $this->getContentHandler()->getDefaultFormat();
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @see Content::getSupportedFormats
+// */
+// public function getSupportedFormats() {
+// return $this->getContentHandler()->getSupportedFormats();
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @param String $format
+// *
+// * @return boolean
+// *
+// * @see Content::isSupportedFormat
+// */
+// public function isSupportedFormat( $format ) {
+// if ( !$format ) {
+// return true; // this means "use the default"
+// }
+//
+// return $this->getContentHandler()->isSupportedFormat( $format );
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @param String $format The serialization format to check.
+// *
+// * @throws MWException If the format is not supported by this content handler.
+// */
+// protected function checkFormat( $format ) {
+// if ( !$this->isSupportedFormat( $format ) ) {
+// throw new MWException(
+// "Format $format is not supported for content model " .
+// $this->getModel()
+// );
+// }
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @param String $format
+// *
+// * @return String
+// *
+// * @see Content::serialize
+// */
+// public function serialize( $format = null ) {
+// return $this->getContentHandler()->serializeContent( $this, $format );
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @return boolean
+// *
+// * @see Content::isEmpty
+// */
+// public function isEmpty() {
+// return $this->getSize() === 0;
+// }
+//
+// /**
+// * Subclasses may override this to implement (light weight) validation.
+// *
+// * @since 1.21
+// *
+// * @return boolean Always true.
+// *
+// * @see Content::isValid
+// */
+// public function isValid() {
+// return true;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @param Content $that
+// *
+// * @return boolean
+// *
+// * @see Content::equals
+// */
+// public function equals( Content $that = null ) {
+// if ( is_null( $that ) ) {
+// return false;
+// }
+//
+// if ( $that === $this ) {
+// return true;
+// }
+//
+// if ( $that->getModel() !== $this->getModel() ) {
+// return false;
+// }
+//
+// return $this->getNativeData() === $that->getNativeData();
+// }
+//
+// /**
+// * Returns a list of DataUpdate objects for recording information about this
+// * Content in some secondary data store.
+// *
+// * This default implementation returns a LinksUpdate Object and calls the
+// * SecondaryDataUpdates hook.
+// *
+// * Subclasses may override this to determine the secondary data updates more
+// * efficiently, preferably without the need to generate a parser output Object.
+// * They should however make sure to call SecondaryDataUpdates to give extensions
+// * a chance to inject additional updates.
+// *
+// * @since 1.21
+// *
+// * @param Title $title
+// * @param Content $old
+// * @param boolean $recursive
+// * @param ParserOutput $parserOutput
+// *
+// * @return DataUpdate[]
+// *
+// * @see Content::getSecondaryDataUpdates()
+// */
+// public function getSecondaryDataUpdates( Title $title, Content $old = null,
+// $recursive = true, ParserOutput $parserOutput = null
+// ) {
+// if ( $parserOutput === null ) {
+// $parserOutput = $this->getParserOutput( $title, null, null, false );
+// }
+//
+// $updates = [
+// new LinksUpdate( $title, $parserOutput, $recursive )
+// ];
+//
+// Hooks::run( 'SecondaryDataUpdates', [ $title, $old, $recursive, $parserOutput, &$updates ] );
+//
+// return $updates;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @return Title[]|null
+// *
+// * @see Content::getRedirectChain
+// */
+// public function getRedirectChain() {
+// global $wgMaxRedirects;
+// $title = $this->getRedirectTarget();
+// if ( is_null( $title ) ) {
+// return null;
+// }
+// // recursive check to follow double redirects
+// $recurse = $wgMaxRedirects;
+// $titles = [ $title ];
+// while ( --$recurse > 0 ) {
+// if ( $title->isRedirect() ) {
+// $page = WikiPage::factory( $title );
+// $newtitle = $page->getRedirectTarget();
+// } else {
+// break;
+// }
+// // Redirects to some special pages are not permitted
+// if ( $newtitle instanceof Title && $newtitle->isValidRedirectTarget() ) {
+// // The new title passes the checks, so make that our current
+// // title so that further recursion can be checked
+// $title = $newtitle;
+// $titles[] = $newtitle;
+// } else {
+// break;
+// }
+// }
+//
+// return $titles;
+// }
+//
+// /**
+// * Subclasses that implement redirects should override this.
+// *
+// * @since 1.21
+// *
+// * @return Title|null
+// *
+// * @see Content::getRedirectTarget
+// */
+// public function getRedirectTarget() {
+// return null;
+// }
+//
+// /**
+// * @note Migrated here from Title::newFromRedirectRecurse.
+// *
+// * @since 1.21
+// *
+// * @return Title|null
+// *
+// * @see Content::getUltimateRedirectTarget
+// */
+// public function getUltimateRedirectTarget() {
+// $titles = $this->getRedirectChain();
+//
+// return $titles ? array_pop( $titles ) : null;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @return boolean
+// *
+// * @see Content::isRedirect
+// */
+// public function isRedirect() {
+// return $this->getRedirectTarget() !== null;
+// }
+//
+// /**
+// * This default implementation always returns $this.
+// * Subclasses that implement redirects should override this.
+// *
+// * @since 1.21
+// *
+// * @param Title $target
+// *
+// * @return Content $this
+// *
+// * @see Content::updateRedirect
+// */
+// public function updateRedirect( Title $target ) {
+// return $this;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @return null
+// *
+// * @see Content::getSection
+// */
+// public function getSection( $sectionId ) {
+// return null;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @return null
+// *
+// * @see Content::replaceSection
+// */
+// public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) {
+// return null;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @return Content $this
+// *
+// * @see Content::preSaveTransform
+// */
+// public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+// return $this;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @return Content $this
+// *
+// * @see Content::addSectionHeader
+// */
+// public function addSectionHeader( $header ) {
+// return $this;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @return Content $this
+// *
+// * @see Content::preloadTransform
+// */
+// public function preloadTransform( Title $title, ParserOptions $popts, $params = [] ) {
+// return $this;
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @return Status
+// *
+// * @see Content::prepareSave
+// */
+// public function prepareSave( WikiPage $page, $flags, $parentRevId, User $user ) {
+// if ( $this->isValid() ) {
+// return Status::newGood();
+// } else {
+// return Status::newFatal( "invalid-content-data" );
+// }
+// }
+//
+// /**
+// * @since 1.21
+// *
+// * @param WikiPage $page
+// * @param ParserOutput $parserOutput
+// *
+// * @return LinksDeletionUpdate[]
+// *
+// * @see Content::getDeletionUpdates
+// */
+// public function getDeletionUpdates( WikiPage $page, ParserOutput $parserOutput = null ) {
+// return [
+// new LinksDeletionUpdate( $page ),
+// ];
+// }
+//
+// /**
+// * This default implementation always returns false. Subclasses may override
+// * this to supply matching logic.
+// *
+// * @since 1.21
+// *
+// * @param MagicWord $word
+// *
+// * @return boolean Always false.
+// *
+// * @see Content::matchMagicWord
+// */
+// public function matchMagicWord( MagicWord $word ) {
+// return false;
+// }
+//
+// /**
+// * This super implementation calls the hook ConvertContent to enable custom conversions.
+// * Subclasses may override this to implement conversion for "their" content model.
+// *
+// * @param String $toModel
+// * @param String $lossy
+// *
+// * @return Content|boolean
+// *
+// * @see Content::convert()
+// */
+// public function convert( $toModel, $lossy = '' ) {
+// if ( $this->getModel() === $toModel ) {
+// // nothing to do, shorten out.
+// return $this;
+// }
+//
+// $lossy = ( $lossy === 'lossy' ); // String flag, convert to boolean for convenience
+// $result = false;
+//
+// Hooks::run( 'ConvertContent', [ $this, $toModel, $lossy, &$result ] );
+//
+// return $result;
+// }
+//
+// /**
+// * Returns a ParserOutput Object containing information derived from this content.
+// * Most importantly, unless $generateHtml was false, the return value contains an
+// * HTML representation of the content.
+// *
+// * Subclasses that want to control the parser output may override this, but it is
+// * preferred to override fillParserOutput() instead.
+// *
+// * Subclasses that override getParserOutput() itself should take care to call the
+// * ContentGetParserOutput hook.
+// *
+// * @since 1.24
+// *
+// * @param Title $title Context title for parsing
+// * @param int|null $revId Revision ID (for {{REVISIONID}})
+// * @param ParserOptions|null $options Parser options
+// * @param boolean $generateHtml Whether or not to generate HTML
+// *
+// * @return ParserOutput Containing information derived from this content.
+// */
+// public function getParserOutput( Title $title, $revId = null,
+// ParserOptions $options = null, $generateHtml = true
+// ) {
+// if ( $options === null ) {
+// $options = $this->getContentHandler()->makeParserOptions( 'canonical' );
+// }
+//
+// $po = new ParserOutput();
+//
+// if ( Hooks::run( 'ContentGetParserOutput',
+// [ $this, $title, $revId, $options, $generateHtml, &$po ] ) ) {
+//
+// // Save and restore the old value, just in case something is reusing
+// // the ParserOptions Object in some weird way.
+// $oldRedir = $options->getRedirectTarget();
+// $options->setRedirectTarget( $this->getRedirectTarget() );
+// $this->fillParserOutput( $title, $revId, $options, $generateHtml, $po );
+// $options->setRedirectTarget( $oldRedir );
+// }
+//
+// Hooks::run( 'ContentAlterParserOutput', [ $this, $title, $po ] );
+//
+// return $po;
+// }
+//
+// /**
+// * Fills the provided ParserOutput with information derived from the content.
+// * Unless $generateHtml was false, this includes an HTML representation of the content.
+// *
+// * This is called by getParserOutput() after consulting the ContentGetParserOutput hook.
+// * Subclasses are expected to override this method (or getParserOutput(), if need be).
+// * Subclasses of TextContent should generally override getHtml() instead.
+// *
+// * This placeholder implementation always throws an exception.
+// *
+// * @since 1.24
+// *
+// * @param Title $title Context title for parsing
+// * @param int|null $revId Revision ID (for {{REVISIONID}})
+// * @param ParserOptions $options Parser options
+// * @param boolean $generateHtml Whether or not to generate HTML
+// * @param ParserOutput &$output The output Object to fill (reference).
+// *
+// * @throws MWException
+// */
+// protected function fillParserOutput( Title $title, $revId,
+// ParserOptions $options, $generateHtml, ParserOutput &$output
+// ) {
+// // Don't make abstract, so subclasses that override getParserOutput() directly don't fail.
+// throw new MWException( 'Subclasses of AbstractContent must override fillParserOutput!' );
+// }
+// }
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/XomwContent.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/XomwContent.java
new file mode 100644
index 000000000..d078b7050
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/XomwContent.java
@@ -0,0 +1,519 @@
+/*
+XOWA: the XOWA Offline Wiki Application
+Copyright (C) 2012-2017 gnosygnu@gmail.com
+
+XOWA is licensed under the terms of the General Public License (GPL) Version 3,
+or alternatively under the terms of the Apache License Version 2.0.
+
+You may use XOWA according to either of these licenses as is most appropriate
+for your project on a case-by-case basis.
+
+The terms of each license can be found in the source code repository:
+
+GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
+Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
+*/
+package gplx.xowa.mediawiki.includes.content; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*;
+// /**
+// * A content Object represents page content, e.g. the text to show on a page.
+// * Content objects have no knowledge about how they relate to wiki pages.
+// */
+//
+// /**
+// * Base interface for content objects.
+// *
+// * @ingroup Content
+// */
+// interface Content {
+//
+// /**
+// * @since 1.21
+// *
+// * @return String A String representing the content in a way useful for
+// * building a full text search index. If no useful representation exists,
+// * this method returns an empty String.
+// *
+// * @todo Test that this actually works
+// * @todo Make sure this also works with LuceneSearch / WikiSearch
+// */
+// public function getTextForSearchIndex();
+//
+// /**
+// * @since 1.21
+// *
+// * @return String|boolean The wikitext to include when another page includes this
+// * content, or false if the content is not includable in a wikitext page.
+// *
+// * @todo Allow native handling, bypassing wikitext representation, like
+// * for includable special pages.
+// * @todo Allow transclusion into other content models than Wikitext!
+// * @todo Used in WikiPage and MessageCache to get message text. Not so
+// * nice. What should we use instead?!
+// */
+// public function getWikitextForTransclusion();
+//
+// /**
+// * Returns a textual representation of the content suitable for use in edit
+// * summaries and log messages.
+// *
+// * @since 1.21
+// *
+// * @param int $maxLength Maximum length of the summary text.
+// *
+// * @return String The summary text.
+// */
+// public function getTextForSummary( $maxLength = 250 );
+//
+// /**
+// * Returns native representation of the data. Interpretation depends on
+// * the data model used, as given by getDataModel().
+// *
+// * @since 1.21
+// *
+// * @return mixed The native representation of the content. Could be a
+// * String, a nested array structure, an Object, a binary blob...
+// * anything, really.
+// *
+// * @note Caller must be aware of content model!
+// */
+// public function getNativeData();
+//
+// /**
+// * Returns the content's nominal size in "bogo-bytes".
+// *
+// * @return int
+// */
+// public function getSize();
+//
+// /**
+// * Returns the ID of the content model used by this Content Object.
+// * Corresponds to the CONTENT_MODEL_XXX constants.
+// *
+// * @since 1.21
+// *
+// * @return String The model id
+// */
+// public function getModel();
+//
+// /**
+// * Convenience method that returns the ContentHandler singleton for handling
+// * the content model that this Content Object uses.
+// *
+// * Shorthand for ContentHandler::getForContent( $this )
+// *
+// * @since 1.21
+// *
+// * @return ContentHandler
+// */
+// public function getContentHandler();
+//
+// /**
+// * Convenience method that returns the default serialization format for the
+// * content model that this Content Object uses.
+// *
+// * Shorthand for $this->getContentHandler()->getDefaultFormat()
+// *
+// * @since 1.21
+// *
+// * @return String
+// */
+// public function getDefaultFormat();
+//
+// /**
+// * Convenience method that returns the list of serialization formats
+// * supported for the content model that this Content Object uses.
+// *
+// * Shorthand for $this->getContentHandler()->getSupportedFormats()
+// *
+// * @since 1.21
+// *
+// * @return String[] List of supported serialization formats
+// */
+// public function getSupportedFormats();
+//
+// /**
+// * Returns true if $format is a supported serialization format for this
+// * Content Object, false if it isn't.
+// *
+// * Note that this should always return true if $format is null, because null
+// * stands for the default serialization.
+// *
+// * Shorthand for $this->getContentHandler()->isSupportedFormat( $format )
+// *
+// * @since 1.21
+// *
+// * @param String $format The serialization format to check.
+// *
+// * @return boolean Whether the format is supported
+// */
+// public function isSupportedFormat( $format );
+//
+// /**
+// * Convenience method for serializing this Content Object.
+// *
+// * Shorthand for $this->getContentHandler()->serializeContent( $this, $format )
+// *
+// * @since 1.21
+// *
+// * @param String $format The desired serialization format, or null for the default format.
+// *
+// * @return String Serialized form of this Content Object.
+// */
+// public function serialize( $format = null );
+//
+// /**
+// * Returns true if this Content Object represents empty content.
+// *
+// * @since 1.21
+// *
+// * @return boolean Whether this Content Object is empty
+// */
+// public function isEmpty();
+//
+// /**
+// * Returns whether the content is valid. This is intended for local validity
+// * checks, not considering global consistency.
+// *
+// * Content needs to be valid before it can be saved.
+// *
+// * This default implementation always returns true.
+// *
+// * @since 1.21
+// *
+// * @return boolean
+// */
+// public function isValid();
+//
+// /**
+// * Returns true if this Content objects is conceptually equivalent to the
+// * given Content Object.
+// *
+// * Contract:
+// *
+// * - Will return false if $that is null.
+// * - Will return true if $that === $this.
+// * - Will return false if $that->getModel() != $this->getModel().
+// * - Will return false if $that->getNativeData() is not equal to $this->getNativeData(),
+// * where the meaning of "equal" depends on the actual data model.
+// *
+// * Implementations should be careful to make equals() transitive and reflexive:
+// *
+// * - $a->equals( $b ) <=> $b->equals( $a )
+// * - $a->equals( $b ) && $b->equals( $c ) ==> $a->equals( $c )
+// *
+// * @since 1.21
+// *
+// * @param Content $that The Content Object to compare to.
+// *
+// * @return boolean True if this Content Object is equal to $that, false otherwise.
+// */
+// public function equals( Content $that = null );
+//
+// /**
+// * Return a copy of this Content Object. The following must be true for the
+// * Object returned:
+// *
+// * if $copy = $original->copy()
+// *
+// * - get_class($original) === get_class($copy)
+// * - $original->getModel() === $copy->getModel()
+// * - $original->equals( $copy )
+// *
+// * If and only if the Content Object is immutable, the copy() method can and
+// * should return $this. That is, $copy === $original may be true, but only
+// * for immutable content objects.
+// *
+// * @since 1.21
+// *
+// * @return Content A copy of this Object
+// */
+// public function copy();
+//
+// /**
+// * Returns true if this content is countable as a "real" wiki page, provided
+// * that it's also in a countable location (e.g. a current revision in the
+// * main namespace).
+// *
+// * @since 1.21
+// *
+// * @param boolean|null $hasLinks If it is known whether this content contains
+// * links, provide this information here, to avoid redundant parsing to
+// * find out.
+// *
+// * @return boolean
+// */
+// public function isCountable( $hasLinks = null );
+//
+// /**
+// * Parse the Content Object and generate a ParserOutput from the result.
+// * $result->getText() can be used to obtain the generated HTML. If no HTML
+// * is needed, $generateHtml can be set to false; in that case,
+// * $result->getText() may return null.
+// *
+// * @note To control which options are used in the cache key for the
+// * generated parser output, implementations of this method
+// * may call ParserOutput::recordOption() on the output Object.
+// *
+// * @param Title $title The page title to use as a context for rendering.
+// * @param int $revId Optional revision ID being rendered.
+// * @param ParserOptions $options Any parser options.
+// * @param boolean $generateHtml Whether to generate HTML (default: true). If false,
+// * the result of calling getText() on the ParserOutput Object returned by
+// * this method is undefined.
+// *
+// * @since 1.21
+// *
+// * @return ParserOutput
+// */
+// public function getParserOutput( Title $title, $revId = null,
+// ParserOptions $options = null, $generateHtml = true );
+//
+// // TODO: make RenderOutput and RenderOptions super classes
+//
+// /**
+// * Returns a list of DataUpdate objects for recording information about this
+// * Content in some secondary data store. If the optional second argument,
+// * $old, is given, the updates may model only the changes that need to be
+// * made to replace information about the old content with information about
+// * the new content.
+// *
+// * This default implementation calls
+// * $this->getParserOutput( $content, $title, null, null, false ),
+// * and then calls getSecondaryDataUpdates( $title, $recursive ) on the
+// * resulting ParserOutput Object.
+// *
+// * Subclasses may implement this to determine the necessary updates more
+// * efficiently, or make use of information about the old content.
+// *
+// * @note Implementations should call the SecondaryDataUpdates hook, like
+// * AbstractContent does.
+// *
+// * @param Title $title The context for determining the necessary updates
+// * @param Content $old An optional Content Object representing the
+// * previous content, i.e. the content being replaced by this Content
+// * Object.
+// * @param boolean $recursive Whether to include recursive updates (default:
+// * false).
+// * @param ParserOutput $parserOutput Optional ParserOutput Object.
+// * Provide if you have one handy, to avoid re-parsing of the content.
+// *
+// * @return DataUpdate[] A list of DataUpdate objects for putting information
+// * about this content Object somewhere.
+// *
+// * @since 1.21
+// */
+// public function getSecondaryDataUpdates( Title $title, Content $old = null,
+// $recursive = true, ParserOutput $parserOutput = null );
+//
+// /**
+// * Construct the redirect destination from this content and return an
+// * array of Titles, or null if this content doesn't represent a redirect.
+// * The last element in the array is the final destination after all redirects
+// * have been resolved (up to $wgMaxRedirects times).
+// *
+// * @since 1.21
+// *
+// * @return Title[]|null List of Titles, with the destination last.
+// */
+// public function getRedirectChain();
+//
+// /**
+// * Construct the redirect destination from this content and return a Title,
+// * or null if this content doesn't represent a redirect.
+// * This will only return the immediate redirect target, useful for
+// * the redirect table and other checks that don't need full recursion.
+// *
+// * @since 1.21
+// *
+// * @return Title|null The corresponding Title.
+// */
+// public function getRedirectTarget();
+//
+// /**
+// * Construct the redirect destination from this content and return the
+// * Title, or null if this content doesn't represent a redirect.
+// *
+// * This will recurse down $wgMaxRedirects times or until a non-redirect
+// * target is hit in order to provide (hopefully) the Title of the final
+// * destination instead of another redirect.
+// *
+// * There is usually no need to override the default behavior, subclasses that
+// * want to implement redirects should override getRedirectTarget().
+// *
+// * @since 1.21
+// *
+// * @return Title|null
+// */
+// public function getUltimateRedirectTarget();
+//
+// /**
+// * Returns whether this Content represents a redirect.
+// * Shorthand for getRedirectTarget() !== null.
+// *
+// * @since 1.21
+// *
+// * @return boolean
+// */
+// public function isRedirect();
+//
+// /**
+// * If this Content Object is a redirect, this method updates the redirect target.
+// * Otherwise, it does nothing.
+// *
+// * @since 1.21
+// *
+// * @param Title $target The new redirect target
+// *
+// * @return Content A new Content Object with the updated redirect (or $this
+// * if this Content Object isn't a redirect)
+// */
+// public function updateRedirect( Title $target );
+//
+// /**
+// * Returns the section with the given ID.
+// *
+// * @since 1.21
+// *
+// * @param String|int $sectionId Section identifier as a number or String
+// * (e.g. 0, 1 or 'T-1'). The ID "0" retrieves the section before the first heading, "1" the
+// * text between the first heading (included) and the second heading (excluded), etc.
+// *
+// * @return Content|boolean|null The section, or false if no such section
+// * exist, or null if sections are not supported.
+// */
+// public function getSection( $sectionId );
+//
+// /**
+// * Replaces a section of the content and returns a Content Object with the
+// * section replaced.
+// *
+// * @since 1.21
+// *
+// * @param String|int|null|boolean $sectionId Section identifier as a number or String
+// * (e.g. 0, 1 or 'T-1'), null/false or an empty String for the whole page
+// * or 'new' for a new section.
+// * @param Content $with New content of the section
+// * @param String $sectionTitle New section's subject, only if $section is 'new'
+// *
+// * @return String|null Complete article text, or null if error
+// */
+// public function replaceSection( $sectionId, Content $with, $sectionTitle = '' );
+//
+// /**
+// * Returns a Content Object with pre-save transformations applied (or this
+// * Object if no transformations apply).
+// *
+// * @since 1.21
+// *
+// * @param Title $title
+// * @param User $user
+// * @param ParserOptions $parserOptions
+// *
+// * @return Content
+// */
+// public function preSaveTransform( Title $title, User $user, ParserOptions $parserOptions );
+//
+// /**
+// * Returns a new WikitextContent Object with the given section heading
+// * prepended, if supported. The default implementation just returns this
+// * Content Object unmodified, ignoring the section header.
+// *
+// * @since 1.21
+// *
+// * @param String $header
+// *
+// * @return Content
+// */
+// public function addSectionHeader( $header );
+//
+// /**
+// * Returns a Content Object with preload transformations applied (or this
+// * Object if no transformations apply).
+// *
+// * @since 1.21
+// *
+// * @param Title $title
+// * @param ParserOptions $parserOptions
+// * @param array $params
+// *
+// * @return Content
+// */
+// public function preloadTransform( Title $title, ParserOptions $parserOptions, $params = [] );
+//
+// /**
+// * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent() and in
+// * similar places.
+// *
+// * This may be used to check the content's consistency with global state. This function should
+// * NOT write any information to the database.
+// *
+// * Note that this method will usually be called inside the same transaction
+// * bracket that will be used to save the new revision.
+// *
+// * Note that this method is called before any update to the page table is
+// * performed. This means that $page may not yet know a page ID.
+// *
+// * @since 1.21
+// *
+// * @param WikiPage $page The page to be saved.
+// * @param int $flags Bitfield for use with EDIT_XXX constants, see WikiPage::doEditContent()
+// * @param int $parentRevId The ID of the current revision
+// * @param User $user
+// *
+// * @return Status A status Object indicating whether the content was
+// * successfully prepared for saving. If the returned status indicates
+// * an error, a rollback will be performed and the transaction aborted.
+// *
+// * @see WikiPage::doEditContent()
+// */
+// public function prepareSave( WikiPage $page, $flags, $parentRevId, User $user );
+//
+// /**
+// * Returns a list of updates to perform when this content is deleted.
+// * The necessary updates may be taken from the Content Object, or depend on
+// * the current state of the database.
+// *
+// * @since 1.21
+// *
+// * @param WikiPage $page The deleted page
+// * @param ParserOutput $parserOutput Optional parser output Object
+// * for efficient access to meta-information about the content Object.
+// * Provide if you have one handy.
+// *
+// * @return DataUpdate[] A list of DataUpdate instances that will clean up the
+// * database after deletion.
+// */
+// public function getDeletionUpdates( WikiPage $page,
+// ParserOutput $parserOutput = null );
+//
+// /**
+// * Returns true if this Content Object matches the given magic word.
+// *
+// * @since 1.21
+// *
+// * @param MagicWord $word The magic word to match
+// *
+// * @return boolean Whether this Content Object matches the given magic word.
+// */
+// public function matchMagicWord( MagicWord $word );
+//
+// /**
+// * Converts this content Object into another content Object with the given content model,
+// * if that is possible.
+// *
+// * @param String $toModel The desired content model, use the CONTENT_MODEL_XXX flags.
+// * @param String $lossy Optional flag, set to "lossy" to allow lossy conversion. If lossy
+// * conversion is not allowed, full round-trip conversion is expected to work without losing
+// * information.
+// *
+// * @return Content|boolean A content Object with the content model $toModel, or false if
+// * that conversion is not supported.
+// */
+// public function convert( $toModel, $lossy = '' );
+// // @todo ImagePage and CategoryPage interfere with per-content action handlers
+// // @todo nice&sane integration of GeSHi syntax highlighting
+// // [11:59] Hooks are ugly; make CodeHighlighter interface and a
+// // config to set the class which handles syntax highlighting
+// // [12:00] And default it to a DummyHighlighter
+//
+// }
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/XomwMessageContent.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/XomwMessageContent.java
new file mode 100644
index 000000000..8cd077220
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/XomwMessageContent.java
@@ -0,0 +1,168 @@
+/*
+XOWA: the XOWA Offline Wiki Application
+Copyright (C) 2012-2017 gnosygnu@gmail.com
+
+XOWA is licensed under the terms of the General Public License (GPL) Version 3,
+or alternatively under the terms of the Apache License Version 2.0.
+
+You may use XOWA according to either of these licenses as is most appropriate
+for your project on a case-by-case basis.
+
+The terms of each license can be found in the source code repository:
+
+GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
+Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
+*/
+package gplx.xowa.mediawiki.includes.content; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*;
+// mMessage = wfMessage( $msg );
+// } else {
+// $this->mMessage = clone $msg;
+// }
+//
+// if ( $params ) {
+// $this->mMessage = $this->mMessage->params( $params );
+// }
+// }
+//
+// /**
+// * Fully parse the text from wikitext to HTML.
+// *
+// * @return String Parsed HTML.
+// */
+// public function getHtml() {
+// return $this->mMessage->parse();
+// }
+//
+// /**
+// * Returns the message text. {{-transformation is done.
+// *
+// * @return String Unescaped message text.
+// */
+// public function getWikitext() {
+// return $this->mMessage->text();
+// }
+//
+// /**
+// * Returns the message Object, with any parameters already substituted.
+// *
+// * @return Message The message Object.
+// */
+// public function getNativeData() {
+// // NOTE: Message objects are mutable. Cloning here makes MessageContent immutable.
+// return clone $this->mMessage;
+// }
+//
+// /**
+// * @return String
+// *
+// * @see Content::getTextForSearchIndex
+// */
+// public function getTextForSearchIndex() {
+// return $this->mMessage->plain();
+// }
+//
+// /**
+// * @return String
+// *
+// * @see Content::getWikitextForTransclusion
+// */
+// public function getWikitextForTransclusion() {
+// return $this->getWikitext();
+// }
+//
+// /**
+// * @param int $maxlength Maximum length of the summary text, defaults to 250.
+// *
+// * @return String The summary text.
+// *
+// * @see Content::getTextForSummary
+// */
+// public function getTextForSummary( $maxlength = 250 ) {
+// return substr( $this->mMessage->plain(), 0, $maxlength );
+// }
+//
+// /**
+// * @return int
+// *
+// * @see Content::getSize
+// */
+// public function getSize() {
+// return strlen( $this->mMessage->plain() );
+// }
+//
+// /**
+// * @return Content A copy of this Object
+// *
+// * @see Content::copy
+// */
+// public function copy() {
+// // MessageContent is immutable (because getNativeData() returns a clone of the Message Object)
+// return $this;
+// }
+//
+// /**
+// * @param boolean|null $hasLinks
+// *
+// * @return boolean Always false.
+// *
+// * @see Content::isCountable
+// */
+// public function isCountable( $hasLinks = null ) {
+// return false;
+// }
+//
+// /**
+// * @param Title $title Unused.
+// * @param int $revId Unused.
+// * @param ParserOptions $options Unused.
+// * @param boolean $generateHtml Whether to generate HTML (default: true).
+// *
+// * @return ParserOutput
+// *
+// * @see Content::getParserOutput
+// */
+// public function getParserOutput( Title $title, $revId = null,
+// ParserOptions $options = null, $generateHtml = true ) {
+// if ( $generateHtml ) {
+// $html = $this->getHtml();
+// } else {
+// $html = '';
+// }
+//
+// $po = new ParserOutput( $html );
+// // Message objects are in the user language.
+// $po->recordOption( 'userlang' );
+//
+// return $po;
+// }
+//
+// }
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/title/XomwMediaWikiTitleCodec.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/title/XomwMediaWikiTitleCodec.java
index 041035cb1..c3a5e923d 100644
--- a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/title/XomwMediaWikiTitleCodec.java
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/title/XomwMediaWikiTitleCodec.java
@@ -348,14 +348,14 @@ public class XomwMediaWikiTitleCodec implements XomwTitleFormatter {
break;
} while (true);
-// $fragment = strstr(dbkey, '#');
-// if (false !== $fragment) {
-// parts['fragment'] = str_replace('_', ' ', substr($fragment, 1));
-// dbkey = substr(dbkey, 0, strlen(dbkey) - strlen($fragment));
-// // remove whitespace again: prevents "Foo_bar_#"
-// // becoming "Foo_bar_"
-// dbkey = preg_replace('/_*$/', '', dbkey);
-// }
+ byte[] fragment = XophpString.strstr(dbkey, Byte_ascii.Hash_bry);
+ if (null != fragment) {
+ parts.fragment = XophpString.str_replace(Byte_ascii.Underline, Byte_ascii.Space, XophpString.substr(fragment, 1));
+ dbkey = XophpString.substr(dbkey, 0, XophpString.strlen(dbkey) - XophpString.strlen(fragment));
+ // remove whitespace again: prevents "Foo_bar_#"
+ // becoming "Foo_bar_"
+ dbkey = Bry_.Replace(dbkey, Byte_ascii.Underline_bry, Bry_.Empty);
+ }
// Reject illegal characters.
// $rxTc = self::getTitleInvalidRegex();
@@ -382,7 +382,7 @@ public class XomwMediaWikiTitleCodec implements XomwTitleFormatter {
// throw new XomwMalformedTitleException('title-invalid-relative', text);
// }
- // Magic tilde sequences? Nu-uh!
+ // Magic tilde sequences?
// if (strpos(dbkey, '~~~') !== false) {
// throw new XomwMalformedTitleException('title-invalid-magic-tilde', text);
// }