asciidork-parser 0.33.0

Asciidork parser
Documentation
use crate::internal::*;
use crate::variants::token::*;
use ast::short::block::*;

impl<'arena> Parser<'arena> {
  pub(crate) fn parse_image_block(
    &mut self,
    mut lines: ContiguousLines<'arena>,
    meta: ChunkMeta<'arena>,
  ) -> Result<Block<'arena>> {
    let mut line = lines.consume_current().unwrap();
    let loc = line.loc().unwrap();
    line.discard_assert(MacroName);
    line.discard_assert(Colon);
    let is_uri = line.current_token().kind(UriScheme);
    let target = line.consume_macro_target(self.bump);
    let attrs = self.parse_block_attr_list(&mut line)?;
    self.restore_lines(lines);
    let kind = self.parse_image_kind(&target, is_uri, &attrs, Some(&meta.attrs))?;
    Ok(Block {
      meta,
      context: Context::Image,
      content: Content::Empty(EmptyMetadata::Image { target, attrs, kind }),
      loc: loc.into(),
    })
  }

  pub(crate) fn parse_image_kind(
    &mut self,
    target: &SourceString<'arena>,
    target_is_uri: bool,
    macro_attrs: &AttrList<'arena>,
    block_attrs: Option<&MultiAttrList<'arena>>,
  ) -> Result<ImageKind<'arena>> {
    if self.should_resolve_as_inline_svg(target, macro_attrs, block_attrs) {
      self.parse_inline_svg(target, target_is_uri, macro_attrs)
    } else {
      Ok(ImageKind::Standard)
    }
  }

  pub(crate) fn parse_inline_svg(
    &mut self,
    target: &SourceString<'arena>,
    target_is_uri: bool,
    attrs: &AttrList<'arena>,
  ) -> Result<ImageKind<'arena>> {
    let Some(resolver) = self.include_resolver.as_mut() else {
      self.err_at("No include resolver supplied for inline svg", target.loc)?;
      return Ok(ImageKind::InlineSvg(None));
    };

    if target_is_uri && !self.document.meta.is_true("allow-uri-read") {
      self.err_at(
        "Cannot include URL contents (allow-uri-read not enabled)",
        target.loc,
      )?;
      return Ok(ImageKind::InlineSvg(None));
    }

    let include_target = if target_is_uri {
      IncludeTarget::Uri(target.src.to_string())
    } else if let Some(base_dir) = resolver.get_base_dir().map(Path::new) {
      let mut path = base_dir;
      if let Some(imagesdir) = self.document.meta.str("imagesdir") {
        path = path.join(imagesdir);
      }
      path = path.join(&*target.src);
      IncludeTarget::FilePath(path.to_string())
    } else {
      self.err_at(
        "Base dir required to resolve relative-path inline svg for include",
        target.loc,
      )?;
      return Ok(ImageKind::InlineSvg(None));
    };

    let mut buffer = BumpVec::new_in(self.bump);
    match resolver.resolve(include_target, &mut buffer, self.document.meta.safe_mode) {
      Ok(_) => {
        if let Err(msg) = self.normalize_encoding(None, &mut buffer) {
          self.err_at(
            format!("Error resolving file contents for inline svg: {msg}"),
            target.loc,
          )?;
          return Ok(ImageKind::InlineSvg(None));
        }
      }
      Err(msg) => {
        self.err_at(format!("Error including inline svg: {msg}"), target.loc)?;
        return Ok(ImageKind::InlineSvg(None));
      }
    }

    let mut buf = &buffer[..];
    if let Some(caps) = regx::SVG_PREAMBLE.captures(buf) {
      buf = &buffer[(caps[0].len() - 4)..];
    }
    buf = buf.trim_ascii();

    let width = attrs.named("width").or_else(|| attrs.str_positional_at(1));
    let height = attrs.named("height").or_else(|| attrs.str_positional_at(2));
    let image = if width.is_none() && height.is_none() {
      BumpString::from_utf8_lossy_in(buf, self.bump)
    } else {
      let mut start = BumpString::with_capacity_in(48, self.bump);
      start.push_str("<svg ");
      if let Some(width) = width {
        start.push_str("width=\"");
        start.push_str(width);
        start.push('"');
      }
      if let Some(height) = height {
        start.push_str("height=\"");
        start.push_str(height);
        start.push('"');
      }
      let stripped = regx::SVG_STRIP_ATTRS.replace_all(buf, b"");
      let modified = regx::SVG_START_TAG
        .replace(&stripped, &*start.into_bytes())
        .into_owned();
      BumpString::from_utf8_lossy_in(&modified, self.bump)
    };
    if image.is_empty() {
      self.err_at("Empty svg file", target.loc)?;
      return Ok(ImageKind::InlineSvg(None));
    }
    Ok(ImageKind::InlineSvg(Some(image)))
  }

  fn should_resolve_as_inline_svg(
    &self,
    target: &str,
    macro_attrs: &AttrList<'arena>,
    block_attrs: Option<&MultiAttrList<'arena>>,
  ) -> bool {
    if (!macro_attrs.has_option("inline") && block_attrs.is_none_or(|a| !a.has_option("inline")))
      || macro_attrs.named("format").is_some_and(|f| f != "svg")
    {
      return false;
    }
    if self.document.meta.safe_mode >= SafeMode::Secure {
      return false;
    }
    if macro_attrs.named("format").is_none() && !regx::SVG_TARGET.is_match(target) {
      return false;
    }
    true
  }
}