{"record":{"id":"624feb96bb70f324","repo":"instructure/canvas-lms","slug":"invalid-xml-syntax","errorCode":null,"errorMessage":"Invalid xml syntax","messagePattern":"Invalid xml syntax","errorType":"exception","errorClass":"CCImportError","httpStatus":null,"severity":"error","filePath":"lib/cc/importer/blti_converter.rb","lineNumber":120,"sourceCode":"      end\n      if (icon = get_node_val(doc, \"#{link_css_path} > #{blti}|icon\"))\n        tool[:settings] ||= {}\n        tool[:settings][:icon_url] = icon.strip\n      end\n      tool\n    end\n\n    def convert_blti_xml(xml)\n      doc = create_xml_doc(xml)\n      unless doc.namespaces.to_s.downcase.include? \"imsglobal\"\n        raise CCImportError, I18n.t(\"Invalid XML Configuration\")\n      end\n\n      begin\n        tool = convert_blti_link(doc)\n        check_for_unescaped_url_properties(tool) if tool\n      rescue Nokogiri::XML::XPath::SyntaxError\n        raise CCImportError, I18n.t(:invalid_xml_syntax, \"Invalid xml syntax\")\n      end\n      tool\n    end\n\n    def check_for_unescaped_url_properties(obj)\n      # Recursively look for properties named 'url'\n      case obj\n      when Hash\n        obj.select { |k, v| k.to_s == \"url\" && v.is_a?(String) }\n           .each_value { |v| check_for_unescaped_url(v) }\n        obj.each_value { |v| check_for_unescaped_url_properties(v) }\n      when Array\n        obj.each { |o| check_for_unescaped_url_properties(o) }\n      end\n    end\n\n    def check_for_unescaped_url(url)\n      if /(.*[^=]*\\?*=)[^&;]*=/.match?(url)","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/cc/importer/blti_converter.rb#L102-L138","documentation":"After namespace validation, convert_blti_xml runs XPath queries via convert_blti_link. A Nokogiri::XML::XPath::SyntaxError rescued there is re-raised as CCImportError 'Invalid xml syntax', meaning the document could not be evaluated with the expected XPath expressions — typically malformed or structurally unexpected XML.","triggerScenarios":"convert_blti_xml is given XML that parses loosely but breaks the XPath evaluation inside convert_blti_link — e.g. truncated documents, unescaped characters, or content served with a non-XML doctype/structure.","commonSituations":"Tool config URL returns truncated HTML/XML; the XML contains unescaped ampersands or invalid entities; a proxy mangles the response body; the vendor serves a partial file.","solutions":["Validate the XML with a parser (xmllint --noout config.xml) and fix syntax errors such as unescaped & or unclosed tags","Log/inspect response.body before conversion to see exactly what the server returned","Re-download the configuration XML from the tool provider"],"exampleFix":"# before (invalid)\n<launch_url>https://tool.example.com/launch?a=1&b=2</launch_url>\n\n# after (valid)\n<launch_url>https://tool.example.com/launch?a=1&amp;b=2</launch_url>","handlingStrategy":"validation","validationCode":"Nokogiri::XML(xml) { |c| c.strict }.parse # raises SyntaxError before import if malformed","typeGuard":"def well_formed_xml?(xml)\n  Nokogiri::XML(xml) { |c| c.strict }\n  true\nrescue Nokogiri::XML::SyntaxError\n  false\nend","tryCatchPattern":"begin\n  tool = converter.convert_blti_xml(xml)\nrescue CCImportError => e\n  if e.message.include?('Invalid xml syntax')\n    Rails.logger.error(\"Malformed BLTI XML: #{xml.first(200)}\")\n  end\n  raise\nend","preventionTips":["Validate fetched XML with xmllint or strict Nokogiri parsing before import","Escape special characters (&, <) when generating config XML","Log response bodies on failure to spot HTML error pages served as XML"],"tags":["xml","nokogiri","lti","parse-error"],"backgroundTag":"xml-parse-error","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}