2022-08-12 22:29:46 +00:00
|
|
|
use crate::avm2::script::TranslationUnit;
|
2023-02-09 16:54:38 +00:00
|
|
|
use crate::avm2::Activation;
|
2022-08-12 22:29:46 +00:00
|
|
|
use crate::avm2::Error;
|
2023-02-09 16:54:38 +00:00
|
|
|
use crate::avm2::{Namespace, NamespaceData};
|
2022-08-12 22:29:46 +00:00
|
|
|
use crate::either::Either;
|
|
|
|
use crate::string::{AvmString, WStr, WString};
|
|
|
|
use gc_arena::{Collect, MutationContext};
|
|
|
|
use std::fmt::Debug;
|
|
|
|
use swf::avm2::types::{Index, Multiname as AbcMultiname};
|
|
|
|
|
|
|
|
/// A `QName`, likely "qualified name", consists of a namespace and name string.
|
|
|
|
///
|
|
|
|
/// This is technically interchangeable with `xml::XMLName`, as they both
|
|
|
|
/// implement `QName`; however, AVM2 and XML have separate representations.
|
|
|
|
///
|
|
|
|
/// A property cannot be retrieved or set without first being resolved into a
|
|
|
|
/// `QName`. All other forms of names and multinames are either versions of
|
|
|
|
/// `QName` with unspecified parameters, or multiple names to be checked in
|
|
|
|
/// order.
|
2023-02-09 16:54:38 +00:00
|
|
|
#[derive(Clone, Copy, Collect)]
|
2022-08-12 22:29:46 +00:00
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct QName<'gc> {
|
|
|
|
ns: Namespace<'gc>,
|
|
|
|
name: AvmString<'gc>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> PartialEq for QName<'gc> {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
// Implemented by hand to enforce order of comparisons for perf
|
|
|
|
self.name == other.name && self.ns == other.ns
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Eq for QName<'gc> {}
|
|
|
|
|
|
|
|
impl<'gc> QName<'gc> {
|
|
|
|
pub fn new(ns: Namespace<'gc>, name: impl Into<AvmString<'gc>>) -> Self {
|
|
|
|
Self {
|
|
|
|
ns,
|
|
|
|
name: name.into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Pull a `QName` from the multiname pool.
|
|
|
|
///
|
|
|
|
/// This function returns an Err if the multiname does not exist or is not
|
|
|
|
/// a `QName`.
|
|
|
|
pub fn from_abc_multiname(
|
|
|
|
translation_unit: TranslationUnit<'gc>,
|
|
|
|
multiname_index: Index<AbcMultiname>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
2022-09-13 21:04:04 +00:00
|
|
|
) -> Result<Self, Error<'gc>> {
|
2022-08-12 22:29:46 +00:00
|
|
|
if multiname_index.0 == 0 {
|
|
|
|
return Err("Attempted to load a trait name of index zero".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let actual_index = multiname_index.0 as usize - 1;
|
|
|
|
let abc = translation_unit.abc();
|
2022-09-13 21:04:04 +00:00
|
|
|
let abc_multiname: Result<_, Error<'gc>> = abc
|
2022-08-12 22:29:46 +00:00
|
|
|
.constant_pool
|
|
|
|
.multinames
|
|
|
|
.get(actual_index)
|
|
|
|
.ok_or_else(|| format!("Unknown multiname constant {}", multiname_index.0).into());
|
|
|
|
|
|
|
|
Ok(match abc_multiname? {
|
|
|
|
AbcMultiname::QName { namespace, name } => Self {
|
2023-02-09 16:54:38 +00:00
|
|
|
ns: translation_unit.pool_namespace(*namespace, mc)?,
|
2022-08-12 22:29:46 +00:00
|
|
|
name: translation_unit.pool_string(name.0, mc)?,
|
|
|
|
},
|
|
|
|
_ => return Err("Attempted to pull QName from non-QName multiname".into()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Constructs a `QName` from a fully qualified name.
|
|
|
|
///
|
|
|
|
/// A fully qualified name can be any of the following formats:
|
|
|
|
/// NAMESPACE::LOCAL_NAME
|
|
|
|
/// NAMESPACE.LOCAL_NAME (Where the LAST dot is used to split the namespace & local_name)
|
|
|
|
/// LOCAL_NAME (Use the public namespace)
|
2023-02-09 16:54:38 +00:00
|
|
|
pub fn from_qualified_name(name: AvmString<'gc>, activation: &mut Activation<'_, 'gc>) -> Self {
|
|
|
|
let mc = activation.context.gc_context;
|
2022-08-12 22:29:46 +00:00
|
|
|
let parts = name
|
|
|
|
.rsplit_once(WStr::from_units(b"::"))
|
|
|
|
.or_else(|| name.rsplit_once(WStr::from_units(b".")));
|
|
|
|
|
|
|
|
if let Some((package_name, local_name)) = parts {
|
|
|
|
Self {
|
2023-02-09 16:54:38 +00:00
|
|
|
ns: Namespace::package(AvmString::new(mc, package_name), mc),
|
2022-08-12 22:29:46 +00:00
|
|
|
name: AvmString::new(mc, local_name),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Self {
|
2023-02-09 16:54:38 +00:00
|
|
|
ns: activation.avm2().public_namespace,
|
2022-08-12 22:29:46 +00:00
|
|
|
name,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Converts this `QName` to a fully qualified name.
|
|
|
|
pub fn to_qualified_name(self, mc: MutationContext<'gc, '_>) -> AvmString<'gc> {
|
|
|
|
match self.to_qualified_name_no_mc() {
|
|
|
|
Either::Left(avm_string) => avm_string,
|
|
|
|
Either::Right(wstring) => AvmString::new(mc, wstring),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Like `to_qualified_name`, but avoids the need for a `MutationContext`
|
|
|
|
/// by returning `Either::Right(wstring)` when it would otherwise
|
|
|
|
/// be necessary to allocate a new `AvmString`.
|
|
|
|
///
|
|
|
|
/// This method is intended for contexts like `Debug` impls where
|
|
|
|
/// a `MutationContext` is not available. Normally, you should
|
|
|
|
/// use `to_qualified_name`
|
|
|
|
pub fn to_qualified_name_no_mc(self) -> Either<AvmString<'gc>, WString> {
|
|
|
|
let uri = self.namespace().as_uri();
|
|
|
|
let name = self.local_name();
|
|
|
|
if uri.is_empty() {
|
|
|
|
Either::Left(name)
|
|
|
|
} else {
|
|
|
|
Either::Right({
|
|
|
|
let mut buf = WString::from(uri.as_wstr());
|
|
|
|
buf.push_str(WStr::from_units(b"::"));
|
|
|
|
buf.push_str(&name);
|
|
|
|
buf
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-13 17:17:24 +00:00
|
|
|
// Like `to_qualified_name`, but uses a `.` instead of `::` separate
|
|
|
|
// the namespace and local name. This matches the output produced by
|
|
|
|
// Flash Player in error messages
|
|
|
|
pub fn to_qualified_name_err_message(self, mc: MutationContext<'gc, '_>) -> AvmString<'gc> {
|
|
|
|
let mut buf = WString::new();
|
|
|
|
let uri = self.namespace().as_uri();
|
|
|
|
if !uri.is_empty() {
|
|
|
|
buf.push_str(&uri);
|
|
|
|
buf.push_char('.');
|
|
|
|
}
|
|
|
|
buf.push_str(&self.local_name());
|
|
|
|
AvmString::new(mc, buf)
|
|
|
|
}
|
|
|
|
|
2022-08-12 22:29:46 +00:00
|
|
|
pub fn local_name(&self) -> AvmString<'gc> {
|
|
|
|
self.name
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn namespace(self) -> Namespace<'gc> {
|
|
|
|
self.ns
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the string value of this QName, including the namespace URI.
|
|
|
|
pub fn as_uri(&self, mc: MutationContext<'gc, '_>) -> AvmString<'gc> {
|
2023-02-09 16:54:38 +00:00
|
|
|
let ns = match &*self.ns.0 {
|
|
|
|
NamespaceData::Namespace(s) => s,
|
|
|
|
NamespaceData::PackageInternal(s) => s,
|
|
|
|
NamespaceData::Protected(s) => s,
|
|
|
|
NamespaceData::Explicit(s) => s,
|
|
|
|
NamespaceData::StaticProtected(s) => s,
|
|
|
|
NamespaceData::Private(s) => s,
|
|
|
|
NamespaceData::Any => WStr::from_units(b"*"),
|
2022-08-12 22:29:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if ns.is_empty() {
|
|
|
|
return self.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut uri = WString::from(ns);
|
|
|
|
uri.push_str(WStr::from_units(b"::"));
|
|
|
|
uri.push_str(&self.name);
|
|
|
|
AvmString::new(mc, uri)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Debug for QName<'gc> {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
|
|
|
match self.to_qualified_name_no_mc() {
|
2022-10-26 23:46:09 +00:00
|
|
|
Either::Left(name) => write!(f, "{name}"),
|
|
|
|
Either::Right(name) => write!(f, "{name}"),
|
2022-08-12 22:29:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|