use crate::gallery::Gallery;
use crate::image::Image;
use crate::inclusion::IncludeOnlyDataMw;
use crate::indicator::{IndicatorAttrs, IndicatorBody, IndicatorDataMw};
use crate::private::Sealed;
use crate::{
assert_element, attribute_contains_word, clean_link, full_link, inner_data,
parse_target, set_inner_data, Error, Result, Wikicode, WikinodeIterator,
};
use kuchikiki::{Attribute, ExpandedName, NodeRef};
use std::ops::Deref;
use urlencoding::{decode, encode};
mod behavior_switch;
pub use behavior_switch::BehaviorSwitch;
#[derive(Debug, Clone)]
pub enum Wikinode {
BehaviorSwitch(BehaviorSwitch),
Category(Category),
Comment(Comment),
DisplaySpace(DisplaySpace),
ExtLink(ExtLink),
Gallery(Gallery),
Heading(Heading),
HtmlEntity(HtmlEntity),
Image(Image),
IncludeOnly(IncludeOnly),
Indicator(Indicator),
InterwikiLink(InterwikiLink),
LanguageLink(LanguageLink),
Nowiki(Nowiki),
Placeholder(Placeholder),
Redirect(Redirect),
Section(Section),
WikiLink(WikiLink),
Generic(Wikicode),
}
impl Deref for Wikinode {
type Target = NodeRef;
fn deref(&self) -> &Self::Target {
self.as_node()
}
}
impl Wikinode {
pub(crate) fn new_from_node(node: &NodeRef) -> Self {
if node.as_comment().is_some() {
return Comment::new_from_node(node).into();
}
if let Some(element) = node.as_element() {
let tag_name = element.name.local.clone();
let attributes = element.attributes.borrow();
if let Some(typeof_) = attributes.get("typeof") {
if typeof_.split(' ').any(|val| {
val == "mw:Placeholder"
|| val.starts_with("mw:Placeholder/")
}) {
return Placeholder::new_from_node(node).into();
}
}
match tag_name {
local_name!("a") => {
if let Some(rel) = attributes.get("rel") {
if attribute_contains_word(rel, InterwikiLink::REL) {
return InterwikiLink::new_from_node(node).into();
} else if attribute_contains_word(rel, WikiLink::REL) {
return WikiLink::new_from_node(node).into();
} else if attribute_contains_word(rel, ExtLink::REL) {
return ExtLink::new_from_node(node).into();
}
}
}
local_name!("h1")
| local_name!("h2")
| local_name!("h3")
| local_name!("h4")
| local_name!("h5")
| local_name!("h6") => {
return Heading::new_from_node(node).into();
}
local_name!("link") => {
if let Some(rel) = attributes.get("rel") {
if attribute_contains_word(rel, Category::REL) {
return Category::new_from_node(node).into();
} else if attribute_contains_word(
rel,
LanguageLink::REL,
) {
return LanguageLink::new_from_node(node).into();
} else if attribute_contains_word(rel, Redirect::REL) {
return Redirect::new_from_node(node).into();
}
}
}
local_name!("meta") => {
if let Some(property) = attributes.get("property") {
if property.starts_with("mw:PageProp/") {
return BehaviorSwitch::new_from_node(node).into();
}
} else if let Some(typeof_) = attributes.get("typeof") {
if attribute_contains_word(typeof_, IncludeOnly::TYPEOF)
{
return IncludeOnly::new_from_node(node).into();
} else if attribute_contains_word(
typeof_,
Indicator::TYPEOF,
) {
return Indicator::new_from_node(node).into();
}
}
}
local_name!("section") => {
if attributes.contains("data-mw-section-id") {
return Section::new_from_node(node).into();
}
}
local_name!("span") => {
if let Some(typeof_) = attributes.get("typeof") {
if attribute_contains_word(typeof_, Nowiki::TYPEOF) {
return Nowiki::new_from_node(node).into();
} else if attribute_contains_word(
typeof_,
HtmlEntity::TYPEOF,
) {
return HtmlEntity::new_from_node(node).into();
} else if attribute_contains_word(
typeof_,
DisplaySpace::TYPEOF,
) {
return DisplaySpace::new_from_node(node).into();
} else if typeof_
.split(' ')
.any(|val| val.starts_with(Image::TYPEOF_PREFIX))
{
return Image::new_from_node(node).into();
}
}
}
local_name!("ul") => {
if let Some(typeof_) = attributes.get("typeof") {
if attribute_contains_word(typeof_, Gallery::TYPEOF) {
return Gallery::new_from_node(node).into();
}
}
}
_ => {}
}
}
Self::Generic(Wikicode::new_from_node(node))
}
pub fn as_behavior_switch(&self) -> Option<BehaviorSwitch> {
match self {
Self::BehaviorSwitch(switch) => Some(switch.clone()),
_ => None,
}
}
pub fn as_category(&self) -> Option<Category> {
match self {
Self::Category(category) => Some(category.clone()),
_ => None,
}
}
pub fn as_comment(&self) -> Option<Comment> {
match self {
Self::Comment(comment) => Some(comment.clone()),
_ => None,
}
}
pub fn as_displayspace(&self) -> Option<DisplaySpace> {
match self {
Self::DisplaySpace(displayspace) => Some(displayspace.clone()),
_ => None,
}
}
pub fn as_extlink(&self) -> Option<ExtLink> {
match self {
Self::ExtLink(extlink) => Some(extlink.clone()),
_ => None,
}
}
pub fn as_gallery(&self) -> Option<Gallery> {
match self {
Self::Gallery(gallery) => Some(gallery.clone()),
_ => None,
}
}
pub fn as_generic(&self) -> Option<Wikicode> {
match self {
Self::Generic(node) => Some(node.clone()),
_ => None,
}
}
pub fn as_heading(&self) -> Option<Heading> {
match self {
Self::Heading(heading) => Some(heading.clone()),
_ => None,
}
}
pub fn as_html_entity(&self) -> Option<HtmlEntity> {
match self {
Self::HtmlEntity(entity) => Some(entity.clone()),
_ => None,
}
}
pub fn as_image(&self) -> Option<Image> {
match self {
Self::Image(image) => Some(image.clone()),
_ => None,
}
}
pub fn as_includeonly(&self) -> Option<IncludeOnly> {
match self {
Self::IncludeOnly(includeonly) => Some(includeonly.clone()),
_ => None,
}
}
pub fn as_indicator(&self) -> Option<Indicator> {
match self {
Self::Indicator(indicator) => Some(indicator.clone()),
_ => None,
}
}
pub fn as_interwiki_link(&self) -> Option<InterwikiLink> {
match self {
Self::InterwikiLink(link) => Some(link.clone()),
_ => None,
}
}
pub fn as_language_link(&self) -> Option<LanguageLink> {
match self {
Self::LanguageLink(link) => Some(link.clone()),
_ => None,
}
}
pub fn as_nowiki(&self) -> Option<Nowiki> {
match self {
Self::Nowiki(nowiki) => Some(nowiki.clone()),
_ => None,
}
}
pub fn as_placeholder(&self) -> Option<Placeholder> {
match self {
Self::Placeholder(placeholder) => Some(placeholder.clone()),
_ => None,
}
}
pub fn as_redirect(&self) -> Option<Redirect> {
match self {
Self::Redirect(redirect) => Some(redirect.clone()),
_ => None,
}
}
pub fn as_section(&self) -> Option<Section> {
match self {
Self::Section(section) => Some(section.clone()),
_ => None,
}
}
pub fn as_wikilink(&self) -> Option<WikiLink> {
match self {
Self::WikiLink(wikilink) => Some(wikilink.clone()),
_ => None,
}
}
}
impl Sealed for Wikinode {}
impl WikinodeIterator for Wikinode {
fn as_node(&self) -> &NodeRef {
match self {
Self::BehaviorSwitch(switch) => switch,
Self::Category(category) => category,
Self::Comment(comment) => comment,
Self::DisplaySpace(displayspace) => displayspace,
Self::ExtLink(extlink) => extlink,
Self::Gallery(gallery) => gallery,
Self::Heading(heading) => heading,
Self::HtmlEntity(entity) => entity,
Self::Image(image) => image,
Self::IncludeOnly(includeonly) => includeonly,
Self::Indicator(indicator) => indicator,
Self::InterwikiLink(link) => link,
Self::LanguageLink(link) => link,
Self::Nowiki(nowiki) => nowiki,
Self::Placeholder(placeholder) => placeholder,
Self::Redirect(redirect) => redirect,
Self::Section(section) => section,
Self::WikiLink(wikilink) => wikilink,
Self::Generic(code) => code,
}
}
}
macro_rules! impl_traits {
( $name:ident ) => {
impl From<$name> for Wikinode {
fn from(node: $name) -> Self {
Self::$name(node)
}
}
impl Deref for $name {
type Target = NodeRef;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl WikinodeIterator for $name {
fn as_node(&self) -> &NodeRef {
&self.0
}
}
impl Sealed for $name {}
};
}
impl_traits!(BehaviorSwitch);
impl_traits!(Category);
impl_traits!(Comment);
impl_traits!(DisplaySpace);
impl_traits!(ExtLink);
impl_traits!(Gallery);
impl_traits!(Heading);
impl_traits!(HtmlEntity);
impl_traits!(Image);
impl_traits!(IncludeOnly);
impl_traits!(Indicator);
impl_traits!(InterwikiLink);
impl_traits!(LanguageLink);
impl_traits!(Nowiki);
impl_traits!(Placeholder);
impl_traits!(Redirect);
impl_traits!(Section);
impl_traits!(WikiLink);
#[derive(Debug, Clone)]
pub struct Comment(NodeRef);
impl Comment {
pub fn new(text: &str) -> Self {
Self(NodeRef::new_comment(text))
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
if element.as_comment().is_none() {
unreachable!("Non-comment node passed");
}
Self(element.clone())
}
pub fn text(&self) -> String {
self.as_comment().unwrap().borrow().to_string()
}
pub fn set_text(&self, text: &str) {
self.as_comment().unwrap().replace(text.into());
}
}
#[derive(Debug, Clone)]
pub struct WikiLink(NodeRef);
impl WikiLink {
const REL: &'static str = "mw:WikiLink";
pub(crate) const SELECTOR: &'static str = "[rel=\"mw:WikiLink\"]";
pub fn new(target: &str, text: &NodeRef) -> Self {
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("a")),
vec![
(
ExpandedName::new(ns!(), local_name!("href")),
Attribute {
prefix: None,
value: full_link(target),
},
),
(
ExpandedName::new(ns!(), local_name!("rel")),
Attribute {
prefix: None,
value: Self::REL.to_string(),
},
),
],
);
element.append(text.clone());
Self(element)
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
pub fn raw_target(&self) -> String {
self.as_element()
.unwrap()
.attributes
.borrow()
.get("href")
.unwrap()
.to_string()
}
pub fn target(&self) -> String {
parse_target(&self.raw_target())
}
pub fn set_target(&self, target: &str) {
self.as_element()
.unwrap()
.attributes
.borrow_mut()
.insert("href", full_link(target));
}
pub fn is_isbn_magic_link(&self) -> bool {
!self
.as_element()
.unwrap()
.attributes
.borrow()
.contains("title")
&& self.text_contents().starts_with("ISBN ")
}
}
#[derive(Debug, Clone)]
pub struct ExtLink(NodeRef);
impl ExtLink {
const REL: &'static str = "mw:ExtLink";
pub(crate) const SELECTOR: &'static str = "[rel~=\"mw:ExtLink\"]";
pub fn new(target: &str, text: &NodeRef) -> Self {
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("a")),
vec![
(
ExpandedName::new(ns!(), local_name!("href")),
Attribute {
prefix: None,
value: target.to_string(),
},
),
(
ExpandedName::new(ns!(), local_name!("rel")),
Attribute {
prefix: None,
value: Self::REL.to_string(),
},
),
],
);
element.append(text.clone());
Self(element)
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
pub fn target(&self) -> String {
self.as_element()
.unwrap()
.attributes
.borrow()
.get("href")
.unwrap()
.to_string()
}
pub fn set_target(&self, target: &str) {
self.as_element()
.unwrap()
.attributes
.borrow_mut()
.insert("href", target.to_string());
}
pub fn is_magic_link(&self) -> bool {
if let Some(classes) =
self.as_element().unwrap().attributes.borrow().get("class")
{
classes.split(' ').any(|class| class == "mw-magiclink")
} else {
false
}
}
}
#[derive(Debug, Clone)]
pub struct InterwikiLink(NodeRef);
impl InterwikiLink {
const REL: &'static str = "mw:WikiLink/Interwiki";
pub fn new(target: &str, text: &NodeRef) -> Self {
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("a")),
vec![
(
ExpandedName::new(ns!(), local_name!("href")),
Attribute {
prefix: None,
value: target.to_string(),
},
),
(
ExpandedName::new(ns!(), local_name!("rel")),
Attribute {
prefix: None,
value: Self::REL.to_string(),
},
),
],
);
element.append(text.clone());
Self(element)
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
pub fn target(&self) -> String {
self.as_element()
.unwrap()
.attributes
.borrow()
.get("href")
.unwrap()
.to_string()
}
pub fn set_target(&self, target: &str) {
self.as_element()
.unwrap()
.attributes
.borrow_mut()
.insert("href", target.to_string());
}
}
#[derive(Debug, Clone)]
pub struct Nowiki(NodeRef);
impl Nowiki {
const TYPEOF: &'static str = "mw:Nowiki";
pub fn new(text: &str) -> Self {
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("span")),
vec![(
ExpandedName::new(ns!(), "typeof"),
Attribute {
prefix: None,
value: Self::TYPEOF.to_string(),
},
)],
);
element.append(NodeRef::new_text(text));
Self(element)
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
}
#[derive(Debug, Clone)]
pub struct HtmlEntity(NodeRef);
impl HtmlEntity {
const TYPEOF: &'static str = "mw:Entity";
pub fn new(text: &str) -> Self {
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("span")),
vec![(
ExpandedName::new(ns!(), "typeof"),
Attribute {
prefix: None,
value: Self::TYPEOF.to_string(),
},
)],
);
element.append(NodeRef::new_text(text));
Self(element)
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
}
#[derive(Debug, Clone)]
pub struct Section(NodeRef);
impl Section {
pub(crate) const SELECTOR: &'static str = "section[data-mw-section-id]";
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
pub fn section_id(&self) -> i32 {
self.as_element()
.unwrap()
.attributes
.borrow()
.get("data-mw-section-id")
.expect("No data-mw-section-id attribute on section")
.parse()
.expect("Invalid data-mw-section-id attribute")
}
pub fn is_editable(&self) -> bool {
self.section_id() >= 0
}
pub fn is_pseudo_section(&self) -> bool {
let id = self.section_id();
id == -2 || id == 0
}
pub fn heading(&self) -> Option<Heading> {
if !self.is_pseudo_section() {
self.select_first(Heading::SELECTOR)
.map(|node| Heading::new_from_node(&node))
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct Heading(NodeRef);
impl Heading {
pub(crate) const SELECTOR: &'static str = "h1, h2, h3, h4, h5, h6";
pub fn new(level: u32, contents: &NodeRef) -> Result<Self> {
if !(1..=6).contains(&level) {
return Err(Error::InvalidHeadingLevel(level));
}
let element = NodeRef::new_element(
crate::build_qual_name(format!("h{level}").into()),
vec![],
);
element.append(contents.clone());
Ok(Self(element))
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
pub fn level(&self) -> u32 {
match self.as_element().unwrap().name.local {
local_name!("h1") => 1,
local_name!("h2") => 2,
local_name!("h3") => 3,
local_name!("h4") => 4,
local_name!("h5") => 5,
local_name!("h6") => 6,
_ => unreachable!("Non h[1-6] used in Heading"),
}
}
}
#[derive(Debug, Clone)]
pub struct Category(NodeRef);
impl Category {
const REL: &'static str = "mw:PageProp/Category";
pub(crate) const SELECTOR: &'static str = "[rel=\"mw:PageProp/Category\"]";
pub fn new(category: &str, sortkey: Option<&str>) -> Self {
let href = Self::build_href(
&full_link(category),
sortkey.map(encode).as_deref(),
);
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("link")),
vec![
(
ExpandedName::new(ns!(), local_name!("href")),
Attribute {
prefix: None,
value: href,
},
),
(
ExpandedName::new(ns!(), local_name!("rel")),
Attribute {
prefix: None,
value: Self::REL.to_string(),
},
),
],
);
Self(element)
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
fn split_href(&self) -> (String, Option<String>) {
let href = self
.as_element()
.unwrap()
.attributes
.borrow()
.get("href")
.unwrap()
.to_string();
let sp: Vec<_> = href.splitn(2, '#').collect();
if sp.len() == 2 {
(sp[0].to_string(), Some(sp[1].to_string()))
} else {
(sp[0].to_string(), None)
}
}
fn build_href(category: &str, sortkey: Option<&str>) -> String {
match sortkey {
Some(sortkey) => {
format!("{category}#{sortkey}")
}
None => category.to_string(),
}
}
pub fn category(&self) -> String {
let (category, _) = self.split_href();
clean_link(&category)
}
pub fn sort_key(&self) -> Option<String> {
let (_, sort_key) = self.split_href();
sort_key.map(|key| {
decode(&key).expect("Unable to decode sort key").to_string()
})
}
pub fn set_category(&self, category: &str) {
let (_, sort_key) = self.split_href();
self.set_href(&Self::build_href(
&full_link(category),
sort_key.as_deref(),
));
}
pub fn set_sort_key(&self, sort_key: Option<&str>) {
let (category, _) = self.split_href();
self.set_href(&Self::build_href(
&category,
sort_key.map(encode).as_deref(),
));
}
fn set_href(&self, href: &str) {
self.as_element()
.unwrap()
.attributes
.borrow_mut()
.insert("href", href.to_string());
}
}
#[derive(Debug, Clone)]
pub struct LanguageLink(NodeRef);
impl LanguageLink {
const REL: &'static str = "mw:PageProp/Language";
pub fn new(target: &str) -> Self {
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("link")),
vec![
(
ExpandedName::new(ns!(), local_name!("href")),
Attribute {
prefix: None,
value: target.to_string(),
},
),
(
ExpandedName::new(ns!(), local_name!("rel")),
Attribute {
prefix: None,
value: Self::REL.to_string(),
},
),
],
);
Self(element)
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
pub fn target(&self) -> String {
self.as_element()
.unwrap()
.attributes
.borrow()
.get("href")
.unwrap()
.to_string()
}
pub fn set_target(&self, target: &str) {
self.as_element()
.unwrap()
.attributes
.borrow_mut()
.insert("href", target.to_string());
}
}
#[derive(Debug, Clone)]
pub struct Redirect(NodeRef);
impl Redirect {
const REL: &'static str = "mw:PageProp/redirect";
pub(crate) const SELECTOR: &'static str = "[rel=\"mw:PageProp/redirect\"]";
pub fn new(target: &str) -> Self {
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("link")),
vec![
(
ExpandedName::new(ns!(), local_name!("href")),
Attribute {
prefix: None,
value: "".to_string(),
},
),
(
ExpandedName::new(ns!(), local_name!("rel")),
Attribute {
prefix: None,
value: Self::REL.to_string(),
},
),
],
);
let redirect = Self(element);
redirect.set_target(target);
redirect
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
pub fn is_external(&self) -> bool {
!self.raw_target().starts_with("./")
}
pub fn raw_target(&self) -> String {
self.as_element()
.unwrap()
.attributes
.borrow()
.get("href")
.unwrap()
.to_string()
}
pub fn target(&self) -> String {
let raw = self.raw_target();
if raw.starts_with("./") {
clean_link(&raw)
} else {
raw
}
}
pub fn set_target(&self, target: &str) {
let new = if target.starts_with("http://")
|| target.starts_with("https://")
{
target.to_string()
} else {
full_link(target)
};
self.as_element()
.unwrap()
.attributes
.borrow_mut()
.insert("href", new);
}
}
#[derive(Debug, Clone)]
pub struct IncludeOnly(NodeRef);
impl IncludeOnly {
const TYPEOF: &'static str = "mw:Includes/IncludeOnly";
pub fn new(wikitext: &str) -> Result<Self> {
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("meta")),
vec![(
ExpandedName::new(ns!(), "typeof"),
Attribute {
prefix: None,
value: Self::TYPEOF.to_string(),
},
)],
);
let includeonly = Self(element);
includeonly.set_wikitext(wikitext)?;
Ok(includeonly)
}
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
pub fn wikitext(&self) -> Result<String> {
let data: IncludeOnlyDataMw = inner_data(self)?;
Ok(data
.src
.strip_prefix("<includeonly>")
.unwrap()
.strip_suffix("</includeonly>")
.unwrap()
.to_string())
}
pub fn set_wikitext(&self, wikitext: &str) -> Result<()> {
set_inner_data(
self,
crate::inclusion::IncludeOnlyDataMw {
src: format!("<includeonly>{wikitext}</includeonly>"),
},
)
}
}
#[derive(Debug, Clone)]
pub struct Placeholder(NodeRef);
impl Placeholder {
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
}
#[derive(Debug, Clone)]
pub struct DisplaySpace(NodeRef);
impl DisplaySpace {
const TYPEOF: &'static str = "mw:DisplaySpace";
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
}
#[derive(Debug, Clone)]
pub struct Indicator(NodeRef);
impl Indicator {
const TYPEOF: &'static str = "mw:Extension/indicator";
pub(crate) fn new_from_node(element: &NodeRef) -> Self {
assert_element(element);
Self(element.clone())
}
pub fn new(name: &str, wikitext: &str) -> Result<Self> {
let data = IndicatorDataMw {
name: "indicator".to_string(),
attrs: IndicatorAttrs {
name: name.to_string(),
},
body: IndicatorBody {
extsrc: wikitext.to_string(),
},
};
let element = NodeRef::new_element(
crate::build_qual_name(local_name!("meta")),
vec![
(
ExpandedName::new(ns!(), "typeof"),
Attribute {
prefix: None,
value: Self::TYPEOF.to_string(),
},
),
(
ExpandedName::new(ns!(), "data-mw"),
Attribute {
prefix: None,
value: serde_json::to_string(&data)?,
},
),
],
);
Ok(Self(element))
}
pub fn name(&self) -> Result<String> {
Ok(self.inner()?.attrs.name)
}
pub fn set_name(&self, name: &str) -> Result<()> {
let mut data = self.inner()?;
data.attrs.name = name.to_string();
self.set_inner(data)?;
Ok(())
}
pub fn wikitext(&self) -> Result<String> {
Ok(self.inner()?.body.extsrc)
}
pub fn set_wikitext(&self, wikitext: &str) -> Result<()> {
let mut data = self.inner()?;
data.body.extsrc = wikitext.to_string();
self.set_inner(data)?;
Ok(())
}
fn inner(&self) -> Result<IndicatorDataMw> {
inner_data(self)
}
fn set_inner(&self, data: IndicatorDataMw) -> Result<()> {
set_inner_data(self, data)
}
}