///|
/// SVG XML Parser
/// Parses SVG markup into SVGNode tree

///|
/// SVG Parser state
priv struct SVGParser {
  events : Array[SVGXmlEvent]
  mut pos : Int
}

///|
/// SVG parse context for reusable resources
priv struct SVGParseContext {
  symbols : SymbolRegistry
  defs : DefsRegistry
  clips : ClipPathRegistry
  masks : MaskRegistry
  filter_graphs : FilterGraphRegistry
  patterns : PatternRegistry
  gradients : GradientRegistry
  markers : MarkerRegistry
  stylesheets : Array[@css_cascade.Stylesheet]
  element_stack : Array[@css_selector.Element]
  sibling_index_stack : Array[Int]
  sibling_count_stack : Array[Int]
  previous_sibling_stack : Array[@css_selector.Element?]
  selector_element_slots : Array[@css_selector.Element?]
  style_stack : Array[InheritedStyle]
  viewport_stack : Array[(Double, Double)]
  length_viewport_stack : Array[(Double, Double)]
  css_viewport : (Double, Double)
  media_environment : @css_media.MediaEnvironment
  element_state_resolver : ((String) -> ElementState)?
  sample_time_seconds : Double
}

///|
fn SVGParseContext::new(
  stylesheets : Array[@css_cascade.Stylesheet],
  initial_length_viewport? : (Double, Double) = (300.0, 150.0),
  environment? : RenderEnvironment = RenderEnvironment::default(),
) -> SVGParseContext {
  let color_scheme = match environment.color_scheme {
    Light => @css_media.Light
    Dark => Dark
  }
  {
    symbols: SymbolRegistry::new(),
    defs: DefsRegistry::new(),
    clips: ClipPathRegistry::new(),
    masks: MaskRegistry::new(),
    filter_graphs: FilterGraphRegistry::new(),
    patterns: PatternRegistry::new(),
    gradients: GradientRegistry::new(),
    markers: MarkerRegistry::new(),
    stylesheets,
    element_stack: [],
    sibling_index_stack: [],
    sibling_count_stack: [],
    previous_sibling_stack: [],
    selector_element_slots: [],
    style_stack: [],
    viewport_stack: [],
    length_viewport_stack: [initial_length_viewport],
    css_viewport: initial_length_viewport,
    media_environment: {
      viewport_width: initial_length_viewport.0,
      viewport_height: initial_length_viewport.1,
      device_pixel_ratio: environment.device_pixel_ratio,
      color_scheme,
    },
    element_state_resolver: environment.element_state_resolver,
    sample_time_seconds: environment.sample_time_seconds,
  }
}

///|
priv struct InheritedStyle {
  fill : Paint
  fill_rule : FillRule
  clip_rule : FillRule
  fill_opacity : Double
  stroke : StrokeStyle
  stroke_opacity : Double
  color : Color
  paint_order : PaintOrder
  marker_start : String?
  marker_mid : String?
  marker_end : String?
  image_sampling : ImageSampling
  stop_color : Color
  stop_opacity : Double
  css_style : @css_style.Style
  custom_properties : Map[String, String]
  root_font_size : Double
}

///|
fn InheritedStyle::default() -> InheritedStyle {
  {
    fill: SolidColor(Color::black()),
    fill_rule: NonZero,
    clip_rule: NonZero,
    fill_opacity: 1.0,
    stroke: StrokeStyle::default(),
    stroke_opacity: 1.0,
    color: Color::black(),
    paint_order: PaintOrder::default(),
    marker_start: None,
    marker_mid: None,
    marker_end: None,
    image_sampling: Bilinear,
    stop_color: Color::black(),
    stop_opacity: 1.0,
    css_style: @css_style.Style::default(),
    custom_properties: Map([]),
    root_font_size: 16.0,
  }
}

///|
fn SVGParseContext::current_style(self : SVGParseContext) -> InheritedStyle {
  if self.style_stack.length() == 0 {
    InheritedStyle::default()
  } else {
    self.style_stack[self.style_stack.length() - 1]
  }
}

///|
fn SVGParseContext::current_sibling_index(self : SVGParseContext) -> Int {
  if self.sibling_index_stack.length() == 0 {
    1
  } else {
    self.sibling_index_stack[self.sibling_index_stack.length() - 1]
  }
}

///|
fn SVGParseContext::current_sibling_count(self : SVGParseContext) -> Int {
  if self.sibling_count_stack.length() == 0 {
    1
  } else {
    self.sibling_count_stack[self.sibling_count_stack.length() - 1]
  }
}

///|
fn SVGParseContext::current_previous_sibling(
  self : SVGParseContext,
) -> @css_selector.Element? {
  if self.previous_sibling_stack.length() == 0 {
    None
  } else {
    self.previous_sibling_stack[self.previous_sibling_stack.length() - 1]
  }
}

///|
fn SVGParseContext::record_selector_element(
  self : SVGParseContext,
  tag : String,
  attrs : Array[(String, String)],
) -> @css_selector.Element {
  let parent : @css_selector.Element? = if self.element_stack.length() == 0 {
    None
  } else {
    Some(self.element_stack[self.element_stack.length() - 1])
  }
  let element = style_element(
    tag,
    attrs,
    parent,
    self.current_previous_sibling(),
    self.current_sibling_index(),
    self.current_sibling_count(),
  )
  let depth = self.element_stack.length()
  while self.selector_element_slots.length() <= depth {
    self.selector_element_slots.push(None)
  }
  self.selector_element_slots[depth] = Some(element)
  element
}

///|
fn SVGParseContext::cascaded_style(
  self : SVGParseContext,
  tag : String,
  attrs : Array[(String, String)],
) -> @css_cascade.CascadedValues {
  let element = self.record_selector_element(tag, attrs)
  let state = match self.element_state_resolver {
    Some(resolve) =>
      resolve(get_attr(attrs, "id").unwrap_or("")).to_forced_pseudo_states()
    None => @css_selector.ForcedPseudoStates::none()
  }
  cascade_element_styles(
    self.stylesheets,
    element,
    attrs,
    self.media_environment,
    forced_states=state,
  )
}

///|
fn resolve_element_computed_style(
  node : SVGNode,
  tag : String,
  attrs : Array[(String, String)],
  ctx : SVGParseContext,
  parent : InheritedStyle,
  apply_noncomputed : Bool,
) -> InheritedStyle {
  let length_viewport = ctx.current_length_viewport().unwrap_or((300.0, 150.0))
  resolve_computed_style(
    node,
    attrs,
    ctx.cascaded_style(tag, attrs),
    parent,
    apply_noncomputed,
    length_viewport,
    ctx.css_viewport,
    is_root=ctx.element_stack.length() == 0,
    stylesheets=ctx.stylesheets,
    sample_time_seconds=ctx.sample_time_seconds,
  )
}

///|
fn element_style_property_is_declared(
  tag : String,
  attrs : Array[(String, String)],
  ctx : SVGParseContext,
  name : String,
) -> Bool {
  ctx.cascaded_style(tag, attrs).has(name)
}

///|
fn inherited_style_from_node(
  node : SVGNode,
  css_style : @css_style.Style,
  custom_properties : Map[String, String],
  stop_color : Color,
  stop_opacity : Double,
  root_font_size : Double,
) -> InheritedStyle {
  {
    fill: node.fill,
    fill_rule: node.fill_rule,
    clip_rule: node.clip_rule,
    fill_opacity: node.fill_opacity,
    stroke: node.stroke,
    stroke_opacity: node.stroke_opacity,
    color: node.color.unwrap_or(Color::black()),
    paint_order: node.paint_order,
    marker_start: node.marker_start,
    marker_mid: node.marker_mid,
    marker_end: node.marker_end,
    image_sampling: node.image_sampling,
    stop_color,
    stop_opacity,
    css_style,
    custom_properties,
    root_font_size,
  }
}

///|
fn apply_inherited_markers(child : SVGNode, parent : SVGNode) -> Unit {
  if !child.marker_start_is_set {
    child.marker_start = parent.marker_start
  }
  if !child.marker_mid_is_set {
    child.marker_mid = parent.marker_mid
  }
  if !child.marker_end_is_set {
    child.marker_end = parent.marker_end
  }
}

///|
fn SVGParseContext::current_viewport(
  self : SVGParseContext,
) -> (Double, Double)? {
  if self.viewport_stack.length() == 0 {
    None
  } else {
    Some(self.viewport_stack[self.viewport_stack.length() - 1])
  }
}

///|
fn SVGParseContext::current_length_viewport(
  self : SVGParseContext,
) -> (Double, Double)? {
  if self.length_viewport_stack.length() == 0 {
    None
  } else {
    Some(self.length_viewport_stack[self.length_viewport_stack.length() - 1])
  }
}

///|
fn SVGParser::new(events : Array[SVGXmlEvent]) -> SVGParser {
  { events, pos: 0 }
}

///|
fn SVGParser::is_end(self : SVGParser) -> Bool {
  self.pos >= self.events.length() || self.events[self.pos] is XmlEof
}

///|
fn SVGParser::peek_event(self : SVGParser) -> SVGXmlEvent {
  if self.pos < self.events.length() {
    self.events[self.pos]
  } else {
    XmlEof
  }
}

///|
fn SVGParser::advance_event(self : SVGParser) -> SVGXmlEvent {
  if self.pos < self.events.length() {
    let event = self.events[self.pos]
    self.pos += 1
    event
  } else {
    XmlEof
  }
}

///|
/// Parse SVG markup string into SVGDocument with resources
pub fn parse_svg_document(svg_str : String) -> SVGDocument? {
  parse_svg_document_in_viewport(svg_str, 300.0, 150.0)
}

///|
fn parse_svg_document_in_viewport(
  svg_str : String,
  viewport_width : Double,
  viewport_height : Double,
) -> SVGDocument? {
  parse_svg_document_in_environment(
    svg_str,
    viewport_width,
    viewport_height,
    RenderEnvironment::default(),
  )
}

///|
fn parse_svg_document_in_environment(
  svg_str : String,
  viewport_width : Double,
  viewport_height : Double,
  environment : RenderEnvironment,
) -> SVGDocument? {
  parse_svg_document_with_options(
    svg_str,
    viewport_width,
    viewport_height,
    { ..RenderOptions::default(), environment, },
    [],
  )
}

///|
fn parse_svg_document_with_options(
  svg_str : String,
  viewport_width : Double,
  viewport_height : Double,
  options : RenderOptions,
  diagnostics : Array[RenderDiagnostic],
) -> SVGDocument? {
  let state = TextResourceLoadState::new(
    options, diagnostics, viewport_width, viewport_height,
  )
  parse_svg_document_with_state(
    svg_str,
    viewport_width,
    viewport_height,
    options,
    state,
    options.environment.base_uri,
  )
}

///|
fn parse_svg_document_with_state(
  svg_str : String,
  viewport_width : Double,
  viewport_height : Double,
  options : RenderOptions,
  resource_state : TextResourceLoadState,
  base_uri : String,
) -> SVGDocument? {
  let raw_events = match read_xml_events(svg_str) {
    Some(events) => events
    None => return None
  }
  let events = canonicalize_svg_reference_events(raw_events, base_uri)
  let parser = SVGParser::new(events)
  let stylesheets = load_document_stylesheets(events, resource_state, base_uri)
  let ctx = SVGParseContext::new(
    stylesheets,
    initial_length_viewport=(viewport_width, viewport_height),
    environment=options.environment,
  )
  for uri in collect_external_document_uris(events, stylesheets) {
    match
      resource_state.load_svg_document(uri, viewport_width, viewport_height) {
      Some(document) => merge_external_document(ctx, document, uri)
      None => ()
    }
  }
  match parse_element(parser, ctx, "") {
    Some(root) =>
      Some({
        root,
        symbols: ctx.symbols,
        definitions: ctx.defs,
        clips: ctx.clips,
        masks: ctx.masks,
        filter_graphs: ctx.filter_graphs,
        patterns: ctx.patterns,
        gradients: ctx.gradients,
        markers: ctx.markers,
      })
    None => None
  }
}

///|
fn parse_element(
  parser : SVGParser,
  ctx : SVGParseContext,
  parent_tag : String,
) -> SVGNode? {
  let mut element : SVGXmlElement? = None
  let mut self_closing = false
  while !parser.is_end() && element is None {
    match parser.advance_event() {
      XmlStart(found) => element = Some(found)
      XmlEmpty(found) => {
        element = Some(found)
        self_closing = true
      }
      XmlEnd(_) | XmlEof => return None
      _ => ()
    }
  }
  let element = match element {
    Some(element) => element
    None => return None
  }
  let tag_name = element.name
  let attrs = element.attributes
  let _ = ctx.record_selector_element(tag_name, attrs)
  match tag_name {
    "defs" => {
      if !self_closing {
        let temp = SVGNode::new(Group)
        apply_attributes(temp, attrs)
        let style = resolve_element_computed_style(
          temp,
          tag_name,
          attrs,
          ctx,
          ctx.current_style(),
          true,
        )
        parse_children(parser, temp, tag_name, attrs, ctx, style)
      }
      Group |> SVGNode::new |> Some
    }
    "symbol" => {
      let symbol_node = SVGNode::new(Group)
      let id = match get_attr(attrs, "id") {
        Some(v) => v
        None => ""
      }
      symbol_node.id = id
      apply_attributes(symbol_node, attrs)
      let symbol_style = resolve_element_computed_style(
        symbol_node,
        tag_name,
        attrs,
        ctx,
        ctx.current_style(),
        true,
      )
      let view_box = match get_attr(attrs, "viewBox") {
        Some(v) => parse_view_box(v)
        None => None
      }
      let width = match get_attr(attrs, "width") {
        Some(v) => v |> parse_length |> Some
        None => None
      }
      let height = match get_attr(attrs, "height") {
        Some(v) => v |> parse_length |> Some
        None => None
      }
      let display_none = match get_attr(attrs, "display") {
        Some(v) => trim_string(v) == "none"
        None => false
      }
      let preserve = match get_attr(attrs, "preserveAspectRatio") {
        Some(v) => parse_preserve_aspect_ratio(v)
        None => PreserveAspectRatio::default()
      }
      if !self_closing {
        parse_children(parser, symbol_node, tag_name, attrs, ctx, symbol_style)
      }
      let symbol = match view_box {
        Some(vb) =>
          {
            ..Symbol::with_viewbox(id, symbol_node, vb),
            preserve_aspect_ratio: preserve,
          }
        None =>
          { ..Symbol::new(id, symbol_node), preserve_aspect_ratio: preserve }
      }
      let symbol = { ..symbol, width, height, display_none }
      ctx.symbols.add(symbol)
      Group |> SVGNode::new |> Some
    }
    "clipPath" => {
      if !self_closing {
        parse_clip_path(parser, attrs, ctx, tag_name)
      } else {
        parse_clip_path_from_attrs(attrs, ctx)
      }
      Group |> SVGNode::new |> Some
    }
    "mask" => {
      if !self_closing {
        parse_mask(parser, attrs, ctx, tag_name)
      } else {
        parse_mask_from_attrs(attrs, ctx)
      }
      Group |> SVGNode::new |> Some
    }
    "filter" => {
      if !self_closing {
        parse_filter_graph(parser, attrs, ctx, tag_name)
      } else {
        register_filter_graph(attrs, ctx, [])
      }
      Group |> SVGNode::new |> Some
    }
    "marker" => {
      if !self_closing {
        parse_marker(parser, attrs, ctx, tag_name, parent_tag)
      } else {
        parse_marker_from_attrs(attrs, ctx, parent_tag)
      }
      Group |> SVGNode::new |> Some
    }
    "image" => {
      let node = parse_image(attrs, ctx)
      if !self_closing {
        skip_to_end_tag(parser, tag_name)
      }
      node
    }
    "pattern" => {
      if !self_closing {
        parse_pattern(parser, attrs, ctx, tag_name, parent_tag)
      } else {
        parse_pattern_from_attrs(attrs, ctx, parent_tag)
      }
      Group |> SVGNode::new |> Some
    }
    "linearGradient" => {
      if !self_closing {
        let temp = SVGNode::new(Group)
        let gradient_style = resolve_element_computed_style(
          temp,
          tag_name,
          attrs,
          ctx,
          ctx.current_style(),
          false,
        )
        parse_linear_gradient(
          parser, attrs, ctx, tag_name, parent_tag, gradient_style,
        )
      } else {
        parse_linear_gradient_from_attrs(attrs, ctx, parent_tag)
      }
      Group |> SVGNode::new |> Some
    }
    "radialGradient" => {
      if !self_closing {
        let temp = SVGNode::new(Group)
        let gradient_style = resolve_element_computed_style(
          temp,
          tag_name,
          attrs,
          ctx,
          ctx.current_style(),
          false,
        )
        parse_radial_gradient(
          parser, attrs, ctx, tag_name, parent_tag, gradient_style,
        )
      } else {
        parse_radial_gradient_from_attrs(attrs, ctx, parent_tag)
      }
      Group |> SVGNode::new |> Some
    }
    "use" => {
      let node = parse_use(attrs, ctx)
      if !self_closing {
        skip_to_end_tag(parser, tag_name)
      }
      match node {
        Some(_) => node
        None => Group |> SVGNode::new |> Some
      }
    }
    "style" => {
      if !self_closing {
        skip_to_end_tag(parser, tag_name)
      }
      Group |> SVGNode::new |> Some
    }
    "text" => {
      let content = if self_closing {
        Some("")
      } else {
        parse_plain_text_content(parser, tag_name)
      }
      match content {
        Some(text) => {
          let node = SVGNode::new(Text(x=0.0, y=0.0, text~, font_size=16.0))
          apply_attributes(node, attrs)
          let computed_style = resolve_element_computed_style(
            node,
            tag_name,
            attrs,
            ctx,
            ctx.current_style(),
            true,
          )
          let viewport = ctx.current_length_viewport().unwrap_or((300.0, 150.0))
          let length_context = LengthContext::new(
            viewport.0,
            viewport.1,
            font_size=computed_style.css_style.font_size,
            root_font_size=computed_style.root_font_size,
            css_viewport_width=ctx.css_viewport.0,
            css_viewport_height=ctx.css_viewport.1,
          )
          let x = get_attr(attrs, "x")
            .map(fn(value) {
              parse_length_with_context(value, Horizontal, length_context)
            })
            .unwrap_or(0.0)
          let y = get_attr(attrs, "y")
            .map(fn(value) {
              parse_length_with_context(value, Vertical, length_context)
            })
            .unwrap_or(0.0)
          node.shape = Text(
            x~,
            y~,
            text~,
            font_size=computed_style.css_style.font_size,
          )
          Some(node)
        }
        None => {
          let node = SVGNode::new(Group)
          apply_attributes(node, attrs)
          Some(node)
        }
      }
    }
    _ => {
      // Create node based on tag
      let node = create_node_for_tag(tag_name)
      // Apply attributes
      apply_attributes(node, attrs)
      let computed_style = resolve_element_computed_style(
        node,
        tag_name,
        attrs,
        ctx,
        ctx.current_style(),
        true,
      )
      let parent_length_viewport = ctx
        .current_length_viewport()
        .unwrap_or((300.0, 150.0))
      let element_length_context = LengthContext::new(
        parent_length_viewport.0,
        parent_length_viewport.1,
        font_size=computed_style.css_style.font_size,
        root_font_size=computed_style.root_font_size,
        css_viewport_width=ctx.css_viewport.0,
        css_viewport_height=ctx.css_viewport.1,
      )
      let element_declarations = cascaded_style_declarations(
        ctx.cascaded_style(tag_name, attrs),
      )
      fn geometry_value(name : String) -> String? {
        resolved_element_noncomputed_value(
          attrs,
          element_declarations,
          computed_style.custom_properties,
          name,
        )
      }
      if tag_name == "svg" {
        if parent_tag != "" {
          if geometry_value("width") is Some(v) {
            node.viewport_width = Some(
              parse_length_with_context(v, Horizontal, element_length_context),
            )
          }
          if geometry_value("height") is Some(v) {
            node.viewport_height = Some(
              parse_length_with_context(v, Vertical, element_length_context),
            )
          }
        }
        if get_attr(attrs, "overflow") is Some(v) {
          if trim_string(v) == "visible" {
            node.clip_overflow = false
          }
        }
        let x = match geometry_value("x") {
          Some(v) =>
            parse_length_with_context(v, Horizontal, element_length_context)
          None => 0.0
        }
        let y = match geometry_value("y") {
          Some(v) =>
            parse_length_with_context(v, Vertical, element_length_context)
          None => 0.0
        }
        if x != 0.0 || y != 0.0 {
          node.transform = Transform::translate(x, y).multiply(node.transform)
        }
      }
      let mut pushed = false
      if tag_name == "svg" && !self_closing {
        let viewport = (
          node.viewport_width.unwrap_or(
            geometry_value("width")
            .map(fn(value) {
              parse_length_with_context(
                value,
                Horizontal,
                element_length_context,
              )
            })
            .unwrap_or(parent_length_viewport.0),
          ),
          node.viewport_height.unwrap_or(
            geometry_value("height")
            .map(fn(value) {
              parse_length_with_context(value, Vertical, element_length_context)
            })
            .unwrap_or(parent_length_viewport.1),
          ),
        )
        ctx.viewport_stack.push(viewport)
        let child_length_viewport = match node.view_box {
          Some(view_box) => (view_box.width, view_box.height)
          None =>
            (
              node.viewport_width.unwrap_or(viewport.0),
              node.viewport_height.unwrap_or(viewport.1),
            )
        }
        ctx.length_viewport_stack.push(child_length_viewport)
        pushed = true
      }
      // Parse children (for container elements)
      if !self_closing {
        match node.shape {
          Group =>
            parse_children(parser, node, tag_name, attrs, ctx, computed_style)
          _ =>
            // Skip to end tag for non-container elements
            skip_to_end_tag(parser, tag_name)
        }
      }
      if pushed {
        let _ = ctx.viewport_stack.pop()
        let _ = ctx.length_viewport_stack.pop()
      }
      Some(node)
    }
  }
}

///|
fn skip_to_end_tag(parser : SVGParser, tag_name : String) -> Unit {
  let mut depth = 0
  while !parser.is_end() {
    match parser.advance_event() {
      XmlStart(_) => depth += 1
      XmlEmpty(_) => ()
      XmlEnd(name) => {
        if depth == 0 && name == tag_name {
          return
        }
        if depth > 0 {
          depth -= 1
        }
      }
      XmlEof => return
      _ => ()
    }
  }
}

///|
fn parse_plain_text_content(parser : SVGParser, tag_name : String) -> String? {
  let content = StringBuilder::new()
  let mut has_nested_element = false
  while !parser.is_end() {
    match parser.advance_event() {
      XmlText(text) | XmlCData(text) => content.write_string(text)
      XmlStart(element) => {
        has_nested_element = true
        skip_to_end_tag(parser, element.name)
      }
      XmlEmpty(_) => has_nested_element = true
      XmlEnd(name) =>
        if name == tag_name {
          return if has_nested_element {
            None
          } else {
            Some(content.to_string())
          }
        }
      XmlEof => return None
      _ => ()
    }
  }
  None
}

///|
fn count_direct_child_elements(parser : SVGParser, parent_tag : String) -> Int {
  let mut pos = parser.pos
  let mut depth = 0
  let mut count = 0
  while pos < parser.events.length() {
    match parser.events[pos] {
      XmlStart(_) => {
        if depth == 0 {
          count += 1
        }
        depth += 1
      }
      XmlEmpty(_) => if depth == 0 { count += 1 }
      XmlEnd(name) => {
        if depth == 0 && name == parent_tag {
          break
        }
        if depth > 0 {
          depth -= 1
        }
      }
      XmlEof => break
      _ => ()
    }
    pos += 1
  }
  count
}

///|
fn parse_children(
  parser : SVGParser,
  parent : SVGNode,
  parent_tag : String,
  parent_attrs : Array[(String, String)],
  ctx : SVGParseContext,
  parent_style : InheritedStyle,
) -> Unit {
  let parent_element : @css_selector.Element? = if ctx.element_stack.length() ==
    0 {
    None
  } else {
    Some(ctx.element_stack[ctx.element_stack.length() - 1])
  }
  let depth = ctx.element_stack.length()
  let parent_element = if depth < ctx.selector_element_slots.length() {
    match ctx.selector_element_slots[depth] {
      Some(element) => element
      None =>
        style_element(
          parent_tag,
          parent_attrs,
          parent_element,
          ctx.current_previous_sibling(),
          ctx.current_sibling_index(),
          ctx.current_sibling_count(),
        )
    }
  } else {
    style_element(
      parent_tag,
      parent_attrs,
      parent_element,
      ctx.current_previous_sibling(),
      ctx.current_sibling_index(),
      ctx.current_sibling_count(),
    )
  }
  ctx.element_stack.push(parent_element)
  ctx.style_stack.push(parent_style)
  ctx.sibling_index_stack.push(0)
  ctx.sibling_count_stack.push(count_direct_child_elements(parser, parent_tag))
  ctx.previous_sibling_stack.push(None)
  while !parser.is_end() {
    match parser.peek_event() {
      XmlEnd(name) => {
        let _ = parser.advance_event()
        if name == parent_tag {
          break
        }
      }
      XmlStart(_) | XmlEmpty(_) => {
        let current_index = ctx.sibling_index_stack.length() - 1
        ctx.sibling_index_stack[current_index] += 1
        match parse_element(parser, ctx, parent_tag) {
          Some(child) => {
            let child_depth = ctx.element_stack.length()
            if child_depth < ctx.selector_element_slots.length() {
              ctx.previous_sibling_stack[current_index] = ctx.selector_element_slots[child_depth]
            }
            apply_inherited_markers(child, parent)
            parent.children.push(child)
            if child.id.length() > 0 {
              ctx.defs.add(child.id, child)
            }
          }
          None => break
        }
      }
      XmlEof => break
      _ => {
        let _ = parser.advance_event()
      }
    }
  }
  let _ = ctx.previous_sibling_stack.pop()
  let _ = ctx.sibling_count_stack.pop()
  let _ = ctx.sibling_index_stack.pop()
  let _ = ctx.style_stack.pop()
  let _ = ctx.element_stack.pop()
}