Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F5961342
ParserPipelineFactory.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
24 KB
Referenced Files
None
Subscribers
None
ParserPipelineFactory.php
View Options
<?php
declare
(
strict_types
=
1
);
namespace
Wikimedia\Parsoid\Wt2Html
;
use
Wikimedia\Assert\Assert
;
use
Wikimedia\Parsoid\Config\Env
;
use
Wikimedia\Parsoid\Core\InternalException
;
use
Wikimedia\Parsoid\Core\SelectiveUpdateData
;
use
Wikimedia\Parsoid\DOM\Document
;
use
Wikimedia\Parsoid\Utils\PHPUtils
;
use
Wikimedia\Parsoid\Utils\Utils
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\AddAnnotationIds
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\AddLinkAttributes
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\CleanUp
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\DedupeStyles
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\DisplaySpace
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\HandleLinkNeighbours
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\Headings
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\LiFixups
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\TableFixups
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Handlers\UnpackDOMFragments
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\AddMediaInfo
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\AddMetaData
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\AddRedLinks
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\ComputeDSR
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\ConvertOffsets
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\LangConverter
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\Linter
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\MarkFosteredContent
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\MigrateTemplateMarkerMetas
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\MigrateTrailingNLs
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\Normalize
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\ProcessEmbeddedDocs
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\ProcessTreeBuilderFixups
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\PWrap
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\RunExtensionProcessors
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\UpdateTemplateOutput
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\WrapAnnotations
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\WrapSections
;
use
Wikimedia\Parsoid\Wt2Html\DOM\Processors\WrapTemplates
;
use
Wikimedia\Parsoid\Wt2Html\TreeBuilder\TreeBuilderStage
;
use
Wikimedia\Parsoid\Wt2Html\TT\AttributeExpander
;
use
Wikimedia\Parsoid\Wt2Html\TT\BehaviorSwitchHandler
;
use
Wikimedia\Parsoid\Wt2Html\TT\DOMFragmentBuilder
;
use
Wikimedia\Parsoid\Wt2Html\TT\ExtensionHandler
;
use
Wikimedia\Parsoid\Wt2Html\TT\ExternalLinkHandler
;
use
Wikimedia\Parsoid\Wt2Html\TT\IncludeOnly
;
use
Wikimedia\Parsoid\Wt2Html\TT\LanguageVariantHandler
;
use
Wikimedia\Parsoid\Wt2Html\TT\ListHandler
;
use
Wikimedia\Parsoid\Wt2Html\TT\NoInclude
;
use
Wikimedia\Parsoid\Wt2Html\TT\OnlyInclude
;
use
Wikimedia\Parsoid\Wt2Html\TT\ParagraphWrapper
;
use
Wikimedia\Parsoid\Wt2Html\TT\PreHandler
;
use
Wikimedia\Parsoid\Wt2Html\TT\QuoteTransformer
;
use
Wikimedia\Parsoid\Wt2Html\TT\SanitizerHandler
;
use
Wikimedia\Parsoid\Wt2Html\TT\TemplateHandler
;
use
Wikimedia\Parsoid\Wt2Html\TT\TokenStreamPatcher
;
use
Wikimedia\Parsoid\Wt2Html\TT\WikiLinkHandler
;
/**
* This class assembles parser pipelines from parser stages
*/
class
ParserPipelineFactory
{
private
static
$initialized
=
false
;
private
static
$globalPipelineId
=
0
;
public
static
function
init
():
void
{
if
(
self
::
$initialized
)
{
return
;
}
self
::
$stages
[
"FullParseDOMTransform"
][
"processors"
]
=
array_merge
(
self
::
NESTED_PIPELINE_DOM_TRANSFORMS
,
self
::
FULL_PARSE_GLOBAL_DOM_TRANSFORMS
);
self
::
$stages
[
"SelectiveUpdateFragmentDOMTransform"
][
"processors"
]
=
array_merge
(
self
::
NESTED_PIPELINE_DOM_TRANSFORMS
,
self
::
SELECTIVE_UPDATE_FRAGMENT_GLOBAL_DOM_TRANSFORMS
);
self
::
$initialized
=
true
;
}
private
const
DOM_PROCESSOR_CONFIG
=
[
'addmetadata'
=>
AddMetaData
::
class
,
'annwrap'
=>
WrapAnnotations
::
class
,
'convertoffsets'
=>
ConvertOffsets
::
class
,
'dsr'
=>
ComputeDSR
::
class
,
'embedded-docs'
=>
ProcessEmbeddedDocs
::
class
,
'extpp'
=>
RunExtensionProcessors
::
class
,
'fostered'
=>
MarkFosteredContent
::
class
,
'linter'
=>
Linter
::
class
,
'lang-converter'
=>
LangConverter
::
class
,
'media'
=>
AddMediaInfo
::
class
,
'migrate-metas'
=>
MigrateTemplateMarkerMetas
::
class
,
'migrate-nls'
=>
MigrateTrailingNLs
::
class
,
'normalize'
=>
Normalize
::
class
,
'process-fixups'
=>
ProcessTreeBuilderFixups
::
class
,
'pwrap'
=>
PWrap
::
class
,
'redlinks'
=>
AddRedLinks
::
class
,
'sections'
=>
WrapSections
::
class
,
// Don't process HTML in embedded attributes
'tplwrap'
=>
WrapTemplates
::
class
,
'update-template'
=>
UpdateTemplateOutput
::
class
,
'ann-ids'
=>
[
'name'
=>
'AddAnnotationIds'
,
'handlers'
=>
[
[
'nodeName'
=>
'meta'
,
'action'
=>
[
AddAnnotationIds
::
class
,
'handler'
]
]
],
'withAnnotations'
=>
true
],
'linkneighbours+dom-unpack'
=>
[
'name'
=>
'HandleLinkNeighbours,UnpackDOMFragments'
,
'handlers'
=>
[
// Link prefixes and suffixes
[
'nodeName'
=>
'a'
,
'action'
=>
[
HandleLinkNeighbours
::
class
,
'handler'
]
],
[
'nodeName'
=>
null
,
'action'
=>
[
UnpackDOMFragments
::
class
,
'handler'
]
]
]
],
'fixups'
=>
[
'name'
=>
'MigrateTrailingCategories,TableFixups'
,
'tplInfo'
=>
true
,
'handlers'
=>
[
// 1. Move trailing categories in <li>s out of the list
[
'nodeName'
=>
'li'
,
'action'
=>
[
LiFixups
::
class
,
'migrateTrailingSolTransparentLinks'
]
],
[
'nodeName'
=>
'dt'
,
'action'
=>
[
LiFixups
::
class
,
'migrateTrailingSolTransparentLinks'
]
],
[
'nodeName'
=>
'dd'
,
'action'
=>
[
LiFixups
::
class
,
'migrateTrailingSolTransparentLinks'
]
],
// 2. Fix up issues from templated table cells and table cell attributes
[
'nodeName'
=>
'td'
,
'action'
=>
[
TableFixups
::
class
,
'handleTableCellTemplates'
]
],
[
'nodeName'
=>
'th'
,
'action'
=>
[
TableFixups
::
class
,
'handleTableCellTemplates'
]
],
]
],
'fixups+dedupe-styles'
=>
[
'name'
=>
'MigrateTrailingCategories,TableFixups,DedupeStyles'
,
'tplInfo'
=>
true
,
'handlers'
=>
[
// 1. Move trailing categories in <li>s out of the list
[
'nodeName'
=>
'li'
,
'action'
=>
[
LiFixups
::
class
,
'migrateTrailingSolTransparentLinks'
]
],
[
'nodeName'
=>
'dt'
,
'action'
=>
[
LiFixups
::
class
,
'migrateTrailingSolTransparentLinks'
]
],
[
'nodeName'
=>
'dd'
,
'action'
=>
[
LiFixups
::
class
,
'migrateTrailingSolTransparentLinks'
]
],
// 2. Fix up issues from templated table cells and table cell attributes
[
'nodeName'
=>
'td'
,
'action'
=>
[
TableFixups
::
class
,
'handleTableCellTemplates'
]
],
[
'nodeName'
=>
'th'
,
'action'
=>
[
TableFixups
::
class
,
'handleTableCellTemplates'
]
],
// 3. Deduplicate template styles
// (should run after dom-fragment expansion + after extension post-processors)
[
'nodeName'
=>
'style'
,
'action'
=>
[
DedupeStyles
::
class
,
'dedupe'
]
]
]
],
// Strip marker metas -- removes left over marker metas (ex: metas
// nested in expanded tpl/extension output).
'strip-metas'
=>
[
'name'
=>
'CleanUp-stripMarkerMetas'
,
'handlers'
=>
[
[
'nodeName'
=>
'meta'
,
'action'
=>
[
CleanUp
::
class
,
'stripMarkerMetas'
]
]
]
],
'displayspace+linkclasses'
=>
[
'name'
=>
'DisplaySpace+AddLinkAttributes'
,
'handlers'
=>
[
[
'nodeName'
=>
null
,
'action'
=>
[
DisplaySpace
::
class
,
'leftHandler'
]
],
[
'nodeName'
=>
null
,
'action'
=>
[
DisplaySpace
::
class
,
'rightHandler'
]
],
[
'nodeName'
=>
'a'
,
'action'
=>
[
AddLinkAttributes
::
class
,
'handler'
]
]
]
],
'gen-anchors'
=>
[
'name'
=>
'Headings-genAnchors'
,
'handlers'
=>
[
[
'nodeName'
=>
null
,
'action'
=>
[
Headings
::
class
,
'genAnchors'
]
],
]
],
'dedupe-heading-ids'
=>
[
'name'
=>
'Headings-dedupeIds'
,
'handlers'
=>
[
[
'nodeName'
=>
null
,
'action'
=>
[
Headings
::
class
,
'dedupeHeadingIds'
]
]
]
],
'heading-ids'
=>
[
'name'
=>
'Headings-genAnchors'
,
'handlers'
=>
[
[
'nodeName'
=>
null
,
'action'
=>
[
Headings
::
class
,
'genAnchors'
]
],
[
'nodeName'
=>
null
,
'action'
=>
[
Headings
::
class
,
'dedupeHeadingIds'
]
]
]
],
'cleanup'
=>
[
'name'
=>
'CleanUp-handleEmptyElts,CleanUp-cleanup'
,
'tplInfo'
=>
true
,
'handlers'
=>
[
// Strip empty elements from template content
[
'nodeName'
=>
null
,
'action'
=>
[
CleanUp
::
class
,
'handleEmptyElements'
]
],
// Additional cleanup
[
'nodeName'
=>
null
,
'action'
=>
[
CleanUp
::
class
,
'finalCleanup'
]
]
]
],
'saveDP'
=>
[
'name'
=>
'CleanUp-saveDataParsoid'
,
'tplInfo'
=>
true
,
'handlers'
=>
[
// Save data.parsoid into data-parsoid html attribute.
// Make this its own thing so that any changes to the DOM
// don't affect other handlers that run alongside it.
[
'nodeName'
=>
null
,
'action'
=>
[
CleanUp
::
class
,
'saveDataParsoid'
]
]
]
]
];
// NOTES about ordering / inclusion:
//
// media:
// This is run at all levels for now - gallery extension's "packed" mode
// would otherwise need a post-processing pass to scale media after it
// has been fetched. That introduces an ordering dependency that may
// or may not complicate things.
// migrate-metas:
// - Run this after 'pwrap' because it can add additional opportunities for
// meta migration which we will miss if we run this before p-wrapping.
// - We could potentially move this just before 'tplwrap' by seeing this
// as a preprocessing pass for that. But, we will have to update the pass
// to update DSR properties where required.
// - In summary, this can at most be moved before 'media' or after
// 'migrate-nls' without needing any other changes.
// dsr, tplwrap:
// DSR computation and template wrapping cannot be skipped for top-level content
// even if they are part of nested level pipelines, because such content might be
// embedded in attributes and they may need to be processed independently.
//
// Nested (non-top-level) pipelines can never include the following:
// - lang-converter, convertoffsets, dedupe-styles, cleanup, saveDP
//
// FIXME: Perhaps introduce a config flag in the processor config that
// verifies this property against a pipeline's 'toplevel' state.
public
const
NESTED_PIPELINE_DOM_TRANSFORMS
=
[
'fostered'
,
'process-fixups'
,
'normalize'
,
'pwrap'
,
'media'
,
'migrate-metas'
,
'migrate-nls'
,
'dsr'
,
'tplwrap'
,
'ann-ids'
,
'annwrap'
,
'linkneighbours+dom-unpack'
];
// NOTES about ordering:
// lang-converter, redlinks:
// Language conversion and redlink marking are done here
// *before* we cleanup and save data-parsoid because they
// are also used in pb2pb/html2html passes, and we want to
// keep their input/output formats consistent.
public
const
FULL_PARSE_GLOBAL_DOM_TRANSFORMS
=
[
// FIXME: It should be documented in the spec that an extension's
// wtDOMProcess handler is run once on the top level document.
'extpp'
,
'fixups+dedupe-styles'
,
'linter'
,
'strip-metas'
,
'lang-converter'
,
'redlinks'
,
'displayspace+linkclasses'
,
// Benefits from running after determining which media are redlinks
'heading-ids'
,
'sections'
,
'convertoffsets'
,
'cleanup'
,
'embedded-docs'
,
'saveDP'
,
'addmetadata'
];
// Skipping sections, addmetadata from the above pipeline
//
// FIXME: Skip extpp, linter, lang-converter, redlinks, heading-ids, convertoffsets, saveDP for now.
// This replicates behavior prior to this refactor.
public
const
FULL_PARSE_EMBEDDED_DOC_DOM_TRANSFORMS
=
[
'fixups+dedupe-styles'
,
'strip-metas'
,
'displayspace+linkclasses'
,
'cleanup'
,
// Need to run this recursively
'embedded-docs'
,
// FIXME This means the data-* from embedded HTML fragments won't end up
// in the pagebundle. But, if we try to call this on those fragments,
// we get multiple calls to store embedded docs. So, we may need to
// write a custom traverser if we want these embedded data* objects
// in the pagebundle (this is not a regression since they weren't part
// of the pagebundle all this while anyway.)
/* 'saveDP' */
];
public
const
SELECTIVE_UPDATE_FRAGMENT_GLOBAL_DOM_TRANSFORMS
=
[
'extpp'
,
// FIXME: this should be a different processor
'fixups'
,
'strip-metas'
,
'redlinks'
,
'displayspace+linkclasses'
,
'gen-anchors'
,
'convertoffsets'
,
'cleanup'
,
// FIXME: This will probably need some special-case code to first
// strip old metadata before adding fresh metadata.
'addmetadata'
];
public
const
SELECTIVE_UPDATE_GLOBAL_DOM_TRANSFORMS
=
[
'update-template'
,
'linter'
,
'lang-converter'
,
/* FIXME: Are lang converters idempotent? */
'heading-ids'
,
'sections'
,
'saveDP'
];
private
static
$stages
=
[
"Tokenizer"
=>
[
"class"
=>
PegTokenizer
::
class
,
],
"TokenTransform2"
=>
[
"class"
=>
TokenTransformManager
::
class
,
"transformers"
=>
[
OnlyInclude
::
class
,
IncludeOnly
::
class
,
NoInclude
::
class
,
TemplateHandler
::
class
,
ExtensionHandler
::
class
,
// Expand attributes after templates to avoid expanding unused branches.
// No expansion of quotes, paragraphs etc in attributes,
// as with the legacy parser - up to end of TokenTransform2.
AttributeExpander
::
class
,
// now all attributes expanded to tokens or string
// more convenient after attribute expansion
WikiLinkHandler
::
class
,
ExternalLinkHandler
::
class
,
LanguageVariantHandler
::
class
,
// This converts dom-fragment-token tokens all the way to DOM
// and wraps them in DOMFragment wrapper tokens which will then
// get unpacked into the DOM by a dom-fragment unpacker.
DOMFragmentBuilder
::
class
],
],
"TokenTransform3"
=>
[
"class"
=>
TokenTransformManager
::
class
,
"transformers"
=>
[
TokenStreamPatcher
::
class
,
// add <pre>s
PreHandler
::
class
,
QuoteTransformer
::
class
,
// add before transforms that depend on behavior switches
// examples: toc generation, edit sections
BehaviorSwitchHandler
::
class
,
ListHandler
::
class
,
SanitizerHandler
::
class
,
// Wrap tokens into paragraphs post-sanitization so that
// tags that converted to text by the sanitizer have a chance
// of getting wrapped into paragraphs. The sanitizer does not
// require the existence of p-tags for its functioning.
ParagraphWrapper
::
class
],
],
// Build a tree out of the fully processed token stream
"TreeBuilder"
=>
[
"class"
=>
TreeBuilderStage
::
class
,
],
// DOM transformer for top-level documents.
// This performs a lot of post-processing of the DOM
// (Template wrapping, broken wikitext/html detection, etc.)
"FullParseDOMTransform"
=>
[
"class"
=>
DOMPostProcessor
::
class
,
"processors"
=>
null
// Will be initialized in an init function()
],
// DOM transformer for fragments of a top-level document
"NestedFragmentDOMTransform"
=>
[
"class"
=>
DOMPostProcessor
::
class
,
"processors"
=>
self
::
NESTED_PIPELINE_DOM_TRANSFORMS
],
// DOM transformations to run on attribute-embedded docs of the top level doc
"FullParseEmbeddedDocsDOMTransform"
=>
[
"class"
=>
DOMPostProcessor
::
class
,
"processors"
=>
self
::
FULL_PARSE_EMBEDDED_DOC_DOM_TRANSFORMS
],
// DOM transformer for fragments during selective updates.
// This may eventually become identical to NestedFrgmentDOMTransform,
// but at this time, it is unclear if that will materialize.
"SelectiveUpdateFragmentDOMTransform"
=>
[
"class"
=>
DOMPostProcessor
::
class
,
"processors"
=>
null
// Will be initialized in an init function()
],
// DOM transformer for the top-level page during selective updates.
"SelectiveUpdateDOMTransform"
=>
[
// For use in the top-level of the selective-update pipeline
"class"
=>
DOMPostProcessor
::
class
,
"processors"
=>
self
::
SELECTIVE_UPDATE_GLOBAL_DOM_TRANSFORMS
]
];
private
static
$pipelineRecipes
=
[
// This pipeline takes wikitext as input and emits a fully
// processed DOM as output. This is the pipeline used for
// all top-level documents.
"fullparse-wikitext-to-dom"
=>
[
"alwaysToplevel"
=>
true
,
"outType"
=>
"DOM"
,
"stages"
=>
[
"Tokenizer"
,
"TokenTransform2"
,
"TokenTransform3"
,
"TreeBuilder"
,
"FullParseDOMTransform"
]
],
"fullparse-embedded-docs-dom-to-dom"
=>
[
"alwaysToplevel"
=>
true
,
"outType"
=>
"DOM"
,
"stages"
=>
[
"FullParseEmbeddedDocsDOMTransform"
]
],
// This pipeline takes a DOM and emits a fully processed DOM as output.
"selective-update-dom-to-dom"
=>
[
"alwaysToplevel"
=>
true
,
"outType"
=>
"DOM"
,
"stages"
=>
[
"SelectiveUpdateDOMTransform"
]
],
// This pipeline takes wikitext as input and emits a partially
// processed DOM as output. This is the pipeline used for processing
// page fragments to DOM in a selective page update context
// This is always toplevel because the wikitext being updated
// is found at the toplevel of the page.
"selective-update-fragment-wikitext-to-dom"
=>
[
"alwaysToplevel"
=>
true
,
"outType"
=>
"DOM"
,
"stages"
=>
[
"Tokenizer"
,
"TokenTransform2"
,
"TokenTransform3"
,
"TreeBuilder"
,
"SelectiveUpdateFragmentDOMTransform"
]
],
// This pipeline takes wikitext as input and emits a fully
// processed DOM as output. This is the pipeline used for
// wikitext fragments of a top-level document that should be
// processed to a DOM fragment. This pipeline doesn't run all
// of the DOM transformations in the DOMTransform pipeline.
// We will like use a specialized DOMTransform stage here.
"wikitext-to-fragment"
=>
[
// FIXME: This is known to be always *not* top-level
// We could use a different flag to lock these pipelines too.
"outType"
=>
"DOM"
,
"stages"
=>
[
"Tokenizer"
,
"TokenTransform2"
,
"TokenTransform3"
,
"TreeBuilder"
,
"NestedFragmentDOMTransform"
]
],
// This pipeline takes tokens from stage 2 and emits a DOM fragment
// as output - this runs the same DOM transforms as the 'wikitext-to-fragment'
// pipeline and will get a spcialized DOMTransform stage as above.
"expanded-tokens-to-fragment"
=>
[
"outType"
=>
"DOM"
,
"stages"
=>
[
"TokenTransform3"
,
"TreeBuilder"
,
"NestedFragmentDOMTransform"
]
],
// This pipeline takes wikitext as input and emits tokens that
// have had all templates, extensions, links, images processed
"wikitext-to-expanded-tokens"
=>
[
"outType"
=>
"Tokens"
,
"stages"
=>
[
"Tokenizer"
,
"TokenTransform2"
]
],
// This pipeline takes tokens from the PEG tokenizer and emits
// tokens that have had all templates and extensions processed.
"peg-tokens-to-expanded-tokens"
=>
[
"outType"
=>
"Tokens"
,
"stages"
=>
[
"TokenTransform2"
]
]
];
private
static
$supportedOptions
=
[
// If true, templates found in content will have its contents expanded
'expandTemplates'
,
// If true, indicates pipeline is processing the expanded content of a
// template or its arguments
'inTemplate'
,
// If true, indicates that we are in a <includeonly> context
// (in current usage, isInclude === inTemplate)
'isInclude'
,
// The extension tag that is being processed (Ex: ref, references)
// (in current usage, only used for native tag implementation)
'extTag'
,
// Extension-specific options
'extTagOpts'
,
// Content being parsed is used in an inline context
'inlineContext'
,
// Are we processing content of attributes?
// (in current usage, used for transcluded attr. keys/values)
'attrExpansion'
,
];
private
array
$pipelineCache
=
[];
private
Env
$env
;
public
function
__construct
(
Env
$env
)
{
$this
->
env
=
$env
;
}
/**
* Default options processing
*
* @param array $options
* @return array
*/
private
function
defaultOptions
(
array
$options
):
array
{
// default: not in a template
$options
[
'inTemplate'
]
??=
false
;
// default: not an include context
$options
[
'isInclude'
]
??=
false
;
// default: wrap templates
$options
[
'expandTemplates'
]
??=
true
;
// Catch pipeline option typos
foreach
(
$options
as
$k
=>
$v
)
{
Assert
::
invariant
(
in_array
(
$k
,
self
::
$supportedOptions
,
true
),
'Invalid cacheKey option: '
.
$k
);
}
return
$options
;
}
public
static
function
procNamesToProcs
(
array
$procNames
):
array
{
$processors
=
[];
foreach
(
$procNames
as
$name
)
{
$proc
=
self
::
DOM_PROCESSOR_CONFIG
[
$name
];
if
(
!
is_array
(
$proc
)
)
{
$proc
=
[
'name'
=>
Utils
::
stripNamespace
(
$proc
),
'Processor'
=>
$proc
,
];
}
$proc
[
'shortcut'
]
=
$name
;
$processors
[]
=
$proc
;
}
return
$processors
;
}
/**
* Generic pipeline creation from the above recipes.
*
* @param string $type
* @param string $cacheKey
* @param array $options
* @return ParserPipeline
*/
private
function
makePipeline
(
string
$type
,
string
$cacheKey
,
array
$options
):
ParserPipeline
{
self
::
init
();
if
(
!
isset
(
self
::
$pipelineRecipes
[
$type
]
)
)
{
throw
new
InternalException
(
'Unsupported Pipeline: '
.
$type
);
}
$recipe
=
self
::
$pipelineRecipes
[
$type
];
$pipeStages
=
[];
$prevStage
=
null
;
$recipeStages
=
$recipe
[
"stages"
];
foreach
(
$recipeStages
as
$stageId
)
{
$stageData
=
self
::
$stages
[
$stageId
];
// @phan-suppress-next-line PhanNonClassMethodCall,PhanTypeExpectedObjectOrClassName
$stage
=
new
$stageData
[
"class"
](
$this
->
env
,
$options
,
$stageId
,
$prevStage
);
if
(
isset
(
$stageData
[
"transformers"
]
)
)
{
foreach
(
$stageData
[
"transformers"
]
as
$tName
)
{
$stage
->
addTransformer
(
new
$tName
(
$stage
,
$options
)
);
}
}
elseif
(
isset
(
$stageData
[
"processors"
]
)
)
{
$stage
->
registerProcessors
(
self
::
procNamesToProcs
(
$stageData
[
"processors"
]
)
);
}
$prevStage
=
$stage
;
$pipeStages
[]
=
$stage
;
}
return
new
ParserPipeline
(
$recipe
[
'alwaysToplevel'
]
??
false
,
$type
,
$recipe
[
"outType"
],
$cacheKey
,
$pipeStages
,
$this
->
env
);
}
private
function
getCacheKey
(
string
$cacheKey
,
array
$options
):
string
{
if
(
empty
(
$options
[
'isInclude'
]
)
)
{
$cacheKey
.=
'::noInclude'
;
}
if
(
empty
(
$options
[
'expandTemplates'
]
)
)
{
$cacheKey
.=
'::noExpand'
;
}
if
(
!
empty
(
$options
[
'inlineContext'
]
)
)
{
$cacheKey
.=
'::inlineContext'
;
}
if
(
!
empty
(
$options
[
'inTemplate'
]
)
)
{
$cacheKey
.=
'::inTemplate'
;
}
if
(
!
empty
(
$options
[
'attrExpansion'
]
)
)
{
$cacheKey
.=
'::attrExpansion'
;
}
if
(
isset
(
$options
[
'extTag'
]
)
)
{
$cacheKey
.=
'::'
.
$options
[
'extTag'
];
// FIXME: This is not the best strategy. But, instead of
// premature complexity, let us see how extensions want to
// use this and then figure out what constraints are needed.
if
(
isset
(
$options
[
'extTagOpts'
]
)
)
{
$cacheKey
.=
'::'
.
PHPUtils
::
jsonEncode
(
$options
[
'extTagOpts'
]
);
}
}
return
$cacheKey
;
}
public
function
parse
(
string
$src
):
Document
{
$pipe
=
$this
->
getPipeline
(
'fullparse-wikitext-to-dom'
);
$pipe
->
init
(
[
'frame'
=>
$this
->
env
->
topFrame
,
'toFragment'
=>
false
,
]
);
// Top-level doc parsing always start in SOL state
return
$pipe
->
parseChunkily
(
$src
,
[
'sol'
=>
true
]
)->
ownerDocument
;
}
/**
* @param SelectiveUpdateData $selparData
* @param array $options Options for selective DOM update
* - mode: (string) One of "template", "section", "generic"
* For now, defaults to 'template', if absent
*/
public
function
selectiveDOMUpdate
(
SelectiveUpdateData
$selparData
,
array
$options
=
[]
):
Document
{
$pipe
=
$this
->
getPipeline
(
'selective-update-dom-to-dom'
);
$pipe
->
init
(
[
'frame'
=>
$this
->
env
->
topFrame
,
'toFragment'
=>
false
,
]
);
return
$pipe
->
selectiveParse
(
$selparData
,
$options
);
}
/**
* Get a pipeline of a given type. Pipelines are cached as they are
* frequently created.
*
* @param string $type
* @param array $options These also determine the key under which the
* pipeline is cached for reuse.
* @return ParserPipeline
*/
public
function
getPipeline
(
string
$type
,
array
$options
=
[]
):
ParserPipeline
{
$options
=
$this
->
defaultOptions
(
$options
);
$cacheKey
=
$this
->
getCacheKey
(
$type
,
$options
);
$this
->
pipelineCache
[
$cacheKey
]
??=
[];
if
(
$this
->
pipelineCache
[
$cacheKey
]
)
{
$pipe
=
array_pop
(
$this
->
pipelineCache
[
$cacheKey
]
);
}
else
{
$pipe
=
$this
->
makePipeline
(
$type
,
$cacheKey
,
$options
);
}
// Debugging aid: Assign unique id to the pipeline
$pipe
->
setPipelineId
(
self
::
$globalPipelineId
++
);
return
$pipe
;
}
/**
* Callback called by a pipeline at the end of its processing. Returns the
* pipeline to the cache.
*
* @param ParserPipeline $pipe
*/
public
function
returnPipeline
(
ParserPipeline
$pipe
):
void
{
$cacheKey
=
$pipe
->
getCacheKey
();
$this
->
pipelineCache
[
$cacheKey
]
??=
[];
if
(
count
(
$this
->
pipelineCache
[
$cacheKey
]
)
<
100
)
{
$this
->
pipelineCache
[
$cacheKey
][]
=
$pipe
;
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Oct 3, 08:01 (2 w, 17 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
0c/61/e3a23cfb261e57f5590284322600
Default Alt Text
ParserPipelineFactory.php (24 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment