Skip to main content Link Search Menu Expand Document Toggle dark mode Copy Code (external link)

Namespace Registration

Namespace registration maps XML namespaces to .NET assembly namespaces, enabling the parser to discover and instantiate custom components when it encounters unknown XML elements.

How It Works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Template XML                                          β”‚
β”‚  <custom:StatCard ... />                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ XML Namespace                                         β”‚
β”‚  xmlns:custom='http://mycompany.com/components'      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Namespace Registration (scrybersettings.json)        β”‚
β”‚  XMLNamespace: http://mycompany.com/components       β”‚
β”‚  AssemblyPrefix: MyCompany.Components, MyAssembly    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Type Resolution                                       β”‚
β”‚  Load assembly β†’ Reflect types β†’ Match "StatCard"   β”‚
β”‚  Found: MyCompany.Components.StatCard               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Component Instantiation                               β”‚
β”‚  Activator.CreateInstance(typeof(StatCard))          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Configuration

JSON Configuration

scrybersettings.json:

{
  "Scryber": {
    "Parsing": {
      "Namespaces": [
        {
          "XMLNamespace": "http://mycompany.com/schemas/components",
          "AssemblyPrefix": "MyCompany.Components, MyCompany.Components"
        },
        {
          "XMLNamespace": "http://mycompany.com/schemas/charts",
          "AssemblyPrefix": "MyCompany.Charts, MyCompany.Charts"
        }
      ]
    }
  }
}

Configuration Properties

Property Description Example
XMLNamespace XML namespace URI (must match xmlns:prefix declaration) http://mycompany.com/components
AssemblyPrefix Assembly-qualified namespace: Namespace, AssemblyName MyCompany.Components, MyCompany.Components

Template Declaration

Once registered, use the namespace in templates:

<?xml version='1.0' encoding='utf-8' ?>
<html xmlns='http://www.w3.org/1999/xhtml'
      xmlns:custom='http://mycompany.com/schemas/components'
      xmlns:charts='http://mycompany.com/schemas/charts'>
    <body>
        <main>
            
            <!-- Resolves to MyCompany.Components.StatCard -->
            <custom:StatCard value='100' label='Sales' />
            
            <!-- Resolves to MyCompany.Charts.BarChart -->
            <charts:BarChart data='{@:ChartData}' />
            
        </main>
    </body>
</html>

Type Resolution Process

1. Assembly Loading

When the parser encounters an unknown element (e.g., <custom:StatCard>), it:

  1. Extracts the namespace prefix (custom)
  2. Looks up the XML namespace URI (http://mycompany.com/schemas/components)
  3. Finds the AssemblyPrefix (MyCompany.Components, MyCompany.Components)
  4. Loads or locates the assembly (MyCompany.Components.dll)

2. Type Reflection

Implementation in ParserDefintionFactory.UnsafeGetType():

private static Type UnsafeGetType(string xmlnamespace, string name, 
    ParsingOptions options, bool throwNotFound, out NamespaceKey key)
{
    // Check cache first
    string fullkey = xmlnamespace + ":" + name;
    if (_knownTypes.TryGetValue(fullkey, out Type found))
    {
        key = default;
        return found;
    }

    // Look up assembly for XML namespace
    string assemblyPrefixString = 
        options.LookupAssemblyForXmlNamespace(xmlnamespace, throwNotFound);
    
    if (string.IsNullOrEmpty(assemblyPrefixString))
        return null;

    // Parse assembly-qualified name
    string[] parts = assemblyPrefixString.Split(',');
    string namespacePrefix = parts[0].Trim();
    string assemblyName = parts.Length > 1 ? parts[1].Trim() : null;

    // Load assembly
    Assembly assembly = null;
    if (!string.IsNullOrEmpty(assemblyName))
    {
        assembly = Assembly.Load(assemblyName);
    }

    // Build full type name
    string fullTypeName = namespacePrefix + "." + name;
    
    // Try to get type
    Type type = null;
    if (assembly != null)
    {
        type = assembly.GetType(fullTypeName, false);
    }
    else
    {
        type = Type.GetType(fullTypeName, false);
    }

    // Cache result
    if (type != null)
    {
        _knownTypes[fullkey] = type;
    }

    return type;
}

3. Type Caching

Resolved types are cached in _knownTypes dictionary to avoid repeated reflection:

private static Dictionary<string, Type> _knownTypes = 
    new Dictionary<string, Type>();

Complete Example

1. Component Assembly

MyCompany.Components/StatCard.cs:

using Scryber;
using Scryber.Components;

namespace MyCompany.Components
{
    [PDFParsableComponent("StatCard")]
    public class StatCard : Panel
    {
        [PDFAttribute("value")]
        public string Value { get; set; }
        
        [PDFAttribute("label")]
        public string Label { get; set; }
        
        public StatCard() : base(ObjectTypes.Panel)
        {
        }
        
        protected override void OnInit(InitContext context)
        {
            base.OnInit(context);
            BuildContent();
        }
        
        private void BuildContent()
        {
            var valueLabel = new Label { Text = Value };
            valueLabel.Style.Font.FontSize = 24;
            this.Contents.Add(valueLabel);
            
            var labelText = new Label { Text = Label };
            labelText.Style.Font.FontSize = 12;
            this.Contents.Add(labelText);
        }
    }
}

2. Configuration

scrybersettings.json:

{
  "Scryber": {
    "Parsing": {
      "Namespaces": [
        {
          "XMLNamespace": "http://mycompany.com/schemas/components",
          "AssemblyPrefix": "MyCompany.Components, MyCompany.Components"
        }
      ]
    }
  }
}

3. Template

Dashboard.pdfx:

<?xml version='1.0' encoding='utf-8' ?>
<html xmlns='http://www.w3.org/1999/xhtml'
      xmlns:custom='http://mycompany.com/schemas/components'>
    <body>
        <main>
            <custom:StatCard value='$125,432' label='Total Sales' />
            <custom:StatCard value='1,847' label='Active Users' />
        </main>
    </body>
</html>

4. Application

Program.cs:

using Microsoft.Extensions.DependencyInjection;
using Scryber;
using Scryber.Components;

// Setup dependency injection
var services = new ServiceCollection();
services.AddScryber();  // Loads scrybersettings.json
var provider = services.BuildServiceProvider();

// Parse document with custom components
using (var reader = new StreamReader("Dashboard.pdfx"))
{
    var doc = Document.ParseDocument(reader, ParseSourceType.DynamicContent);
    doc.ProcessDocument("Dashboard.pdf");
}

Advanced Configuration

Multiple Namespaces

Register multiple component libraries:

{
  "Scryber": {
    "Parsing": {
      "Namespaces": [
        {
          "XMLNamespace": "http://mycompany.com/components",
          "AssemblyPrefix": "MyCompany.Components, MyCompany.Components"
        },
        {
          "XMLNamespace": "http://mycompany.com/charts",
          "AssemblyPrefix": "MyCompany.Charting, MyCompany.Charting"
        },
        {
          "XMLNamespace": "http://thirdparty.com/widgets",
          "AssemblyPrefix": "ThirdParty.Widgets, ThirdParty.Widgets"
        }
      ]
    }
  }
}

Nested Namespaces

Components can exist in nested namespaces:

Code structure:

MyCompany.Components
β”œβ”€β”€ StatCard.cs
β”œβ”€β”€ Cards
β”‚   └── ProductCard.cs
└── Charts
    └── BarChart.cs

Configuration:

{
  "Scryber": {
    "Parsing": {
      "Namespaces": [
        {
          "XMLNamespace": "http://mycompany.com/components",
          "AssemblyPrefix": "MyCompany.Components, MyCompany.Components"
        },
        {
          "XMLNamespace": "http://mycompany.com/components/cards",
          "AssemblyPrefix": "MyCompany.Components.Cards, MyCompany.Components"
        },
        {
          "XMLNamespace": "http://mycompany.com/components/charts",
          "AssemblyPrefix": "MyCompany.Components.Charts, MyCompany.Components"
        }
      ]
    }
  }
}

Template:

<html xmlns='http://www.w3.org/1999/xhtml'
      xmlns:custom='http://mycompany.com/components'
      xmlns:cards='http://mycompany.com/components/cards'
      xmlns:charts='http://mycompany.com/components/charts'>
    
    <custom:StatCard ... />       <!-- MyCompany.Components.StatCard -->
    <cards:ProductCard ... />     <!-- MyCompany.Components.Cards.ProductCard -->
    <charts:BarChart ... />       <!-- MyCompany.Components.Charts.BarChart -->
    
</html>

Implementation Details

Namespace Lookup

ParsingOptions.LookupAssemblyForXmlNamespace():

public string LookupAssemblyForXmlNamespace(string xmlnamespace, bool throwNotFound)
{
    NamespaceDefn found = null;
    
    // Search registered namespaces
    foreach (NamespaceDefn defn in this.Namespaces)
    {
        if (string.Equals(defn.XmlNamespace, xmlnamespace, 
            StringComparison.OrdinalIgnoreCase))
        {
            found = defn;
            break;
        }
    }
    
    if (found == null)
    {
        if (throwNotFound)
            throw new PDFParserException(
                $"No assembly registered for XML namespace: {xmlnamespace}");
        return null;
    }
    
    return found.AssemblyPrefix;
}

Namespace Discovery

ParserDefintionFactory.PopulateNamespaceFromAssembly():

When an assembly is loaded, Scryber scans for types with [PDFParsableComponent]:

private static void PopulateNamespaceFromAssembly(
    string xmlnamespace, Assembly assembly, NamespaceDefn defn)
{
    Type[] alltypes = assembly.GetTypes();
    
    foreach (Type atype in alltypes)
    {
        // Check for PDFParsableComponent attribute
        Attribute found = System.Attribute.GetCustomAttribute(
            atype, typeof(PDFParsableComponentAttribute), false);
        
        if (found != null)
        {
            PDFParsableComponentAttribute parseable = 
                (PDFParsableComponentAttribute)found;
            
            string name = string.IsNullOrEmpty(parseable.Name) 
                ? atype.Name 
                : parseable.Name;
            
            // Cache type definition
            defn.Components[name] = new ComponentTypeDefinition(atype, name);
        }
    }
}

Best Practices

Namespace URI Design

  • Use your company domain: http://mycompany.com/schemas/...
  • Include version for breaking changes: http://mycompany.com/schemas/v2/components
  • Group related components: http://mycompany.com/components/charts
  • Document namespace URIs

Assembly Organization

  • Group related components in same assembly
  • Use consistent naming: Company.Product.Components
  • Version assemblies appropriately
  • Document component libraries

Component Discovery

  • Mark all components with [PDFParsableComponent]
  • Use descriptive element names
  • Avoid name collisions across namespaces
  • Document component APIs

Performance

  • Namespace resolution is cached - registration order doesn’t impact performance
  • Assembly loading happens once per namespace
  • Type reflection is cached - repeated use is fast

Troubleshooting

β€œNo assembly registered for XML namespace”

  • Verify namespace URI matches exactly (including http/https, case)
  • Check scrybersettings.json is loaded
  • Ensure JSON syntax is valid
  • Verify namespace is in Parsing:Namespaces[] array

β€œCould not load type β€˜MyCompany.Components.StatCard’”

  • Check assembly name matches AssemblyPrefix
  • Verify assembly is referenced by application
  • Ensure namespace matches AssemblyPrefix
  • Check component class is public
  • Verify [PDFParsableComponent] attribute is present

Type not found in assembly

  • Check class is public
  • Verify namespace matches configuration
  • Ensure class name matches element name (or [PDFParsableComponent(β€œName”)])
  • Check assembly is compiled and up-to-date

Namespace prefix undeclared

  • Add xmlns:prefix='...' to Document root element
  • Verify URI matches registered namespace exactly
  • Check for typos in namespace URI